diff --git a/mcp/conformance_test.go b/mcp/conformance_test.go index 3393efcb..d97c1c37 100644 --- a/mcp/conformance_test.go +++ b/mcp/conformance_test.go @@ -135,6 +135,15 @@ func incTool(_ context.Context, _ *CallToolRequest, args incInput) (*CallToolRes return nil, incOutput{args.X + 1}, nil } +func progressTool(ctx context.Context, req *CallToolRequest, args any) (*CallToolResult, any, error) { + for i := range 10 { + if err := req.Progress(ctx, fmt.Sprintf("message %d", i), float64(i+1), 10); err != nil { + return nil, nil, err + } + } + return nil, nil, nil +} + // runServerTest runs the server conformance test. // It must be executed in a synctest bubble. func runServerTest(t *testing.T, test *conformanceTest) { @@ -152,6 +161,8 @@ func runServerTest(t *testing.T, test *conformanceTest) { AddTool(s, &Tool{Name: "structured"}, structuredTool) case "tomorrow": AddTool(s, &Tool{Name: "tomorrow"}, tomorrowTool) + case "progress": + AddTool(s, &Tool{Name: "progress"}, progressTool) case "inc": inSchema, err := jsonschema.For[incInput](nil) if err != nil { @@ -233,15 +244,17 @@ func runServerTest(t *testing.T, test *conformanceTest) { return nil, err, false } serverMessages = append(serverMessages, msg) - if req, ok := msg.(*jsonrpc.Request); ok && req.IsCall() { - // Pair up the next outgoing response with this request. - // We assume requests arrive in the same order every time. - if len(outResponses) == 0 { - t.Fatalf("no outgoing response for request %v", req) + if req, ok := msg.(*jsonrpc.Request); ok { + if req.IsCall() { + // Pair up the next outgoing response with this request. + // We assume requests arrive in the same order every time. + if len(outResponses) == 0 { + t.Fatalf("no outgoing response for request %v", req) + } + outResponses[0].ID = req.ID + writeMsg(outResponses[0]) + outResponses = outResponses[1:] } - outResponses[0].ID = req.ID - writeMsg(outResponses[0]) - outResponses = outResponses[1:] continue } return msg.(*jsonrpc.Response), nil, true diff --git a/mcp/progress.go b/mcp/progress.go new file mode 100644 index 00000000..470fb628 --- /dev/null +++ b/mcp/progress.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package mcp + +import ( + "context" + "errors" +) + +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 { + token, ok := r.Params.GetMeta()[progressTokenKey] + if !ok { + return ErrNoProgressToken + } + params := &ProgressNotificationParams{ + Message: msg, + ProgressToken: token, + Progress: progress, + Total: total, + } + return r.Session.NotifyProgress(ctx, params) +} diff --git a/mcp/testdata/conformance/server/tools.txtar b/mcp/testdata/conformance/server/tools.txtar index b582dda8..6cefe623 100644 --- a/mcp/testdata/conformance/server/tools.txtar +++ b/mcp/testdata/conformance/server/tools.txtar @@ -16,6 +16,7 @@ greet structured tomorrow inc +progress -- client -- { @@ -40,7 +41,9 @@ inc { "jsonrpc": "2.0", "id": 9, "method": "tools/call", "params": {"name": "tomorrow", "arguments": { "Now": "2025-06-18T15:04:05Z" } } } { "jsonrpc": "2.0", "id": 10, "method": "tools/call", "params": {"name": "greet" } } { "jsonrpc": "2.0", "id": 11, "method": "tools/call", "params": {"name": "inc", "arguments": { "x": 3 } } } -{ "jsonrpc": "2.0", "id": 11, "method": "tools/call", "params": {"name": "inc" } } +{ "jsonrpc": "2.0", "id": 12, "method": "tools/call", "params": {"name": "inc" } } +{ "jsonrpc": "2.0", "id": 13, "method": "tools/call", "params": {"name": "progress" } } +{ "jsonrpc": "2.0", "id": 14, "method": "tools/call", "params": {"name": "progress", "_meta": { "progressToken": "abc123" } } } -- server -- { @@ -106,6 +109,12 @@ inc "additionalProperties": false } }, + { + "inputSchema": { + "type": "object" + }, + "name": "progress" + }, { "inputSchema": { "type": "object", @@ -267,7 +276,7 @@ inc } { "jsonrpc": "2.0", - "id": 11, + "id": 12, "result": { "content": [ { @@ -280,3 +289,123 @@ inc } } } +{ + "jsonrpc": "2.0", + "id": 13, + "result": { + "content": [ + { + "type": "text", + "text": "no progress token" + } + ], + "isError": true + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 0", + "progress": 1, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 1", + "progress": 2, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 2", + "progress": 3, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 3", + "progress": 4, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 4", + "progress": 5, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 5", + "progress": 6, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 6", + "progress": 7, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 7", + "progress": 8, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 8", + "progress": 9, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "message": "message 9", + "progress": 10, + "progressToken": "abc123", + "total": 10 + } +} +{ + "jsonrpc": "2.0", + "id": 14, + "result": { + "content": [] + } +}