Skip to content

Commit accdc47

Browse files
committed
mcp: Add example of using Middleware for logging purposes
1 parent de4b788 commit accdc47

File tree

2 files changed

+460
-0
lines changed

2 files changed

+460
-0
lines changed

examples/logging-middleware/main.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
// logging-middleware demonstrates a server with comprehensive logging. This
6+
// example shows how to add detailed observability to MCP protocol operations
7+
// using middleware patterns defined in the MCP Go SDK design.
8+
package main
9+
10+
import (
11+
"context"
12+
"flag"
13+
"fmt"
14+
"log"
15+
"log/slog"
16+
"net/http"
17+
"os"
18+
"time"
19+
20+
"github.com/modelcontextprotocol/go-sdk/mcp"
21+
)
22+
23+
var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")
24+
25+
// LoggingMiddleware provides comprehensive MCP-level logging
26+
func LoggingMiddleware(logger *slog.Logger) mcp.Middleware[*mcp.ServerSession] {
27+
return func(next mcp.MethodHandler[*mcp.ServerSession]) mcp.MethodHandler[*mcp.ServerSession] {
28+
return func(
29+
ctx context.Context,
30+
session *mcp.ServerSession,
31+
method string,
32+
params mcp.Params,
33+
) (mcp.Result, error) {
34+
start := time.Now()
35+
36+
// Note: The actual HTTP session ID (from Mcp-Session-Id header) is not
37+
// accessible from middleware with the current SDK design. This would require
38+
// SDK changes to pass the session ID through context. For now, we use the
39+
// session pointer as a unique identifier within this process.
40+
sessionID := fmt.Sprintf("%p", session)
41+
42+
logger.Info("MCP method started",
43+
"method", method,
44+
"session_id", sessionID,
45+
"has_params", params != nil,
46+
)
47+
48+
// Log method parameters (be careful with sensitive data)
49+
if params != nil {
50+
logger.Debug("MCP method params",
51+
"method", method,
52+
"session_id", sessionID,
53+
"params_type", fmt.Sprintf("%T", params),
54+
)
55+
}
56+
57+
// Call the actual handler
58+
result, err := next(ctx, session, method, params)
59+
60+
duration := time.Since(start)
61+
62+
if err != nil {
63+
logger.Error("MCP method failed",
64+
"method", method,
65+
"session_id", sessionID,
66+
"duration_ms", duration.Milliseconds(),
67+
"error", err.Error(),
68+
)
69+
} else {
70+
logger.Info("MCP method completed",
71+
"method", method,
72+
"session_id", sessionID,
73+
"duration_ms", duration.Milliseconds(),
74+
"has_result", result != nil,
75+
)
76+
77+
if result != nil {
78+
logger.Debug("MCP method result",
79+
"method", method,
80+
"session_id", sessionID,
81+
"result_type", fmt.Sprintf("%T", result),
82+
)
83+
}
84+
}
85+
86+
return result, err
87+
}
88+
}
89+
}
90+
91+
type GreetArgs struct {
92+
Name string `json:"name"`
93+
}
94+
95+
// createServer creates a new MCP server with comprehensive logging middleware
96+
func createServer(logger *slog.Logger) *mcp.Server {
97+
server := mcp.NewServer("logging-middleware-example", "1.0.0", nil)
98+
99+
// Add logging middleware to the server
100+
server.AddReceivingMiddleware(LoggingMiddleware(logger))
101+
102+
// Add a simple greeting tool
103+
server.AddTools(
104+
mcp.NewServerTool(
105+
"greet",
106+
"Greet someone with comprehensive logging",
107+
func(
108+
ctx context.Context,
109+
ss *mcp.ServerSession,
110+
params *mcp.CallToolParamsFor[GreetArgs],
111+
) (*mcp.CallToolResultFor[struct{}], error) {
112+
message := fmt.Sprintf("Hello, %s! This greeting was logged via MCP middleware.", params.Arguments.Name)
113+
114+
// Additional tool-specific logging
115+
logger.Info("greet tool executed",
116+
"name", params.Arguments.Name,
117+
"message_length", len(message),
118+
)
119+
120+
return &mcp.CallToolResultFor[struct{}]{
121+
Content: []mcp.Content{
122+
&mcp.TextContent{Text: message},
123+
},
124+
}, nil
125+
},
126+
mcp.Input(
127+
mcp.Property("name", mcp.Description("Name to greet")),
128+
),
129+
),
130+
)
131+
132+
// Add a tool that demonstrates error logging
133+
server.AddTools(
134+
mcp.NewServerTool(
135+
"error_demo",
136+
"Demonstrate error logging via middleware",
137+
func(
138+
ctx context.Context,
139+
ss *mcp.ServerSession,
140+
params *mcp.CallToolParamsFor[struct {
141+
ShouldError bool `json:"should_error"`
142+
}],
143+
) (*mcp.CallToolResultFor[struct{}], error) {
144+
if params.Arguments.ShouldError {
145+
return nil, fmt.Errorf("demonstration error as requested")
146+
}
147+
148+
return &mcp.CallToolResultFor[struct{}]{
149+
Content: []mcp.Content{
150+
&mcp.TextContent{Text: "No error occurred"},
151+
},
152+
}, nil
153+
},
154+
mcp.Input(
155+
mcp.Property("should_error", mcp.Description("Whether to return an error")),
156+
),
157+
),
158+
)
159+
160+
logger.Info("MCP server configured with logging middleware",
161+
"tools_count", 2,
162+
)
163+
164+
return server
165+
}
166+
167+
func main() {
168+
flag.Parse()
169+
170+
logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
171+
if err != nil {
172+
log.Fatal("Failed to open log file:", err)
173+
}
174+
defer logFile.Close()
175+
176+
logger := slog.New(slog.NewJSONHandler(logFile, &slog.HandlerOptions{
177+
Level: slog.LevelDebug,
178+
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
179+
if a.Key == slog.TimeKey {
180+
return slog.String("timestamp", a.Value.Time().Format(time.RFC3339))
181+
}
182+
return a
183+
},
184+
}))
185+
186+
server := createServer(logger)
187+
188+
if *httpAddr != "" {
189+
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
190+
return server
191+
}, nil)
192+
log.Printf("MCP handler listening at %s", *httpAddr)
193+
http.ListenAndServe(*httpAddr, handler)
194+
} else {
195+
t := mcp.NewLoggingTransport(mcp.NewStdioTransport(), os.Stderr)
196+
if err := server.Run(context.Background(), t); err != nil {
197+
log.Printf("Server failed: %v", err)
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)