Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,38 @@ export TELEMETRY_ENABLED=false

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

### MCP Tracing

The project includes automatic tracing for MCP tool calls following [OpenTelemetry MCP Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/pull/2083).

**Key Features:**

- Automatic span creation for tool calls
- Detailed attributes (method name, tool name, arguments, results)
- Error capture and correlation
- Compatible with Sentry performance monitoring

**Implementation:**

All MCP tools are automatically wrapped with Sentry tracing using the `WithSentryTracing` wrapper:

```go
mcp.AddTool(server, &mcp.Tool{
Name: "my_tool",
Description: "Tool description",
}, WithSentryTracing("my_tool", m.handleMyTool))
```

**Span Attributes:**

Each tool call creates a span with:

- Operation: `mcp.server`
- Name: `tools/call {tool_name}`
- Attributes: `mcp.method.name`, `mcp.tool.name`, `mcp.request.argument.*`, `mcp.tool.result.*`

See `docs/MCP_TRACING.md` for complete documentation on span conventions, attributes, and examples.

## Security Considerations

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

3. Register tool in `internal/cli/mcp/server.go`:
3. Register tool in `internal/cli/mcp/server.go` with Sentry tracing:

```go
mcp.AddTool(server, &mcp.Tool{
Name: "new_tool",
Description: "Description of what the tool does",
}, func(ctx context.Context, req *mcp.CallToolRequest, args NewToolArgs) (*mcp.CallToolResult, any, error) {
return m.handleNewTool(ctx, req, args)
})
}, WithSentryTracing("new_tool", m.handleNewTool))
```

4. Test the tool:
Expand Down
260 changes: 260 additions & 0 deletions docs/MCP_TRACING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# MCP Tracing with Sentry

This document describes the MCP (Model Context Protocol) tracing integration with Sentry for the GitHub Actions Utils CLI.

## Quick Start

Automatic instrumentation for MCP tool calls that creates Sentry spans following OpenTelemetry conventions.

### Usage

Register a tool with Sentry tracing:

```go
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.

### What Gets Captured

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`)

### Benefits

✅ **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

### Disable Telemetry

```bash
export TELEMETRY_ENABLED=false
```

## Overview

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:

- Automatic span creation for tool calls
- Detailed attributes following MCP semantic conventions
- Error capture and correlation
- Tool result tracking

## Implementation

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 extraction
- `internal/cli/mcp/server.go` - Tool registration with tracing

## Detailed Usage

### Wrapping a Tool Handler

Use the `WithSentryTracing` wrapper when registering tools:

```go
mcp.AddTool(server, &mcp.Tool{
Name: "my_tool",
Description: "Does something useful",
}, WithSentryTracing("my_tool", m.handleMyTool))
```

The wrapper:

1. Creates a span for the tool execution
2. Sets MCP-specific attributes
3. Captures tool arguments
4. Tracks results and errors
5. Reports to Sentry

### Example

See `internal/cli/mcp/server.go` for a complete example:

```go
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))
}
```

## Span Conventions

All spans follow the OpenTelemetry MCP semantic conventions:

### Span Name

Tool call spans use the format: `tools/call {tool_name}`

Examples:

- `tools/call get_action_parameters`
- `tools/call my_custom_tool`

### Span Operation

All MCP tool spans use the operation: `mcp.server`

### Common Attributes

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-Specific Attributes

#### Tool Arguments

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.`

#### Tool Results

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"]` |

### Request Metadata

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 |

## Span Status

Spans are marked with appropriate status:

- `ok` - Tool executed successfully
- `internal_error` - Tool returned an error

## Error Capture

When a tool handler returns an error:

1. The span status is set to `internal_error`
2. `mcp.tool.result.is_error` is set to `true`
3. The error is captured to Sentry with full context
4. The error is propagated to the MCP client

## Example Span Data

Here's an example of what a tool call span looks like in Sentry:

```json
{
"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"
}
}
```

## Viewing Traces

In Sentry:

1. Go to **Performance** → **Traces**
2. Filter by operation: `mcp.server`
3. See tool calls with full context

## Comparison with JavaScript SDK

This implementation closely follows the Sentry JavaScript SDK's MCP integration:

### Similarities

- Follows same OpenTelemetry MCP conventions
- Uses identical attribute names and values
- Implements same span creation patterns
- Captures results and errors similarly

### Differences

- **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)

### Why the Difference?

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

## Future Enhancements

Potential improvements to consider:

1. **Session Management**: Track client/server info across requests
2. **Transport Wrapping**: Intercept all MCP messages (not just tool calls)
3. **Resource Tracing**: Add spans for resource access
4. **Prompt Tracing**: Add spans for prompt requests
5. **Notification Tracing**: Track MCP notifications
6. **Result Content**: Optionally capture full result payloads (with PII filtering)

## References

- [OpenTelemetry MCP Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/pull/2083)
- [MCP Specification](https://modelcontextprotocol.io/)
- [Sentry Go SDK](https://docs.sentry.io/platforms/go/)
- [Sentry JavaScript MCP Integration](https://github.com/getsentry/sentry-javascript/tree/develop/packages/core/src/integrations/mcp-server)
Loading