# Support for the MCP base protocol 1. [Lifecycle](#lifecycle) 1. [Transports](#transports) 1. [Stdio Transport](#stdio-transport) 1. [Streamable Transport](#streamable-transport) 1. [Custom transports](#custom-transports) 1. [Concurrency](#concurrency) 1. [Authorization](#authorization) 1. [Security](#security) 1. [Utilities](#utilities) 1. [Cancellation](#cancellation) 1. [Ping](#ping) 1. [Progress](#progress) ## Lifecycle The SDK provides an API for defining both MCP clients and servers, and connecting them over various transports. When a client and server are connected, it creates a logical session, which follows the MCP spec's [lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle). In this SDK, both a [`Client`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client) and [`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server) can handle multiple peers. Every time a new peer is connected, it creates a new session. - A `Client` is a logical MCP client, configured with various [`ClientOptions`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions). - When a client is connected to a server using [`Client.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Client.Connect), it creates a [`ClientSession`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession). This session is initialized during the `Connect` method, and provides methods to communicate with the server peer. - A `Server` is a logical MCP server, configured with various [`ServerOptions`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions). - When a server is connected to a client using [`Server.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.Connect), it creates a [`ServerSession`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession). This session is not initialized until the client sends the `notifications/initialized` message. Use `ServerOptions.InitializedHandler` to listen for this event, or just use the session through various feature handlers (such as a `ToolHandler`). Requests to the server are rejected until the client has initialized the session. Both `ClientSession` and `ServerSession` have a `Close` method to terminate the session, and a `Wait` method to await session termination by the peer. Typically, it is the client's responsibility to end the session. ```go func Example_lifecycle() { ctx := context.Background() // Create a client and server. // Wait for the client to initialize the session. client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{ InitializedHandler: func(context.Context, *mcp.InitializedRequest) { fmt.Println("initialized!") }, }) // Connect the server and client using in-memory transports. // // Connect the server first so that it's ready to receive initialization // messages from the client. t1, t2 := mcp.NewInMemoryTransports() serverSession, err := server.Connect(ctx, t1, nil) if err != nil { log.Fatal(err) } clientSession, err := client.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } // Now shut down the session by closing the client, and waiting for the // server session to end. if err := clientSession.Close(); err != nil { log.Fatal(err) } if err := serverSession.Wait(); err != nil { log.Fatal(err) } // Output: initialized! } ``` ## Transports A [transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) can be used to send JSON-RPC messages from client to server, or vice-versa. In the SDK, this is achieved by implementing the [`Transport`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Transport) interface, which creates a (logical) bidirectional stream of JSON-RPC messages. Most transport implementations described below are specific to either the client or server: a "client transport" is something that can be used to connect a client to a server, and a "server transport" is something that can be used to connect a server to a client. However, it's possible for a transport to be both a client and server transport, such as the `InMemoryTransport` used in the lifecycle example above. Transports should not be reused for multiple connections: if you need to create multiple connections, use different transports. ### Stdio Transport In the [`stdio`](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#stdio) transport clients communicate with an MCP server running in a subprocess using newline-delimited JSON over its stdin/stdout. **Client-side**: the client side of the `stdio` transport is implemented by [`CommandTransport`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#CommandTransport), which starts the a `exec.Cmd` as a subprocess and communicates over its stdin/stdout. **Server-side**: the server side of the `stdio` transport is implemented by `StdioTransport`, which connects over the current processes `os.Stdin` and `os.Stdout`. ### Streamable Transport The [streamable transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) API is implemented across three types: - `StreamableHTTPHandler`: an`http.Handler` that serves streamable MCP sessions. - `StreamableServerTransport`: a `Transport` that implements the server side of the streamable transport. - `StreamableClientTransport`: a `Transport` that implements the client side of the streamable transport. To create a streamable MCP server, you create a `StreamableHTTPHandler` and pass it an `mcp.Server`: ```go func ExampleStreamableHTTPHandler() { // Create a new streamable handler, using the same MCP server for every request. // // Here, we configure it to serves application/json responses rather than // text/event-stream, just so the output below doesn't use random event ids. server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.1.0"}, nil) handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server { return server }, &mcp.StreamableHTTPOptions{JSONResponse: true}) httpServer := httptest.NewServer(handler) defer httpServer.Close() // The SDK is currently permissive of some missing keys in "params". resp := mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}`, httpServer.URL) fmt.Println(resp) // Output: // {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-06-18","serverInfo":{"name":"server","version":"v0.1.0"}}} } ``` The `StreamableHTTPHandler` handles the HTTP requests and creates a new `StreamableServerTransport` for each new session. The transport is then used to communicate with the client. On the client side, you create a `StreamableClientTransport` and use it to connect to the server: ```go transport := &mcp.StreamableClientTransport{ Endpoint: "http://localhost:8080/mcp", } client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...}) ``` The `StreamableClientTransport` handles the HTTP requests and communicates with the server using the streamable transport protocol. #### Stateless Mode #### Sessionless mode ### Custom transports ### Concurrency In general, MCP offers no guarantees about concurrency semantics: if a client or server sends a notification, the spec says nothing about when the peer observes that notification relative to other request. However, the Go SDK implements the following heuristics: - If a notifying method (such as `notifications/progress` or `notifications/initialized`) returns, then it is guaranteed that the peer observes that notification before other notifications or calls from the same client goroutine. - Calls (such as `tools/call`) are handled asynchronously with respect to each other. See [modelcontextprotocol/go-sdk#26](https://github.com/modelcontextprotocol/go-sdk/issues/26) for more background. ## Authorization ## Security ## Utilities ### Cancellation Cancellation is implemented with context cancellation. Cancelling a context used in a method on `ClientSession` or `ServerSession` will terminate the RPC and send a "notifications/cancelled" message to the peer. When an RPC exits due to a cancellation error, there's a guarantee that the cancellation notification has been sent, but there's no guarantee that the server has observed it (see [concurrency](#concurrency)). ```go func Example_cancellation() { // For this example, we're going to be collecting observations from the // server and client. var clientResult, serverResult string var wg sync.WaitGroup wg.Add(2) // Create a server with a single slow tool. // When the client cancels its request, the server should observe // cancellation. server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) started := make(chan struct{}, 1) // signals that the server started handling the tool call mcp.AddTool(server, &mcp.Tool{Name: "slow"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { started <- struct{}{} defer wg.Done() select { case <-time.After(5 * time.Second): serverResult = "tool done" case <-ctx.Done(): serverResult = "tool canceled" } return &mcp.CallToolResult{}, nil, nil }) // Connect a client to the server. client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) ctx := context.Background() t1, t2 := mcp.NewInMemoryTransports() if _, err := server.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } session, err := client.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer session.Close() // Make a tool call, asynchronously. ctx, cancel := context.WithCancel(context.Background()) go func() { defer wg.Done() _, err = session.CallTool(ctx, &mcp.CallToolParams{Name: "slow"}) clientResult = fmt.Sprintf("%v", err) }() // As soon as the server has started handling the call, cancel it from the // client side. <-started cancel() wg.Wait() fmt.Println(clientResult) fmt.Println(serverResult) // Output: // context canceled // tool canceled } ``` ### Ping [Ping](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/ping) support is symmetrical for client and server. To initiate a ping, call [`ClientSession.Ping`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Ping) or [`ServerSession.Ping`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Ping). To have the client or server session automatically ping its peer, and close the session if the ping fails, set [`ClientOptions.KeepAlive`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.KeepAlive) or [`ServerOptions.KeepAlive`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.KeepAlive). ### Progress [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress) reporting is possible by reading the progress token from request metadata and calling either [`ClientSession.NotifyProgress`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.NotifyProgress) or [`ServerSession.NotifyProgress`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.NotifyProgress). To listen to progress notifications, set [`ClientOptions.ProgressNotificationHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ProgressNotificationHandler) or [`ServerOptions.ProgressNotificationHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.ProgressNotificationHandler). Issue #460 discusses some potential ergonomic improvements to this API. ```go func Example_progress() { server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(server, &mcp.Tool{Name: "makeProgress"}, func(ctx context.Context, req *mcp.CallToolRequest, _ any) (*mcp.CallToolResult, any, error) { if token := req.Params.GetProgressToken(); token != nil { for i := range 3 { params := &mcp.ProgressNotificationParams{ Message: "frobbing widgets", ProgressToken: token, Progress: float64(i), Total: 2, } req.Session.NotifyProgress(ctx, params) // ignore error } } return &mcp.CallToolResult{}, nil, nil }) client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ProgressNotificationHandler: func(_ context.Context, req *mcp.ProgressNotificationClientRequest) { fmt.Printf("%s %.0f/%.0f\n", req.Params.Message, req.Params.Progress, req.Params.Total) }, }) ctx := context.Background() t1, t2 := mcp.NewInMemoryTransports() if _, err := server.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } session, err := client.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer session.Close() if _, err := session.CallTool(ctx, &mcp.CallToolParams{ Name: "makeProgress", Meta: mcp.Meta{"progressToken": "abc123"}, }); err != nil { log.Fatal(err) } // Output: // frobbing widgets 0/2 // frobbing widgets 1/2 // frobbing widgets 2/2 } ```