mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
feat(connections): connect Sentry with a long-lived user auth token (#20823)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents use app connections to call provider tools, such as the
Sentry MCP server
> - The Sentry connection has only one method: browser sign-in through
the hosted MCP OAuth server
> - That grant expires. An unattended triage agent loses Sentry access
until a person signs in again
> - During that time, a scheduled triage routine completes each run but
reads no Sentry data
> - This pull request adds a second Sentry method that uses a user auth
token
> - The benefit is a Sentry connection that stays valid until a person
revokes the token
## Linked Issues or Issue Description
**Subsystem affected**
App connections: the Sentry entry in the connection catalog.
**Problem or motivation**
A Sentry connection that was created with browser sign-in on 2026-10-11
changed to "OAuth authorization expired. Reconnect this app to
continue." at 04:01 UTC on 2026-11-10. The agent that used it had no
Sentry tools after that time. Each hourly triage run reported the
blocker but could not continue. Unattended agents need a Sentry
connection that does not need a person to sign in again each month.
**Proposed solution**
The hosted Sentry MCP server accepts a Sentry user auth token in the
`Authorization: Sentry-Bearer <token>` header. It reserves `Bearer` for
its own OAuth tokens. Paperclip API-key methods already support a custom
header prefix. Add a Sentry API-key method that uses this header.
**Alternatives considered**
Reconnect with browser sign-in again. That restores access only until
the next grant expiry.
## What Changed
- Add the `mcp-user-token` method ("Use a user auth token") to Sentry in
`scripts/ingest-app-definitions.mjs`. It uses the same server URL and
sends the token as `Authorization: Sentry-Bearer <token>`.
- Regenerate `packages/shared/src/app-definitions/sentry.json`. I did
not include unrelated output that the content corpus drift caused for
other providers.
- Add a permission review entry for the new method in
`doc/connections/tool-method-permission-reviews.json`.
- Add a test for the new method and its header prefix.
- Document the token method, its required scopes, and its ownership risk
in `doc/connections/SENTRY.md`.
## Verification
- `pnpm exec vitest run packages/shared/src/app-definitions.test.ts`: 40
passed. The new assertion fails on `master` because Sentry has only one
method there.
- `pnpm exec vitest run src/__tests__/tool-access-service.test.ts -t
Sentry` (server): 2 passed.
- `pnpm exec vitest run ui/src/pages/apps/app-connect-policy.test.ts
server/src/__tests__/satisfied-connection-intents.test.ts`: 64 passed.
- `pnpm verify` returned exit 2 (a doc is a global CI input). Required
CI supplies the broad coverage.
- Live probe of `https://mcp.sentry.dev/mcp` with an `initialize`
request: `Authorization: Sentry-Bearer <invalid token>` starts an MCP
session (the token is checked on tool calls). `Authorization: Bearer
<same token>` returns `401 invalid_token`. This confirms the server
accepts the header scheme.
- Not done yet: a real agent tool call with a real token after
deployment.
## Risks
- Low risk. This adds a method. The existing OAuth method and saved
connections do not change.
- A user auth token acts as its creator and lasts until revoked. The
method guidance and docs say this. Rotate the token when that person
leaves.
- If Sentry changes the `Sentry-Bearer` scheme, connections that use
this method fail on tool calls. The OAuth method still works.
> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use
(shell, browser, web search).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ce953ca52
commit
34f2bb78ee
6 files changed
+131
-15
No files matched your search
@@ -4,6 +4,25 @@ Paperclip connects to Sentry's [official MCP server](https://mcp.sentry.dev/)
|
||||
at `https://mcp.sentry.dev/mcp`. Browser sign-in uses the MCP authorization
|
||||
server. A normal connection does not require a customer-created OAuth app.
|
||||
|
||||
## User auth token
|
||||
|
||||
Browser sign-in grants expire. A staging company connected on 2026-10-11
|
||||
needed reauthorization at 04:01 UTC on 2026-11-10, and its agents lost Sentry
|
||||
access until a person signed in again. For unattended agents such as a triage
|
||||
routine, use **Use a user auth token** instead.
|
||||
|
||||
Create a [user auth token](https://sentry.io/settings/account/api/auth-tokens/)
|
||||
with `org:read`, `project:read`, `project:write`, `team:read`, `team:write` and
|
||||
`event:write`. Paperclip sends it to the same hosted server as
|
||||
`Authorization: Sentry-Bearer <token>`. The hosted server reserves `Bearer` for
|
||||
its own OAuth tokens and forwards a `Sentry-Bearer` token to Sentry without
|
||||
storing or refreshing it. See the
|
||||
[Sentry MCP README](https://github.com/getsentry/sentry-mcp#remote-server).
|
||||
|
||||
The token acts as the person who created it and is limited by that person's
|
||||
organization role. It lasts until revoked, so rotate it if that person leaves
|
||||
the organization.
|
||||
|
||||
## Discovery
|
||||
|
||||
The method pins the [MCP protected-resource metadata](https://mcp.sentry.dev/.well-known/oauth-protected-resource/mcp).
|
||||
|
||||
@@ -2016,6 +2016,22 @@
|
||||
"liveProof": "not-run",
|
||||
"evidenceLimit": "Provider documentation was unavailable or not readable during this review; metadata/registered source only. Account-bound proof remains outstanding."
|
||||
},
|
||||
{
|
||||
"app": "sentry",
|
||||
"method": "mcp-user-token",
|
||||
"auth": "api_key",
|
||||
"policy": "provider-key",
|
||||
"requestedScopes": [],
|
||||
"capability": "provider-controlled",
|
||||
"supportedActions": "Sentry API actions allowed by the token's scopes and its creator's organization role, including issue triage and project/team changes.",
|
||||
"evidence": [
|
||||
"https://github.com/getsentry/sentry-mcp#remote-server",
|
||||
"https://docs.sentry.io/account/auth-tokens/"
|
||||
],
|
||||
"reviewedAt": "2026-11-11",
|
||||
"liveProof": "not-run",
|
||||
"keyPermissions": "Create a user auth token with org:read, project:read, project:write, team:read, team:write and event:write. The token acts as its creator; Paperclip cannot increase an existing token's permissions."
|
||||
},
|
||||
{
|
||||
"app": "shopify",
|
||||
"method": "ucp-commerce",
|
||||
|
||||
@@ -717,6 +717,15 @@ describe("AppDefinition catalog", () => {
|
||||
discoveryUrl: "https://mcp.sentry.dev/.well-known/oauth-protected-resource/mcp",
|
||||
},
|
||||
});
|
||||
// A user auth token does not expire with the 30-day browser grant. The
|
||||
// hosted server reserves `Bearer` for its own OAuth tokens.
|
||||
expect(APP_DEFINITIONS.find((app) => app.slug === "sentry")?.methods[1]).toMatchObject({
|
||||
key: "mcp-user-token",
|
||||
transport: "mcp_remote",
|
||||
auth: "api_key",
|
||||
defaults: { serverUrl: "https://mcp.sentry.dev/mcp" },
|
||||
keyPlacement: { location: "header", name: "Authorization", prefix: "Sentry-Bearer " },
|
||||
});
|
||||
expect(method("zapier")).toMatchObject({
|
||||
key: "generated-url",
|
||||
auth: "none",
|
||||
|
||||
@@ -42,6 +42,46 @@
|
||||
"project",
|
||||
"environment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "mcp-user-token",
|
||||
"transport": "mcp_remote",
|
||||
"auth": "api_key",
|
||||
"ownershipModes": [
|
||||
"customer"
|
||||
],
|
||||
"whenToUse": "Use a user auth token when an unattended agent needs Sentry access that does not expire with a browser sign-in grant.",
|
||||
"defaults": {
|
||||
"serverUrl": "https://mcp.sentry.dev/mcp"
|
||||
},
|
||||
"guidanceMd": "Connect with a Sentry user auth token for unattended agents. Browser sign-in grants expire and need the owner to sign in again; a user auth token lasts until you revoke it.",
|
||||
"riskTier": "S2",
|
||||
"label": "Use a user auth token",
|
||||
"credentialFields": [
|
||||
{
|
||||
"key": "authorization",
|
||||
"label": "Sentry user auth token",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"placeholder": "sntryu_...",
|
||||
"secret": true,
|
||||
"helperMd": "Create a user auth token with org:read, project:read, project:write, team:read, team:write and event:write. The token acts as its creator; Paperclip cannot increase an existing token's permissions."
|
||||
}
|
||||
],
|
||||
"keyPlacement": {
|
||||
"location": "header",
|
||||
"name": "Authorization",
|
||||
"prefix": "Sentry-Bearer "
|
||||
},
|
||||
"consoleLinks": {
|
||||
"keys": "https://sentry.io/settings/account/api/auth-tokens/",
|
||||
"docs": "https://github.com/getsentry/sentry-mcp#remote-server"
|
||||
},
|
||||
"requiredResourceFilters": [
|
||||
"organization",
|
||||
"project",
|
||||
"environment"
|
||||
]
|
||||
}
|
||||
],
|
||||
"docsUrl": "https://mcp.sentry.dev/.well-known/oauth-authorization-server",
|
||||
|
||||
@@ -776,19 +776,51 @@ const apps = [
|
||||
"developer",
|
||||
"sentry.io",
|
||||
["https://mcp.sentry.dev/*"],
|
||||
method(
|
||||
"mcp-oauth",
|
||||
"mcp_remote",
|
||||
"oauth",
|
||||
{
|
||||
serverUrl: "https://mcp.sentry.dev/mcp",
|
||||
discoveryUrl:
|
||||
"https://mcp.sentry.dev/.well-known/oauth-protected-resource/mcp",
|
||||
},
|
||||
"S2",
|
||||
"Connect the Sentry organization and projects agents need for incident work.",
|
||||
{ requiredResourceFilters: ["organization", "project", "environment"] },
|
||||
),
|
||||
[
|
||||
method(
|
||||
"mcp-oauth",
|
||||
"mcp_remote",
|
||||
"oauth",
|
||||
{
|
||||
serverUrl: "https://mcp.sentry.dev/mcp",
|
||||
discoveryUrl:
|
||||
"https://mcp.sentry.dev/.well-known/oauth-protected-resource/mcp",
|
||||
},
|
||||
"S2",
|
||||
"Connect the Sentry organization and projects agents need for incident work.",
|
||||
{ requiredResourceFilters: ["organization", "project", "environment"] },
|
||||
),
|
||||
method(
|
||||
"mcp-user-token",
|
||||
"mcp_remote",
|
||||
"api_key",
|
||||
{ serverUrl: "https://mcp.sentry.dev/mcp" },
|
||||
"S2",
|
||||
"Connect with a Sentry user auth token for unattended agents. Browser sign-in grants expire and need the owner to sign in again; a user auth token lasts until you revoke it.",
|
||||
{
|
||||
label: "Use a user auth token",
|
||||
whenToUse:
|
||||
"Use a user auth token when an unattended agent needs Sentry access that does not expire with a browser sign-in grant.",
|
||||
credentialFields: [
|
||||
{
|
||||
...field("authorization", "Sentry user auth token", "sntryu_..."),
|
||||
helperMd:
|
||||
"Create a user auth token with org:read, project:read, project:write, team:read, team:write and event:write. The token acts as its creator, limited to that person's organization role.",
|
||||
},
|
||||
],
|
||||
keyPlacement: {
|
||||
location: "header",
|
||||
name: "Authorization",
|
||||
prefix: "Sentry-Bearer ",
|
||||
},
|
||||
consoleLinks: {
|
||||
keys: "https://sentry.io/settings/account/api/auth-tokens/",
|
||||
docs: "https://github.com/getsentry/sentry-mcp#remote-server",
|
||||
},
|
||||
requiredResourceFilters: ["organization", "project", "environment"],
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
[
|
||||
"vercel",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"packages/shared/src/app-definitions-url.test.ts": "7378d29fd8428e4807dd234b495aa9043f40e6d7",
|
||||
"packages/shared/src/app-definitions.generated.ts": "d74319a5c9684633b4a0a23c8bfa682e0d3bf267",
|
||||
"packages/shared/src/app-definitions.ingestion-report.json": "a7b60c7c16a484a411989315e9f08d48258fa647",
|
||||
"packages/shared/src/app-definitions.test.ts": "e0584a8d9eb648d3cc42d594a2e5617240475dab",
|
||||
"packages/shared/src/app-definitions.test.ts": "3c048c63a6d2c630a984f6f10e1dbdd9bf7c4f68",
|
||||
"packages/shared/src/app-definitions.ts": "eaadd6b7831cc23998a1a3ec6fd1d3a5032a503b",
|
||||
"packages/shared/src/app-definitions/1password.json": "c11a4777de2c78805b2d391da5eb075a9511e0f8",
|
||||
"packages/shared/src/app-definitions/agentmail.json": "edd0799b1980d0f5f43b89c8f7a1173dc623f7fa",
|
||||
@@ -159,7 +159,7 @@
|
||||
"packages/shared/src/app-definitions/resend.json": "38f7595265f9ff6afdc84b9bce18602cd2c6ebfa",
|
||||
"packages/shared/src/app-definitions/responses-api.json": "29ad92e4c41a0dc0e3a3b07f518200bdc8e2e6ff",
|
||||
"packages/shared/src/app-definitions/sanity.json": "15968d52eb75008b170f45a82e79e0b77516b50d",
|
||||
"packages/shared/src/app-definitions/sentry.json": "5a85dd024f11382a802557b0c5374b856e29b56a",
|
||||
"packages/shared/src/app-definitions/sentry.json": "0afeca8c0e76be5d8324fa6d18fb970a38bb2b88",
|
||||
"packages/shared/src/app-definitions/shopify.json": "f6bff7db55083d96060d8bd46a08aa38b33c3080",
|
||||
"packages/shared/src/app-definitions/similarweb.json": "37a912287b7ad323cf783eb32d01b943a93e83fd",
|
||||
"packages/shared/src/app-definitions/slack.json": "733de01cdeec0dc66d732639925c9bb9fb7dc53d",
|
||||
|
||||
Reference in new issue
Block a user