Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions mcp/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions mcp/progress.go
Original file line number Diff line number Diff line change
@@ -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)
}
133 changes: 131 additions & 2 deletions mcp/testdata/conformance/server/tools.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ greet
structured
tomorrow
inc
progress

-- client --
{
Expand All @@ -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 --
{
Expand Down Expand Up @@ -106,6 +109,12 @@ inc
"additionalProperties": false
}
},
{
"inputSchema": {
"type": "object"
},
"name": "progress"
},
{
"inputSchema": {
"type": "object",
Expand Down Expand Up @@ -267,7 +276,7 @@ inc
}
{
"jsonrpc": "2.0",
"id": 11,
"id": 12,
"result": {
"content": [
{
Expand All @@ -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": []
}
}