go-sdk icon indicating copy to clipboard operation
go-sdk copied to clipboard

Proposal: progress reporting (analogue to FastMCP)

Open unbekanntes-pferd opened this issue 3 months ago • 8 comments

Is your feature request related to a problem? Please describe. For long running requests, I understand clients can provide a progress token - this can be used to notify about progress. I'd like to see a convenience method as part of e.g. a tool to provide updates on progress, e.g. for larger up- and downloads.

Describe the solution you'd like In FastMCP there's a way to do this with very convenient DX (see description: https://gofastmcp.com/servers/progress#progress-reporting) - essentially providing the current state and an optional total value in a function and updating it.

Describe alternatives you've considered I've seen there's a notify func but I'm not sure if this covers what I need.

Additional context As stated above, this is about long running requests (e.g. a download or upload of a larger file, processing many items etc.)

unbekanntes-pferd avatar Sep 12 '25 15:09 unbekanntes-pferd

Agreed, our progress support is raw.

Please see https://github.com/modelcontextprotocol/go-sdk/pull/462 for a potential improvement. What do you think of that API?

findleyr avatar Sep 12 '25 17:09 findleyr

Concrete proposal, implemented in #462. I'm not tied to the name 'Progress', we could call it 'ReportProgress', but I opted for the shorter name.

var ErrNoProgressToken = errors.New("no progress token")

// Progress reports progress on the current request.
//
// An error is returned if sending progress failed. If there was no progress
// token, this error is ErrNoProgressToken.
func (r *ServerRequest[P]) Progress(ctx context.Context, msg string, progress, total float64) error

As an alternative, we could have a concept of a progress reporter that closes over the progress token and session:

func (ServerRequest[P]) Progress() *ProgressReporter
func (*ProgressReporter) Report(context.Context, msg string, progress, total float64) error

This would be more complicated, but has a couple minor advantages: (1) it's easier to pass around the ProgressReporter than the generic ServerRequest, and (2) we could have additional helper methods on the ProgressReporter in the future.

findleyr avatar Sep 12 '25 18:09 findleyr

Agreed, our progress support is raw.

Please see #462 for a potential improvement. What do you think of that API?

Nice - many thanks for the very quick response and proposal - this would certainly help implement the use case I described as an example (simple up- or download). I'm not sure about the arguments though: Should a message msg always be required? Same goes for the total amount - it might be unknown at times.

I'd also vote for keeping the shorter name (Progress).

unbekanntes-pferd avatar Sep 15 '25 09:09 unbekanntes-pferd

I'm not sure either of these is the best API.

For a file upload or download, I'd want something like this:

r := NewProgressReporter("downloading " + filename, fileSize) // set message and total; progress is 0
for .... {
    n, err := f.Read(buf)
    r.Add(n) // increment progress
    ....
}

And I'd want to send progress reports once every few seconds instead of on every read. (The notifications are ultimately for people, presumably.) So maybe NewProgressReporter takes a duration, and Add only notifies at that interval.

I don't think we have enough data to know what the right API is. I'd like to see more examples in the wild.

jba avatar Sep 15 '25 13:09 jba

@jba I've been thinking about this a bit more as well: for all of these, it's easy to write to write a small wrapper that (more or less) translates to any other API. The one primary value they all add is not having to think about the progress token or Meta.

So we should choose the API that is most useful in the common case. I'd lean toward as just a Progress method on ServerRequest as both simplest and most discoverable.

findleyr avatar Sep 15 '25 13:09 findleyr

It's too simple. People will happily call it in an inner loop (e.g. reading a chunk from a file) without realizing they are spamming the client.

jba avatar Sep 16 '25 12:09 jba

Note from the proposal meeting: For now, we think that we will leave this as-is until there is more interest, or an alternative API that doesn't suffer from the backwards-compatibility problems of the one I proposed above.

@jba suggests adding an example for an ergonomic wrapper, to point people in the right direction.

findleyr avatar Oct 08 '25 18:10 findleyr

The example should use golang.org/x/time/rate to rate limit the notifications.

jba avatar Oct 08 '25 20:10 jba