# 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