Skip to content

Commit d94f2f1

Browse files
committed
address review comments
1 parent 0f0a134 commit d94f2f1

File tree

6 files changed

+158
-43
lines changed

6 files changed

+158
-43
lines changed

docs/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ These docs are a work-in-progress.
33

44
# Features
55

6-
This doc mirrors the official MCP spec hosted at
7-
https://modelcontextprotocol.io/specification/2025-06-18
6+
These docs mirror the [official MCP spec](https://modelcontextprotocol.io/specification/2025-06-18).
87

98
## Base Protocol
109

docs/protocol.md

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ connect to the server:
174174
transport := &mcp.StreamableClientTransport{
175175
Endpoint: "http://localhost:8080/mcp",
176176
}
177-
client, err := mcp.Connect(context.Background(), transport, &mcp.ClientOptions{...})
177+
client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...})
178178
```
179179

180180
The `StreamableClientTransport` handles the HTTP requests and communicates with
@@ -199,11 +199,12 @@ or server sends a notification, the spec says nothing about when the peer
199199
observes that notification relative to other request. However, the Go SDK
200200
implements the following heuristics:
201201

202-
- If a notifying method (such as progress notification or
202+
- If a notifying method (such as `notifications/progress` or
203203
`notifications/initialized`) returns, then it is guaranteed that the peer
204-
observes that notification before other notifications or calls.
204+
observes that notification before other notifications or calls from the same
205+
client goroutine.
205206
- Calls (such as `tools/call`) are handled asynchronously with respect to
206-
eachother.
207+
each other.
207208

208209
See
209210
[modelcontextprotocol/go-sdk#26](https://github.com/modelcontextprotocol/go-sdk/issues/26)
@@ -225,16 +226,70 @@ Cancellation is implemented with context cancellation. Cancelling a context
225226
used in a method on `ClientSession` or `ServerSession` will terminate the RPC
226227
and send a "notifications/cancelled" message to the peer.
227228

228-
```go
229-
ctx, cancel := context.WithCancel(context.Background())
230-
go cs.CallTool(ctx, &CallToolParams{Name: "slow"})
231-
cancel() // cancel the tool call
232-
```
233-
234229
When an RPC exits due to a cancellation error, there's a guarantee that the
235230
cancellation notification has been sent, but there's no guarantee that the
236231
server has observed it (see [concurrency](#concurrency)).
237232

233+
```go
234+
func Example_cancellation() {
235+
// For this example, we're going to be collecting observations from the
236+
// server and client.
237+
var clientResult, serverResult string
238+
var wg sync.WaitGroup
239+
wg.Add(2)
240+
241+
// Create a server with a single slow tool.
242+
// When the client cancels its request, the server should observe
243+
// cancellation.
244+
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
245+
started := make(chan struct{}, 1) // signals that the server started handling the tool call
246+
mcp.AddTool(server, &mcp.Tool{Name: "slow"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
247+
started <- struct{}{}
248+
defer wg.Done()
249+
select {
250+
case <-time.After(5 * time.Second):
251+
serverResult = "tool done"
252+
case <-ctx.Done():
253+
serverResult = "tool canceled"
254+
}
255+
return &mcp.CallToolResult{}, nil, nil
256+
})
257+
258+
// Connect a client to the server.
259+
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
260+
ctx := context.Background()
261+
t1, t2 := mcp.NewInMemoryTransports()
262+
if _, err := server.Connect(ctx, t1, nil); err != nil {
263+
log.Fatal(err)
264+
}
265+
session, err := client.Connect(ctx, t2, nil)
266+
if err != nil {
267+
log.Fatal(err)
268+
}
269+
defer session.Close()
270+
271+
// Make a tool call, asynchronously.
272+
ctx, cancel := context.WithCancel(context.Background())
273+
go func() {
274+
defer wg.Done()
275+
_, err = session.CallTool(ctx, &mcp.CallToolParams{Name: "slow"})
276+
clientResult = fmt.Sprintf("%v", err)
277+
}()
278+
279+
// As soon as the server has started handling the call, cancel it from the
280+
// client side.
281+
<-started
282+
cancel()
283+
wg.Wait()
284+
285+
fmt.Println(clientResult)
286+
fmt.Println(serverResult)
287+
// Output:
288+
// context canceled
289+
// tool canceled
290+
}
291+
```
292+
238293
### Ping
239294

240295
[Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping)
@@ -270,13 +325,13 @@ Issue #460 discusses some potential ergonomic improvements to this API.
270325
func Example_progress() {
271326
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
272327
mcp.AddTool(server, &mcp.Tool{Name: "makeProgress"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
273-
token, ok := req.Params.GetMeta()["progressToken"]
274-
if ok {
328+
if token := req.Params.GetProgressToken(); token != nil {
275329
for i := range 3 {
276330
params := &mcp.ProgressNotificationParams{
277-
Message: fmt.Sprintf("progress %d", i),
331+
Message: "frobbing widgets",
278332
ProgressToken: token,
279333
Progress: float64(i),
334+
Total: 2,
280335
}
281336
req.Session.NotifyProgress(ctx, params) // ignore error
282337
}
@@ -285,7 +340,7 @@ func Example_progress() {
285340
})
286341
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
287342
ProgressNotificationHandler: func(_ context.Context, req *mcp.ProgressNotificationClientRequest) {
288-
fmt.Println(req.Params.Message)
343+
fmt.Printf("%s %.0f/%.0f\n", req.Params.Message, req.Params.Progress, req.Params.Total)
289344
},
290345
})
291346
ctx := context.Background()
@@ -306,8 +361,8 @@ func Example_progress() {
306361
log.Fatal(err)
307362
}
308363
// Output:
309-
// progress 0
310-
// progress 1
311-
// progress 2
364+
// frobbing widgets 0/2
365+
// frobbing widgets 1/2
366+
// frobbing widgets 2/2
312367
}
313368
```

internal/docs/README.src.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ These docs are a work-in-progress.
22

33
# Features
44

5-
This doc mirrors the official MCP spec hosted at
6-
https://modelcontextprotocol.io/specification/2025-06-18
5+
These docs mirror the [official MCP spec](https://modelcontextprotocol.io/specification/2025-06-18).
76

87
## Base Protocol
98

internal/docs/doc.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
// Use of this source code is governed by an MIT-style
33
// license that can be found in the LICENSE file.
44

5-
//go:generate go run golang.org/x/example/internal/cmd/weave@latest -o ../../docs/README.md ./README.src.md
6-
//go:generate go run golang.org/x/example/internal/cmd/weave@latest -o ../../docs/protocol.md ./protocol.src.md
7-
//go:generate go run golang.org/x/example/internal/cmd/weave@latest -o ../../docs/client.md ./client.src.md
8-
//go:generate go run golang.org/x/example/internal/cmd/weave@latest -o ../../docs/server.md ./server.src.md
9-
//go:generate go run golang.org/x/example/internal/cmd/weave@latest -o ../../docs/troubleshooting.md ./troubleshooting.src.md
5+
//go:generate -command weave go run golang.org/x/example/internal/cmd/weave@latest
6+
//go:generate weave -o ../../docs/README.md ./README.src.md
7+
//go:generate weave -o ../../docs/protocol.md ./protocol.src.md
8+
//go:generate weave -o ../../docs/client.md ./client.src.md
9+
//go:generate weave -o ../../docs/server.md ./server.src.md
10+
//go:generate weave -o ../../docs/troubleshooting.md ./troubleshooting.src.md
1011

1112
// The doc package generates the documentation at /doc, via go:generate.
1213
//

internal/docs/protocol.src.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ connect to the server:
106106
transport := &mcp.StreamableClientTransport{
107107
Endpoint: "http://localhost:8080/mcp",
108108
}
109-
client, err := mcp.Connect(context.Background(), transport, &mcp.ClientOptions{...})
109+
client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...})
110110
```
111111

112112
The `StreamableClientTransport` handles the HTTP requests and communicates with
@@ -131,11 +131,12 @@ or server sends a notification, the spec says nothing about when the peer
131131
observes that notification relative to other request. However, the Go SDK
132132
implements the following heuristics:
133133

134-
- If a notifying method (such as progress notification or
134+
- If a notifying method (such as `notifications/progress` or
135135
`notifications/initialized`) returns, then it is guaranteed that the peer
136-
observes that notification before other notifications or calls.
136+
observes that notification before other notifications or calls from the same
137+
client goroutine.
137138
- Calls (such as `tools/call`) are handled asynchronously with respect to
138-
eachother.
139+
each other.
139140

140141
See
141142
[modelcontextprotocol/go-sdk#26](https://github.com/modelcontextprotocol/go-sdk/issues/26)
@@ -157,16 +158,12 @@ Cancellation is implemented with context cancellation. Cancelling a context
157158
used in a method on `ClientSession` or `ServerSession` will terminate the RPC
158159
and send a "notifications/cancelled" message to the peer.
159160

160-
```go
161-
ctx, cancel := context.WithCancel(context.Background())
162-
go cs.CallTool(ctx, &CallToolParams{Name: "slow"})
163-
cancel() // cancel the tool call
164-
```
165-
166161
When an RPC exits due to a cancellation error, there's a guarantee that the
167162
cancellation notification has been sent, but there's no guarantee that the
168163
server has observed it (see [concurrency](#concurrency)).
169164

165+
%include ../../mcp/mcp_example_test.go cancellation -
166+
170167
### Ping
171168

172169
[Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping)

mcp/mcp_example_test.go

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"context"
99
"fmt"
1010
"log"
11+
"sync"
12+
"time"
1113

1214
"github.com/modelcontextprotocol/go-sdk/mcp"
1315
)
@@ -58,13 +60,13 @@ func Example_lifeCycle() {
5860
func Example_progress() {
5961
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
6062
mcp.AddTool(server, &mcp.Tool{Name: "makeProgress"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
61-
token, ok := req.Params.GetMeta()["progressToken"]
62-
if ok {
63+
if token := req.Params.GetProgressToken(); token != nil {
6364
for i := range 3 {
6465
params := &mcp.ProgressNotificationParams{
65-
Message: fmt.Sprintf("progress %d", i),
66+
Message: "frobbing widgets",
6667
ProgressToken: token,
6768
Progress: float64(i),
69+
Total: 2,
6870
}
6971
req.Session.NotifyProgress(ctx, params) // ignore error
7072
}
@@ -73,7 +75,7 @@ func Example_progress() {
7375
})
7476
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
7577
ProgressNotificationHandler: func(_ context.Context, req *mcp.ProgressNotificationClientRequest) {
76-
fmt.Println(req.Params.Message)
78+
fmt.Printf("%s %.0f/%.0f\n", req.Params.Message, req.Params.Progress, req.Params.Total)
7779
},
7880
})
7981
ctx := context.Background()
@@ -94,9 +96,71 @@ func Example_progress() {
9496
log.Fatal(err)
9597
}
9698
// Output:
97-
// progress 0
98-
// progress 1
99-
// progress 2
99+
// frobbing widgets 0/2
100+
// frobbing widgets 1/2
101+
// frobbing widgets 2/2
100102
}
101103

102104
// !-progress
105+
106+
// !+cancellation
107+
108+
func Example_cancellation() {
109+
// For this example, we're going to be collecting observations from the
110+
// server and client.
111+
var clientResult, serverResult string
112+
var wg sync.WaitGroup
113+
wg.Add(2)
114+
115+
// Create a server with a single slow tool.
116+
// When the client cancels its request, the server should observe
117+
// cancellation.
118+
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
119+
started := make(chan struct{}, 1) // signals that the server started handling the tool call
120+
mcp.AddTool(server, &mcp.Tool{Name: "slow"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) {
121+
started <- struct{}{}
122+
defer wg.Done()
123+
select {
124+
case <-time.After(5 * time.Second):
125+
serverResult = "tool done"
126+
case <-ctx.Done():
127+
serverResult = "tool canceled"
128+
}
129+
return &mcp.CallToolResult{}, nil, nil
130+
})
131+
132+
// Connect a client to the server.
133+
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
134+
ctx := context.Background()
135+
t1, t2 := mcp.NewInMemoryTransports()
136+
if _, err := server.Connect(ctx, t1, nil); err != nil {
137+
log.Fatal(err)
138+
}
139+
session, err := client.Connect(ctx, t2, nil)
140+
if err != nil {
141+
log.Fatal(err)
142+
}
143+
defer session.Close()
144+
145+
// Make a tool call, asynchronously.
146+
ctx, cancel := context.WithCancel(context.Background())
147+
go func() {
148+
defer wg.Done()
149+
_, err = session.CallTool(ctx, &mcp.CallToolParams{Name: "slow"})
150+
clientResult = fmt.Sprintf("%v", err)
151+
}()
152+
153+
// As soon as the server has started handling the call, cancel it from the
154+
// client side.
155+
<-started
156+
cancel()
157+
wg.Wait()
158+
159+
fmt.Println(clientResult)
160+
fmt.Println(serverResult)
161+
// Output:
162+
// context canceled
163+
// tool canceled
164+
}
165+
166+
// !-cancellation

0 commit comments

Comments
 (0)