@@ -82,7 +82,7 @@ func (s *Server) listTools(_ context.Context, _ *ClientConnection, params *proto
82
82
return res , nil
83
83
}
84
84
85
- func (s * Server ) callTool (ctx context.Context , _ * ClientConnection , params * protocol.CallToolParams ) (* protocol.CallToolResult , error ) {
85
+ func (s * Server ) callTool (ctx context.Context , cc * ClientConnection , params * protocol.CallToolParams ) (* protocol.CallToolResult , error ) {
86
86
s .mu .Lock ()
87
87
var tool * Tool
88
88
if i := slices .IndexFunc (s .tools , func (t * Tool ) bool {
@@ -95,7 +95,7 @@ func (s *Server) callTool(ctx context.Context, _ *ClientConnection, params *prot
95
95
if tool == nil {
96
96
return nil , fmt .Errorf ("%s: unknown tool %q" , jsonrpc2 .ErrInvalidParams , params .Name )
97
97
}
98
- return tool .Handler (ctx , params .Arguments )
98
+ return tool .Handler (ctx , cc , params .Arguments )
99
99
}
100
100
101
101
// Run runs the server over the given transport, which must be persistent.
@@ -148,10 +148,31 @@ type ClientConnection struct {
148
148
conn * jsonrpc2.Connection
149
149
150
150
mu sync.Mutex
151
- initializeParams * protocol.InitializeParams // set once initialize has been received
151
+ initializeParams * protocol.InitializeParams
152
+ initialized bool
153
+ }
154
+
155
+ // Ping makes an MCP "ping" request to the client.
156
+ func (cc * ClientConnection ) Ping (ctx context.Context ) error {
157
+ return call (ctx , cc .conn , "ping" , nil , nil )
152
158
}
153
159
154
160
func (cc * ClientConnection ) handle (ctx context.Context , req * jsonrpc2.Request ) (any , error ) {
161
+ cc .mu .Lock ()
162
+ initialized := cc .initialized
163
+ cc .mu .Unlock ()
164
+
165
+ // From the spec:
166
+ // "The client SHOULD NOT send requests other than pings before the server
167
+ // has responded to the initialize request."
168
+ switch req .Method {
169
+ case "initialize" , "ping" :
170
+ default :
171
+ if ! initialized {
172
+ return nil , fmt .Errorf ("method %q is invalid during session ininitialization" , req .Method )
173
+ }
174
+ }
175
+
155
176
// TODO: embed the incoming request ID in the ClientContext (or, more likely,
156
177
// a wrapper around it), so that we can correlate responses and notifications
157
178
// to the handler; this is required for the new session-based transport.
@@ -160,6 +181,10 @@ func (cc *ClientConnection) handle(ctx context.Context, req *jsonrpc2.Request) (
160
181
case "initialize" :
161
182
return dispatch (ctx , cc , req , cc .initialize )
162
183
184
+ case "ping" :
185
+ // The spec says that 'ping' expects an empty object result.
186
+ return struct {}{}, nil
187
+
163
188
case "tools/list" :
164
189
return dispatch (ctx , cc , req , cc .server .listTools )
165
190
@@ -176,6 +201,17 @@ func (cc *ClientConnection) initialize(ctx context.Context, _ *ClientConnection,
176
201
cc .initializeParams = params
177
202
cc .mu .Unlock ()
178
203
204
+ // Mark the connection as initialized when this method exits. TODO:
205
+ // Technically, the server should not be considered initialized until it has
206
+ // *responded*, but we don't have adequate visibility into the jsonrpc2
207
+ // connection to implement that easily. In any case, once we've initialized
208
+ // here, we can handle requests.
209
+ defer func () {
210
+ cc .mu .Lock ()
211
+ cc .initialized = true
212
+ cc .mu .Unlock ()
213
+ }()
214
+
179
215
return & protocol.InitializeResult {
180
216
// TODO(rfindley): support multiple protocol versions.
181
217
ProtocolVersion : "2024-11-05" ,
@@ -204,11 +240,14 @@ func (cc *ClientConnection) Wait() error {
204
240
return cc .conn .Wait ()
205
241
}
206
242
207
- func dispatch [TParams , TResult any ](ctx context.Context , conn * ClientConnection , req * jsonrpc2.Request , f func (context.Context , * ClientConnection , TParams ) (TResult , error )) (TResult , error ) {
243
+ // dispatch turns a strongly type handler into a jsonrpc2 handler.
244
+ //
245
+ // Importantly, it returns nil if the handler returned an error, which is a
246
+ // requirement of the jsonrpc2 package.
247
+ func dispatch [TConn , TParams , TResult any ](ctx context.Context , conn TConn , req * jsonrpc2.Request , f func (context.Context , TConn , TParams ) (TResult , error )) (any , error ) {
208
248
var params TParams
209
249
if err := json .Unmarshal (req .Params , & params ); err != nil {
210
- var zero TResult
211
- return zero , err
250
+ return nil , err
212
251
}
213
252
return f (ctx , conn , params )
214
253
}
0 commit comments