From 7eadde0a23e458915ac7b68a6b1e14cdd971df1b Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Wed, 14 May 2025 18:28:24 +0000 Subject: [PATCH] internal/mcp/design: further clean-up Do a pass through the design doc, adding a bit more clean-up and commentary. Additionally, update ServerTool and ServerPrompt to be consistent with the design. Change-Id: I1b75c4690d86fe8946d512cb36a1fc2938b3c883 Reviewed-on: https://go-review.googlesource.com/c/tools/+/672576 Reviewed-by: Jonathan Amsterdam Reviewed-by: Sam Thanawalla Auto-Submit: Robert Findley LUCI-TryBot-Result: Go LUCI --- design/design.md | 237 +++++++++++++++++++++++++++------------------ mcp/client.go | 2 +- mcp/mcp.go | 8 +- mcp/prompt.go | 18 ++-- mcp/prompt_test.go | 4 +- mcp/server.go | 8 +- mcp/tool.go | 8 +- mcp/tool_test.go | 4 +- 8 files changed, 168 insertions(+), 121 deletions(-) diff --git a/design/design.md b/design/design.md index 21b43ac..be6b4fe 100644 --- a/design/design.md +++ b/design/design.md @@ -4,10 +4,12 @@ This document discusses the design of a Go SDK for the [model context protocol](https://modelcontextprotocol.io/specification/2025-03-26). It is intended to seed a GitHub discussion about the official Go MCP SDK. -The github.com/modelcontextprotocol/go-sdk/mcp package contains a prototype that we built -to explore the MCP design space. Many of the ideas there are present in this -document. However, we have diverged and expanded on the APIs of that prototype, -and this document should be considered canonical. +The +[github.com/modelcontextprotocol/go-sdk/mcp](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp@master) +package contains a prototype that we built to explore the MCP design space. +Many of the ideas there are present in this document. However, we have diverged +from and expanded on the APIs of that prototype, and this document should be +considered canonical. ## Similarities and differences with mark3labs/mcp-go @@ -24,9 +26,7 @@ not viable for an official SDK. In yet others, we simply think there is room for API refinement, and we should take this opportunity to consider our options. Therefore, we wrote this document as though it were proposing a new implementation. Nevertheless, much of the API discussed here originated from or -was inspired by mcp-go and other unofficial SDKs, and if the consensus of this -discussion is close enough to mcp-go or any other unofficial SDK, we can start -from a fork. +was inspired by mcp-go and other unofficial SDKs. Since mcp-go is so influential and popular, we have noted significant differences from its API in the sections below. Although the API here is not @@ -58,8 +58,7 @@ SDK. An official SDK should aim to be: In the sections below, we visit each aspect of the MCP spec, in approximately the order they are presented by the [official spec](https://modelcontextprotocol.io/specification/2025-03-26) -For each, we discuss considerations for the Go implementation. In many cases an -API is suggested, though in some there may be open questions. +For each, we discuss considerations for the Go implementation, and propose a Go API. ## Foundations @@ -68,22 +67,26 @@ API is suggested, though in some there may be open questions. In the sections that follow, it is assumed that most of the MCP API lives in a single shared package, the `mcp` package. This is inconsistent with other MCP SDKs, but is consistent with Go packages like `net/http`, `net/rpc`, or -`google.golang.org/grpc`. +`google.golang.org/grpc`. We believe that having a single package aids +discoverability in package documentation and in the IDE. Furthermore, it avoids +somwhat arbitrary decisions about package structure that may be rendered +inaccurate by future evolution of the spec. Functionality that is not directly related to MCP (like jsonschema or jsonrpc2) belongs in a separate package. -Therefore, this is the package layout. `module.path` is a placeholder for the -final module path of the mcp module +Therefore, this is the package layout, assuming +github.com/modelcontextprotocol/go-sdk as the module path. -- `module.path/mcp`: the bulk of the user facing API -- `module.path/jsonschema`: a jsonschema implementation, with validation -- `module.path/internal/jsonrpc2`: a fork of x/tools/internal/jsonrpc2_v2 +- `github.com/modelcontextprotocol/go-sdk/mcp`: the bulk of the user facing API +- `github.com/modelcontextprotocol/go-sdk/jsonschema`: a jsonschema implementation, with validation +- `github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2`: a fork of x/tools/internal/jsonrpc2_v2 The JSON-RPC implementation is hidden, to avoid tight coupling. As described in the next section, the only aspects of JSON-RPC that need to be exposed in the SDK are the message types, for the purposes of defining custom transports. We -can expose these types from the `mcp` package via aliases or wrappers. +can expose these types by promoting them from the `mcp` package using aliases +or wrappers. **Difference from mcp-go**: Our `mcp` package includes all the functionality of mcp-go's `mcp`, `client`, `server` and `transport` packages. @@ -98,12 +101,15 @@ defines two transports: - **streamable http**: communication over a relatively complicated series of text/event-stream GET and HTTP POST requests. -Additionally, version `2024-11-05` of the spec defined a simpler HTTP transport: +Additionally, version `2024-11-05` of the spec defined a simpler (yet stateful) +HTTP transport: - **sse**: client issues a hanging GET request and receives messages via `text/event-stream`, and sends messages via POST to a session endpoint. -Furthermore, the spec [states](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#custom-transports) that it must be possible for users to define their own custom transports. +Furthermore, the spec +[states](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#custom-transports) +that it must be possible for users to define their own custom transports. Given the diversity of the transport implementations, they can be challenging to abstract. However, since JSON-RPC requires a bidirectional stream, we can @@ -124,13 +130,12 @@ type Stream interface { } ``` -Methods accept a Go `Context` and return an `error`, -as is idiomatic for APIs that do I/O. +Methods accept a Go `Context` and return an `error`, as is idiomatic for APIs +that do I/O. -A `Transport` is something that connects a logical JSON-RPC -stream, and nothing more. Streams must be closeable in order to -implement client and server shutdown, and therefore conform to the `io.Closer` -interface. +A `Transport` is something that connects a logical JSON-RPC stream, and nothing +more. Streams must be closeable in order to implement client and server +shutdown, and therefore conform to the `io.Closer` interface. Other SDKs define higher-level transports, with, for example, methods to send a notification or make a call. Those are jsonrpc2 operations on top of the @@ -142,6 +147,16 @@ language server `gopls`, which we propose to fork for the MCP SDK. It already handles concerns like client/server connection, request lifecycle, cancellation, and shutdown. +**Differences from mcp-go**: The Go team has a battle-tested JSON-RPC +implementation that we use for gopls, our Go LSP server. We are using the new +version of this library as part of our MCP SDK. It handles all JSON-RPC 2.0 +features, including cancellation. + +The `Transport` interface here is lower-level than that of mcp-go, but serves a +similar purpose. We believe the lower-level interface is easier to implement. + +#### stdio transports + In the MCP Spec, the **stdio** transport uses newline-delimited JSON to communicate over stdin/stdout. It's possible to model both client side and server side of this communication with a shared type that communicates over an @@ -178,13 +193,20 @@ func NewStdIOTransport() *StdIOTransport func (t *StdIOTransport) Connect(context.Context) (Stream, error) ``` +#### HTTP transports + The HTTP transport APIs are even more asymmetrical. Since connections are initiated via HTTP requests, the client developer will create a transport, but the server developer will typically install an HTTP handler. Internally, the -HTTP handler will create a transport for each new client connection. +HTTP handler will create a logical transport for each new client connection. Importantly, since they serve many connections, the HTTP handlers must accept a -callback to get an MCP server for each new session. +callback to get an MCP server for each new session. As described below, MCP +servers can optionally connect to multiple clients. This allows customization +of per-session servers: if the MCP server is stateless, the user can return the +same MCP server for each connection. On the other hand, if any per-session +customization is required, it is possible by returning a different `Server` +instance for each connection. ```go // SSEHTTPHandler is an http.Handler that serves SSE-based MCP sessions as defined by @@ -212,7 +234,7 @@ see [Middleware](#Middleware) below. By default, the SSE handler creates messages endpoints with the `?sessionId=...` query parameter. Users that want more control over the management of sessions and session endpoints may write their own handler, and -create `SSEServerTransport` instances themselves, for incoming GET requests. +create `SSEServerTransport` instances themselves for incoming GET requests. ```go // A SSEServerTransport is a logical SSE session created through a hanging GET @@ -233,6 +255,9 @@ type SSEServerTransport struct { /* ... */ } // The transport is itself an [http.Handler]. It is the caller's responsibility // to ensure that the resulting transport serves HTTP requests on the given // session endpoint. +// +// Most callers should instead use an [SSEHandler], which transparently handles +// the delegation to SSEServerTransports. func NewSSEServerTransport(endpoint string, w http.ResponseWriter) *SSEServerTransport // ServeHTTP handles POST requests to the transport endpoint. @@ -282,7 +307,27 @@ func NewStreamableClientTransport(url string) *StreamableClientTransport { func (*StreamableClientTransport) Connect(context.Context) (Stream, error) ``` -Finally, we also provide a couple of transport implementations for special scenarios. +**Differences from mcp-go**: In mcp-go, server authors create an `MCPServer`, +populate it with tools, resources and so on, and then wrap it in an `SSEServer` +or `StdioServer`. Users can manage their own sessions with `RegisterSession` +and `UnregisterSession`. Rather than use a server constructor to get a distinct +server for each connection, there is a concept of a "session tool" that +overlays tools for a specific session. + +We find the similarity in names among the three server types to be confusing, +and we could not discover any uses of the session methods in the open-source +ecosystem. Furthermore, we believe that a server factor (`getServer`) provides +equivalent functionality as the per-session logic of mcp-go, with a smaller API +surface and fewer overlapping concepts. + +Additionally, individual handlers and transports here have a minimal API, and +do not expose internal details. Customization of things like handlers or +session management is intended to be implemented with middleware and/or +compositional patterns. + +#### Other transports + +We also provide a couple of transport implementations for special scenarios. An InMemoryTransport can be used when the client and server reside in the same process. A LoggingTransport is a middleware layer that logs RPC logs to a desired location, specified as an io.Writer. @@ -302,32 +347,6 @@ type LoggingTransport struct { /* ... */ } func NewLoggingTransport(delegate Transport, w io.Writer) *LoggingTransport ``` -**Differences from mcp-go**: The Go team has a battle-tested JSON-RPC -implementation that we use for gopls, our Go LSP server. We are using the new -version of this library as part of our MCP SDK. It handles all JSON-RPC 2.0 -features, including cancellation. - -The `Transport` interface here is lower-level than that of mcp-go, but serves a -similar purpose. We believe the lower-level interface is easier to implement. - -In mcp-go, server authors create an `MCPServer`, populate it with tools, -resources and so on, and then wrap it in an `SSEServer` or `StdioServer`. These -also use session IDs, which are exposed. Users can manage their own sessions -with `RegisterSession` and `UnregisterSession`. - -We find the similarity in names among the three server types to be confusing, -and we could not discover any uses of the session methods in the open-source -ecosystem. In our design, server authors create a `Server`, and then -connect it to a `Transport`. An `SSEHTTPHandler` manages sessions for -incoming SSE connections, but does not expose them. HTTP handlers accept a -server constructor, rather than Server, to allow for stateful or "per-session" -servers. - -Individual handlers and transports here have a minimal smaller API, and do not -expose internal details. Customization of things like handlers or session -management is intended to be implemented with middleware and/or compositional -patterns. - ### Protocol types Types needed for the protocol are generated from the @@ -373,7 +392,7 @@ type Content struct { ``` **Differences from mcp-go**: these types are largely similar, but our type -generation flattens types rather than using struct embedding. +generator flattens types rather than using struct embedding. ### Clients and Servers @@ -387,13 +406,18 @@ and resources from servers. Additionally, handlers for these features may themselves be stateful, for example if a tool handler caches state from earlier requests in the session. -We believe that in the common case, any change to a client or server, -such as adding a tool, is intended for all its peers. -It is therefore more useful to allow multiple connections from a client, and to -a server. This is similar to the `net/http` packages, in which an `http.Client` -and `http.Server` each may handle multiple unrelated connections. When users -add features to a client or server, all connected peers are notified of the -change. +We believe that in the common case, any change to a client or server, such as +adding a tool, is intended for all its peers. It is therefore more useful to +allow multiple connections from a client, and to a server. This is similar to +the `net/http` packages, in which an `http.Client` and `http.Server` each may +handle multiple unrelated connections. When users add features to a client or +server, all connected peers are notified of the change. + +Supporting multiple connections to servers (and from clients) still allows for +stateful components, as it is up to the user to decide whether or not to create +distinct servers/clients for each connection. For example, if the user wants to +create a distinct server for each new connection, they can do so in the +`getServer` factory passed to transport handlers. Following the terminology of the [spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management), @@ -423,6 +447,7 @@ func (*ClientSession) Client() *Client func (*ClientSession) Close() error func (*ClientSession) Wait() error // Methods for calling through the ClientSession are described below. +// For example: ClientSession.ListTools. type Server struct { /* ... */ } func NewServer(name, version string, opts *ServerOptions) *Server @@ -436,6 +461,7 @@ func (*ServerSession) Server() *Server func (*ServerSession) Close() error func (*ServerSession) Wait() error // Methods for calling through the ServerSession are described below. +// For example: ServerSession.ListRoots. ``` Here's an example of these APIs from the client side: @@ -472,17 +498,18 @@ session until the client disconnects: func (*Server) Run(context.Context, Transport) ``` -**Differences from mcp-go**: the Server APIs are very similar to mcp-go, -though the association between servers and transports is different. In -mcp-go, a single server is bound to what we would call an `SSEHTTPHandler`, -and reused for all client sessions. As discussed above, the transport -abstraction here is differentiated from HTTP serving, and the `Server.Connect` -method provides a consistent API for binding to an arbitrary transport. Servers -here do not have methods for sending notifications or calls, because they are -logically distinct from the `ServerSession`. In mcp-go, servers are `n:1`, -but there is no abstraction of a server session: sessions are addressed in -Server APIs through their `sessionID`: `SendNotificationToAllClients`, -`SendNotificationToClient`, `SendNotificationToSpecificClient`. +**Differences from mcp-go**: the Server APIs are similar to mcp-go, though the +association between servers and transports is different. In mcp-go, a single +server is bound to what we would call an `SSEHTTPHandler`, and reused for all +sessions. Per-session behavior is implemented though a 'session tool' overlay. +As discussed above, the transport abstraction here is differentiated from HTTP +serving, and the `Server.Connect` method provides a consistent API for binding +to an arbitrary transport. Servers here do not have methods for sending +notifications or calls, because they are logically distinct from the +`ServerSession`. In mcp-go, servers are `n:1`, but there is no abstraction of a +server session: sessions are addressed in Server APIs through their +`sessionID`: `SendNotificationToAllClients`, `SendNotificationToClient`, +`SendNotificationToSpecificClient`. The client API here is different, since clients and client sessions are conceptually distinct. The `ClientSession` is closer to mcp-go's notion of @@ -503,27 +530,25 @@ func (*ClientSession) ListTools(context.Context, *ListToolsParams) (*ListToolsRe ``` Our SDK has a method for every RPC in the spec, and except for `CallTool`, -their signatures all share this form. -We do this, rather than providing more convenient shortcut signatures, -to maintain backward compatibility if the spec makes backward-compatible changes -such as adding a new property to the request parameters +their signatures all share this form. We do this, rather than providing more +convenient shortcut signatures, to maintain backward compatibility if the spec +makes backward-compatible changes such as adding a new property to the request +parameters (as in [this commit](https://github.com/modelcontextprotocol/modelcontextprotocol/commit/2fce8a077688bf8011e80af06348b8fe1dae08ac), for example). -To avoid boilerplate, we don't repeat this -signature for RPCs defined in the spec; readers may assume it when we mention a -"spec method." +To avoid boilerplate, we don't repeat this signature for RPCs defined in the +spec; readers may assume it when we mention a "spec method." `CallTool` is the only exception: for convenience, it takes the tool name and -arguments, with an options struct for additional request fields. -Our SDK has a method for every RPC in the spec, and their signatures all share -this form. To avoid boilerplate, we don't repeat this signature for RPCs -defined in the spec; readers may assume it when we mention a "spec method." +arguments, with an options struct for additional request fields. See the +section on Tools below for details. -Why do we use params instead of the full request? JSON-RPC requests consist of a method -name and a set of parameters, and the method is already encoded in the Go method name. -Technically, the MCP spec could add a field to a request while preserving backward -compatibility, which would break the Go SDK's compatibility. But in the unlikely event -that were to happen, we would add that field to the Params struct. +Why do we use params instead of the full JSON-RPC request? As much as possible, +we endeavor to hide JSON-RPC details when they are not relevant to the business +logic of your client or server. In this case, the additional information in the +JSON-RPC request is just the request ID and method name; the request ID is +irrelevant, and the method name is implied by the name of the Go method +providing the API. We believe that any change to the spec that would require callers to pass a new a parameter is not backward compatible. Therefore, it will always work to pass @@ -595,9 +620,9 @@ code. ```go type JSONRPCError struct { - Code int64 `json:"code"` - Message string `json:"message"` - Data json.RawMessage `json:"data,omitempty"` + Code int64 `json:"code"` + Message string `json:"message"` + Data json.RawMessage `json:"data,omitempty"` } ``` @@ -658,9 +683,9 @@ func (c *ServerSession) Ping(ctx context.Context, *PingParams) error Additionally, client and server sessions can be configured with automatic keepalive behavior. If the `KeepAlive` option is set to a non-zero duration, -it defines an -interval for regular "ping" requests. If the peer fails to respond to pings -originating from the keepalive check, the session is automatically closed. +it defines an interval for regular "ping" requests. If the peer fails to +respond to pings originating from the keepalive check, the session is +automatically closed. ```go type ClientOptions struct { @@ -847,6 +872,19 @@ directly with assignment or a struct literal. Client sessions can call the spec method `ListTools` or an iterator method `Tools` to list the available tools. +As mentioned above, the client session method `CallTool` has a non-standard +signature, so that `CallTool` can handle the marshalling of tool arguments: the +type of `CallToolParams.Arguments` is `json.RawMessage`, to delegate +unmarshalling to the tool handler. + +```go +func (c *ClientSession) CallTool(ctx context.Context, name string, args map[string]any, opts *CallToolOptions) (_ *CallToolResult, err error) + +type CallToolOptions struct { + ProgressToken any // string or int +} +``` + **Differences from mcp-go**: using variadic options to configure tools was significantly inspired by mcp-go. However, the distinction between `ToolOption` and `SchemaOption` allows for recursive application of schema options. @@ -972,6 +1010,11 @@ s.AddResources(&mcp.ServerResource{ Server sessions also support the spec methods `ListResources` and `ListResourceTemplates`, and the corresponding iterator methods `Resources` and `ResourceTemplates`. +**Differences from mcp-go**: for symmetry with tools and prompts, we use +`AddResources` rather than `AddResource`. Additionally, the `ResourceHandler` +returns a `ReadResourceResult`, rather than just its content, for compatibility +with future evolution of the spec. + #### Subscriptions ClientSessions can manage change notifications on particular resources: @@ -1105,3 +1148,7 @@ more pages exist. In addition to the `List` methods, the SDK provides an iterator method for each list operation. This simplifies pagination for clients by automatically handling the underlying pagination logic. See [Iterator Methods](#iterator-methods) above. + +**Differences with mcp-go**: the PageSize configuration is set with a +configuration field rather than a variadic option. Additionally, this design +proposes pagination by default, as this is likely desirable for most servers. diff --git a/mcp/client.go b/mcp/client.go index 4a7694a..a4ea94f 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -208,7 +208,7 @@ func (c *ClientSession) CallTool(ctx context.Context, name string, args map[stri // NOTE: the following struct should consist of all fields of callToolParams except name and arguments. -// CallToolOptions contains options to [Client.CallTools]. +// CallToolOptions contains options to [ClientSession.CallTool]. type CallToolOptions struct { ProgressToken any // string or int } diff --git a/mcp/mcp.go b/mcp/mcp.go index c85fe67..b2b71f1 100644 --- a/mcp/mcp.go +++ b/mcp/mcp.go @@ -10,13 +10,13 @@ // To get started, create either a [Client] or [Server], and connect it to a // peer using a [Transport]. The diagram below illustrates how this works: // -// Client Server -// ⇅ (jsonrpc2) ⇅ -// Client Transport ⇄ Server Transport ⇄ ServerSession +// Client Server +// ⇅ (jsonrpc2) ⇅ +// ClientSession ⇄ Client Transport ⇄ Server Transport ⇄ ServerSession // // A [Client] is an MCP client, which can be configured with various client // capabilities. Clients may be connected to a [Server] instance -// using the [Client.Start] method. +// using the [Client.Connect] method. // // Similarly, a [Server] is an MCP server, which can be configured with various // server capabilities. Servers may be connected to one or more [Client] diff --git a/mcp/prompt.go b/mcp/prompt.go index 6cb008d..b17dbfc 100644 --- a/mcp/prompt.go +++ b/mcp/prompt.go @@ -20,8 +20,8 @@ type PromptHandler func(context.Context, *ServerSession, map[string]string) (*Ge // A Prompt is a prompt definition bound to a prompt handler. type ServerPrompt struct { - Definition *Prompt - Handler PromptHandler + Prompt *Prompt + Handler PromptHandler } // NewPrompt is a helper to use reflection to create a prompt for the given @@ -41,7 +41,7 @@ func NewPrompt[TReq any](name, description string, handler func(context.Context, panic(fmt.Sprintf("handler request type must be a struct")) } prompt := &ServerPrompt{ - Definition: &Prompt{ + Prompt: &Prompt{ Name: name, Description: description, }, @@ -54,7 +54,7 @@ func NewPrompt[TReq any](name, description string, handler func(context.Context, if prop.Type != "string" { panic(fmt.Sprintf("handler type must consist only of string fields")) } - prompt.Definition.Arguments = append(prompt.Definition.Arguments, &PromptArgument{ + prompt.Prompt.Arguments = append(prompt.Prompt.Arguments, &PromptArgument{ Name: name, Description: prop.Description, Required: required[name], @@ -95,16 +95,16 @@ func (s promptSetter) set(p *ServerPrompt) { s(p) } // Required and Description, and panics when encountering any other option. func Argument(name string, opts ...SchemaOption) PromptOption { return promptSetter(func(p *ServerPrompt) { - i := slices.IndexFunc(p.Definition.Arguments, func(arg *PromptArgument) bool { + i := slices.IndexFunc(p.Prompt.Arguments, func(arg *PromptArgument) bool { return arg.Name == name }) var arg *PromptArgument if i < 0 { - i = len(p.Definition.Arguments) + i = len(p.Prompt.Arguments) arg = &PromptArgument{Name: name} - p.Definition.Arguments = append(p.Definition.Arguments, arg) + p.Prompt.Arguments = append(p.Prompt.Arguments, arg) } else { - arg = p.Definition.Arguments[i] + arg = p.Prompt.Arguments[i] } for _, opt := range opts { switch v := opt.(type) { @@ -116,6 +116,6 @@ func Argument(name string, opts ...SchemaOption) PromptOption { panic(fmt.Sprintf("unsupported prompt argument schema option %T", opt)) } } - p.Definition.Arguments[i] = arg + p.Prompt.Arguments[i] = arg }) } diff --git a/mcp/prompt_test.go b/mcp/prompt_test.go index ae2d20a..b706ced 100644 --- a/mcp/prompt_test.go +++ b/mcp/prompt_test.go @@ -46,8 +46,8 @@ func TestNewPrompt(t *testing.T) { }, } for _, test := range tests { - if diff := cmp.Diff(test.want, test.prompt.Definition.Arguments); diff != "" { - t.Errorf("NewPrompt(%v) mismatch (-want +got):\n%s", test.prompt.Definition.Name, diff) + if diff := cmp.Diff(test.want, test.prompt.Prompt.Arguments); diff != "" { + t.Errorf("NewPrompt(%v) mismatch (-want +got):\n%s", test.prompt.Prompt.Name, diff) } } } diff --git a/mcp/server.go b/mcp/server.go index 3b5bac9..d65a767 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -53,8 +53,8 @@ func NewServer(name, version string, opts *ServerOptions) *Server { name: name, version: version, opts: *opts, - prompts: newFeatureSet(func(p *ServerPrompt) string { return p.Definition.Name }), - tools: newFeatureSet(func(t *ServerTool) string { return t.Definition.Name }), + prompts: newFeatureSet(func(p *ServerPrompt) string { return p.Prompt.Name }), + tools: newFeatureSet(func(t *ServerTool) string { return t.Tool.Name }), resources: newFeatureSet(func(r *ServerResource) string { return r.Resource.URI }), } } @@ -170,7 +170,7 @@ func (s *Server) listPrompts(_ context.Context, _ *ServerSession, params *ListPr defer s.mu.Unlock() res := new(ListPromptsResult) for p := range s.prompts.all() { - res.Prompts = append(res.Prompts, p.Definition) + res.Prompts = append(res.Prompts, p.Prompt) } return res, nil } @@ -191,7 +191,7 @@ func (s *Server) listTools(_ context.Context, _ *ServerSession, params *ListTool defer s.mu.Unlock() res := new(ListToolsResult) for t := range s.tools.all() { - res.Tools = append(res.Tools, t.Definition) + res.Tools = append(res.Tools, t.Tool) } return res, nil } diff --git a/mcp/tool.go b/mcp/tool.go index 79a45d1..47d9040 100644 --- a/mcp/tool.go +++ b/mcp/tool.go @@ -17,8 +17,8 @@ type ToolHandler func(context.Context, *ServerSession, *CallToolParams) (*CallTo // A Tool is a tool definition that is bound to a tool handler. type ServerTool struct { - Definition *Tool - Handler ToolHandler + Tool *Tool + Handler ToolHandler } // NewTool is a helper to make a tool using reflection on the given handler. @@ -59,7 +59,7 @@ func NewTool[TReq any](name, description string, handler func(context.Context, * return res, nil } t := &ServerTool{ - Definition: &Tool{ + Tool: &Tool{ Name: name, Description: description, InputSchema: schema, @@ -94,7 +94,7 @@ func (s toolSetter) set(t *ServerTool) { s(t) } func Input(opts ...SchemaOption) ToolOption { return toolSetter(func(t *ServerTool) { for _, opt := range opts { - opt.set(t.Definition.InputSchema) + opt.set(t.Tool.InputSchema) } }) } diff --git a/mcp/tool_test.go b/mcp/tool_test.go index f6239a3..a0cfc76 100644 --- a/mcp/tool_test.go +++ b/mcp/tool_test.go @@ -83,8 +83,8 @@ func TestNewTool(t *testing.T) { }, } for _, test := range tests { - if diff := cmp.Diff(test.want, test.tool.Definition.InputSchema, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" { - t.Errorf("NewTool(%v) mismatch (-want +got):\n%s", test.tool.Definition.Name, diff) + if diff := cmp.Diff(test.want, test.tool.Tool.InputSchema, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" { + t.Errorf("NewTool(%v) mismatch (-want +got):\n%s", test.tool.Tool.Name, diff) } } }