Skip to main content
Glama
fers4t

bugsink-mcp

by fers4t
README.md
# bugsink-mcp

A full read/write [MCP](https://modelcontextprotocol.io) server for [Bugsink](https://www.bugsink.com/)
(self-hosted, Sentry-protocol-compatible error tracking), built directly against Bugsink's
`/api/canonical/0/` OpenAPI schema.

It exists because the existing `bugsink-mcp` package on npm only wraps the **read** side of the
API (list/get). This one covers the full canonical API surface: resolving, muting, reopening and
deleting issues, commenting, and creating/updating teams, projects and releases — plus a couple of
correctness fixes over the original (see [Fixes over the original](#fixes-over-the-original)).

## Setup

Requires Node.js 18.17+.

1. In Bugsink, go to **Account Settings → Auth Tokens** and create a token.
2. Add the server to your MCP client.

### Claude Code

One-liner, using the [`claude mcp` CLI](https://docs.claude.com/en/docs/claude-code/mcp):

```bash
claude mcp add bugsink \
  -e BUGSINK_URL=https://bugs.example.com \
  -e BUGSINK_TOKEN=<your token> \
  -- npx -y github:fers4t/bugsink-mcp
```

This writes the equivalent of the following into `~/.claude.json` (or pass `--scope project` to
write a shared `.mcp.json` instead of the user-local config):

```json
{
  "mcpServers": {
    "bugsink": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "github:fers4t/bugsink-mcp"],
      "env": {
        "BUGSINK_URL": "https://bugs.example.com",
        "BUGSINK_TOKEN": "<your token>"
      }
    }
  }
}
```

Restart Claude Code (or start a new session) afterwards — MCP tool lists are loaded at session
start and won't pick up a config change mid-conversation.

### Other MCP clients

Any client that reads a `mcpServers` map (Cursor, Windsurf, etc.) can use the same JSON shape
shown above — point it at `command: npx`, `args: ["-y", "github:fers4t/bugsink-mcp"]`, and the two
required env vars.

`npx` installs straight from GitHub (no npm publish involved) and runs the `prepare` script to
compile TypeScript on first use, then reuses its local cache on subsequent launches.

### Environment variables

| Variable             | Required | Description                                              |
| --------------------- | -------- | ---------------------------------------------------------- |
| `BUGSINK_URL`         | yes      | Base URL of your Bugsink instance, e.g. `https://bugs.example.com` |
| `BUGSINK_TOKEN`       | yes      | Personal API token (Bearer auth)                          |
| `BUGSINK_TIMEOUT_MS`  | no       | Per-request timeout in ms (default `15000`, clamped to `1000–120000`) |

See `.env.example`. Never commit a real token — the server reads it from the environment only.

## Tools

### Connectivity

| Tool | Description |
| --- | --- |
| `test_connection` | Verify the URL/token work and report how many teams are visible |

### Teams

| Tool | Description |
| --- | --- |
| `list_teams` | List all teams |
| `get_team` | Get a team by UUID |
| `create_team` | Create a team |
| `update_team` | Rename a team or change its visibility |

### Projects

| Tool | Description |
| --- | --- |
| `list_projects` | List projects, optionally filtered by team |
| `get_project` | Get a project (including its DSN), optionally expanding the team |
| `create_project` | Create a project under a team |
| `update_project` | Update name/visibility/alert flags/retention/team |

### Issues

| Tool | Description |
| --- | --- |
| `list_issues` | List issues for a project (sort by `digest_order`, `digested_event_count` or `last_seen`) |
| `get_issue` | Get an issue by UUID or friendly ID (e.g. `PROJ-123`) |
| `delete_issue` | **Irreversibly** delete an issue and its events |
| `resolve_issue` | Resolve now |
| `resolve_issue_in_next_release` | Resolve, regressing if a later release reports it again |
| `resolve_issue_in_latest_release` | Resolve as of the current latest release |
| `reopen_issue` | Reopen a resolved issue |
| `mute_issue` | Mute indefinitely |
| `mute_issue_for` | Mute for N periods (e.g. 3 days) |
| `mute_issue_until` | Mute until an event-count threshold is crossed in a period |
| `unmute_issue` | Unmute |
| `add_issue_comment` | Add a comment to an issue |

### Events

| Tool | Description |
| --- | --- |
| `list_events` | List events (occurrences) for an issue |
| `get_event` | Get full event detail, including tags/contexts/raw payload |
| `get_stacktrace` | Render a rendered-Markdown stacktrace for an event |

### Releases

| Tool | Description |
| --- | --- |
| `list_releases` | List releases for a project |
| `get_release` | Get a release by UUID |
| `create_release` | Create a release (needed for `resolve_issue_in_next_release` to regress correctly) |

## Fixes over the original

- **`get_stacktrace` actually works.** Bugsink's stacktrace endpoint returns `text/markdown`, not
  JSON. The original package called `res.json()` unconditionally and threw
  `Unexpected token '#', "# TypeError"... is not valid JSON` on every call. This server reads that
  endpoint as text.
- **Write support**: resolve/mute/reopen/delete/comment on issues, and create/update for teams,
  projects and releases — none of which the read-only original exposed.
- **Bounded timeouts** (`AbortSignal.timeout`, default 15s) on every request, so a hung upstream
  can't hang the MCP process indefinitely.
- **Safe retries**: idempotent `GET` requests get a bounded exponential-backoff-with-jitter retry
  on `502/503/504` or network failure. Mutating requests (`POST`/`PATCH`/`DELETE`) are never
  auto-retried, since retrying a write whose response was merely lost could duplicate side effects
  (e.g. a second identical comment).
- **Input validation** via Zod on every tool argument (numeric IDs, UUIDs, enums), so malformed
  input is rejected locally with a clear message instead of reaching the API.
- **No secrets in output.** Errors report the HTTP status and a truncated response body; the
  `Authorization` header value is never included in any log or thrown error.

## Security notes

- This is a local **stdio** process — it does not open a network listener. Access control is
  whatever your MCP client already provides plus possession of `BUGSINK_TOKEN`.
- All diagnostics go to `stderr`; `stdout` is reserved for the JSON-RPC protocol stream.
- `BUGSINK_URL` must be `http:` or `https:`; plain HTTP to a non-local host prints a warning
  (your token would be sent unencrypted) but is not blocked, to support internal networks.
- Treat `BUGSINK_TOKEN` like any other credential: keep it out of version control, rotate it in
  Bugsink if it ever leaks, and scope it to the least-privileged user your workflow allows.

## Development

```bash
npm install     # installs deps and runs the TypeScript build (prepare script)
npm run typecheck
npm run build   # compile src/ -> dist/
npm run dev     # watch mode
```

## License

MIT