bugsink-remote-mcp
by yakovyarmo
README.md
# bugsink-remote-mcp
[](https://deploy.workers.cloudflare.com/?url=https://github.com/yakovyarmo/bugsink-remote-mcp)
A remote [MCP](https://modelcontextprotocol.io) server for [Bugsink](https://www.bugsink.com), the self-hosted, Sentry-compatible error tracker. It runs as a Cloudflare Worker behind **Cloudflare Access Managed OAuth**, so you can add it to claude.ai (or any MCP client that supports OAuth) as a custom connector. An assistant can then see which bugs are happening, dig into events and stacktraces, and resolve issues once they're fixed.
- One connector serves several Bugsink instances.
- It works when the Bugsink hosts themselves sit behind Cloudflare Access, using a service token.
- There's no local process and no tokens on laptops. Credentials live only as Worker secrets.
Existing Bugsink MCP servers run locally over stdio and send only the Bugsink token, so they can't reach a Bugsink behind Access, and they can't be used as hosted connectors.
## Tools
Every tool takes `site`, one of the keys in `BUGSINK_SITES`. It's optional when only one site is configured.
| Tool | Writes? | What it does |
|---|---|---|
| `list_projects` | no | Lists the projects on that Bugsink |
| `list_issues` | no | Lists issues. Default: open issues across all projects, newest activity first. Filters: `project`, `status` (`open`/`resolved`/`muted`/`all`), `seen_since`; `sort=events`; `limit` |
| `get_issue` | no | One issue, by friendly ID (`MYSITE-SERVERSIDE-12`) or UUID, plus its 5 latest events |
| `list_events` | no | Events of one issue, newest first |
| `get_event` | no | One event: page URL, request headers, cookies and body, referer, browser and OS, user agent, the user (id, email, username, IP), exception, the last 15 breadcrumbs and the stacktrace. `raw=true` adds the full Sentry payload |
| `resolve_issue` | **yes** | `when=now` resolves immediately. `when=next_release` resolves once a newer release is reported to Bugsink |
Nothing can delete or mute issues, or edit projects or teams.
- **Credential values are hidden** wherever they appear, replaced with `[credential hidden]`. That covers session cookies (`SESS…`/`SSESS…`, anything named like `session`), `Authorization` headers, and fields named like a token, password, secret, CSRF value or API key. It applies in Cookie strings, query strings, URL fragments and any URL in the event (request, referer, breadcrumbs), including `user:password@` in URLs. JWTs and `Bearer …` values are hidden wherever they appear, whatever the field is called. Path segments that look like one-time login hashes (32+ URL-safe characters with an uppercase letter and a digit, such as Drupal's `/user/reset/…/<hash>`) are hidden too. Name-based hiding can miss a credential stored under an unusual name, so treat the output as sensitive. Other personal data in events is returned as is: emails, IPs, usernames, other cookies and request bodies. **Anyone who can use the connector can read it**, so keep the Access policy tight.
- **Results are marked untrusted.** Every result starts with a note that Bugsink content was written by visitors' browsers and servers and must not be treated as instructions.
## How a request flows
```
MCP client ──OAuth──▶ Cloudflare Access (Managed OAuth, policy: your email)
│ adds Cf-Access-Jwt-Assertion
▼
Worker https://bugsink-mcp.example.com/mcp
- verifies the Access JWT (issuer, audience, email allow-list)
- MCP over Streamable HTTP (agents/mcp/server createMcpHandler, stateless)
│ Bearer <Bugsink token> (+ CF-Access-Client-Id/Secret)
▼
Bugsink /api/canonical/0/ (one or more instances)
```
- **Access is the only way in.** The Worker has no `workers.dev` or preview URL (`workers_dev: false`, `preview_urls: false`). It also verifies the Access JWT itself as a second line of defence; service-token JWTs carry no email, so they never pass.
- **The token can't be sent elsewhere.** The Bugsink client only calls `/api/canonical/0/` on the configured origin, including when it follows pagination `next` URLs. Redirects (an Access login page) and 401/403 come back as clear tool errors.
## Setup
You need a Cloudflare account with Zero Trust (the free plan is enough), and a zone in that account for the Worker's hostname. Managed OAuth only works for apps in the same account as your Zero Trust organization.
### 1. Prepare Bugsink and Access
1. **Bugsink API tokens.** In each Bugsink UI, open Tokens and create a token. A token can do anything Bugsink's API allows; this connector only exposes the tools above.
2. **If Access protects your Bugsink hosts:** under Zero Trust → Access → Service credentials, create a service token. Add a *Service Auth* policy that includes it to each Bugsink Access app.
3. **Access app for the Worker.**
- Create a self-hosted app on exactly the hostname you'll give the Worker, with an Allow policy for your email. If a wildcard app already covers the zone, still create this dedicated app; Access applies the most specific hostname first.
- Under Advanced settings, turn on **Managed OAuth** for this app only.
- Allow your MCP client's OAuth redirect URI. For claude.ai, check Anthropic's custom connector docs for the current callback URL.
- Suggested token settings: access token 5–15 min, grant session 1–2 weeks.
- Note the app's **Application Audience (AUD) tag**.
### 2a. Deploy with the button
1. Click **Deploy to Cloudflare** above. Cloudflare copies this repo into your GitHub or GitLab account and deploys it.
2. Set the variables (`MCP_HOSTNAME`, `ACCESS_TEAM_DOMAIN`, `ACCESS_AUD`, `ALLOWED_EMAILS`, `BUGSINK_SITES`) and the secrets from `.dev.vars.example`. Each field explains where to find its value.
- If you use a site name other than `main`, add a secret named `BUGSINK_TOKEN_<SITE>` for it.
- If Access doesn't protect your Bugsink, leave the service token secrets empty or delete them.
3. In the Worker's **Settings → Domains & Routes**, add your hostname as a custom domain. The Worker has no `workers.dev` URL on purpose.
### 2b. Deploy from a clone
1. `cp wrangler.jsonc wrangler.local.jsonc` (git-ignored). Set the variables there and add your custom domain under `routes`.
2. Deploy: `npx wrangler deploy -c wrangler.local.jsonc`. It needs `wrangler login`, or a `CLOUDFLARE_API_TOKEN` with Account → Workers Scripts: Edit, plus Workers Routes: Edit and DNS: Edit on the Worker's zone.
3. Add the secrets:
```bash
npx wrangler secret put BUGSINK_TOKEN_MAIN -c wrangler.local.jsonc # one per site: BUGSINK_TOKEN_<SITE>
npx wrangler secret put BUGSINK_ACCESS_CLIENT_ID -c wrangler.local.jsonc # only with Access-protected Bugsink
npx wrangler secret put BUGSINK_ACCESS_CLIENT_SECRET -c wrangler.local.jsonc # only with Access-protected Bugsink
```
### 3. Connect
In claude.ai, go to Settings → Connectors → Add custom connector and enter `https://<your hostname>/mcp`. Sign in through Access when prompted.
**Checks**
- `curl -i https://<your hostname>/mcp` without a login should get a 401 from Access.
- If the connector says `Missing Cloudflare Access token`, the request reached the Worker without passing through Access. Check that the Access app covers the hostname.
- If it says `Access verification is not configured`, `ACCESS_AUD` or `ACCESS_TEAM_DOMAIN` is still empty.
### Service token expiry
Access service tokens expire.
- **Add an alert:** Notifications → Add → *Expiring Access Service Token* warns a week ahead.
- **Refresh:** **Refresh** in the dashboard extends the expiry date.
- **Rotate:** to rotate the secret, use **Rotate secret** with a grace period, then `npx wrangler secret put BUGSINK_ACCESS_CLIENT_SECRET` (add `-c wrangler.local.jsonc` for a clone deployment). The Client ID doesn't change.
## Development
```bash
npm install
npm run typecheck
npm test # unit + MCP handler tests against an in-memory Bugsink
npm run smoke # read-only check against a live Bugsink (never resolves anything)
```
`npm run smoke` reads these environment variables:
- `SMOKE_SITE`, `SMOKE_BUGSINK_URL` and `SMOKE_BUGSINK_TOKEN`.
- `BUGSINK_ACCESS_CLIENT_ID` / `BUGSINK_ACCESS_CLIENT_SECRET`, if Access protects the host.
For `wrangler dev`, copy `.dev.vars.example` to `.dev.vars`.
Versions are pinned on purpose: `agents@0.33.0` requires exactly `@modelcontextprotocol/server@2.4.0`. Upgrade the two together.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues