Skip to content

Commit b11d134

Browse files
authored
feat(mcp): add Sentry tracing for MCP tool calls (#1)
* feat(mcp): add Sentry tracing for MCP tool calls * remove analysis of sentry-javascript * remove implementation summary * consolidate documentation
1 parent 1e8273a commit b11d134

File tree

5 files changed

+756
-9
lines changed

5 files changed

+756
-9
lines changed

AGENTS.md

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,38 @@ export TELEMETRY_ENABLED=false
268268

269269
**Sentry DSN:** `https://445c4c2185068fa980b83ddbe4bf1fd7@o188824.ingest.us.sentry.io/4510306572828672`
270270

271+
### MCP Tracing
272+
273+
The project includes automatic tracing for MCP tool calls following [OpenTelemetry MCP Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/pull/2083).
274+
275+
**Key Features:**
276+
277+
- Automatic span creation for tool calls
278+
- Detailed attributes (method name, tool name, arguments, results)
279+
- Error capture and correlation
280+
- Compatible with Sentry performance monitoring
281+
282+
**Implementation:**
283+
284+
All MCP tools are automatically wrapped with Sentry tracing using the `WithSentryTracing` wrapper:
285+
286+
```go
287+
mcp.AddTool(server, &mcp.Tool{
288+
Name: "my_tool",
289+
Description: "Tool description",
290+
}, WithSentryTracing("my_tool", m.handleMyTool))
291+
```
292+
293+
**Span Attributes:**
294+
295+
Each tool call creates a span with:
296+
297+
- Operation: `mcp.server`
298+
- Name: `tools/call {tool_name}`
299+
- Attributes: `mcp.method.name`, `mcp.tool.name`, `mcp.request.argument.*`, `mcp.tool.result.*`
300+
301+
See `docs/MCP_TRACING.md` for complete documentation on span conventions, attributes, and examples.
302+
271303
## Security Considerations
272304

273305
- **No credentials in code**: Never commit API keys or certificates
@@ -303,15 +335,13 @@ func (m *MCPServer) handleNewTool(ctx context.Context, req *mcp.CallToolRequest,
303335
}
304336
```
305337

306-
3. Register tool in `internal/cli/mcp/server.go`:
338+
3. Register tool in `internal/cli/mcp/server.go` with Sentry tracing:
307339

308340
```go
309341
mcp.AddTool(server, &mcp.Tool{
310342
Name: "new_tool",
311343
Description: "Description of what the tool does",
312-
}, func(ctx context.Context, req *mcp.CallToolRequest, args NewToolArgs) (*mcp.CallToolResult, any, error) {
313-
return m.handleNewTool(ctx, req, args)
314-
})
344+
}, WithSentryTracing("new_tool", m.handleNewTool))
315345
```
316346

317347
4. Test the tool:

docs/MCP_TRACING.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# MCP Tracing with Sentry
2+
3+
This document describes the MCP (Model Context Protocol) tracing integration with Sentry for the GitHub Actions Utils CLI.
4+
5+
## Quick Start
6+
7+
Automatic instrumentation for MCP tool calls that creates Sentry spans following OpenTelemetry conventions.
8+
9+
### Usage
10+
11+
Register a tool with Sentry tracing:
12+
13+
```go
14+
mcp.AddTool(server, &mcp.Tool{
15+
Name: "my_tool",
16+
Description: "My awesome tool",
17+
}, WithSentryTracing("my_tool", m.handleMyTool))
18+
```
19+
20+
That's it! The tool is now automatically traced.
21+
22+
### What Gets Captured
23+
24+
Every tool call creates a span with:
25+
26+
- **Operation**: `mcp.server`
27+
- **Name**: `tools/call my_tool`
28+
- **Attributes**:
29+
- Method name (`mcp.method.name`)
30+
- Tool name (`mcp.tool.name`)
31+
- All arguments (`mcp.request.argument.*`)
32+
- Result metadata (`mcp.tool.result.*`)
33+
- Transport info (`mcp.transport`, `network.transport`)
34+
- Error status (`mcp.tool.result.is_error`)
35+
36+
### Benefits
37+
38+
**Zero boilerplate**: One wrapper function, that's it\
39+
**Type-safe**: Uses Go generics\
40+
**Automatic**: Arguments and results captured automatically\
41+
**Standard**: Follows OpenTelemetry MCP conventions\
42+
**Production-ready**: Error capture, proper span lifecycle
43+
44+
### Disable Telemetry
45+
46+
```bash
47+
export TELEMETRY_ENABLED=false
48+
```
49+
50+
## Overview
51+
52+
The MCP Server integration automatically instruments tool calls with Sentry spans, following the [OpenTelemetry MCP Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/pull/2083). This provides comprehensive observability for MCP tool execution, including:
53+
54+
- Automatic span creation for tool calls
55+
- Detailed attributes following MCP semantic conventions
56+
- Error capture and correlation
57+
- Tool result tracking
58+
59+
## Implementation
60+
61+
The implementation is based on the Sentry JavaScript SDK's MCP integration, adapted for Go. Key files:
62+
63+
- `internal/cli/mcp/sentry.go` - Tracing wrapper and attribute extraction
64+
- `internal/cli/mcp/server.go` - Tool registration with tracing
65+
66+
## Detailed Usage
67+
68+
### Wrapping a Tool Handler
69+
70+
Use the `WithSentryTracing` wrapper when registering tools:
71+
72+
```go
73+
mcp.AddTool(server, &mcp.Tool{
74+
Name: "my_tool",
75+
Description: "Does something useful",
76+
}, WithSentryTracing("my_tool", m.handleMyTool))
77+
```
78+
79+
The wrapper:
80+
81+
1. Creates a span for the tool execution
82+
2. Sets MCP-specific attributes
83+
3. Captures tool arguments
84+
4. Tracks results and errors
85+
5. Reports to Sentry
86+
87+
### Example
88+
89+
See `internal/cli/mcp/server.go` for a complete example:
90+
91+
```go
92+
func (m *MCPServer) RegisterTools(server *mcp.Server) {
93+
// Register get_action_parameters tool with Sentry tracing
94+
mcp.AddTool(server, &mcp.Tool{
95+
Name: "get_action_parameters",
96+
Description: "Fetch and parse a GitHub Action's action.yml file...",
97+
}, WithSentryTracing("get_action_parameters", m.handleGetActionParameters))
98+
}
99+
```
100+
101+
## Span Conventions
102+
103+
All spans follow the OpenTelemetry MCP semantic conventions:
104+
105+
### Span Name
106+
107+
Tool call spans use the format: `tools/call {tool_name}`
108+
109+
Examples:
110+
111+
- `tools/call get_action_parameters`
112+
- `tools/call my_custom_tool`
113+
114+
### Span Operation
115+
116+
All MCP tool spans use the operation: `mcp.server`
117+
118+
### Common Attributes
119+
120+
All spans include these attributes:
121+
122+
| Attribute | Type | Description | Example |
123+
| -------------------------- | ------ | ---------------------------- | ---------------------------- |
124+
| `mcp.method.name` | string | The MCP method name | `"tools/call"` |
125+
| `mcp.tool.name` | string | The tool being called | `"get_action_parameters"` |
126+
| `mcp.transport` | string | Transport method used | `"stdio"` |
127+
| `network.transport` | string | OSI transport layer protocol | `"pipe"` |
128+
| `network.protocol.version` | string | JSON-RPC version | `"2.0"` |
129+
| `sentry.origin` | string | Sentry origin identifier | `"auto.function.mcp_server"` |
130+
| `sentry.source` | string | Sentry source type | `"route"` |
131+
132+
### Tool-Specific Attributes
133+
134+
#### Tool Arguments
135+
136+
Tool arguments are automatically extracted and set with the prefix `mcp.request.argument`:
137+
138+
```
139+
mcp.request.argument.actionref = "actions/checkout@v5"
140+
```
141+
142+
The argument names are:
143+
144+
- Extracted from JSON struct tags
145+
- Converted to lowercase
146+
- Prefixed with `mcp.request.argument.`
147+
148+
#### Tool Results
149+
150+
Result metadata is captured:
151+
152+
| Attribute | Type | Description | Example |
153+
| ------------------------------- | ------- | ---------------------------------- | ---------- |
154+
| `mcp.tool.result.is_error` | boolean | Whether the tool returned an error | `false` |
155+
| `mcp.tool.result.content_count` | int | Number of content items returned | `1` |
156+
| `mcp.tool.result.content` | string | JSON array of content types | `["text"]` |
157+
158+
### Request Metadata
159+
160+
If available, the following are extracted from the request:
161+
162+
| Attribute | Type | Description |
163+
| ---------------- | ------ | ------------------------- |
164+
| `mcp.request.id` | string | Unique request identifier |
165+
| `mcp.session.id` | string | MCP session identifier |
166+
167+
## Span Status
168+
169+
Spans are marked with appropriate status:
170+
171+
- `ok` - Tool executed successfully
172+
- `internal_error` - Tool returned an error
173+
174+
## Error Capture
175+
176+
When a tool handler returns an error:
177+
178+
1. The span status is set to `internal_error`
179+
2. `mcp.tool.result.is_error` is set to `true`
180+
3. The error is captured to Sentry with full context
181+
4. The error is propagated to the MCP client
182+
183+
## Example Span Data
184+
185+
Here's an example of what a tool call span looks like in Sentry:
186+
187+
```json
188+
{
189+
"op": "mcp.server",
190+
"description": "tools/call get_action_parameters",
191+
"status": "ok",
192+
"data": {
193+
"mcp.method.name": "tools/call",
194+
"mcp.tool.name": "get_action_parameters",
195+
"mcp.transport": "stdio",
196+
"network.transport": "pipe",
197+
"network.protocol.version": "2.0",
198+
"mcp.request.argument.actionref": "actions/checkout@v5",
199+
"mcp.tool.result.is_error": false,
200+
"mcp.tool.result.content_count": 1,
201+
"mcp.tool.result.content": "[\"text\"]",
202+
"sentry.origin": "auto.function.mcp_server",
203+
"sentry.source": "route"
204+
}
205+
}
206+
```
207+
208+
## Viewing Traces
209+
210+
In Sentry:
211+
212+
1. Go to **Performance****Traces**
213+
2. Filter by operation: `mcp.server`
214+
3. See tool calls with full context
215+
216+
## Comparison with JavaScript SDK
217+
218+
This implementation closely follows the Sentry JavaScript SDK's MCP integration:
219+
220+
### Similarities
221+
222+
- Follows same OpenTelemetry MCP conventions
223+
- Uses identical attribute names and values
224+
- Implements same span creation patterns
225+
- Captures results and errors similarly
226+
227+
### Differences
228+
229+
- **Language**: Go vs TypeScript
230+
- **SDK Integration**: Direct wrapper vs transport interception
231+
- JS: Wraps transport layer to intercept all messages
232+
- Go: Wraps individual tool handlers (simpler, more idiomatic)
233+
- **Type Safety**: Go uses generics for type-safe wrappers
234+
- **Session Management**: Not yet implemented (stateless server)
235+
236+
### Why the Difference?
237+
238+
The Go MCP SDK has a different architecture:
239+
240+
- Tool handlers are registered directly with type safety
241+
- No need to wrap transport layer for basic tool tracing
242+
- Simpler approach that achieves the same observability goals
243+
244+
## Future Enhancements
245+
246+
Potential improvements to consider:
247+
248+
1. **Session Management**: Track client/server info across requests
249+
2. **Transport Wrapping**: Intercept all MCP messages (not just tool calls)
250+
3. **Resource Tracing**: Add spans for resource access
251+
4. **Prompt Tracing**: Add spans for prompt requests
252+
5. **Notification Tracing**: Track MCP notifications
253+
6. **Result Content**: Optionally capture full result payloads (with PII filtering)
254+
255+
## References
256+
257+
- [OpenTelemetry MCP Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/pull/2083)
258+
- [MCP Specification](https://modelcontextprotocol.io/)
259+
- [Sentry Go SDK](https://docs.sentry.io/platforms/go/)
260+
- [Sentry JavaScript MCP Integration](https://github.com/getsentry/sentry-javascript/tree/develop/packages/core/src/integrations/mcp-server)

0 commit comments

Comments
 (0)