Commit graph

367 commits

Author SHA1 Message Date
Jonathan Amsterdam
e3e8aaf024
mcp: align PromptHandler args with other handlers (#348)
The second arg is a GetPromptRequest.

Fixes #300.
2025-08-22 10:44:05 -04:00
Rob Findley
5fd06ae8b7 mcp: unexport InitializeClientRequest
This request is synthetic, and should not be observed by the user.
Unexport it and rename InitializeServerRequest to just
InitializeRequest.
2025-08-22 10:40:57 -04:00
Kartik Verma
c058c6a93b
feat/Issue-13: elicitation support (#188)
fixes: #13 

 Implements elicitation functionality from MCP 2025-06-18 specification.

 Changes
 - Add ElicitParams and ElicitResult protocol types 
- Add ServerSession.Elicit() method and ClientOptions.ElicitationHandler
 - Schema validation enforces top-level properties only
 - Support for accept/decline/cancel actions with progress tokens

Tests
 - Integration test in TestEndToEnd
 - Schema validation, error handling, and capability declaration tests
2025-08-22 09:29:51 -04:00
Jonathan Amsterdam
62db914082
mcp: export ToolFor (#347)
This is needed so clients can modify tool schemas, and not merely to
wrap ToolHandlers.

(A consequence of returning a copy of Tool instead of modifying it.)
2025-08-21 17:54:17 -04:00
Robert Findley
dd8e3af18d
README: soften the warning, and reference roadmap (#345)
We can warn a little less loudly against using the SDK.
2025-08-21 17:41:38 -04:00
Jonathan Amsterdam
bf79d784b9
mcp: remove references to ClientRequest[T] for concrete T (#343)
See related PR about ServerRequest[T].
2025-08-21 15:24:58 -04:00
Robert Findley
d8e18b34cf
mcp: fix bugs handling pointer types (#344)
Some bad merging in #338 led to a bad bug in the handling of structured
output for pointer output types. Add a conformance test and fix the bug.
2025-08-21 14:43:22 -04:00
Jonathan Amsterdam
6378df6f24
all: remove ServerRequest[T] for concrete T (#342)
Replace all occurrences of ServerRequest[*CallToolParams] and other
concrete instantiations with CallToolRequest and the like.

Make the XXXRequest types aliases, to preserve the convenience of
generics for the internal machinery (see shared.go, for example.)

I will expand the aliases in a followup PR.
2025-08-21 12:10:53 -04:00
Robert Findley
43ad1eb8b4
mcp: treat pointers equivalently to non-pointers when deriving schema
As reported in #199 and #200, the fact that we return a possibly "null"
schema for pointer types breaks various clients, which expect schemas to
be of type "object". This is an unfortunate footgun. For now, assume
that the user wants us to treat pointers equivalently to non-pointers.
If we want to change this behavior in the future, we can do so behind an
option.

+ a test

Also fix the handling of nil results in the case where the output schema
is non-nil: we must provide structured content in this case. (This was
causing the test to fail).

Fixes #199
Fixes #200
2025-08-21 10:57:28 -04:00
Jonathan Amsterdam
5b1f328fd4
mcp: uncomment test, change struct{} to any (#341)
Address comments on #325.
2025-08-21 10:18:52 -04:00
Jonathan Amsterdam
32cf71de3a
mcp: change tool handler design (#325)
Change the design of tool handlers by removing genericity from the common path.
- `ToolHandler` gets the args as a `json.RawMessage`. Using Server.AddTool does no unmarshaling or schema validation.
- `ToolHandlerFor`, installed with `AddTool`, are the only generic pieces.

TODO:
uncomment and fix some tests
2025-08-20 19:36:34 -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
Hongxiang Jiang
3f10c19b45
mcp: replace type struct{} with any for user-defined fields (#334)
This change adopts a clearer convention:
- any is used for fields where the structure is defined by the client or
server implementer.
- an named empty struct type is the place holder type to be defined by
the MCP spec in the future. 

The "Experimental" fields in "ClientCapabilities" and
"ServerCapabilities" now use "map[string]any". This allows clients and
servers to negotiate custom capabilities that contain complex data,
rather than being restricted to a simple on/off flag.

The "Metadata" field in "CreateMessageParams" was also changed to "any"
to properly serve its purpose as a flexible container for server-defined
data.
2025-08-20 15:01:44 -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
Sam Thanawalla
48abccbf6b
mcp/server: expose InitializeParams to ServerSession (#336)
This CL enables the server session to see what capabilities the client
has by introducing the InitializeParams() function.

This CL also adds a test to ensure ClientCapabilities is accurate.

Fixes #141
2025-08-20 14:39:17 -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
Shusaku Yasoda
719f68d0bf
improve error handling for tools and server methods (#311)
- Fix tool error handling to properly distinguish between protocol
errors and tool execution errors
- Return structured JSON-RPC errors directly for protocol-level issues
- Embed regular tool errors in CallToolResult as per MCP specification
- Improve server error responses for unknown prompts and tools
- Add comprehensive tests for both structured and regular error handling
2025-08-20 12:31:11 -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
Rob Findley
0eb03e0d76 mcp: factor out reusable functions from TestStreamableServerTransport
The next CL will test stateless and distributable server transport
configurations, using the HTTP testing strategy of
TestStreamableServerTransport.
2025-08-20 09:59:52 -04:00
cryo
732b97fdaf
mcp: add syntax and scheme validation to AddResourceTemplate (#253)
Add template validation and scheme check in `AddResourceTemplate`.
2025-08-19 17:43:26 -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
cryo
112ca4e3b1 mcp: Ensure keepalive goroutine is started exactly once 2025-08-19 09:46:54 -04:00
Jonathan Amsterdam
52734fd414
mcp: rename logging symbols (#321)
Fixes #279.
2025-08-19 07:49:56 -04:00
Matt L
75f9999590
feat(mcp): export initialized result in client session (#320)
Export ServerCapabilities and related subtypes and update protocol,
server, and tests to use exported types. Add InitializeResult method on
ClientSession for accessing the initialize result from sessions.

Fixes: https://github.com/modelcontextprotocol/go-sdk/issues/166

Co-authored-by: Shashank Pachava <spachava753@gmail.com>
2025-08-18 14:49: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
Manuel Martinez
eb5eb06341
ci: add go vet to lint step (#316)
- Adds `go vet` call to the lint step in GA workflow.
- Enforces a compatible `go-version` with the minimum in the matrix for
the `test` step.
- Fixes some minor `.yml` formatting

Fixes #281

---

There's some discussion in #281 about adding
[staticcheck](https://staticcheck.dev/docs/) as well but when I ran it,
it found some hits (see below) that are presumably not severe, and I
wouldn't want to block deployments before getting some feedback from
maintainers.

```
internal/jsonrpc2/conn.go:695:4: this value of writeErr is never used (SA4006)
internal/jsonrpc2/conn.go:697:5: this value of err is never used (SA4006)
internal/jsonrpc2/conn.go:700:4: this value of err is never used (SA4006)
mcp/mcp_test.go:501:2: var resource3 is unused (U1000)
mcp/mcp_test.go:509:5: var embeddedResources is unused (U1000)
mcp/mcp_test.go:513:6: func handleEmbeddedResource is unused (U1000)
mcp/streamable.go:124:3: unnecessary assignment to the blank identifier (S1005)
mcp/streamable.go:291:2: field lastStreamID is unused (U1000)
mcp/streamable.go:293:2: field opts is unused (U1000)
mcp/streamable_test.go:542:29: argument ctx is overwritten before first use (SA4009)
```
2025-08-18 12:11:25 -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
54c99810f6 mcp: add a test for streamable accept headers
Add a test following up on the fix for #290.
2025-08-14 15:42:57 -04:00
Tim Gossett
cfa5c1d802
mcp: check that UnmarshalJSON methods for Content don't panic on nil
Introduces a new test file content_nil_test.go which verifies that UnmarshalJSON methods for various Content types do not panic when unmarshaling onto nil pointers.
Adds a nil check in contentFromWire function to guard against a nil wire.Content parameter.
Tests cover different scenarios, including valid and invalid content types, as well as cases with empty or missing content fields.

For #205
2025-08-14 14:27:25 -04:00
Kamdyn Shaeffer
16f2857700
mcp/tool: duplicate tools should not error (#295)
This CL causes the dup to fail.
2025-08-14 13:38:31 -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
87f222477b
mcp: add a test for streamable sampling during a tool call
Add a test that attempts (and fails) to reproduce the bug reported in
issue #285.

For #285
2025-08-13 10:11:01 -04:00
Jonathan Amsterdam
6e03217c83
mcp: introduce Requests (#267)
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.
2025-08-12 17:49:50 -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
Sam Thanawalla
679f777825
Update pull_request_template.md (#287)
pull_request_template: update template to reflect correct form
2025-08-12 14:14:57 -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
Robert Findley
119a58314d
mcp: address some comments from #234 (#248)
I inadvertently merged #234 without pushing my latest patches. This CL
adds the missing changes.
2025-08-11 12:45:14 -04:00
Kartik Verma
cb27392b2d
.gitignore: add
Add `.gitignore` for some common files.
2025-08-11 08:33:50 -04:00
Jonathan Amsterdam
be1ddf5f80 jsonrpc: paper over race in jsonrpc2 test
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.
2025-08-09 16:00:45 -04:00
Jonathan Amsterdam
cccc086518 mcp: clarify Server.Run use
Make it clear that it's not needed for an HTTP server.
2025-08-09 15:59:26 -04:00
Robert Findley
4e413da771
mcp: rename mcp{Params,Result} to is{Params,Result} (#270)
For some reason, when making these marker methods, the standard naming
convention escaped me.
2025-08-09 11:06:59 -04:00
Robert Findley
388e000aa2
.github: update go 1.25 builds to rc3 (#271)
Now that rc3 is out, update our 1.25 builds to use it.
2025-08-09 11:04:44 -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
c03cd684ab internal/jsonrpc2: allow asynchronous writes
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.
2025-08-08 12:38:43 -04:00