This document describes the MCP (Model Context Protocol) tracing integration with Sentry for the GitHub Actions Utils CLI.
Automatic instrumentation for MCP tool calls that creates Sentry spans following OpenTelemetry conventions.
Register a tool with Sentry tracing:
mcp.AddTool(server, &mcp.Tool{
Name: "my_tool",
Description: "My awesome tool",
}, WithSentryTracing("my_tool", m.handleMyTool))That's it! The tool is now automatically traced.
Every tool call creates a span with:
- Operation:
mcp.server - Name:
tools/call my_tool - Attributes:
- Method name (
mcp.method.name) - Tool name (
mcp.tool.name) - All arguments (
mcp.request.argument.*) - Result metadata (
mcp.tool.result.*) - Transport info (
mcp.transport,network.transport) - Error status (
mcp.tool.result.is_error)
- Method name (
✅ Zero boilerplate: One wrapper function, that's it
✅ Type-safe: Uses Go generics
✅ Automatic: Arguments and results captured automatically
✅ Standard: Follows OpenTelemetry MCP conventions
✅ Production-ready: Error capture, proper span lifecycle
export TELEMETRY_ENABLED=falseThe MCP Server integration automatically instruments tool calls with Sentry spans, following the OpenTelemetry MCP Semantic Conventions. This provides comprehensive observability for MCP tool execution, including:
- Automatic span creation for tool calls
- Detailed attributes following MCP semantic conventions
- Error capture and correlation
- Tool result tracking
The implementation is based on the Sentry JavaScript SDK's MCP integration, adapted for Go. Key files:
internal/cli/mcp/sentry.go- Tracing wrapper and attribute extractioninternal/cli/mcp/server.go- Tool registration with tracing
Use the WithSentryTracing wrapper when registering tools:
mcp.AddTool(server, &mcp.Tool{
Name: "my_tool",
Description: "Does something useful",
}, WithSentryTracing("my_tool", m.handleMyTool))The wrapper:
- Creates a span for the tool execution
- Sets MCP-specific attributes
- Captures tool arguments
- Tracks results and errors
- Reports to Sentry
See internal/cli/mcp/server.go for a complete example:
func (m *MCPServer) RegisterTools(server *mcp.Server) {
// Register get_action_parameters tool with Sentry tracing
mcp.AddTool(server, &mcp.Tool{
Name: "get_action_parameters",
Description: "Fetch and parse a GitHub Action's action.yml file...",
}, WithSentryTracing("get_action_parameters", m.handleGetActionParameters))
}All spans follow the OpenTelemetry MCP semantic conventions:
Tool call spans use the format: tools/call {tool_name}
Examples:
tools/call get_action_parameterstools/call my_custom_tool
All MCP tool spans use the operation: mcp.server
All spans include these attributes:
| Attribute | Type | Description | Example |
|---|---|---|---|
mcp.method.name |
string | The MCP method name | "tools/call" |
mcp.tool.name |
string | The tool being called | "get_action_parameters" |
mcp.transport |
string | Transport method used | "stdio" |
network.transport |
string | OSI transport layer protocol | "pipe" |
network.protocol.version |
string | JSON-RPC version | "2.0" |
sentry.origin |
string | Sentry origin identifier | "auto.function.mcp_server" |
sentry.source |
string | Sentry source type | "route" |
Tool arguments are automatically extracted and set with the prefix mcp.request.argument:
mcp.request.argument.actionref = "actions/checkout@v5"
The argument names are:
- Extracted from JSON struct tags
- Converted to lowercase
- Prefixed with
mcp.request.argument.
Result metadata is captured:
| Attribute | Type | Description | Example |
|---|---|---|---|
mcp.tool.result.is_error |
boolean | Whether the tool returned an error | false |
mcp.tool.result.content_count |
int | Number of content items returned | 1 |
mcp.tool.result.content |
string | JSON array of content types | ["text"] |
If available, the following are extracted from the request:
| Attribute | Type | Description |
|---|---|---|
mcp.request.id |
string | Unique request identifier |
mcp.session.id |
string | MCP session identifier |
Spans are marked with appropriate status:
ok- Tool executed successfullyinternal_error- Tool returned an error
When a tool handler returns an error:
- The span status is set to
internal_error mcp.tool.result.is_erroris set totrue- The error is captured to Sentry with full context
- The error is propagated to the MCP client
Here's an example of what a tool call span looks like in Sentry:
{
"op": "mcp.server",
"description": "tools/call get_action_parameters",
"status": "ok",
"data": {
"mcp.method.name": "tools/call",
"mcp.tool.name": "get_action_parameters",
"mcp.transport": "stdio",
"network.transport": "pipe",
"network.protocol.version": "2.0",
"mcp.request.argument.actionref": "actions/checkout@v5",
"mcp.tool.result.is_error": false,
"mcp.tool.result.content_count": 1,
"mcp.tool.result.content": "[\"text\"]",
"sentry.origin": "auto.function.mcp_server",
"sentry.source": "route"
}
}In Sentry:
- Go to Performance → Traces
- Filter by operation:
mcp.server - See tool calls with full context
This implementation closely follows the Sentry JavaScript SDK's MCP integration:
- Follows same OpenTelemetry MCP conventions
- Uses identical attribute names and values
- Implements same span creation patterns
- Captures results and errors similarly
- Language: Go vs TypeScript
- SDK Integration: Direct wrapper vs transport interception
- JS: Wraps transport layer to intercept all messages
- Go: Wraps individual tool handlers (simpler, more idiomatic)
- Type Safety: Go uses generics for type-safe wrappers
- Session Management: Not yet implemented (stateless server)
The Go MCP SDK has a different architecture:
- Tool handlers are registered directly with type safety
- No need to wrap transport layer for basic tool tracing
- Simpler approach that achieves the same observability goals
Potential improvements to consider:
- Session Management: Track client/server info across requests
- Transport Wrapping: Intercept all MCP messages (not just tool calls)
- Resource Tracing: Add spans for resource access
- Prompt Tracing: Add spans for prompt requests
- Notification Tracing: Track MCP notifications
- Result Content: Optionally capture full result payloads (with PII filtering)