From 097945fefa96d35bb610870cc0bfaa8c2b628f34 Mon Sep 17 00:00:00 2001 From: harsh543 Date: Sun, 11 Oct 2026 22:25:40 -0700 Subject: [PATCH] docs: use request summaries for HTTP troubleshooting --- docs/troubleshooting.md | 37 ++++++++++++++-------------- internal/docs/troubleshooting.src.md | 14 +++++++---- mcp/streamable_example_test.go | 29 +++++++++------------- 3 files changed, 39 insertions(+), 41 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 950eef727..81d3da510 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -60,38 +60,37 @@ That example uses a `bytes.Buffer`, but you can also log to a file, or to ## Inspecting HTTP traffic -There are a couple different ways to investigate traffic to an HTTP transport -([streamable](protocol.md#streamable-transport) or legacy SSE). - -The first is to use an HTTP middleware: +For the [streamable HTTP transport](protocol.md#streamable-transport), use +`StreamableHTTPOptions.OnRequestSummary` to inspect decoded message metadata +without recording request parameters: ```go -func ExampleStreamableHTTPHandler_middleware() { +func ExampleStreamableHTTPHandler_requestSummary() { 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{Stateless: true}) - loggingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // Example debugging; you could also capture the response. - body, err := io.ReadAll(req.Body) - if err != nil { - log.Fatal(err) - } - req.Body.Close() // ignore error - req.Body = io.NopCloser(bytes.NewBuffer(body)) - fmt.Println(req.Method, string(body)) - handler.ServeHTTP(w, req) + }, &mcp.StreamableHTTPOptions{ + Stateless: true, + OnRequestSummary: func(_ context.Context, summary mcp.StreamableHTTPRequestSummary) { + fmt.Printf("method=%q notification=%t response=%t\n", + summary.Method, summary.IsNotification, summary.IsResponse) + }, }) - httpServer := httptest.NewServer(loggingHandler) + httpServer := httptest.NewServer(handler) defer httpServer.Close() // The SDK is currently permissive of some missing keys in "params". mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}`, httpServer.URL) // Output: - // POST {"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}} + // method="initialize" notification=false response=false } ``` -The second is to use a general purpose tool to inspect http traffic, such as +The callback runs before validation and dispatch. It does not run for batches +or requests rejected before message decoding. To observe those failures, wrap +the handler in HTTP middleware that records properties such as the HTTP method +and status code without reading or logging the request body. + +For full HTTP traffic, including legacy SSE, use a general-purpose tool such as [wireshark](https://www.wireshark.org/) or [tcpdump](https://linux.die.net/man/8/tcpdump). diff --git a/internal/docs/troubleshooting.src.md b/internal/docs/troubleshooting.src.md index 83342032a..e896a59d7 100644 --- a/internal/docs/troubleshooting.src.md +++ b/internal/docs/troubleshooting.src.md @@ -30,13 +30,17 @@ That example uses a `bytes.Buffer`, but you can also log to a file, or to ## Inspecting HTTP traffic -There are a couple different ways to investigate traffic to an HTTP transport -([streamable](protocol.md#streamable-transport) or legacy SSE). +For the [streamable HTTP transport](protocol.md#streamable-transport), use +`StreamableHTTPOptions.OnRequestSummary` to inspect decoded message metadata +without recording request parameters: -The first is to use an HTTP middleware: +%include ../../mcp/streamable_example_test.go requestsummary - -%include ../../mcp/streamable_example_test.go httpmiddleware - +The callback runs before validation and dispatch. It does not run for batches +or requests rejected before message decoding. To observe those failures, wrap +the handler in HTTP middleware that records properties such as the HTTP method +and status code without reading or logging the request body. -The second is to use a general purpose tool to inspect http traffic, such as +For full HTTP traffic, including legacy SSE, use a general-purpose tool such as [wireshark](https://www.wireshark.org/) or [tcpdump](https://linux.die.net/man/8/tcpdump). diff --git a/mcp/streamable_example_test.go b/mcp/streamable_example_test.go index 66834f2cf..b9a59e55b 100644 --- a/mcp/streamable_example_test.go +++ b/mcp/streamable_example_test.go @@ -5,7 +5,7 @@ package mcp_test import ( - "bytes" + "context" "fmt" "io" "log" @@ -40,34 +40,29 @@ func ExampleStreamableHTTPHandler() { // !-streamablehandler -// !+httpmiddleware +// !+requestsummary -func ExampleStreamableHTTPHandler_middleware() { +func ExampleStreamableHTTPHandler_requestSummary() { 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{Stateless: true}) - loggingHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // Example debugging; you could also capture the response. - body, err := io.ReadAll(req.Body) - if err != nil { - log.Fatal(err) - } - req.Body.Close() // ignore error - req.Body = io.NopCloser(bytes.NewBuffer(body)) - fmt.Println(req.Method, string(body)) - handler.ServeHTTP(w, req) + }, &mcp.StreamableHTTPOptions{ + Stateless: true, + OnRequestSummary: func(_ context.Context, summary mcp.StreamableHTTPRequestSummary) { + fmt.Printf("method=%q notification=%t response=%t\n", + summary.Method, summary.IsNotification, summary.IsResponse) + }, }) - httpServer := httptest.NewServer(loggingHandler) + httpServer := httptest.NewServer(handler) defer httpServer.Close() // The SDK is currently permissive of some missing keys in "params". mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}`, httpServer.URL) // Output: - // POST {"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}} + // method="initialize" notification=false response=false } -// !-httpmiddleware +// !-requestsummary func mustPostMessage(msg, url string) string { req := orFatal(http.NewRequest("POST", url, strings.NewReader(msg)))