In #202, I added the checkRequest helper to validate incoming requests,
and invoked it in the stremable transports to preemptively reject
invalid HTTP requests, so that a jsonrpc error could be translated to an
HTTP error.
However, this introduced a bug: since cancellation was handled in the
jsonrpc2 layer, we never had to validate it in the mcp layer, and
therefore never added methodInfo. As a result, it was reported as an
invalid request in the http layer.
Add a test, and a fix. The simplest fix was to create stubs that are
placeholders for cancellation.
This was discovered in the course of investigating #285.
Allow other header values for the 'Accept' header that imply
application/json and text/event-stream for requests to the Streamable
HTTP Transport.
Fixes#290
Unit tests here seemed like unnecessary complexity and wouldn't fit
cleanly with the style of the `streamable_test.go` tests. Happy to add
those if desired though.
Handlers and client/server methods take a single request argument
combining the session and params. This slightly simplifies the signature
for handlers, since there is no longer an often-ignored session
argument.
But more importantly, it opens the door to adding more information in
requests, such as auth info and HTTP request headers.
For #243.
As described in #272, there is really no reason for transports to be
closed structs with constructors, since their state must be established
by a call to Connect. Making them open structs simplifies their APIs,
and means that all transports can be extended in the future: we don't
have to create empty Options structs just for the purpose of future
compatibility.
For now, the related constructors and options structs are simply
deprecated (with go:fix directives where possible). A future CL will
remove them prior to the v1.0.0 release.
For #272
Support stateless streamable sessions by adding a GetSessionID function
to StreamableHTTPOptions. If GetSessionID returns "", the session is
stateless, and no validation is performed. This is implemented by
providing the session a trivial initialization state.
To implement this, some parts of #232 (distributed sessions) are copied
over, since they add an API for creating an already-initialized session.
In total, the following new API is added:
- StreamableHTTPOptions.GetSessionID
- ServerSessionOptions (a new parameter to Server.Connect)
- ServerSessionState
- ClientSessionOptions (a new parameter to Client.Connect, for symmetry)
For #10
In jsonprc2_test.go, binder.Bind starts a goroutine.
That goroutine begins to run during jsonrpc2.bindConnection, and
can race with the setting of conn.write in bindConnection.
This PR adds a sleep, which is a poor way to deal with the race, but
the least invasive change. Better ones include running the test
function after Dial returns, or adding a Connection.Ready method
to detect when initialization is complete.
Add a new (currently unexported) jsonResponse option to
StreamableServerTransportOptions, and use it to control the response
content type, serving application/json if set.
Additionally:
- A transportOptions field is added to the StreamableHTTPOptions.
- A messages iterator is added to encapsulate the iteration of stream
messages, since the handling of JSON and SSE responses are otherwise
very different.
- The serving flow is refactored to avoid returning (statusCode,
message), primarily because this seemed liable to lead to redundant
calls to WriteHeader, because only local logic knows whether or not
any data has been written to the response.
- The serving flow is refactored to delegate to responseJSON and
responseSSE, according to the currently unexported jsonResponse
option.
- A bug is fixed where all GET streams were considered persistent: the
terminal condition req.Method == http.MethodPost && nOutstanding == 0
was not right: GET requests may implement stream resumption.
Updates #211
Make several cleanups of the streamable client transport, encountered
during work on JSON support for the streamable server:
- The 'Close' condition is differentiated from asynchronous failures. A
failure should unblock Read with an error, at which point the JSON-RPC
connection will be broken and closed.
- Fields are reordered in streamableClientConn to make guards more
apparent.
- The handling of sessionID is simplified: we simply set the session ID
whenever we receive response headers. No need to have special handling
for the first request, as the serializeation of session initialization
is implemented in Client.Connect.
- Since the above bullet makes Write a trivial wrapper around
postMessage, the two methods are merged.
- A bug is fixed where JSON responses were handled synchronously in
Write. This lead to deadlock when a hanging client->server request is
waiting on a server->client request. Now JSON is handled symmetrically
to SSE: the Write returns once response headers are received.
asynchronous to Write.
- The httpConnection interface is renamed to clientConnection, and
receive the entire InitializeResult.
- streamableClientConn receivers are renamed to be consistently 'c'.
This CL introduces a fundamental change to the jsonrpc2 library:
connection writes, which were previously serialized by the jsonrpc2
library itself, are now allowed to be concurrent.
The change in semantics of the jsonrpc2 library should hopefully be easy
to review, since moving the synchronization to the Writer implementation
is equivalent to the previous logic. However, this change is critical
for the streamable client transport, because it allows for concurrent
http requests to the server.
Consider that a write is a POST to the server, and we don't know that
write succeeded until we get the response header. Previously, we had the
following problem: if the client POSTs a request, and the server blocks
its response on a request made through the hanging GET, the client was
unable to respond because the initial POST is still blocked. We could
update our streamable server transport to force a flush of the response
headers, but we can't guarantee that other servers behave the same way.
Fundamentally, writes in the spec are asynchronous, and we need to
support that.
Add auth.RequireBearerToken and associated types.
This piece of middleware authenticates clients using OAuth 2.0, as
specified in the MCP spec.
For #237.
Usage:
```
st := mcp.NewStreamableServerTransport(...)
http.Handle(path, auth.RequireBearerToken(verifier, nil)(st.ServeHTTP))
```
Add unexported methods to the Params and Result interface, so that
they're harder to implement outside the mcp package.
It looks like these are the only two interfaces we need to lock down:
others are either intentionally open (Transport, Connection), or already
closed (Session).
Fixes#263
Update ServerSession so that the session is not marked as initialized
until 'notifications/initialized' is actually received from the client.
Include a new test of this lifecycle strictness.
Fixes#225
Make the simplifications described in #222. I don't know why we
originally had such fine granularity of locking: perhaps we were going
to execute 'oninitialized' callbacks.
Also update the TODO with a bit more information about jsonrpc2
behavior.
Fixes#222
This CL effectively removes any dependencies on mcp from jsonschema.
This will enable us to move the repo.
This CL also deletes unused marshalStructWithMap and
unmarshalStructWithMap which had a dependency on FieldJSONInfo.
Fixes: #250
Add a client example that lists all features on a stdio server. Since
this is our first client example, organize examples by client / server
-- otherwise it is too difficult to find client examples.
For #33
Marshal the empty schema to true, and its negation to false.
This is technically a breaking behavior change, but a properly written
consumer of JSON Schema will not notice.
For #244.
Fixes#230.
You can make MCP server log error, send response and then abruptly exit, with a json input malformed at the end.
While using json.RawMessage, once a valid first json is decoded, trailing bad input is silently ignored.
Without graciously handling this input, mcp server is currently sending response as well as encountering error.
It should just report error without further processing of request.
Fixes#179.
Use a read loop and incoming channel so that we can select on incoming
messages and unblock calls of ioConn.Read as soon as Close is called.
This avoids the scenario of #224, where a close of the StdioTransport
does not gracefully shut down the JSON-RPC connection.
Fixes#224
As reported in #227, there was a race where streams could be terminated
before the final reply was written. Fix this by checking the number of
outstanding requests in the same critical section that acquires outgoing
messages to write.
Fixes#227
This CL makes the following changes to our Github workflows, motivated
by #227:
- Add -race tests.
- Add go1.25rc2 to the build matrix.
- Check out code before setting up Go, so that the setup step can use
dependency caching.
- Correct MethodHandler type parameter to use fully qualified
mcp.ServerSession
- Update parameter types to use proper mcp.Params and mcp.Result instead
of any
- Ensure example code matches the actual API
## Background
Since the Server has a map of ServersSessions->subscriptions, and takes
care of routing ResourceUpdate messages to ServerSessions, I think the
decision was made to leave *ServerSession out of the
SubscribeHandler/UnsubscribeHandler signatures. However, resource
subscriptions are not specific to the a particular client and this
information is kept private in the Session today.
## Proposal
Align the Subscribe/Unsubscribe handlers with the other two handlers
that are client specific (ProgressNotificationHandler, and
RootListChangeHanlder), and include *ServerSession in the signature.
It comes in practice because because a gateway server that needs to
forward subscriptions still needs this information. The routing logic in
Server is convenient and means that most servers won't need to know
which ServerSessions are subscribed. But I think there are still use
cases where the user will need to know.
- Add ForOptions to hold options for schema inference.
- Replace ForLax with ForOptions.IgnoreBadTypes.
- Add an option to provide schemas for arbitrary types.
- Provide a default mapping from types to schemas that includes stdlib
types with MarshalJSON methods.
- Add Schema.CloneSchemas. This is needed to make copies of the schemas
in the above map: a schema cannot appear twice in a parent schema,
because schema addresses matter when resolving internal references.
Audit all cases where params must not be null, and enforce this using
methodInfo via a new methodFlags bitfield that parameterizes method
requirements. Write extensive conformance tests catching all the
(server-side) crashes that were possible.
We should go further and validate schema against the spec, but that is
more indirect, more complicated, and potentially slower. For now we
adopt this more explicit approach.
Still TODO in a subsequent CL: verify the client side of this with
client conformance tests.
Additionally, improve some error messages that were redundant or leaked
internal implementation details.
Fixes#195
When calling `ServerSession.CreateMessage`, will get an json unmarshal
error because `CreateMessageResult.Content` is an interface, and it
doesn't implement `json.Unmarshaller`.
Implement the `json.Unmarshaller` to `CreateMessageResult`, let it able
to unmarshal the client response without error
This fixes#151, which makes it easier for users to easily use GitHub
Codespaces, providing them with a VS Code like environment with their
preferred settings/themes and the required dependencies, all within the
browser.
Add an isRequest field to methodInfo, used it to reject non-notification
requests that lack a valid ID. Furthermore, lift this validation to the
transport layer for HTTP server transports, so that we can preemptively
reject bad HTTP requests.
Fixes#194Fixes#197
This CL adds an option for servers to advertise support for features
that have not been added yet. This includes Prompts, Tools, and
Resources as these could be dynamically added after initialization.
Fixes: #135.
- move done field outside of mutex hat
- refactor handler into a function that returns `error` to avoid repeated `http.Error` calls
- use an atomic for the signal channel to simplify locking