Repository navigation
docs(openspec): Expose the CLI's commands as the MCP, on @sentry/api - #1669
Open
sentry-junior[bot] wants to merge 37 commits into
Open
sentry-junior[bot] wants to merge 37 commits into
sentry-junior[bot] wants to merge 37 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
BYK
added a commit
that referenced
this pull request
Oct 26, 2026
…ry (#1671) `search_issues` was sending AND/OR to the issues endpoint, which rejects them with `Boolean statements containing "OR" or "AND" are not supported in this search`. That's the top `search_issues` failure in `sentry/mcp-server`: about 93K a week across many users, mostly `is:X OR is:Y`. Two things in MCP let them through: - The repair prompt (`search-issues/config.ts`) listed `AND, OR, NOT` as valid syntax and told the agent to keep explicit tokens. - With no agent provider configured, queries went to the API unchecked. **What changes** - **The CLI sanitizer moves to `@sentry/toolkit-core/search-query`.** `rewriteSearchQuery` returns `{ query, notes }` and throws `SearchQueryError`. Each product decides how to report: the CLI wrapper (`cli/src/lib/search-query.ts`) keeps its stderr warning and `ValidationError`, and MCP throws `UserInputError`. CLI behavior is unchanged except one error line, which now reads `key:[val1,val2]` instead of `--query "key:[val1,val2]"`. - **`search_issues` runs it after the optional agent repair**, right before `listIssues`, so it covers the no-provider path too. When it rewrites a query, the result says so and shows the query it ran. Queries it can't rewrite fail with a `UserInputError` that lists alternatives, before any API call. - **New `issueSearch` option** (MCP only for now): - `is:A OR is:B` becomes `status:[A,B]` or `substatus:[A,B]`. `is:` rejects in-lists, but the status and substatus filters it maps to accept them. - Substatus lists must share one parent status, because the endpoint adds a status filter for the first substatus only (`expand_substatus_query_values`). So `is:ongoing OR is:archived_forever` is still rejected. - Parentheses that reduce to plain terms are unwrapped: `is:unresolved (is:regressed OR is:escalating)` becomes `is:unresolved substatus:[regressed,escalating]`. - **The agent prompt and tool description now say** there's no AND/OR/NOT or parentheses, and give the in-list, `status:`, and `substatus:` forms instead. **Checked against the live API** (sentry org, `junior` project, 14d, `X-Hits`): - `status:[unresolved,resolved]` returned 165, which is 159 (`is:unresolved`) + 6 (`is:resolved`). - `substatus:[new,ongoing]` returned 159, which is 53 (`is:new`) + 106 (`is:ongoing`). - `substatus:[ongoing,archived_forever]` returned only unresolved issues, and `[archived_forever,ongoing]` returned none. That's the first-substatus behavior the parent-status check guards against. - `(is:unresolved)` returned `[]` and `NOT is:resolved` returned `[]` (NOT is searched as free text), both with a 200. That's why the prompt now says `!` instead of NOT, and why parentheses get unwrapped. - `is:[unresolved,resolved]` returned a 400: `"in" syntax invalid for "is" search`. **Reviewing the move** Git shows the core file as new, because the CLI path still holds the wrapper. To see only what changed in the moved code: ``` git diff main:packages/cli/src/lib/search-query.ts HEAD:packages/toolkit-core/src/search-query.ts ``` **Not in this PR** - The CLI's `issue list` could opt into `issueSearch`. I left it off to keep CLI behavior unchanged. - MCP's other query tools don't use the shared sanitizer yet. - The 429 retry in `get_sentry_resource` is task 4.1 of #1669. **Checks** - `pnpm run tsc` and `pnpm run lint` pass. - Tests pass for `toolkit-core` (356), `mcp-core` (1833), and the CLI search-query, issue, log, span, and replay tests (532). - The full CLI `test/lib` + `test/commands` run has 6 failures in filesystem-permission tests (completions, upgrade lock, code scanner, file-changes). The completions and upgrade-lock failures also happen on `main` in this sandbox, so they look environment-specific. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC08J1NSPU6S%3A1792772794.175999 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com> Co-authored-by: Andrew Liu <andrew.liu@sentry.io>
BYK
added a commit
that referenced
this pull request
Oct 26, 2026
…1672) The CLI and MCP core pinned different `@sentry/api` versions, so the two products could disagree on generated types and request helpers. This adds `@sentry/api: 0.400.0` to the pnpm catalog and points `packages/cli` and `packages/mcp-core` at `catalog:`. The lockfile now resolves a single `@sentry/api` version. MCP core needed no code changes for the bump. This is task 1 of the `unify-sentry-api-layer` OpenSpec change in #1669. Checks run locally: - `pnpm install --frozen-lockfile` - `pnpm --filter @sentry/mcp-core run tsc` - `pnpm --filter @sentry/mcp-core run test` (131 files, 1,831 passed, 6 skipped) - `pnpm --filter @sentry/mcp-core run build` - `tsc` for `@sentry/mcp-server` and `@sentry/mcp-cloudflare` - Formatter check on changed files The full root quality gate and the CLI suite were not run locally; CI covers them. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
Contributor
Author
|
Plan update (a81439e): retries and 429s.
|
Contributor
Author
|
Correction to my earlier plan comment (b5523f5), following review on #1673:
The |
This was referenced Oct 27, 2026
Merged
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
…tions on missing org (#1678) ## Problem Telemetry from `sentry/mcp-server` (spans over 7 days, extrapolated from 0.3 sampling, so user counts are lower bounds): - **Missing `organizationSlug`:** about 23.8K failures from 861+ users. One user sent ~17.7K of them, all `search_issues` with `period:"1h"`. Without them it's ~6.1K failures across ~860 users. All of these came in on the unscoped `/mcp` URL. They fail in the MCP SDK's input validation before any handler runs, so the zod error message is the only place to add guidance without changing the schema. - **`period: "1h"` on `search_issues`:** about 25.6K failures from ~50 users, mostly that same user. - **Minute periods:** values like `30m`, `15m` and `10m` on `search_errors`, `search_logs` and `search_traces` were never accepted. That's about 1.5K failures from ~300 users. Sentry reads `m` as minutes (read-only GET check: `30m` returned 28 errors, against 1,269 for `24h`), and the issues endpoint accepts `statsPeriod=1h`. ## Changes - **`SEARCH_ISSUES_PERIOD_VALUES`** now includes `1h`. - **`ParamPeriod`** now accepts minutes (`/^\d+[mhdw]$/`), and the rejection message says "(minutes)". - **A missing or null `organizationSlug`** now returns: `Required. Call find_organizations() to list your organizations, then retry with organizationSlug set to one of their slugs.` This works on SDK v1 and v2 and doesn't change the JSON schema. - **Tool definitions** are regenerated. Token overhead goes up by 13 (10,066 → 10,079). ## Held back - **Defaulting to the user's only org.** This would mean making `organizationSlug` optional on ~50 tools, and fetching the org list plus a region lookup on every call that has no org. That's the org endpoint, cache and region-routing code that #1669 reworks, so it's left for that work. - **Listing the user's orgs in the error, with a ready-to-copy retry.** Same blocker: the SDK rejects the call before our handler sees it. ## Checks - `pnpm run tsc` and `pnpm run lint` pass. - mcp-core: 1847 passed, 6 skipped. ## Risk A model that means months by `3m` would get 3 minutes of data. Telemetry shows callers mean minutes; there are only a handful of `6m`/`12m`. None of this fixes the stuck script. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC08J1NSPU6S%3A1792772794.175999 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
…#1679) ## Problem These are the most common field and sort errors in `sentry/mcp-server` telemetry (7 days, extrapolated from 0.3 sampling, so user counts are lower bounds). Each fix was checked against the live API. - **`search_logs` with `min/max(timestamp)`:** ~2.6K failures from ~470 users. Log timestamps are strings, so the fix is `min/max(timestamp_precise)` (nanoseconds). Metrics behaves the same way (43 failures). - **`search_errors` with `first_seen()`:** ~2.3K failures from ~590 users. That function doesn't exist; use `min(timestamp)`, plus `max(timestamp)` or `last_seen()`. About 700 of these came from fields the embedded agent picked. - **`search_errors` sorted by `issue`:** ~1K failures from ~270 users. Sentry rejects this sort even when `issue` is selected; use `-count()` or `issue.id`. ## Changes - **New `tools/support/search-events/field-hints.ts`.** It adds a hint only when the request actually sent the rejected expression and Sentry's error message matches it. - Hints are fixed text and never include caller input, and Sentry's own message is kept. - Anything that doesn't match is re-thrown unchanged as `ApiValidationError`. - **Hints are added in two places:** the validation failure, and the final Events API 400. The second is needed because the validate endpoint accepts the `first_seen()` and `issue`-sort cases. - **`min(timestamp)`** is added to the embedded agent's errors field list. ## Not covered - `timestamp.to_day` / `to_hour` on spans and logs (~600 failures): there's no clear one-line replacement. - Metrics `count()` arity (~350), `p10`/`p25` (~150), and `count(span)` (0). ## Checks - tsc, lint (oxfmt) and all mcp-core tests pass (1837 passed, 6 skipped). - Tool definitions are unchanged (10,066 tokens). ## Relation to #1669 Task 7.3 moves the explore, traces and logs endpoints onto `@sentry/api`. These hints only depend on the error being an `ApiValidationError` and on its message, so they should carry over as long as 400s still map to that class. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC08J1NSPU6S%3A1792772794.175999 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
…rs (#1673) The hosted MCP gets rate limited by the Sentry API a lot. In the last 7 days `sentry/mcp-server` logged about 32.6K `http.client` 429s, nearly all on GETs, usually "Limit is 40 requests in 1 seconds", plus about 72 concurrency-limit hits. `SentryApiService.request()` only retried 502/503/504, so every 429 failed the tool call. `ApiRateLimitError.retryAfter` was read from body fields Sentry never sends, so nothing could say when to try again. Sentry's 429 body is just `{"detail": ...}`. The timing comes in headers set by `src/sentry/middleware/ratelimit.py`: `X-Sentry-Rate-Limit-Reset` is the Unix second when the next window starts, and `Remaining` is `0` only when the fixed window is used up. Concurrency 429s carry a reset that doesn't apply to them. This adds `@sentry/toolkit-core/api-retry`, a pure retry policy with no Node APIs, so it is safe on Workers. It is task 5.0 in #1669. The MCP adopts it here: - **What gets retried:** GET and HEAD requests that get a 429 or a 502/503/504. Writes are never retried. The policy's `idempotent` opt-in exists for the CLI's content-addressed uploads, but the MCP doesn't use it. - **How long a 429 waits:** until `Retry-After` (seconds or HTTP-date) if a proxy sent one, otherwise until `X-Sentry-Rate-Limit-Reset`. The reset is measured against the response `Date` when the local clock is off by more than 2s, plus a small jitter. Concurrency 429s and gateway errors use equal-jitter exponential backoff starting at 250ms. - **Caps:** at most 2 retries, 2s per wait and 3s in total. Sentry's windows are mostly 1s, so the reset is usually under a second away and both retries fit in about 2s. A reset further out than 2s means a longer window. Then the call fails at once instead of holding the Worker request open. The error is an `ApiRateLimitError` with a real `retryAfterMs`, and its message ends with "Retry in N seconds." When there is no header, it says to wait a few seconds and make fewer parallel requests. - **Abort:** aborting the request's `signal` ends the wait immediately. `ApiRateLimitError.retryAfter` (seconds, from the body) is replaced by `retryAfterMs`, taken from the headers. Nothing read the old field. The CLI currently has its own interim retry fix in a separate CLI-only PR. It moves onto this shared module in a follow-up after that PR lands. Once the MCP is on the shared transport (#1669 task 6.1), `SentryApiService` drops this loop so retries don't stack. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A0bac2c6dfda59b43b43e21a1118f2684 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/agent%3A0bac2c6dfda59b43b43e21a1118f2684) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A0bac2c6dfda59b43b43e21a1118f2684/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
…reset on 429 (#1677) ## Problem `packages/cli/src/lib/sentry-client.ts` retried **every** HTTP method, including POST/PUT/DELETE, on 408/429/500/502/503/504 and on network errors and timeouts. That can replay writes. A 429 also only got the generic 1s/2s backoff: the client never read Sentry's `X-Sentry-Rate-Limit-Reset` header, and Sentry doesn't send `Retry-After` on API rate limits. ## Behavior changes - **5xx, timeouts and network errors** now retry only for GET, HEAD and OPTIONS, or when a request sets the new `idempotent: true` opt-in on `getSdkConfig` / `ApiRequestOptions`. - **429 retries for any method.** Sentry's rate-limit middleware rejects the request in `process_view`, before the endpoint runs, so nothing was applied. - **Retried statuses stay 408/429/500/502/503/504.** Deterministic 5xx like 501 and 505 are not retried. - **PUT and DELETE are deliberately not retried.** Sentry PUTs have side effects (merge, activity), and a replayed DELETE returns 404. - **The 401-refresh retry still covers every method**, and no longer waits 1s first. - **A timed-out write** now gets a "may still have been applied" hint. - **The 429 wait** is the longer of `X-Sentry-Rate-Limit-Reset` (read only when Remaining is 0) and `Retry-After` (seconds or HTTP date), both measured against the response `Date` to guard against clock skew. Concurrency-limit 429s, or 429s with no hint, use jittered backoff: the upper half of min(1s·2^n, 10s). - **Caps are unchanged:** `MAX_RETRIES` is 2 and each wait is at most 10s. - **New `RateLimitError`** (extends `ApiError`, status 429). It's thrown when the wait would exceed the cap or retries run out. The message is agent-friendly: server detail, then "Retry in N seconds; do not repeat the request before then. Make fewer requests in parallel." - **The policy is pure**, with no CLI imports, in `packages/cli/src/lib/retry-policy.ts`. That way the CLI can switch to the shared `@sentry/toolkit-core/api-retry` from #1673 without a rewrite. ## POST callers given the `idempotent` opt-in Each was checked against its getsentry/sentry handler: - **Chunk uploads** (`chunk-upload.ts`): blobs are stored by SHA-1 (`FileBlob.from_files`). - **Artifact-bundle assemble and `pollAssembly`** (`sourcemaps.ts`). - **DIF assemble** in `debug-files.ts`, `proguard.ts` and `dart-symbols.ts`: the server checks the cached assemble status by checksum. **Not opted in:** preprod assemble (each call creates a new `PreprodArtifact`), Seer `/autofix/`, snapshot create and archive trigger, and SDK and `sentry api` writes. ## Tests - The POST-retry body-cloning tests now go through the 429 and opt-in paths. - New cases: - a rate-limited POST is resent with the same body - a 501 is not retried - a 503 on a non-idempotent method is not retried - the reset-header and `Retry-After` wait, clock skew, and the cap leading to `RateLimitError` - the exact rate-limit message text - `api-client.coverage` used 429 as a sample error and now uses 400. ## Checks - `tsc --noEmit` and `pnpm run lint` pass. - The sentry-client, retry-policy, api-client and api-client.coverage tests pass: 204/204. - Full CLI suite on `bf3b710`: 11,482 passed and 6 failed. The same 6 fail on `main` (`37c7193`) in this sandbox. They're chmod-based permission tests (completions ×2, code scanner, init file-changes, telemetry readonly, upgrade lock), and the sandbox runs with full capabilities, so they can't fail the way they expect. - After the 429-any-method change (`7099e2b`), all other CLI test files that mention 429 pass (1,195), apart from the 2 known sandbox permission failures. ## Relation to #1669 This matches the retry policy in #1669 (task 5.0 and the `sentry-api-transport` spec). Task 5.1 will move this behavior into toolkit-core's shared transport unchanged. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC08J1NSPU6S%3A1792772794.175999 --> -- [View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC08J1NSPU6S%3A1792772794.175999/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
The CLI's fallback "immutable" cache tier kept any `/events/<id>/`, `/trace/<id>/`, or `/trace-items/<id>/` response for 24 hours. It was also checked before the `/issues/` volatile pattern, so `getLatestEvent` (`/issues/<id>/events/latest/`) was cached for a day: re-running `sentry issue view` an hour later could show an outdated "latest" event. The getsentry/sentry#128818 spike ([findings](getsentry/sentry#128818 (comment))) found that none of these responses are actually immutable. Event payloads embed `nextEventID`/`previousEventID`, user reports, and release data, reprocessing and merges can change them for the same ID, `latest`/`oldest`/`recommended` resolve to different events over time, and full traces gain spans as they arrive. This removes the `immutable` tier: | Tier | TTL | Matches | | --- | --- | --- | | no-cache | 0 | autofix and root-cause polling (unchanged) | | volatile | 60 s | `/issues/` (now including issue events), project event details, full traces, `dataset=logs\|spans`, trace logs | | stable | 5 m | everything else, now including organization event details and trace-item details | Server `Cache-Control` headers still take precedence, so when getsentry/sentry#128828 ships its `private, max-age=300` headers the CLI follows them without a release. A TODO points at getsentry/sentry-api-schema#81 and #102 for classifying by generated `@sentry/api` operation metadata instead of URL regexes. Notes for review: - **No migration.** Freshness is recomputed from `createdAt` and the current tier on read, so existing 24-hour entries on disk are treated by the new rules immediately. - **`immutableMinTimeToLive` removed.** It pointed at the deleted tier constant. The `http-cache-semantics` default is the same 24 hours, and it only applies to responses where the server sends `immutable`. - **Cost:** repeated reads of project/issue events and full traces will hit the API after 60 seconds instead of 24 hours; organization event and trace-item details after 5 minutes. Task group 2 of the `unify-sentry-api-layer` OpenSpec change in #1669. **Checks run locally:** - New `classifyUrl` cases plus fake-clock expiry tests for org event details (hit at 4 min, miss at 6 min), issue latest event, project event details, full traces, and server `max-age` overriding the fallback. 8 of them fail against the old classifier. - `pnpm exec vitest run test/lib/response-cache*.test.ts`: 60 passed - CLI `typecheck`, `lint`, and `oxlint`/`oxfmt` on the changed files: clean - `pnpm run test:unit` (first revision): 11,583 passed, 6 failed. The same 6 fail on unmodified `main` in my sandbox: filesystem-permission tests (`completions`, `code-scanner`, `file-changes`, `telemetry` readonly DB, `upgrade` lock) that depend on the environment, not this change. <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
BYK
added a commit
that referenced
this pull request
Oct 27, 2026
Moves the CLI's pagination core into `@sentry/toolkit-core/pagination` so the MCP can reuse it. This is task group 3 of the unify-sentry-api-layer proposal (#1669). What moved: page sizing (an even split over the fewest requests, at most 100 rows per page), resume-safe cursors (the skip count is written into `is_prev` so the cursor stays a valid Sentry cursor, page-number cursors keep their page size, and `cli1~` cursors are still read), `paginate`, and `fetchAllPages`. The shared module does no I/O and doesn't depend on the CLI logger, `ApiError`, or Valibot: - `paginate` takes `maxPages` and an optional `warn` sink, and returns `stoppedAtPageCap`. The spec's "Bounded page count" requirement asks for a structured page-cap signal, so the MCP can report it instead of logging. - `fetchAllPages` takes a row parser and returns `{ ok: false, reason }` (`invalid-page`, `repeated-cursor`, or `page-cap`) instead of throwing. The CLI keeps thin wrappers in `packages/cli/src/lib/api/infrastructure.ts` with the same signatures, default limit, `SENTRY_MAX_PAGINATION_PAGES` cap, page-cap warning, and `ApiError` messages. Existing imports and behavior don't change, and the CLI pagination tests pass without edits. `autoPaginate` and the `sentry-cli/no-manual-pagination` lint rule stay CLI-only. The lint rule only scans CLI source, so the loop in toolkit-core isn't covered by it. Tests: `packages/toolkit-core/src/pagination.test.ts` has a case for each `sentry-api-pagination` scenario: a limit above one page and a limit within one page, stopping inside a page, cursors that the API and older clients accept, page-number cursor page size, the page cap, and a repeated cursor. It also covers invalid pages, the `fetchAllPages` page cap, and unreadable `cli1~` cursors. The README now documents the module. Checks: `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, toolkit-core tests (401), and the CLI pagination, replay, and alert suites (319) pass locally. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A0ab4fc2bd5a2e23f2c25efcf/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
sentry-junior Bot
added a commit
that referenced
this pull request
Oct 27, 2026
Moves the CLI's cache policy into `@sentry/toolkit-core/response-cache` so the MCP can use the same rules (task 4.1 of the unify-sentry-api-layer proposal, #1669): - `classifyUrl` and `FALLBACK_TTL_MS`: the volatile/stable/no-cache tiers for responses without server cache headers. - `normalizeUrl` and `buildCacheKey`: identity-scoped keys over the method and full URL. Key derivation now uses WebCrypto HMAC-SHA256 instead of `node:crypto`, so it runs in Workers and browsers. The output is byte-identical, so existing CLI cache files still hit, and an empty identity is rejected instead of producing an unscoped key. - `computeInvalidationPrefixes`: moved unchanged from `cli/src/lib/cache-keys.ts`, with its tests. The module has no storage or I/O. The CLI's disk cache imports it, and its `buildCacheKey` wrapper (which defaults to the active identity) is now async, as are the tests that call it. WebCrypto HMAC runs on the libuv threadpool, so parallel requests can finish their cache lookups in either order. The `getLogs` batching test no longer assumes which batch reaches fetch first. Co-Authored-By: BYK <burak.kaya@sentry.io>
This was referenced Nov 3, 2026
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
…1836) When a tool call leaves out `organizationSlug` and the login can reach exactly one organization, the call now uses that organization. When the login can reach several, a tool that requires the slug fails with a retry the model can run as is, plus the other organizations. This is the MCP version of the CLI recovery in #1634, and it finishes the part #1678 held back. The server never chooses between organizations. ## Why now Over the last 7 days (`sentry/mcp-server` spans, extrapolated counts): - `organizationSlug: Required` failures: about 4.3K, from search_issues, find_projects, search_errors, search_logs, and calls through execute_sentry_tool. `get_sentry_resource` adds about 1.9K more with "`organizationSlug` is required when not using a URL". - The script that sent about 89% of the earlier ~31K/week has stopped. The rest is spread out: no user accounts for more than 2 of the 40 sampled failures. ## How it works - **No schema or handler edits.** `getFilteredInputSchema` makes a required `organizationSlug` optional at registration time, so the MCP SDK no longer rejects the call before our code runs. Then `prepareToolCallParams` runs `applyDefaultOrganization`. Both top-level calls and `execute_sentry_tool` go through it, before `withRoutedCredential`, so stdio account routing (#1802) gets the defaulted slug. - It runs only when the call has no slug after constraint injection and no Sentry URL parameter (it reuses `getTargetOrganizationSlug`). Org-scoped sessions never reach it. - **One organization:** the call uses its slug. If the tool takes `regionUrl` and the caller didn't set one, the call also gets the organization's `regionUrl`. On self-hosted that value is empty, so `regionUrl` stays unset. - **Several organizations:** for a required slug, the error looks like ``Retry example: `search_issues(organizationSlug='org-a', period='1h')` `` and lists the other organizations. Catalog tools get the `execute_sentry_tool(name=…, arguments=…)` form, and values equal to a schema default are left out. For an optional slug (URL-or-ID tools such as `get_sentry_resource`), the call runs as before. - **Listing fails** (for example, a token without `org:read`): the error falls back to the find_organizations hint from #1678. - **Multiple logins:** organizations from every account are merged. ## Fit with #1669 - The organization list comes from `SentryApiService.listOrganizations`, which already uses the shared `endpoints/organizations` wrapper and transport (#1722, #1737). No new endpoint code. - The resolve step lives in mcp-core's constraint and param-prep layer. The design keeps that layer in mcp-core ("an MCP tool fills them from tool parameters and session constraints"), so 11.3 can keep it as is. - The MCP hosts don't wire a `CacheStore` yet (7.1 is still open). Instead, the list is memoized per context and credential for 5 minutes, which matches the `stable` tier. When 7.1 lands, the `/organizations/` request gets cached per identity on its own, and this memo can be dropped. It doesn't conflict with 8.4–8.7 or 11.x. - This doesn't complete a task in the plan. The closest is 11.3's resolve step. ## Token cost `measure-tokens` is unchanged at 9,014. That script reads the raw tool schemas, and those didn't change. In the registered schemas, `organizationSlug` is no longer in the `required` list on 8 direct tools, which comes to 5,014 → 4,990 input-schema tokens. ## Open questions - A `null` slug still gets the find_organizations hint. Accepting `null` would add `anyOf` to every schema. - When an optional-slug tool like `get_sentry_resource` is called by a user with several organizations, it keeps its own error. It doesn't list the organizations yet. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A0222050e9c46ef2289687231ff960e16 --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3A0222050e9c46ef2289687231ff960e16) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A0222050e9c46ef2289687231ff960e16/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
#1828) Task 8.4 of the shared API client plan ([#1669](#1669)): the next domain after organizations (#1741). This moves the team and project endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/`. `SentryApiService` now calls them with its Zod schemas. The code moves as is. **Changes** - `endpoints/teams` adds `listTeams` (on `@sentry/api`'s `listOrganizationTeams`) and `createTeam`. - `endpoints/projects` adds `listProjects` (on `listOrganizationProjects`), `getProject`, `createProject`, `updateProject`, and the project's teams: `listProjectTeams`, `addProjectTeam`, and `removeProjectTeam`. - Writes go through the request core, since the core's `sdkClient` only sends GETs. `getProject` and `listProjectTeams` keep their hand-built requests. `@sentry/api` has operations for both (`getProject`, `listProjectTeams`), but moving them onto those operations is a separate change. - The core gains `nextPageCursor(response)`. `listPage` and `listProjectTeams` use it, and the next domains will too. - The endpoint tests share a scripted-transport helper in `toolkit-core/src/test-utils/`. That folder is left out of the browser typecheck, and `organizations.test.ts` uses the helper now as well. **One change in behaviour:** as with #1741, a response that fails its schema in these calls now throws the shared `ApiResponseValidationError` instead of a `ZodError`. Both still reach the tool error handler as unexpected errors. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
BYK
pushed a commit
that referenced
this pull request
Nov 3, 2026
#1834) Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1828. This moves the project setup endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/`. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `endpoints/client-keys`: `listClientKeys`, `createClientKey`, and `updateClientKey`. `updateClientKey` still sends only the fields that are set, and keeps an explicit `rateLimit: null`. `SentryApiService.updateClientKey` now takes the shared `UpdateClientKeyParams` type, and its local request types are deleted. - `endpoints/repositories`: `listRepositories`, which walks every page, and `linkProjectRepository`, which sends the bulk code-mapping POST with one root mapping. - `endpoints/organizations` gains `updateAgenticOnboardingStatus`. `@sentry/api` has no operation for `POST /organizations/{org}/onboarding/agent/status/`, so that wrapper will stay hand-written. **One change in behaviour:** as with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
BYK
pushed a commit
that referenced
this pull request
Nov 3, 2026
…#1832) Task 6.3 of #1669, on top of the transport's `fresh()` callback from #1720. **What changes** - `ParamFresh` in `mcp-core/src/schema.ts`: `fresh?: boolean`, described as "Bypass cached reads." The description is kept short because every read tool's schema carries it. - `internal/fresh-reads.ts`: `withFreshReadInput(tool)` adds the input and wraps the handler. With `fresh: true`, the handler runs inside an `AsyncLocalStorage` scope, and it never sees `fresh` in its params. `isFreshRead()` reads that scope. - `tools/catalog/index.ts` applies the wrapper to every catalog tool that has `readOnlyHint: true` and `openWorldHint: true`, except `search_docs` and `get_doc`, which read docs.sentry.io rather than the Sentry API. The wrapper sits on the tool itself, so direct calls and `execute_sentry_tool` both get it. No tool file or `execute_sentry_tool` changes. - `SentryApiService`'s transport passes `fresh: isFreshRead`, so the shared client sees the scope only through the callback. Shared code still has no `AsyncLocalStorage`. - `docs/contributing/adding-tools.md` explains the rule. Tool definitions are regenerated. **Behavior today:** none visible. The MCP transport has no cache store yet (that's 7.x), so `fresh` has nothing to bypass until one is wired in. This PR makes the input and the scope ready for that. **Token cost** (`pnpm run measure-tokens`): 9,014 → 9,144 (+130, +1.4%). Of the 14 direct tools, 10 gain the input at 13 tokens each: `find_organizations`, `find_projects`, `search_issues`, `get_sentry_resource`, and the six `search_*` tools. **Checks** (all run with `SENTRY_AUTH_TOKEN` unset) - `pnpm run tsc`, `pnpm run lint` and `docs:check` pass. - mcp-core: 1930 passed, 6 skipped. The new `fresh-reads.test.ts` covers the scope being set only when `fresh: true`, the scope surviving an `await`, `fresh` being stripped from handler params, a non-boolean being rejected, and which catalog tools get the input. `server.test.ts` adds `fresh` to the `get_replay_details` schema keys it expects. - mcp-cloudflare: 447 passed. mcp-server bundle tests: 15 passed. Other in-flight work on `execute_sentry_tool`, `get_sentry_resource`, `find_dropped_events` and `get_alert_rule` doesn't touch these files. Expect only a `toolDefinitions.json` regeneration on rebase. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ac6985c0a2dff5c8ae42a2a02dff4a60b --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ac6985c0a2dff5c8ae42a2a02dff4a60b) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ac6985c0a2dff5c8ae42a2a02dff4a60b/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
BYK
pushed a commit
that referenced
this pull request
Nov 3, 2026
Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1834. This moves the dashboard and release endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/`. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `endpoints/dashboards`: `listDashboards` (one page, with the next cursor) and `getDashboard`. - `endpoints/releases`: `listReleases`, `getRelease`, `listReleaseDeploys`, `listReleaseCommits`, and `getEventCommitters`. With a project, release reads still go to the project's endpoints, otherwise to the organization's. - `api-path` gains `withQuery`, for the path-plus-optional-query pattern the wrappers repeat. `getOrganization` and `getProject` use it too; the URLs they send don't change. `@sentry/api` has no operation for the project-scoped release details route or for event committers, so those wrappers stay hand-written. **One change in behaviour:** as with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. Checked with `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, and the toolkit-core, mcp-core, mcp-server bundle, and mcp-cloudflare tests. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
…1839) Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1838. This moves the cron monitor endpoints and the time range helpers from `SentryApiService` into `toolkit-core`'s shared API client. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `api-client/time-params`: `normalizeStatsPeriod`, `parseStatsPeriod`, `applyTimeParams` (`statsPeriod` or a `start`/`end` pair), and `applyStatsMixinTimeParams` (the `since`/`until` seconds `StatsMixin` endpoints read). It rejects a range that sets both a period and dates, or only one end. `defaultStatsPeriod` holds the check-in list's "24 hours unless a range is given" default, which the uptime-check list reuses in the next PR. Most other `SentryApiService` methods still call these helpers; they now import them from `toolkit-core`. - `endpoints/monitors`: `listMonitors`, `getMonitor` (organization or project route), `deleteMonitorEnvironment` (a 404 still counts as already deleted), `listMonitorCheckIns`, and `getMonitorStats`. `@sentry/api` has no operation for monitor stats or for deleting a monitor environment, so those wrappers stay hand-written. **One change in behaviour:** as with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. Checked with `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, and the toolkit-core, mcp-core, mcp-server bundle, and mcp-cloudflare tests. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1839. This moves the uptime monitor endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/uptime-monitors`. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `listUptimeMonitors` takes a numeric `projectId`, since the endpoint filters by ID. `SentryApiService` still looks the project slug up first. - `getUptimeMonitor` and `listUptimeMonitorChecks`. The checks list still defaults to 24 hours unless a range is given, through `defaultStatsPeriod` from #1839. - `createUptimeMonitor` and `updateUptimeMonitor` share one field list, so the body still holds every field that is set (an explicit `null` included), in the same order as before. - `deleteUptimeMonitor` still counts a 404 as already deleted. `@sentry/api` has no uptime operations at all, so these wrappers stay hand-written. **One change in behaviour:** as with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. Checked with `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, and the toolkit-core, mcp-core, mcp-server bundle, and mcp-cloudflare tests (run on the top of the stack, the replay branch at `73bfc0b3`, before the later rebases), plus the uptime tool tests on this commit. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1840. This moves the trace, profiling, and preprod snapshot endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/`. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `endpoints/traces`: `getTraceMeta` and `getTrace`, with the same defaults (`14d`, `limit=1000`, `project=-1`). - `endpoints/profiling`: `getFlamegraph` (still escapes the transaction name), `getTransactionProfile`, and `getProfileChunks`. `SentryApiService.getProfileChunk` keeps the MCP-only shaping: it throws `ApiNotFoundError` when there are no chunks and backfills `profiler_id`. - `endpoints/snapshots`: `getSnapshot`, `getSnapshotImage`, and `getLatestBaseSnapshot`. They return the body unvalidated, as before. `fetchImageByUrl` stays in `SentryApiService`, since it isn't an endpoint. `@sentry/api` has operations for all of these routes. The wrappers still build their requests by hand so the change stays a move. Switching them to the operations can follow. **Intentional changes:** - As with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. - `getSnapshot` with `compactMetadata: false` no longer sends a trailing `?` with an empty query. - The `ApiNotFoundError` for an empty chunk list carries the validated body as `responseBody`, not the raw one. Checked with `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, and the toolkit-core, mcp-core, mcp-server bundle, and mcp-cloudflare tests on the top of the stack, plus the trace, profile, and snapshot tool tests on this commit. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
Task 8.4 of the shared API client plan ([#1669](#1669)). This is stacked on #1841 and is the last PR in the stack. This moves the replay endpoints from `SentryApiService` into `toolkit-core`'s `api-client/endpoints/replays`. `SentryApiService` calls them with its Zod schemas. The code moves as is. - `searchReplays` keeps the same query: `query`, `per_page`, `project`, `sort`, repeated `environment` and `field`, and the time range. - `getReplay` and `listIssueReplayIds`. `SentryApiService` still unwraps the `{ data }` envelope and picks the issue's entry from the keyed replay-count result. - `visitReplayRecordingSegments` still pages 100 segments at a time, follows the `Link` cursor, and stops at `maxSegments`, with `truncated` set the same way as before. It reads the cursor with the shared `nextPageCursor`, which parses the header the same way the old `getNextCursor` did. - `docs/contributing/api-patterns.md` gets a short "Where Endpoint Code Lives" section that describes the pattern this stack follows. **One change in behaviour:** as with #1741, a response that fails its schema now throws the shared `ApiResponseValidationError` instead of a `ZodError`. Checked with `pnpm run tsc`, `pnpm run lint`, `pnpm run docs:check`, and the toolkit-core, mcp-core, mcp-server bundle, and mcp-cloudflare tests on this branch before the last rebase. `pnpm run tsc` passes again after rebasing onto the current main. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ab061171f2ec2e6cf6f09a6828cadc9fa/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
BYK
added a commit
that referenced
this pull request
Nov 3, 2026
…#1829) Two `get_sentry_resource` input failures from the last 7 days in `sentry/mcp-server`: - **"Either `url` or `resourceType` must be provided": ~4.6K/week.** The calls carry the right value under the wrong key. The most common keys are `resourceId` without a type (~830, all issue short IDs), `issueId` / `issue_id` (~900), `resource` + `id` (~630 / ~360, mostly `resource: "issue"`), `resourceUrl` (~530) and `issueUrl` (~210). - **"Could not determine resource type from URL": ~2.3K/week.** The largest groups are project event URLs `/projects/{project}/events/{eventId}/` (~500), issue list URLs `/issues/?query=…` (~460), alert rule URLs (~380), Explore logs/traces search URLs (~250), and `/api/0/…` URLs (~180). What changed: - New undocumented schema aliases: `issueUrl` and `resourceUrl` map to `url`, and `issueId` and `id` map to `resourceId`. `issueId` also implies `resourceType: issue`. The schema grows from 683 to 710 tokens. - A `resourceId` with no `resourceType` resolves as an issue only when it looks like an issue ID: an uppercase short ID (`PROJECT-123`) or a numeric ID. Hex IDs, slugs and titles still need an explicit type. - The URL parser handles `/[organizations/{org}/]projects/{project}/events/{eventId}/`. These resolve as an event by event ID, which `get_issue_details` already supports. - List and search URLs get a specific error that names the right tool: `search_issues`, `find_alert_rules` / `get_alert_rule`, `search_logs`, `search_traces`, or the web URL for API links. Other unknown URLs keep the old error. Open question: multi-credential routing (`credential-routing.ts`) reads the org only from `url`, so a call with only an `issueUrl` / `resourceUrl` alias falls back to the default credential. I left it alone because routing belongs to the #1669 work. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ae64545cd46d346c18e8b9993b3fbb43e --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ae64545cd46d346c18e8b9993b3fbb43e) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ae64545cd46d346c18e8b9993b3fbb43e/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
…-layer-proposal Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…PI client into @sentry/api Add cli-first-code-mode-mcp: CLI parity with the MCP catalog, a two-tool Code Mode surface (search_sentry, execute_sentry_code) over the CLI's commands, discovery from CLI introspection, every published API route, completion for agents, and an eval-gated catalog removal. Re-scope unify-sentry-api-layer: stop staging in toolkit-core, move the client into @sentry/api next, then the CLI's wrappers, then the endpoints missing from the published schema. Drop the MCP domain wrappers, the compression dictionary, and per-tool porting; park the R2 cache tier. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
3 of 38 tasks
…un the SDK on Workers Rename cli-first-code-mode-mcp to cli-first-mcp. The MCP lists two tools, search_sentry and execute_sentry_command, which dispatches one exposed command with validated JSON parameters through createSentrySDK(), so no code sandbox is needed. An all-tools mode lists each command as a tool for harnesses that search tools themselves. The hosted MCP runs the CLI SDK with storage split by purpose behind Workers backends and per-request state, starting with a workerd build spike. A Durable Object SQLite adapter is the fallback for storage, and the per-domain portable-operation split is the fallback for domains Workers cannot run. Code Mode becomes an optional later tool. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Hosted creates one SDK instance per request with the OAuth grant's token and turns persistence off instead of adding Workers storage backends. Pagination becomes stateless through nextCursor. The remaining cross-user risk is module-level state, which gets an audit and the concurrent two-identity test. Keep @sentry/mcp-server. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…entation issues MCP hosts allowlist and approve by tool name (#1254), so execute becomes execute_sentry_read and execute_sentry_write. The release now follows the @sentry/api switch (8.10). Each task links its tracking issue. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
…-layer-proposal Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
The SDK runs on workerd once five small load-time issues are fixed and the hosted worker moves to a 2026 compatibility date. The bundle fits the 64 MiB limit, but it is the whole CLI because the route loaders import every command. The spike also found that the SDK inherits the worker's process.env, so the worker's own SENTRY_DSN redirected an untargeted command. Records the stdio-only additions and ranks what is left. Refs #1882 Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Harnesses search large tool lists themselves, so the plan drops the search_sentry and execute_sentry_read/write meta-tools: every exposed command is its own MCP tool, with hints from its permission class. Completion reaches agents through schema examples and validation errors, with no completion tool. #1891's all-tools mode is folded into #1890. Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two OpenSpec changes, docs only. Implementation lands in follow-up PRs.
cli-first-mcp(new)The CLI becomes the only product surface, and the MCP exposes its commands.
project edit, DSN create and edit, uptime monitors, monitor view, issue assign, comment, and activity, event attachments, profiles, and dropped events (feat(cli): add event analytics command for dropped events #1812). New capabilities become commands, not MCP tools.search_sentryfinds commands, returns a command's JSON Schema, and completes parameter values.execute_sentry_readandexecute_sentry_writetake{ command, params }; execute is split by permission class because MCP hosts allowlist and approve by tool name (execute_sentry_tool breaks host allowlists and approval UX #1254). It validates the params and checks the command's permission class and the session's constraints. Then it calls thecreateSentrySDK()method. It only dispatches to known commands, so there is no code to sandbox. An all-tools mode lists each exposed command as its own tool, for harnesses that search large tool lists themselves.cwd, terminal), or hidden (auth login/logout,cli upgrade,local, andmonitor run, which runs a child process). A lint check fails when a command doesn't declare one.sentry apiandsentry schema. They are exposed commands, so no extra tool is needed.completemode and validation errors. They replace the embedded natural-language search agents and the OpenAI/Anthropic setup those agents need.nextCursor.unify-sentry-api-layer(re-scoped)toolkit-core. The next step is to movetoolkit-core/src/api-client/into getsentry/sentry-api-schema and reconcile it with the package's existing errors, pagination, and browser client. Then toolkit switches over (8.5, 8.6).lib/api/wrappers then move onto@sentry/api(8.7).cli-first-mcpgroup 6: 11.1, 11.2, and 11.4.Superseded:
refine-project-management-tools.Checks:
openspec validate --strictpasses for both changes, andoxfmt --checkanddocs:checkpass.Implementation issues are filed and linked from each task and from the #1357 tracker (#1875–#1899, #1907–#1911). The release (#1877) follows the
@sentry/apiswitch (#1876).via @BYK.
--
View Session in Sentry