From 0af9c7ba3d3ef4ee497caf0ab011897f2e255bac Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Wed, 9 Jul 2025 13:36:28 -0400 Subject: [PATCH 1/5] mcp: NewClient and NewServer take Implementation Change the name and version arguments to NewClient and NewServer to a *Implementation, to future-proof against the spec. Fixes #109. --- examples/completion/main.go | 2 +- examples/hello/main.go | 2 +- examples/memory/main.go | 2 +- examples/sse/main.go | 4 ++-- internal/readme/client/client.go | 2 +- internal/readme/server/server.go | 2 +- mcp/client.go | 16 +++++++++------- mcp/cmd_test.go | 4 ++-- mcp/example_progress_test.go | 2 +- mcp/mcp_test.go | 28 ++++++++++++++-------------- mcp/protocol.go | 8 ++++---- mcp/server.go | 21 ++++++++++----------- mcp/server_example_test.go | 8 ++++---- mcp/server_test.go | 2 +- mcp/sse_example_test.go | 4 ++-- mcp/sse_test.go | 4 ++-- mcp/streamable_test.go | 8 ++++---- 17 files changed, 60 insertions(+), 59 deletions(-) diff --git a/examples/completion/main.go b/examples/completion/main.go index a24299bc..92a91193 100644 --- a/examples/completion/main.go +++ b/examples/completion/main.go @@ -40,7 +40,7 @@ func main() { // Create the MCP Server instance and assign the handler. // No server running, just showing the configuration. - _ = mcp.NewServer("myServer", "v1.0.0", &mcp.ServerOptions{ + _ = mcp.NewServer(nil, &mcp.ServerOptions{ CompletionHandler: myCompletionHandler, }) diff --git a/examples/hello/main.go b/examples/hello/main.go index 4db20cc8..84cff1e0 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -42,7 +42,7 @@ func PromptHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.GetPromptP func main() { flag.Parse() - server := mcp.NewServer("greeter", "v0.0.1", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) server.AddPrompt(&mcp.Prompt{Name: "greet"}, PromptHi) server.AddResource(&mcp.Resource{ diff --git a/examples/memory/main.go b/examples/memory/main.go index d3d78110..154f4d73 100644 --- a/examples/memory/main.go +++ b/examples/memory/main.go @@ -91,7 +91,7 @@ func main() { kb := knowledgeBase{s: kbStore} // Setup MCP server with knowledge base tools - server := mcp.NewServer("memory", "v0.0.1", nil) + server := mcp.NewServer(nil, nil) mcp.AddTool(server, &mcp.Tool{ Name: "create_entities", Description: "Create multiple new entities in the knowledge graph", diff --git a/examples/sse/main.go b/examples/sse/main.go index c93320ab..99b83e65 100644 --- a/examples/sse/main.go +++ b/examples/sse/main.go @@ -34,10 +34,10 @@ func main() { log.Fatal("http address not set") } - server1 := mcp.NewServer("greeter1", "v0.0.1", nil) + server1 := mcp.NewServer(&mcp.Implementation{Name: "greeter1"}, nil) mcp.AddTool(server1, &mcp.Tool{Name: "greet1", Description: "say hi"}, SayHi) - server2 := mcp.NewServer("greeter2", "v0.0.1", nil) + server2 := mcp.NewServer(&mcp.Implementation{Name: "greeter2"}, nil) mcp.AddTool(server2, &mcp.Tool{Name: "greet2", Description: "say hello"}, SayHi) log.Printf("MCP servers serving at %s", *httpAddr) diff --git a/internal/readme/client/client.go b/internal/readme/client/client.go index 44bc515c..6f2a388f 100644 --- a/internal/readme/client/client.go +++ b/internal/readme/client/client.go @@ -17,7 +17,7 @@ func main() { ctx := context.Background() // Create a new client, with no features. - client := mcp.NewClient("mcp-client", "v1.0.0", nil) + client := mcp.NewClient(nil, nil) // Connect to a server over stdin/stdout transport := mcp.NewCommandTransport(exec.Command("myserver")) diff --git a/internal/readme/server/server.go b/internal/readme/server/server.go index 1fe211ea..7c025412 100644 --- a/internal/readme/server/server.go +++ b/internal/readme/server/server.go @@ -24,7 +24,7 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam func main() { // Create a server with a single tool. - server := mcp.NewServer("greeter", "v1.0.0", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) // Run the server over stdin/stdout, until the client disconnects diff --git a/mcp/client.go b/mcp/client.go index 40d3c792..5b051fda 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -19,8 +19,7 @@ import ( // A Client is an MCP client, which may be connected to an MCP server // using the [Client.Connect] method. type Client struct { - name string - version string + impl *Implementation opts ClientOptions mu sync.Mutex roots *featureSet[*Root] @@ -29,15 +28,18 @@ type Client struct { receivingMethodHandler_ MethodHandler[*ClientSession] } -// NewClient creates a new Client. +// NewClient creates a new [Client]. // // Use [Client.Connect] to connect it to an MCP server. // +// If impl is nil, a default name and version are used, and the title is left empty. // If non-nil, the provided options configure the Client. -func NewClient(name, version string, opts *ClientOptions) *Client { +func NewClient(impl *Implementation, opts *ClientOptions) *Client { + if impl == nil { + impl = &Implementation{Name: "Go MCP SDK Client", Version: "v0.1.0"} + } c := &Client{ - name: name, - version: version, + impl: impl, roots: newFeatureSet(func(r *Root) string { return r.URI }), sendingMethodHandler_: defaultSendingMethodHandler[*ClientSession], receivingMethodHandler_: defaultReceivingMethodHandler[*ClientSession], @@ -118,7 +120,7 @@ func (c *Client) Connect(ctx context.Context, t Transport) (cs *ClientSession, e params := &InitializeParams{ ProtocolVersion: latestProtocolVersion, - ClientInfo: &implementation{Name: c.name, Version: c.version}, + ClientInfo: c.impl, Capabilities: caps, } res, err := handleSend[*InitializeResult](ctx, cs, methodInitialize, params) diff --git a/mcp/cmd_test.go b/mcp/cmd_test.go index bfae0c60..b887d68f 100644 --- a/mcp/cmd_test.go +++ b/mcp/cmd_test.go @@ -32,7 +32,7 @@ func TestMain(m *testing.M) { func runServer() { ctx := context.Background() - server := mcp.NewServer("greeter", "v0.0.1", nil) + server := mcp.NewServer(nil, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) if err := server.Run(ctx, mcp.NewStdioTransport()); err != nil { log.Fatal(err) @@ -87,7 +87,7 @@ func TestServerInterrupt(t *testing.T) { cmd := createServerCommand(t) - client := mcp.NewClient("client", "v0.0.1", nil) + client := mcp.NewClient(nil, nil) session, err := client.Connect(ctx, mcp.NewCommandTransport(cmd)) if err != nil { t.Fatal(err) diff --git a/mcp/example_progress_test.go b/mcp/example_progress_test.go index 902b2347..18086db2 100644 --- a/mcp/example_progress_test.go +++ b/mcp/example_progress_test.go @@ -16,7 +16,7 @@ var nextProgressToken atomic.Int64 // This middleware function adds a progress token to every outgoing request // from the client. func Example_progressMiddleware() { - c := mcp.NewClient("test", "v1", nil) + c := mcp.NewClient(nil, nil) c.AddSendingMiddleware(addProgressToken[*mcp.ClientSession]) _ = c } diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index 70c79b58..51508bdf 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -64,7 +64,7 @@ func TestEndToEnd(t *testing.T) { notificationChans["progress_server"] <- 0 }, } - s := NewServer("testServer", "v1.0.0", sopts) + s := NewServer(nil, sopts) AddTool(s, &Tool{ Name: "greet", Description: "say hi", @@ -125,7 +125,7 @@ func TestEndToEnd(t *testing.T) { notificationChans["progress_client"] <- 0 }, } - c := NewClient("testClient", "v1.0.0", opts) + c := NewClient(nil, opts) rootAbs, err := filepath.Abs(filepath.FromSlash("testdata/files")) if err != nil { t.Fatal(err) @@ -510,7 +510,7 @@ func basicConnection(t *testing.T, config func(*Server)) (*ServerSession, *Clien ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer("testServer", "v1.0.0", nil) + s := NewServer(nil, nil) if config != nil { config(s) } @@ -519,7 +519,7 @@ func basicConnection(t *testing.T, config func(*Server)) (*ServerSession, *Clien t.Fatal(err) } - c := NewClient("testClient", "v1.0.0", nil) + c := NewClient(nil, nil) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -562,13 +562,13 @@ func TestBatching(t *testing.T) { ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer("testServer", "v1.0.0", nil) + s := NewServer(nil, nil) _, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) } - c := NewClient("testClient", "v1.0.0", nil) + c := NewClient(nil, nil) // TODO: this test is broken, because increasing the batch size here causes // 'initialize' to block. Therefore, we can only test with a size of 1. // Since batching is being removed, we can probably just delete this. @@ -632,7 +632,7 @@ func TestMiddleware(t *testing.T) { ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer("testServer", "v1.0.0", nil) + s := NewServer(nil, nil) ss, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) @@ -656,7 +656,7 @@ func TestMiddleware(t *testing.T) { s.AddSendingMiddleware(traceCalls[*ServerSession](&sbuf, "S1"), traceCalls[*ServerSession](&sbuf, "S2")) s.AddReceivingMiddleware(traceCalls[*ServerSession](&sbuf, "R1"), traceCalls[*ServerSession](&sbuf, "R2")) - c := NewClient("testClient", "v1.0.0", nil) + c := NewClient(nil, nil) c.AddSendingMiddleware(traceCalls[*ClientSession](&cbuf, "S1"), traceCalls[*ClientSession](&cbuf, "S2")) c.AddReceivingMiddleware(traceCalls[*ClientSession](&cbuf, "R1"), traceCalls[*ClientSession](&cbuf, "R2")) @@ -741,13 +741,13 @@ func TestNoJSONNull(t *testing.T) { var logbuf safeBuffer ct = NewLoggingTransport(ct, &logbuf) - s := NewServer("testServer", "v1.0.0", nil) + s := NewServer(nil, nil) ss, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) } - c := NewClient("testClient", "v1.0.0", nil) + c := NewClient(nil, nil) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -810,7 +810,7 @@ func TestKeepAlive(t *testing.T) { serverOpts := &ServerOptions{ KeepAlive: 100 * time.Millisecond, } - s := NewServer("testServer", "v1.0.0", serverOpts) + s := NewServer(nil, serverOpts) AddTool(s, greetTool(), sayHi) ss, err := s.Connect(ctx, st) @@ -822,7 +822,7 @@ func TestKeepAlive(t *testing.T) { clientOpts := &ClientOptions{ KeepAlive: 100 * time.Millisecond, } - c := NewClient("testClient", "v1.0.0", clientOpts) + c := NewClient(nil, clientOpts) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -855,7 +855,7 @@ func TestKeepAliveFailure(t *testing.T) { ct, st := NewInMemoryTransports() // Server without keepalive (to test one-sided keepalive) - s := NewServer("testServer", "v1.0.0", nil) + s := NewServer(nil, nil) AddTool(s, greetTool(), sayHi) ss, err := s.Connect(ctx, st) if err != nil { @@ -866,7 +866,7 @@ func TestKeepAliveFailure(t *testing.T) { clientOpts := &ClientOptions{ KeepAlive: 50 * time.Millisecond, } - c := NewClient("testClient", "v1.0.0", clientOpts) + c := NewClient(nil, clientOpts) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) diff --git a/mcp/protocol.go b/mcp/protocol.go index eba9e73d..4f47c961 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -292,7 +292,7 @@ type InitializeParams struct { // attach additional metadata to their responses. Meta `json:"_meta,omitempty"` Capabilities *ClientCapabilities `json:"capabilities"` - ClientInfo *implementation `json:"clientInfo"` + ClientInfo *Implementation `json:"clientInfo"` // The latest version of the Model Context Protocol that the client supports. // The client may decide to support older versions as well. ProtocolVersion string `json:"protocolVersion"` @@ -318,7 +318,7 @@ type InitializeResult struct { // may not match the version that the client requested. If the client cannot // support this version, it must disconnect. ProtocolVersion string `json:"protocolVersion"` - ServerInfo *implementation `json:"serverInfo"` + ServerInfo *Implementation `json:"serverInfo"` } type InitializedParams struct { @@ -863,9 +863,9 @@ func (x *ToolListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) // TODO(jba): add ElicitRequest and related types. -// Describes the name and version of an MCP implementation, with an optional +// An Implementation describes the name and version of an MCP implementation, with an optional // title for UI representation. -type implementation struct { +type Implementation struct { // Intended for programmatic or logical use, but used as a display name in past // specs or fallback (if title isn't present). Name string `json:"name"` diff --git a/mcp/server.go b/mcp/server.go index 16cc8d6b..331a3628 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -32,9 +32,8 @@ const DefaultPageSize = 1000 // sessions by using [Server.Start] or [Server.Run]. type Server struct { // fixed at creation - name string - version string - opts ServerOptions + impl *Implementation + opts ServerOptions mu sync.Mutex prompts *featureSet[*serverPrompt] @@ -68,13 +67,17 @@ type ServerOptions struct { } // NewServer creates a new MCP server. The resulting server has no features: -// add features using [Server.AddTools], [Server.AddPrompts] and [Server.AddResources]. +// add features using the various Server.AddXXX methods, and the [AddTool] function. // // The server can be connected to one or more MCP clients using [Server.Start] // or [Server.Run]. // +// If impl is nil, a default name and version are used, and the title is left empty. // If non-nil, the provided options is used to configure the server. -func NewServer(name, version string, opts *ServerOptions) *Server { +func NewServer(impl *Implementation, opts *ServerOptions) *Server { + if impl == nil { + impl = &Implementation{Name: "Go MCP SDK Server", Version: "v0.1.0"} + } if opts == nil { opts = new(ServerOptions) } @@ -85,8 +88,7 @@ func NewServer(name, version string, opts *ServerOptions) *Server { opts.PageSize = DefaultPageSize } return &Server{ - name: name, - version: version, + impl: impl, opts: *opts, prompts: newFeatureSet(func(p *serverPrompt) string { return p.prompt.Name }), tools: newFeatureSet(func(t *serverTool) string { return t.tool.Name }), @@ -686,10 +688,7 @@ func (ss *ServerSession) initialize(ctx context.Context, params *InitializeParam ProtocolVersion: version, Capabilities: ss.server.capabilities(), Instructions: ss.server.opts.Instructions, - ServerInfo: &implementation{ - Name: ss.server.name, - Version: ss.server.version, - }, + ServerInfo: ss.server.impl, }, nil } diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index fd6eea00..c62455e3 100644 --- a/mcp/server_example_test.go +++ b/mcp/server_example_test.go @@ -28,7 +28,7 @@ func ExampleServer() { ctx := context.Background() clientTransport, serverTransport := mcp.NewInMemoryTransports() - server := mcp.NewServer("greeter", "v0.0.1", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v0.0.1"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) serverSession, err := server.Connect(ctx, serverTransport) @@ -36,7 +36,7 @@ func ExampleServer() { log.Fatal(err) } - client := mcp.NewClient("client", "v0.0.1", nil) + client := mcp.NewClient(nil, nil) clientSession, err := client.Connect(ctx, clientTransport) if err != nil { log.Fatal(err) @@ -59,8 +59,8 @@ func ExampleServer() { // createSessions creates and connects an in-memory client and server session for testing purposes. func createSessions(ctx context.Context) (*mcp.ClientSession, *mcp.ServerSession, *mcp.Server) { - server := mcp.NewServer("server", "v0.0.1", nil) - client := mcp.NewClient("client", "v0.0.1", nil) + server := mcp.NewServer(nil, nil) + client := mcp.NewClient(nil, nil) serverTransport, clientTransport := mcp.NewInMemoryTransports() serverSession, err := server.Connect(ctx, serverTransport) if err != nil { diff --git a/mcp/server_test.go b/mcp/server_test.go index cc94003c..d4243d7c 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -306,7 +306,7 @@ func TestServerCapabilities(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - server := NewServer("", "", nil) + server := NewServer(testImpl, nil) tc.configureServer(server) gotCapabilities := server.capabilities() if diff := cmp.Diff(tc.wantCapabilities, gotCapabilities); diff != "" { diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go index 816e0134..3382a1b9 100644 --- a/mcp/sse_example_test.go +++ b/mcp/sse_example_test.go @@ -27,7 +27,7 @@ func Add(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsF } func ExampleSSEHandler() { - server := mcp.NewServer("adder", "v0.0.1", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "adder"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add two numbers"}, Add) handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }) @@ -36,7 +36,7 @@ func ExampleSSEHandler() { ctx := context.Background() transport := mcp.NewSSEClientTransport(httpServer.URL, nil) - client := mcp.NewClient("test", "v1.0.0", nil) + client := mcp.NewClient(nil, nil) cs, err := client.Connect(ctx, transport) if err != nil { log.Fatal(err) diff --git a/mcp/sse_test.go b/mcp/sse_test.go index 153185d3..869d7503 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -20,7 +20,7 @@ func TestSSEServer(t *testing.T) { for _, closeServerFirst := range []bool{false, true} { t.Run(fmt.Sprintf("closeServerFirst=%t", closeServerFirst), func(t *testing.T) { ctx := context.Background() - server := NewServer("testServer", "v1.0.0", nil) + server := NewServer(nil, nil) AddTool(server, &Tool{Name: "greet"}, sayHi) sseHandler := NewSSEHandler(func(*http.Request) *Server { return server }) @@ -47,7 +47,7 @@ func TestSSEServer(t *testing.T) { HTTPClient: customClient, }) - c := NewClient("testClient", "v1.0.0", nil) + c := NewClient(nil, nil) cs, err := c.Connect(ctx, clientTransport) if err != nil { t.Fatal(err) diff --git a/mcp/streamable_test.go b/mcp/streamable_test.go index da9c4285..a78d86ae 100644 --- a/mcp/streamable_test.go +++ b/mcp/streamable_test.go @@ -32,7 +32,7 @@ func TestStreamableTransports(t *testing.T) { ctx := context.Background() // 1. Create a server with a simple "greet" tool. - server := NewServer("testServer", "v1.0.0", nil) + server := NewServer(nil, nil) AddTool(server, &Tool{Name: "greet", Description: "say hi"}, sayHi) // 2. Start an httptest.Server with the StreamableHTTPHandler, wrapped in a // cookie-checking middleware. @@ -65,7 +65,7 @@ func TestStreamableTransports(t *testing.T) { transport := NewStreamableClientTransport(httpServer.URL, &StreamableClientTransportOptions{ HTTPClient: httpClient, }) - client := NewClient("testClient", "v1.0.0", nil) + client := NewClient(nil, nil) session, err := client.Connect(ctx, transport) if err != nil { t.Fatalf("client.Connect() failed: %v", err) @@ -162,7 +162,7 @@ func TestStreamableServerTransport(t *testing.T) { Tools: &toolCapabilities{ListChanged: true}, }, ProtocolVersion: latestProtocolVersion, - ServerInfo: &implementation{Name: "testServer", Version: "v1.0.0"}, + ServerInfo: &Implementation{Name: "testServer", Version: "v1.0.0"}, }, nil) initializedMsg := req(0, "initialized", &InitializedParams{}) initialize := step{ @@ -328,7 +328,7 @@ func TestStreamableServerTransport(t *testing.T) { t.Run(test.name, func(t *testing.T) { // Create a server containing a single tool, which runs the test tool // behavior, if any. - server := NewServer("testServer", "v1.0.0", nil) + server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil) AddTool(server, &Tool{Name: "tool"}, func(ctx context.Context, ss *ServerSession, params *CallToolParamsFor[any]) (*CallToolResultFor[any], error) { if test.tool != nil { test.tool(t, ctx, ss) From 77027f4268e5c37e0f3c640286c8e16097a404b3 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Wed, 9 Jul 2025 13:39:16 -0400 Subject: [PATCH 2/5] gen readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce4d9f11..1e53b7c6 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ func main() { ctx := context.Background() // Create a new client, with no features. - client := mcp.NewClient("mcp-client", "v1.0.0", nil) + client := mcp.NewClient(nil, nil) // Connect to a server over stdin/stdout transport := mcp.NewCommandTransport(exec.Command("myserver")) @@ -125,7 +125,7 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam func main() { // Create a server with a single tool. - server := mcp.NewServer("greeter", "v1.0.0", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) // Run the server over stdin/stdout, until the client disconnects From 233ff628822e63beafd91e6d1098f9223b7fbd55 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 10 Jul 2025 07:36:17 -0400 Subject: [PATCH 3/5] must be non-nil --- examples/completion/main.go | 2 +- examples/memory/main.go | 2 +- internal/readme/client/client.go | 2 +- mcp/client.go | 5 +++-- mcp/cmd_test.go | 12 +++++++----- mcp/example_progress_test.go | 2 +- mcp/mcp_test.go | 30 ++++++++++++++++-------------- mcp/server.go | 7 ++++--- mcp/server_example_test.go | 6 +++--- mcp/sse_example_test.go | 4 ++-- mcp/sse_test.go | 4 ++-- mcp/streamable_test.go | 4 ++-- 12 files changed, 43 insertions(+), 37 deletions(-) diff --git a/examples/completion/main.go b/examples/completion/main.go index 92a91193..d6530b5d 100644 --- a/examples/completion/main.go +++ b/examples/completion/main.go @@ -40,7 +40,7 @@ func main() { // Create the MCP Server instance and assign the handler. // No server running, just showing the configuration. - _ = mcp.NewServer(nil, &mcp.ServerOptions{ + _ = mcp.NewServer(&mcp.Implementation{Name: "server"}, &mcp.ServerOptions{ CompletionHandler: myCompletionHandler, }) diff --git a/examples/memory/main.go b/examples/memory/main.go index 154f4d73..61ab1060 100644 --- a/examples/memory/main.go +++ b/examples/memory/main.go @@ -91,7 +91,7 @@ func main() { kb := knowledgeBase{s: kbStore} // Setup MCP server with knowledge base tools - server := mcp.NewServer(nil, nil) + server := mcp.NewServer(&mcp.Implementation{Name: "memory"}, nil) mcp.AddTool(server, &mcp.Tool{ Name: "create_entities", Description: "Create multiple new entities in the knowledge graph", diff --git a/internal/readme/client/client.go b/internal/readme/client/client.go index 6f2a388f..666ee925 100644 --- a/internal/readme/client/client.go +++ b/internal/readme/client/client.go @@ -17,7 +17,7 @@ func main() { ctx := context.Background() // Create a new client, with no features. - client := mcp.NewClient(nil, nil) + client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil) // Connect to a server over stdin/stdout transport := mcp.NewCommandTransport(exec.Command("myserver")) diff --git a/mcp/client.go b/mcp/client.go index 5b051fda..b48ad7a1 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -32,11 +32,12 @@ type Client struct { // // Use [Client.Connect] to connect it to an MCP server. // -// If impl is nil, a default name and version are used, and the title is left empty. +// The first argument must not be nil. +// // If non-nil, the provided options configure the Client. func NewClient(impl *Implementation, opts *ClientOptions) *Client { if impl == nil { - impl = &Implementation{Name: "Go MCP SDK Client", Version: "v0.1.0"} + panic("nil Implementation") } c := &Client{ impl: impl, diff --git a/mcp/cmd_test.go b/mcp/cmd_test.go index b887d68f..bc149f4c 100644 --- a/mcp/cmd_test.go +++ b/mcp/cmd_test.go @@ -32,7 +32,7 @@ func TestMain(m *testing.M) { func runServer() { ctx := context.Background() - server := mcp.NewServer(nil, nil) + server := mcp.NewServer(testImpl, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) if err := server.Run(ctx, mcp.NewStdioTransport()); err != nil { log.Fatal(err) @@ -40,7 +40,7 @@ func runServer() { } func TestServerRunContextCancel(t *testing.T) { - server := mcp.NewServer("greeter", "v0.0.1", nil) + server := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v0.0.1"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) ctx, cancel := context.WithCancel(context.Background()) @@ -55,7 +55,7 @@ func TestServerRunContextCancel(t *testing.T) { }() // send a ping to the server to ensure it's running - client := mcp.NewClient("client", "v0.0.1", nil) + client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) session, err := client.Connect(ctx, clientTransport) if err != nil { t.Fatal(err) @@ -87,7 +87,7 @@ func TestServerInterrupt(t *testing.T) { cmd := createServerCommand(t) - client := mcp.NewClient(nil, nil) + client := mcp.NewClient(testImpl, nil) session, err := client.Connect(ctx, mcp.NewCommandTransport(cmd)) if err != nil { t.Fatal(err) @@ -125,7 +125,7 @@ func TestCmdTransport(t *testing.T) { cmd := createServerCommand(t) - client := mcp.NewClient("client", "v0.0.1", nil) + client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) session, err := client.Connect(ctx, mcp.NewCommandTransport(cmd)) if err != nil { t.Fatal(err) @@ -174,3 +174,5 @@ func requireExec(t *testing.T) { t.Skip("unsupported OS") } } + +var testImpl = &mcp.Implementation{Name: "test", Version: "v1.0.0"} diff --git a/mcp/example_progress_test.go b/mcp/example_progress_test.go index 18086db2..6c771e20 100644 --- a/mcp/example_progress_test.go +++ b/mcp/example_progress_test.go @@ -16,7 +16,7 @@ var nextProgressToken atomic.Int64 // This middleware function adds a progress token to every outgoing request // from the client. func Example_progressMiddleware() { - c := mcp.NewClient(nil, nil) + c := mcp.NewClient(testImpl, nil) c.AddSendingMiddleware(addProgressToken[*mcp.ClientSession]) _ = c } diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index 51508bdf..1952e8d0 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -64,7 +64,7 @@ func TestEndToEnd(t *testing.T) { notificationChans["progress_server"] <- 0 }, } - s := NewServer(nil, sopts) + s := NewServer(testImpl, sopts) AddTool(s, &Tool{ Name: "greet", Description: "say hi", @@ -125,7 +125,7 @@ func TestEndToEnd(t *testing.T) { notificationChans["progress_client"] <- 0 }, } - c := NewClient(nil, opts) + c := NewClient(testImpl, opts) rootAbs, err := filepath.Abs(filepath.FromSlash("testdata/files")) if err != nil { t.Fatal(err) @@ -510,7 +510,7 @@ func basicConnection(t *testing.T, config func(*Server)) (*ServerSession, *Clien ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer(nil, nil) + s := NewServer(testImpl, nil) if config != nil { config(s) } @@ -519,7 +519,7 @@ func basicConnection(t *testing.T, config func(*Server)) (*ServerSession, *Clien t.Fatal(err) } - c := NewClient(nil, nil) + c := NewClient(testImpl, nil) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -562,13 +562,13 @@ func TestBatching(t *testing.T) { ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer(nil, nil) + s := NewServer(testImpl, nil) _, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) } - c := NewClient(nil, nil) + c := NewClient(testImpl, nil) // TODO: this test is broken, because increasing the batch size here causes // 'initialize' to block. Therefore, we can only test with a size of 1. // Since batching is being removed, we can probably just delete this. @@ -632,7 +632,7 @@ func TestMiddleware(t *testing.T) { ctx := context.Background() ct, st := NewInMemoryTransports() - s := NewServer(nil, nil) + s := NewServer(testImpl, nil) ss, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) @@ -656,7 +656,7 @@ func TestMiddleware(t *testing.T) { s.AddSendingMiddleware(traceCalls[*ServerSession](&sbuf, "S1"), traceCalls[*ServerSession](&sbuf, "S2")) s.AddReceivingMiddleware(traceCalls[*ServerSession](&sbuf, "R1"), traceCalls[*ServerSession](&sbuf, "R2")) - c := NewClient(nil, nil) + c := NewClient(testImpl, nil) c.AddSendingMiddleware(traceCalls[*ClientSession](&cbuf, "S1"), traceCalls[*ClientSession](&cbuf, "S2")) c.AddReceivingMiddleware(traceCalls[*ClientSession](&cbuf, "R1"), traceCalls[*ClientSession](&cbuf, "R2")) @@ -741,13 +741,13 @@ func TestNoJSONNull(t *testing.T) { var logbuf safeBuffer ct = NewLoggingTransport(ct, &logbuf) - s := NewServer(nil, nil) + s := NewServer(testImpl, nil) ss, err := s.Connect(ctx, st) if err != nil { t.Fatal(err) } - c := NewClient(nil, nil) + c := NewClient(testImpl, nil) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -810,7 +810,7 @@ func TestKeepAlive(t *testing.T) { serverOpts := &ServerOptions{ KeepAlive: 100 * time.Millisecond, } - s := NewServer(nil, serverOpts) + s := NewServer(testImpl, serverOpts) AddTool(s, greetTool(), sayHi) ss, err := s.Connect(ctx, st) @@ -822,7 +822,7 @@ func TestKeepAlive(t *testing.T) { clientOpts := &ClientOptions{ KeepAlive: 100 * time.Millisecond, } - c := NewClient(nil, clientOpts) + c := NewClient(testImpl, clientOpts) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -855,7 +855,7 @@ func TestKeepAliveFailure(t *testing.T) { ct, st := NewInMemoryTransports() // Server without keepalive (to test one-sided keepalive) - s := NewServer(nil, nil) + s := NewServer(testImpl, nil) AddTool(s, greetTool(), sayHi) ss, err := s.Connect(ctx, st) if err != nil { @@ -866,7 +866,7 @@ func TestKeepAliveFailure(t *testing.T) { clientOpts := &ClientOptions{ KeepAlive: 50 * time.Millisecond, } - c := NewClient(nil, clientOpts) + c := NewClient(testImpl, clientOpts) cs, err := c.Connect(ctx, ct) if err != nil { t.Fatal(err) @@ -895,3 +895,5 @@ func TestKeepAliveFailure(t *testing.T) { t.Errorf("expected connection to be closed by keepalive, but it wasn't. Last error: %v", err) } + +var testImpl = &Implementation{Name: "test", Version: "v1.0.0"} diff --git a/mcp/server.go b/mcp/server.go index 331a3628..060ae538 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -72,11 +72,12 @@ type ServerOptions struct { // The server can be connected to one or more MCP clients using [Server.Start] // or [Server.Run]. // -// If impl is nil, a default name and version are used, and the title is left empty. -// If non-nil, the provided options is used to configure the server. +// The first argument must not be nil. +// +// If non-nil, the provided options are used to configure the server. func NewServer(impl *Implementation, opts *ServerOptions) *Server { if impl == nil { - impl = &Implementation{Name: "Go MCP SDK Server", Version: "v0.1.0"} + panic("nil Implementation") } if opts == nil { opts = new(ServerOptions) diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index c62455e3..3ab7a2a4 100644 --- a/mcp/server_example_test.go +++ b/mcp/server_example_test.go @@ -36,7 +36,7 @@ func ExampleServer() { log.Fatal(err) } - client := mcp.NewClient(nil, nil) + client := mcp.NewClient(&mcp.Implementation{Name: "client"}, nil) clientSession, err := client.Connect(ctx, clientTransport) if err != nil { log.Fatal(err) @@ -59,8 +59,8 @@ func ExampleServer() { // createSessions creates and connects an in-memory client and server session for testing purposes. func createSessions(ctx context.Context) (*mcp.ClientSession, *mcp.ServerSession, *mcp.Server) { - server := mcp.NewServer(nil, nil) - client := mcp.NewClient(nil, nil) + server := mcp.NewServer(testImpl, nil) + client := mcp.NewClient(testImpl, nil) serverTransport, clientTransport := mcp.NewInMemoryTransports() serverSession, err := server.Connect(ctx, serverTransport) if err != nil { diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go index 3382a1b9..d8ce939b 100644 --- a/mcp/sse_example_test.go +++ b/mcp/sse_example_test.go @@ -27,7 +27,7 @@ func Add(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsF } func ExampleSSEHandler() { - server := mcp.NewServer(&mcp.Implementation{Name: "adder"}, nil) + server := mcp.NewServer(&mcp.Implementation{Name: "adder", Version: "v0.0.1"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add two numbers"}, Add) handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }) @@ -36,7 +36,7 @@ func ExampleSSEHandler() { ctx := context.Background() transport := mcp.NewSSEClientTransport(httpServer.URL, nil) - client := mcp.NewClient(nil, nil) + client := mcp.NewClient(&mcp.Implementation{Name: "test", Version: "v1.0.0"}, nil) cs, err := client.Connect(ctx, transport) if err != nil { log.Fatal(err) diff --git a/mcp/sse_test.go b/mcp/sse_test.go index 869d7503..846d68c0 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -20,7 +20,7 @@ func TestSSEServer(t *testing.T) { for _, closeServerFirst := range []bool{false, true} { t.Run(fmt.Sprintf("closeServerFirst=%t", closeServerFirst), func(t *testing.T) { ctx := context.Background() - server := NewServer(nil, nil) + server := NewServer(testImpl, nil) AddTool(server, &Tool{Name: "greet"}, sayHi) sseHandler := NewSSEHandler(func(*http.Request) *Server { return server }) @@ -47,7 +47,7 @@ func TestSSEServer(t *testing.T) { HTTPClient: customClient, }) - c := NewClient(nil, nil) + c := NewClient(testImpl, nil) cs, err := c.Connect(ctx, clientTransport) if err != nil { t.Fatal(err) diff --git a/mcp/streamable_test.go b/mcp/streamable_test.go index a78d86ae..e0e85a1e 100644 --- a/mcp/streamable_test.go +++ b/mcp/streamable_test.go @@ -32,7 +32,7 @@ func TestStreamableTransports(t *testing.T) { ctx := context.Background() // 1. Create a server with a simple "greet" tool. - server := NewServer(nil, nil) + server := NewServer(testImpl, nil) AddTool(server, &Tool{Name: "greet", Description: "say hi"}, sayHi) // 2. Start an httptest.Server with the StreamableHTTPHandler, wrapped in a // cookie-checking middleware. @@ -65,7 +65,7 @@ func TestStreamableTransports(t *testing.T) { transport := NewStreamableClientTransport(httpServer.URL, &StreamableClientTransportOptions{ HTTPClient: httpClient, }) - client := NewClient(nil, nil) + client := NewClient(testImpl, nil) session, err := client.Connect(ctx, transport) if err != nil { t.Fatalf("client.Connect() failed: %v", err) From 490ba34ce709bd4ca1b9ec70afe8e12ff8418cf8 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 10 Jul 2025 07:37:13 -0400 Subject: [PATCH 4/5] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e53b7c6..be91e751 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ func main() { ctx := context.Background() // Create a new client, with no features. - client := mcp.NewClient(nil, nil) + client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil) // Connect to a server over stdin/stdout transport := mcp.NewCommandTransport(exec.Command("myserver")) From 4e6332c0bcff5738ad7e1952ddce1372ab7240b7 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 10 Jul 2025 07:44:09 -0400 Subject: [PATCH 5/5] design.md --- design/design.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/design/design.md b/design/design.md index 610de399..56bef53e 100644 --- a/design/design.md +++ b/design/design.md @@ -323,7 +323,7 @@ Sessions are created from either `Client` or `Server` using the `Connect` method ```go type Client struct { /* ... */ } -func NewClient(name, version string, opts *ClientOptions) *Client +func NewClient(impl *Implementation, opts *ClientOptions) *Client func (*Client) Connect(context.Context, Transport) (*ClientSession, error) func (*Client) Sessions() iter.Seq[*ClientSession] // Methods for adding/removing client features are described below. @@ -338,7 +338,7 @@ func (*ClientSession) Wait() error // For example: ClientSession.ListTools. type Server struct { /* ... */ } -func NewServer(name, version string, opts *ServerOptions) *Server +func NewServer(impl *Implementation, opts *ServerOptions) *Server func (*Server) Connect(context.Context, Transport) (*ServerSession, error) func (*Server) Sessions() iter.Seq[*ServerSession] // Methods for adding/removing server features are described below. @@ -356,7 +356,7 @@ func (*ServerSession) Wait() error Here's an example of these APIs from the client side: ```go -client := mcp.NewClient("mcp-client", "v1.0.0", nil) +client := mcp.NewClient(&mcp.Implementation{Name:"mcp-client", Version:"v1.0.0"}, nil) // Connect to a server over stdin/stdout transport := mcp.NewCommandTransport(exec.Command("myserver")) session, err := client.Connect(ctx, transport) @@ -371,7 +371,7 @@ A server that can handle that client call would look like this: ```go // Create a server with a single tool. -server := mcp.NewServer("greeter", "v1.0.0", nil) +server := mcp.NewServer(&mcp.Implementation{Name:"greeter", Version:"v1.0.0"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi) // Run the server over stdin/stdout, until the client disconnects. if err := server.Run(context.Background(), mcp.NewStdioTransport()); err != nil {