Commit graph go-sdk/mcp/streamable.go
Author SHA1 Message Date
Takeshi Yoneda
a76bae3a11 address review comments
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
2025-08-27 16:03:32 -04:00
Takeshi Yoneda
c24d9856c4 mcp/streamable: fixes broken DELETE request on connection close
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
2025-08-27 16:03:32 -04:00
Hongxiang Jiang
fb69971650
mcp: cleanup transport after keepalive ping fails
An onClose function is passed to the ServerSession and ClientSession to
help cleanup resources from the caller.

The onClose function will be executed as part of the ServerSession and
ClientSession closure.

Fixes #258
2025-08-22 15:26:52 -04:00
Rob Findley
79f063bc98 mcp: handle the Mcp-Protocol-Version header correctly
Handle the protocol version header according to section 2.7 of the spec
(and the other SDKs).

Fixes #198
2025-08-20 16:48:08 -04:00
Jonathan Amsterdam
73b8a7f134
mcp: make request headers available to tools (#333)
Add HTTP request headers to RequestExtra, so tools and other
user-defined handlers can access them.

Fixes #331.
2025-08-20 14:58:44 -04:00
Robert Findley
62d815922f
mcp: minor cleanup of some TODOs (#332)
Do a pass through TODOs to delete or address minor TODOs.
2025-08-20 12:55:19 -04:00
Jonathan Amsterdam
8e6ab130d8
mcp: pass TokenInfo to server handler (#292)
If there is a TokenInfo in the request context of a
StreamableServerTransport, then propagate it through to the
ServerRequest that is passed to server methods like callTool.

Fixes #317.
2025-08-20 10:50:16 -04:00
Rob Findley
1a54234c27 mcp: fix reconnect semantics for hanging GET
A few problems with reconnection cropped up in the review of PR #307.
We should allow for the hanging GET to fail with StatusMethodNotAllowed.
This simply means that the server does not support sending notifications
or requests over the GET, which is allowed in the spec.

Also, we should fix the initial delay of the hanging GET request: it
should start with 0 delay. Fix the math for this and subsequent
attempts. Incidentally, this makes the tests take 3s on my machine, down
from 9s.

Also address some comments from #307.
2025-08-20 09:59:52 -04:00
Rob Findley
98e67fdf62 mcp: improvements for 'stateless' streamable servers; 'distributed' mode
Several improvements for the stateless streamable mode, plus support for
a 'distributed' (or rather, distributable) version of the stateless
server.

- Add a 'Stateless' option to StreamableHTTPOptions and
  StreamableServerTransport, which controls stateless behavior.
  GetSessionID may still return a non-empty session ID.
- Audit validation of stateless mode to allow requests with a session
  id. Propagate this session ID to the temporary connection.
- Peek at requests to allow 'initialize' requests to go through to the
  session, so that version negotiation can occur (FIXME: add tests).

Fixes #284
For #148
2025-08-20 09:59:52 -04:00
Robert Findley
46ba813860
.github: add a staticcheck action (#326)
Add a staticcheck step to the lint job of the Test workflow, and fix
reported diagnostics.
2025-08-19 14:58:01 -04:00
Rob Findley
3db848abec mcp: implement a concurrency model for calls
Implement the concurrency model described in #26: notifications are
synchronous, but calls are asynchronous (except for 'initialize'). To
achieve this, implement jsonrpc2.Async(ctx) to signal asynchronous
handling. This is simpler to use than returning ErrAsyncResponse and
calling Respond, and since this is an internal detail we don't need to
worry too much about whether it's idiomatic.

Add tests that verify both features, for both client and server.

Also:
- replace req.ID.IsValid with req.IsCall
- remove the methodHandler type as we can just use MethodHandler

Fixes #26
2025-08-19 11:21:02 -04:00
Sam Thanawalla
767dacb820
mcp/streamable: remove StreamableReconnectOptions (#319)
Moves MaxRetries into the parent struct and assume a negative integer
signifies to not retry.

Fixes #308
2025-08-18 13:53:34 -04:00
Rob Findley
1afdb1f273 mcp: several fixes for streamable client reconnection
Do a pass through the streamable client reconnection logic, and fix
several bugs.

- Establish the initial GET even if MaxRetries is 0 (#256). This was
  broken because the GET bypasses the initial request and going straight
  to the SSE GET reconnection logic.
- Release the stream ownership when POST requests exit.
- Don't reconnect POST requests if we've received the expected response.
- Move unexported reconnection config to constants. Otherwise it is too
  hard to set ReconnectOptions (you have to use the DefaultOptions and
  mutate).

Fixes #256
2025-08-15 16:48:09 -04:00
Hugh Palmer
0a8fe40c51 mcp: changed streamID's from int64 to random strings (#266)
- Changed StreamID's to store randomly generated strings.
- Updated all tests.
- Resolved conflicts
2025-08-15 09:38:18 -04:00
Rob Findley
bb6dadecca mcp: fix cancellation for HTTP transport
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.
2025-08-14 11:32:51 -04:00
Riley Norton
a834f3cf9a
mcp: allow correct 'Accept' header values
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.
2025-08-13 17:12:13 -04:00
Robert Findley
e097918484
mcp: make transports open structs
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
2025-08-12 15:14:21 -04:00
Robert Findley
dae88535ba
mcp: support stateless streamable sessions (#277)
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
2025-08-11 16:43:46 -04:00
Rob Findley
0375535db4 mcp: implement support for JSON responses in the MCP streamable server
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
2025-08-08 12:38:43 -04:00
Rob Findley
6ea7a6c653 mcp: simplify and fix the streamable client transport
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'.
2025-08-08 12:38:43 -04:00
Rob Findley
f4a9396942 mcp: fix a bug causing premature termination of streams
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
2025-08-05 12:14:02 -04:00
Sam Thanawalla
a9a503f74a
mcp/streamable: add persistent SSE GET listener (#206)
This CL adds the optional persistent SSE GET listener as specified in
section 2.2.
https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#listening-for-messages-from-the-server

This enables server initiated SSE streams.

For #10
2025-07-30 17:00:50 -04:00
Robert Findley
64493be392
mcp: reject requests with bad IDs (#202)
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 #194
Fixes #197
2025-07-30 12:26:40 -04:00
Jonathan Amsterdam
3ac4ca9c5b
mcp: streamable.go: clarifications (#189)
Clarify or remove TODOs.

Make some code clearer.
2025-07-29 10:49:16 -04:00
Jonathan Amsterdam
64b5b9125e
mcp: support application/json in streamable client (#181)
The client handles POST responses with content type application/json.

For: #10
Fixes #129
2025-07-29 10:28:49 -04:00
Jonathan Amsterdam
a5aa370ea0
mcp: various cleanups to streamable code (#184)
- 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
2025-07-28 13:58:37 -04:00
Jonathan Amsterdam
86f71fcb70
mcp: add stream type (#171)
Consolidate several maps into a single struct.

Simplifies the code, for the most part.
2025-07-28 10:27:02 -04:00
Jonathan Amsterdam
c879ac3c61
mcp: gracefully handle a nil server in handlers (#164)
If the getServer function passed to NewSSEHandler or
NewStreamableHTTPHandler returns nil, serve a 400 instead of panicking.

Fixes #161.
2025-07-24 10:35:14 -04:00
Jonathan Amsterdam
ea6162ca1c
mcp: incorporate EventStore into StreamableServerTransport (#156)
Use the EventStore interface to implement resumption on the server side.
2025-07-23 13:42:50 -04:00
Sam Thanawalla
a911cd0ffd
mcp/streamable: add resumability for the Streamable transport (#133)
This CL implements a retry mechanism to resume SSE streams to recover
from network failures.

For #10
2025-07-22 15:58:29 -04:00
Jonathan Amsterdam
1465442b25
mcp: add EventStore (#152)
Introduce an EventStore interface to store events for resumable streams.

Provide an in-memory implementation.

Still to do: connect to streaming transports.

For #10
2025-07-22 10:21:05 -04:00
Jonathan Amsterdam
a519c182e8
mcp: export Event (#145)
Export the Event type in preparation for providing user-definable
storage for resumable streams.

Also, move event code to a separate file.

This PR is just renaming and code motion.
2025-07-21 10:54:59 -04:00
Robert Findley
a26e1dc253
mcp: fail when streamable http client encounters unsupported content (#130)
The streamable HTTP client is not fully implemented, but did not fail
with meaningful error messages when encountering unsupported content
types from other servers. This may be straightforward to fix, but for
now at least provide a meaningful error message.

For modelcontextprotocol/go-sdk#129.
2025-07-14 10:02:33 -04:00
Jonathan Amsterdam
bfa5e30cd1
jsonrpc: package with JSON-RPC symbols (#117)
Move the aliases to the internal/jsonrpc2 package into their own
package.

Fixes #115.
2025-07-10 14:33:23 -04:00
Jonathan Amsterdam
c037ba51d2
mcp: negotiate protocol version (#112)
Negotiate the protocol version properly, and set the header on HTTP
transports.

We follow the logic of the Typescript SDK.

- On initialization, the client sends the latest version it can support,
and accepts the version that the server returns if the client supports
it. If not, the connection fails.

- The server accepts the client's version unless it doesn't support it,
in which case it replies with its latest version.

Fixes #103.
2025-07-10 11:06:55 -04:00
cryo
06ae01b141
mcp/streamable: fix typos (#83)
&emsp; This PR updates the comments for
`StreamableServerTransport.streamRequests` to correct typos and improve
clarity.
2025-07-02 12:38:25 -04:00
Jonathan Amsterdam
9b6327b3b7
mcp: add Session.ID (#67)
Support retrieving the session ID from client and server sessions.

Fixes #65.
2025-07-01 13:49:23 -04:00
SADIK KUZU
032c62c8ad
Fix typos (#66)
This pull request addresses multiple minor documentation and code
comment fixes to correct typos and improve clarity. These changes do not
introduce functional modifications but enhance readability and
professionalism.

### Documentation Fixes:

* Corrected a typo in the "Security Considerations" section header in
`design/design.md`. ("Securty" → "Security")
* Fixed spelling errors in the "Governance and Community" section of
`design/design.md`. ("accomodating" → "accommodating")
* Updated the "Proposals" section in `design/design.md` to fix a typo.
("accomodate" → "accommodate")

### Code Comment Fixes:

* Corrected a typo in a comment in `mcp/logging.go`. ("LoggingMesssage"
→ "LoggingMessage")
* Fixed a typo in a comment in `mcp/streamable.go`. ("priviledged" →
"privileged")

<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->

This PR is for fixing some typos in the repo

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
by code diff

## Breaking Changes
<!-- Will users need to update their code or configurations? -->
N/A

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
2025-06-30 08:11:04 -04:00
Rob Findley
9797d0f3d7 internal/mcp: make parseEventID robust, and add a test
Address comments on CL 682555 about using sscanf for parseEventID,
adding a test for parseEventID and formatEventID.

Change-Id: Ie31c5431c33829eaf244a5927da9e07bf5c300b5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/683336
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
2025-06-23 10:15:21 -07:00
Rob Findley
e228d78d19 internal/mcp: provide a customizable http.Client to client transports
Allow customizing the http.Client used for HTTP MCP client transports,
by adding client options structs.

Change-Id: I2297acb136f8d0f7fa70d58cd244a6a81cc89751
Reviewed-on: https://go-review.googlesource.com/c/tools/+/682756
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-06-19 07:22:49 -07:00
Rob Findley
4012cc3d2e internal/mcp: use JSONRPC aliases consistently
Use JSONRPCMessage, JSONRPCRequest, and JSONRPCResponse aliases
throughout the internal/mcp package, so that no jsonrpc2 names are
exposed in the public API.

Change-Id: Ibce794907538706448c694eafccf599a5892ab17
Reviewed-on: https://go-review.googlesource.com/c/tools/+/682796
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
2025-06-19 06:55:37 -07:00
Rob Findley
1fead0ba64 internal/mcp: rename Stream->Connection
After implementing the Streamable transport, it is apparent to me that
the name 'Stream' is confusing, as it can refer either to a stream in
the streamable transport, or to a JSON-RPC stream.

But the name 'Connection' is available, and a natural name for the
result of the Transport.Connect interface method. Therefore, rename
Stream to Connection, and update the streamable implementation to refer
to its HTTP connections as 'streams', consistent with other SDKs.

Change-Id: Ica55f84caabcbfa5a2fe029cc0c2db57da574875
Reviewed-on: https://go-review.googlesource.com/c/tools/+/682376
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
2025-06-17 17:55:11 -07:00
Rob Findley
d2f2f87bbe internal/mcp: implement streamable HTTP transport
Add an (almost) complete implementation of the streamable HTTP
transport, including:
- A StreamableHTTPHandler for serving sessions.
- A StreamableHTTPServerTransport that supports session resumption.
- A minimal StreamableHTTPClientTransport.

Additionally, include the following minor changes:
- Refactor SSE event reading to use an iterator.
- Start to to aliases for JSON-RPC types (incomplete)
- Fix handling for null tool arguments (again)

Still to do:
- The streamable client does not support retry/resumption, or the
  side-channel GET requests.
- Support configurable session and event retention (garbage collection).
- Expose the HTTP client used for HTTP requests (for auth).

Change-Id: Ieba216ef901169b4660d601386fa0c7c7bb4c4b2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/667515
Reviewed-by: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-06-17 17:54:33 -07:00