diff --git a/mcp/mcp.go b/mcp/mcp.go index bdaa57c..315fba4 100644 --- a/mcp/mcp.go +++ b/mcp/mcp.go @@ -4,18 +4,69 @@ // The mcp package provides an SDK for writing model context protocol clients // and servers. It is a work-in-progress. As of writing, it is a prototype to -// explore the design space of client/server lifecycle and binding. +// explore the design space of client/server transport and binding. // -// To get started, create an MCP client or server with [NewClient] or -// [NewServer], then add features to your client or server using Add -// methods, then connect to a peer using a [Transport] instance and a call to -// [Client.Connect] or [Server.Connect]. +// 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) ⇅ +// ServerConnection ⇄ Client Transport ⇄ Server Transport ⇄ ClientConnection +// +// A [Client] is an MCP client, which can be configured with various client +// capabilities. Clients may be connected to one or more [Server] instances +// using the [Client.Connect] method, which creates a [ServerConnection]. +// +// Similarly, a [Server] is an MCP server, which can be configured with various +// server capabilities. Servers may be connected to one or more [Client] +// instances using the [Server.Connect] method, which creates a +// [ClientConnection]. +// +// A [Transport] connects a bidirectional [Stream] of jsonrpc2 messages. In +// practice, transports in the MCP spec are are either client transports or +// server transports. For example, the [StdIOTransport] is a server transport +// that communicates over stdin/stdout, and its counterpart is a +// [CommandTransport] that communicates with a subprocess over its +// stdin/stdout. +// +// Some transports may hide more complicated details, such as an +// [SSEClientTransport], which reads messages via server-sent events on a +// hanging GET request, and writes them to a POST endpoint. Users of this SDK +// may define their own custom Transports by implementing the [Transport] +// interface. +// +// Here's an example that creates a client that talks to an MCP server running +// as a sidecar process: +// +// import "github.com/modelcontextprotocol/go-sdk/mcp" +// ... +// // Create a new client, with no features. +// client := mcp.NewClient("mcp-client", "v1.0.0", nil) +// // Connect to a server over stdin/stdout +// transport := mcp.NewCommandTransport(exec.Command("myserver")) +// serverConn, err := client.Connect(ctx, transport, nil) +// if err != nil { +// log.Fatal(err) +// } +// // Call a tool on the server. +// content, err := serverConn.CallTool(ctx, "greet", map[string]any{"name": "you"}) +// +// Here is an example of the corresponding server, connected over stdin/stdout: +// +// import "github.com/modelcontextprotocol/go-sdk/mcp" +// ... +// // Create a server with a single tool. +// server := mcp.NewServer("greeter", "v1.0.0", nil) +// server.AddTool(mcp.MakeTool("greet", "say hi", SayHi)) +// // Run the server over stdin/stdout, until the client diconnects +// _ = server.Run(ctx, mcp.NewStdIOTransport(), nil) +// +// # TODO // -// TODO: // - Support pagination. // - Support all client/server operations. -// - Support Streamable HTTP transport. +// - Support streamable HTTP transport. // - Support multiple versions of the spec. // - Implement proper JSON schema support, with both client-side and -// server-side validation.. +// server-side validation. package mcp diff --git a/mcp/example_test.go b/mcp/server_example_test.go similarity index 100% rename from mcp/example_test.go rename to mcp/server_example_test.go diff --git a/mcp/sse.go b/mcp/sse.go index aaaf1ad..e1891bc 100644 --- a/mcp/sse.go +++ b/mcp/sse.go @@ -277,14 +277,16 @@ type SSEClientTransport struct { // NewSSEClientTransport returns a new client transport that connects to the // SSE server at the provided URL. -func NewSSEClientTransport(rawURL string) (*SSEClientTransport, error) { - url, err := url.Parse(rawURL) +// +// NewSSEClientTransport panics if the given URL is invalid. +func NewSSEClientTransport(baseURL string) *SSEClientTransport { + url, err := url.Parse(baseURL) if err != nil { - return nil, err + panic(fmt.Sprintf("invalid base url: %v", err)) } return &SSEClientTransport{ sseEndpoint: url, - }, nil + } } // Connect connects through the client endpoint. diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go new file mode 100644 index 0000000..c781f84 --- /dev/null +++ b/mcp/sse_example_test.go @@ -0,0 +1,50 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package mcp_test + +import ( + "context" + "fmt" + "log" + "net/http" + "net/http/httptest" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +type AddParams struct { + X, Y int +} + +func Add(ctx context.Context, cc *mcp.ClientConnection, params *AddParams) ([]mcp.Content, error) { + return []mcp.Content{ + mcp.TextContent{Text: fmt.Sprintf("%d", params.X+params.Y)}, + }, nil +} + +func ExampleSSEHandler() { + server := mcp.NewServer("adder", "v0.0.1", nil) + server.AddTools(mcp.MakeTool("add", "add two numbers", Add)) + + handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }) + httpServer := httptest.NewServer(handler) + defer httpServer.Close() + + ctx := context.Background() + transport := mcp.NewSSEClientTransport(httpServer.URL) + serverConn, err := mcp.NewClient("test", "v1.0.0", nil).Connect(ctx, transport, nil) + if err != nil { + log.Fatal(err) + } + defer serverConn.Close() + + content, err := serverConn.CallTool(ctx, "add", AddParams{1, 2}) + if err != nil { + log.Fatal(err) + } + fmt.Println(content[0].(mcp.TextContent).Text) + + // Output: 3 +} diff --git a/mcp/sse_test.go b/mcp/sse_test.go index a814afb..7f0da7d 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -31,11 +31,9 @@ func TestSSEServer(t *testing.T) { } } httpServer := httptest.NewServer(sseHandler) + defer httpServer.Close() - clientTransport, err := NewSSEClientTransport(httpServer.URL) - if err != nil { - t.Fatal(err) - } + clientTransport := NewSSEClientTransport(httpServer.URL) client := NewClient("testClient", "v1.0.0", nil) sc, err := client.Connect(ctx, clientTransport, nil)