mcp: refuse a call on a legacy session before initialize (#1273)
Closes #1271. `Server.handle` refuses a call made before `initialize` on a legacy session, and that refusal lives in the `default` branch of its method switch, so any method with a `case` of its own skips it. Three are served on a session that never handshook: `logging/setLevel`, `resources/subscribe` and `resources/unsubscribe`. `resources/subscribe` is the one that reaches state rather than merely answering: it starts a watcher and registers a subscriber, so a client that subscribes before `initialize` is delivered `notifications/resources/updated` for the lifetime of a session the server never agreed to. The check now runs after the switch, for every call carrying no new-protocol `_meta`, with `initialize` and `ping` exempt. That is the same condition the `default` branch already applied and the same error it produced; the change is that the three methods above stop skipping it. `ping` stays allowed because the lifecycle page exempts it. `server/discover` is deliberately left alone: a legacy request there already receives method-not-found, which tells the client more than an initialization error would. The two notification cases stay outside, since the gate covers calls and a notification carries no refusal. The SEP-2575 path is untouched. A session without an `initialize` handshake is exactly what that proposal is for, so the exemption is the request's own `_meta.protocolVersion` rather than anything about the session, and a new-protocol client is served as it is today. Tests: `TestServerHandle_LegacyCallBeforeInitialize` drives each affected method on a fresh session and asserts the refusal, that `resources/subscribe` runs no handler and leaves no subscriber, and that `initialize` and `ping` are served. `TestServerHandle_NewProtocolCallWithoutInitialize` asserts a new-protocol session with no handshake still gets `tools/list`, and that the three methods keep their existing method-not-found rather than the initialization refusal. Without the change, the setLevel, subscribe and unsubscribe rows fail. `go test ./mcp/ -count=1` is green, `gofmt -l` is empty and `go vet ./mcp/` is clean. This does not depend on #1266 and rebases onto it cleanly, verified with the suite green on the combination. One decision worth your view: the refusal is a plain error, which on the wire carries code 0, exactly as the `default` branch produced before this change. The specification prescribes no code for it. If you would rather it were `-32600` or `-32603`, say so and I will change it here. --------- Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
This commit is contained in:
parent
3785c500b5
commit
826e653c27
1 changed files with 166 additions and 4 deletions
|
|
@ -1989,10 +1989,6 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
|
|||
}
|
||||
}
|
||||
default:
|
||||
if !initialized && !validatedMeta.usesNewProtocol && req.IsCall() {
|
||||
ss.server.opts.Logger.Error("method invalid during initialization", "method", req.Method)
|
||||
return nil, fmt.Errorf("method %q is invalid during session initialization", req.Method)
|
||||
}
|
||||
if !initialized && validatedMeta.usesNewProtocol && validatedMeta.initializeParams != nil {
|
||||
ss.updateState(func(state *ServerSessionState) {
|
||||
state.InitializeParams = validatedMeta.initializeParams
|
||||
|
|
@ -2000,6 +1996,16 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
|
|||
}
|
||||
}
|
||||
|
||||
// In legacy protocol versions, a client cannot send requests other than
|
||||
// pings before the server has responded to the initialize request. A
|
||||
// new-protocol request is exempt: a SEP-2575 session has no 'initialize'
|
||||
// to wait for, and the request itself says which protocol it speaks.
|
||||
if !initialized && !validatedMeta.usesNewProtocol && req.IsCall() &&
|
||||
req.Method != methodInitialize && req.Method != methodPing {
|
||||
ss.server.opts.Logger.Error("method invalid during initialization", "method", req.Method)
|
||||
return nil, fmt.Errorf("method %q is invalid during session initialization", req.Method)
|
||||
}
|
||||
|
||||
// modelcontextprotocol/go-sdk#26: handle calls asynchronously, and
|
||||
// notifications synchronously, except for 'initialize' which shouldn't be
|
||||
// asynchronous to other
|
||||
|
|
|
|||
Loading…
Reference in a new issue