// 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. //lint:file-ignore SA1019 examples exercise deprecated SEP-2577 logging APIs for demonstration. package mcp_test import ( "context" "fmt" "log" "log/slog" "sync/atomic" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" ) // !+prompts func Example_prompts() { ctx := context.Background() promptHandler := func(ctx context.Context, req *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{ Description: "Hi prompt", Messages: []*mcp.PromptMessage{ { Role: "user", Content: &mcp.TextContent{Text: "Say hi to " + req.Params.Arguments["name"]}, }, }, }, nil } // Create a server with a single prompt. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) prompt := &mcp.Prompt{ Name: "greet", Arguments: []*mcp.PromptArgument{ { Name: "name", Description: "the name of the person to greet", Required: true, }, }, } s.AddPrompt(prompt, promptHandler) // Create a client. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // List the prompts. for p, err := range cs.Prompts(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(p.Name) } // Get the prompt. res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{ Name: "greet", Arguments: map[string]string{"name": "Pat"}, }) if err != nil { log.Fatal(err) } for _, msg := range res.Messages { fmt.Println(msg.Role, msg.Content.(*mcp.TextContent).Text) } // Output: // greet // user Say hi to Pat } // !-prompts // !+promptcontent func Example_promptContent() { ctx := context.Background() const styleGuide = "- Prefer clarity over cleverness.\n" screenshotPNG := []byte("\x89PNG\r\n\x1a\n") s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) s.AddResource(&mcp.Resource{URI: "doc://style-guide", MIMEType: "text/markdown"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{ URI: "doc://style-guide", MIMEType: "text/markdown", Text: styleGuide, }}, }, nil }) s.AddPrompt(&mcp.Prompt{Name: "review_screenshot"}, func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{ Messages: []*mcp.PromptMessage{ {Role: "user", Content: &mcp.EmbeddedResource{ Resource: &mcp.ResourceContents{ URI: "doc://style-guide", MIMEType: "text/markdown", Text: styleGuide, }, }}, {Role: "user", Content: &mcp.ImageContent{Data: screenshotPNG, MIMEType: "image/png"}}, {Role: "user", Content: &mcp.TextContent{Text: "Review the screenshot against the style guide."}}, }, }, nil }) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{Name: "review_screenshot"}) if err != nil { log.Fatal(err) } for _, msg := range res.Messages { switch content := msg.Content.(type) { case *mcp.TextContent: fmt.Println(msg.Role, "text:", content.Text) case *mcp.ImageContent: fmt.Printf("%s image: %s, %d bytes\n", msg.Role, content.MIMEType, len(content.Data)) case *mcp.EmbeddedResource: fmt.Printf("%s embedded resource: %s (%s)\n", msg.Role, content.Resource.URI, content.Resource.MIMEType) } } // Output: // user embedded resource: doc://style-guide (text/markdown) // user image: image/png, 8 bytes // user text: Review the screenshot against the style guide. } // !-promptcontent // !+logging func Example_logging() { ctx := context.Background() // Create a server. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) // Create a client that displays log messages. done := make(chan struct{}) // solely for the example var nmsgs atomic.Int32 c := mcp.NewClient( &mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ LoggingMessageHandler: func(_ context.Context, r *mcp.LoggingMessageRequest) { m := r.Params.Data.(map[string]any) fmt.Println(m["msg"], m["value"]) if nmsgs.Add(1) == 2 { // number depends on logger calls below close(done) } }, }) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() ss, err := s.Connect(ctx, t1, nil) if err != nil { log.Fatal(err) } defer ss.Close() cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // Set the minimum log level to "info". if err := cs.SetLoggingLevel(ctx, &mcp.SetLoggingLevelParams{Level: "info"}); err != nil { log.Fatal(err) } // Get a slog.Logger for the server session. logger := slog.New(mcp.NewLoggingHandler(ss, nil)) // Log some things. logger.Info("info shows up", "value", 1) logger.Debug("debug doesn't show up", "value", 2) logger.Warn("warn shows up", "value", 3) // Wait for them to arrive on the client. // In a real application, the log messages would appear asynchronously // while other work was happening. <-done // Output: // info shows up 1 // warn shows up 3 } // !-logging // !+resources func Example_resources() { ctx := context.Background() resources := map[string]string{ "file:///a": "a", "file:///dir/x": "x", "file:///dir/y": "y", } handler := func(_ context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { uri := req.Params.URI c, ok := resources[uri] if !ok { return nil, mcp.ResourceNotFoundError(uri) } return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{URI: uri, Text: c}}, }, nil } // Create a server with a single resource. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) s.AddResource(&mcp.Resource{URI: "file:///a"}, handler) s.AddResourceTemplate(&mcp.ResourceTemplate{URITemplate: "file:///dir/{f}"}, handler) // Create a client. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) // Connect the server and client. t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() // List resources and resource templates. for r, err := range cs.Resources(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(r.URI) } for r, err := range cs.ResourceTemplates(ctx, nil) { if err != nil { log.Fatal(err) } fmt.Println(r.URITemplate) } // Read resources. for _, path := range []string{"a", "dir/x", "b"} { res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///" + path}) if err != nil { fmt.Println(err) } else { fmt.Println(res.Contents[0].Text) } } // Output: // file:///a // file:///dir/{f} // a // x // calling "resources/read": Resource not found } // !-resources // !+listchanged func Example_listChanged() { ctx := context.Background() changed := make(chan string, 3) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ToolListChangedHandler: func(context.Context, *mcp.ToolListChangedRequest) { changed <- "tools" }, PromptListChangedHandler: func(context.Context, *mcp.PromptListChangedRequest) { changed <- "prompts" }, ResourceListChangedHandler: func(context.Context, *mcp.ResourceListChangedRequest) { changed <- "resources" }, }) // Nothing is registered before Connect, so the capabilities that carry these // notifications have to be declared explicitly. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{ HasTools: true, HasPrompts: true, HasResources: true, }) t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(context.Context, *mcp.CallToolRequest, struct{}) (*mcp.CallToolResult, any, error) { return &mcp.CallToolResult{}, nil, nil }) fmt.Println("changed:", <-changed) s.AddPrompt(&mcp.Prompt{Name: "review"}, func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { return &mcp.GetPromptResult{}, nil }) fmt.Println("changed:", <-changed) s.AddResource(&mcp.Resource{URI: "file:///a"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { return &mcp.ReadResourceResult{}, nil }) fmt.Println("changed:", <-changed) // Removing a feature notifies the client just as adding one does. s.RemoveTools("greet") fmt.Println("changed:", <-changed) // Output: // changed: tools // changed: prompts // changed: resources // changed: tools } // !-listchanged // !+subscribe func Example_resourceSubscription() { ctx := context.Background() config := "theme=light\n" updated := make(chan string, 1) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ // The notification carries only the URI, so read the resource again to // see what it now holds. ResourceUpdatedHandler: func(context.Context, *mcp.ResourceUpdatedNotificationRequest) { updated <- "config://app" }, }) subscribed := make(chan string, 2) s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{ SubscribeHandler: func(_ context.Context, req *mcp.SubscribeRequest) error { subscribed <- "subscribed to " + req.Params.URI return nil }, UnsubscribeHandler: func(_ context.Context, req *mcp.UnsubscribeRequest) error { subscribed <- "unsubscribed from " + req.Params.URI return nil }, }) s.AddResource(&mcp.Resource{URI: "config://app"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{URI: "config://app", Text: config}}, }, nil }) t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() if err := cs.Subscribe(ctx, &mcp.SubscribeParams{URI: "config://app"}); err != nil { log.Fatal(err) } fmt.Println(<-subscribed) config = "theme=dark\n" if err := s.ResourceUpdated(ctx, &mcp.ResourceUpdatedNotificationParams{URI: "config://app"}); err != nil { log.Fatal(err) } uri := <-updated res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: uri}) if err != nil { log.Fatal(err) } fmt.Print("re-read: ", res.Contents[0].Text) if err := cs.Unsubscribe(ctx, &mcp.UnsubscribeParams{URI: "config://app"}); err != nil { log.Fatal(err) } fmt.Println(<-subscribed) // Output: // subscribed to config://app // re-read: theme=dark // unsubscribed from config://app } // !-subscribe // !+binaryresource func Example_binaryResource() { ctx := context.Background() s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) s.AddResource(&mcp.Resource{URI: "file:///logo.png", MIMEType: "image/png"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { return &mcp.ReadResourceResult{ Contents: []*mcp.ResourceContents{{ URI: "file:///logo.png", MIMEType: "image/png", Blob: []byte("\x89PNG\r\n\x1a\n"), }}, }, nil }) c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil) t1, t2 := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, t1, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, t2, nil) if err != nil { log.Fatal(err) } defer cs.Close() res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///logo.png"}) if err != nil { log.Fatal(err) } contents := res.Contents[0] fmt.Printf("%s: %d bytes of %s, text is %q\n", contents.URI, len(contents.Blob), contents.MIMEType, contents.Text) // Output: // file:///logo.png: 8 bytes of image/png, text is "" } // !-binaryresource // !+mrtr // Example_mrtr demonstrates the [Multi Round-Trip Requests] pattern // (SEP-2322). A tool handler signals "I need more information from the user" // by returning a [mcp.CallToolResult] whose `InputRequests` field carries the // requests for the additional information. Each request can be an // [mcp.ElicitParams] (ask the user a question), // [mcp.CreateMessageParams] (sample from the client's LLM), or // [mcp.ListRootsParams] (list the client's roots). // // On protocol version `2026-07-28` and later, the client's // `clientMultiRoundTripMiddleware` fulfils each input request from the // configured handler and retries the original call transparently. On earlier // protocol versions, the server's `serverMultiRoundTripMiddleware` performs // the equivalent dance from the server side by calling the legacy // server-to-client API (`ServerSession.Elicit`, `CreateMessage`, or // `ListRoots`) and re-invoking the handler with the response. Either way, // the user-facing client call site sees only the final result. // // [Multi Round-Trip Requests]: https://modelcontextprotocol.io/specification/draft/basic/patterns#multi-round-trip-requests func Example_mrtr() { ctx := context.Background() // Server: a "greet" tool that asks the user for their name before // returning a greeting. The handler runs twice per logical call: // once to issue the elicitation, once to consume the response. s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil) mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) { if len(req.Params.InputResponses) == 0 { // First call: ask the user for their name. The map key // ("user_name") is a label the handler chooses; the client must // echo it back in InputResponses. The wire-level method name // ("elicitation/create") is derived from the value's Go type. return &mcp.CallToolResult{ InputRequests: mcp.InputRequestMap{ "user_name": &mcp.ElicitParams{ Message: "What's your name?", RequestedSchema: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ "name": {Type: "string"}, }, }, }, }, // RequestState is an opaque token the client echoes back on // the retry, letting the handler resume its work without // per-session storage. RequestState: "step=1", }, nil, nil } // Retry: read the elicitation response and produce the final greeting. name := req.Params.InputResponses["user_name"].(*mcp.ElicitResult).Content["name"].(string) return &mcp.CallToolResult{ Content: []mcp.Content{&mcp.TextContent{Text: "Hello " + name}}, }, nil, nil }) // Client: declares an elicitation handler. The SDK's MRTR middleware // uses it to fulfil any input request the tool handler returns. c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{ ElicitationHandler: func(_ context.Context, _ *mcp.ElicitRequest) (*mcp.ElicitResult, error) { return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"name": "MCP Go"}}, nil }, }) ct, st := mcp.NewInMemoryTransports() if _, err := s.Connect(ctx, st, nil); err != nil { log.Fatal(err) } cs, err := c.Connect(ctx, ct, nil) if err != nil { log.Fatal(err) } defer cs.Close() // Single call site. The MRTR round trip is invisible to the caller. res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "greet"}) if err != nil { log.Fatal(err) } fmt.Println(res.Content[0].(*mcp.TextContent).Text) } // !-mrtr