Skip to main content
Glama
Pyda-AI

Sentry MCP Server

by Pyda-AI
README.md
# Sentry MCP Server

A stateless FastMCP server for Sentry API integration, supporting multiple Sentry instances (US, EU, self-hosted) with a single deployment.

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                     Customer VPC                             │
│                                                              │
│  ┌──────────────┐         ┌─────────────────────────────┐   │
│  │   Satellite  │ ──MCP──▶│      Sentry MCP Server      │   │
│  └──────────────┘         │  ┌─────────┐ ┌─────────┐    │   │
│                           │  │sentryUS │ │sentryEU │    │   │
│                           │  └────┬────┘ └────┬────┘    │   │
│                           └───────┼───────────┼─────────┘   │
└───────────────────────────────────┼───────────┼─────────────┘
                                    │           │
                                    ▼           ▼
                              sentry.io    de.sentry.io
                                 (US)          (EU)
```

## Features

- **Multi-instance support**: Configure multiple Sentry accounts (US, EU, self-hosted)
- **Base tools + instance parameter**: 11 tools that work across all instances
- **Stateless HTTP**: Scales horizontally, no session state
- **Secure**: Auth tokens stored in K8s secrets, never exposed to Claude

## Tools

| Tool | Description |
|------|-------------|
| `list_sentry_instances` | Discovery - list all configured Sentry accounts |
| `find_organizations` | List organizations accessible with the token |
| `find_teams` | List teams in an organization |
| `find_projects` | List projects in an organization |
| `find_releases` | List releases in an org/project |
| `get_issue_details` | Get detailed info about a specific issue |
| `search_issues` | Search for grouped issues in a project |
| `search_issue_events` | Search events within a specific issue |
| `search_events` | Search events and perform aggregations |
| `get_trace_details` | Get trace overview and span breakdown |
| `get_event_attachment` | Download attachments from an event |

## Quick Start

### 1. Create Internal Integration in Sentry

For each Sentry organization you want to connect:

1. Go to **Settings → Developer Settings → Internal Integrations**
   - US: `https://sentry.io/settings/{org}/developer-settings/`
   - EU: `https://de.sentry.io/settings/{org}/developer-settings/`

2. Click **Create New Internal Integration**

3. Configure:
   - **Name**: `Deeptrace MCP`
   - **Scopes** (read-only):
     - `org:read`
     - `project:read`
     - `event:read`
     - `team:read`
     - `member:read`

4. Click **Save** and copy the generated token (starts with `sntrys_...`)

### 2. Create Kubernetes Secrets

```bash
# US Sentry
kubectl create secret generic sentry-us-creds \
  --from-literal=auth_token="sntrys_eyJ..." \
  -n deeptrace

# EU Sentry
kubectl create secret generic sentry-eu-creds \
  --from-literal=auth_token="sntrys_eyJ..." \
  -n deeptrace
```

### 3. Deploy with Helm

Create a `values.yaml`:

```yaml
instances:
  sentryUS:
    enabled: true
    region: "us"
    orgSlug: "your-org-slug"
    secretName: "sentry-us-creds"
    labels:
      dataResidency: "US"
      description: "US customer data and services"

  sentryEU:
    enabled: true
    region: "eu"
    orgSlug: "your-org-slug-eu"
    secretName: "sentry-eu-creds"
    labels:
      dataResidency: "EU"
      description: "EU customer data (GDPR)"
```

Install:

```bash
helm install sentry-mcp ./helm/sentry-mcp \
  -f values.yaml \
  -n deeptrace
```

### 4. Configure Satellite

Add to your satellite's `values.yaml`:

```yaml
mcpServers:
  sentryMCP:
    create: true
    type: mcp
    connection:
      url: "http://sentry-mcp.deeptrace:8080/mcp"
      transport: "streamable_http"
      stateless: true
      auditLogging: true
    domainAllowlist:
      - "sentry-mcp.deeptrace"
```

## Configuration Reference

### Instance Configuration

| Field | Required | Description |
|-------|----------|-------------|
| `enabled` | No | Enable/disable instance (default: true) |
| `region` | Yes | `us`, `eu`, or `custom` |
| `orgSlug` | Yes | Sentry organization slug |
| `baseUrl` | For custom | Full URL for self-hosted Sentry |
| `secretName` | Yes | K8s secret name with `auth_token` key |
| `labels.dataResidency` | No | Data residency label (e.g., "US", "EU") |
| `labels.description` | No | Human-readable description |
| `labels.team` | No | Team that owns this instance |
| `labels.environment` | No | Environment (production, staging) |

### Self-Hosted Example

```yaml
instances:
  sentrySelfHosted:
    enabled: true
    region: "custom"
    baseUrl: "https://sentry.internal.company.com"
    orgSlug: "internal"
    secretName: "sentry-internal-creds"
    labels:
      dataResidency: "On-Premise"
      description: "Self-hosted Sentry for internal services"
```

## How It Works

### Claude's Workflow

1. **Discovery**: Claude calls `list_sentry_instances()` to see available Sentry accounts
2. **Query**: Claude uses the instance name when calling other tools
3. **Multi-region**: Claude queries both instances when context is ambiguous

Example conversation:

```
User: "Find unresolved errors in the Platform team's Sentry"

Claude: I'll check the available Sentry instances first.
→ list_sentry_instances()
→ Returns: [sentryUS (US), sentryEU (EU)]

Claude: The Platform team uses the EU instance based on the labels.
→ search_issues(instance="sentryEU", query="is:unresolved level:error")
→ "Found 5 unresolved errors in the EU Sentry..."
```

### Why Internal Integration?

| Auth Type | Tied To | Multi-Org? | Best For |
|-----------|---------|------------|----------|
| Personal Token | User | Yes (user's orgs) | Personal scripts |
| Internal Integration | Organization | No (single org) | Automated systems |
| Public Integration | OAuth | Yes (via OAuth) | Third-party SaaS |

Internal Integration is correct because:
- **Not tied to a person** - survives employee departures
- **Organization-scoped** - clear access boundaries
- **No OAuth complexity** - simple bearer token
- **Fine-grained scopes** - request only what you need

## Development

### Local Setup

```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate

# Install dependencies
pip install -e ".[dev]"

# Create local config
mkdir -p /tmp/sentry-mcp
cat > /tmp/sentry-mcp/config.yaml << EOF
instances:
  sentryUS:
    enabled: true
    region: us
    orgSlug: your-org
EOF

# Create local secret
mkdir -p /tmp/secrets/sentryUS
echo "sntrys_your_token" > /tmp/secrets/sentryUS/auth_token

# Run server
SENTRY_MCP_CONFIG=/tmp/sentry-mcp/config.yaml \
SENTRY_MCP_SECRETS=/tmp/secrets \
python -m sentry_mcp
```

### Building Docker Image

```bash
docker build -t deeptrace/sentry-mcp:latest .
```

### Running Tests

```bash
pytest
```

## Troubleshooting

### "Unknown Sentry instance" Error

The instance name in your tool call doesn't match any configured instance.

1. Check `list_sentry_instances()` output
2. Verify the instance is enabled in config
3. Ensure the K8s secret exists and has `auth_token` key

### "No auth token configured" Error

The secret for the instance wasn't found or is empty.

1. Verify secret exists: `kubectl get secret <secretName> -n deeptrace`
2. Check secret has `auth_token` key: `kubectl get secret <secretName> -o yaml`
3. Ensure secret is mounted correctly in deployment

### API Rate Limiting

Sentry has rate limits. If you hit them:

1. Reduce `limit` parameter in search queries
2. Add delays between bulk operations
3. Consider caching frequently-accessed data

## License

MIT