Skip to content

feat(mcp): Serve each exposed CLI command as its own MCP tool - #1922

Open
sentry-junior[bot] wants to merge 18 commits into
feat/command-catalogfrom
feat/cli-first-mcp-execute
Open

sentry-junior[bot] wants to merge 18 commits into
feat/command-catalogfrom
feat/cli-first-mcp-execute

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1890. Part of the cli-first-mcp plan (#1357).

Stacked on #1919 (feat/command-catalog), which is stacked on #1916. Retarget this to main once those merge.

sentry mcp --commands replaces the hand-written tool catalog with one MCP tool for each exposed command in the CLI command catalog. Each tool is named from its command path (sentry issue list → sentry_issue_list). Its description is the command's brief and examples, and its input schema is the catalog's { positional, flags } JSON Schema. Harnesses search large tool lists themselves, so this PR no longer adds the search_sentry, execute_sentry_read, or execute_sentry_write meta-tools. They are gone.

Hints. Read commands get readOnlyHint; write and destructive commands get destructiveHint. sentry api takes its class from the --method of each call. It stays a single tool marked destructive, because no per-call hint exists and splitting it into GET and non-GET tools would add a tool without changing what a host can allowlist. Under --read-only it is marked read-only and refuses every method except GET, HEAD, and OPTIONS. --read-only also drops every write tool: 85 tools become 52.

How a call runs. The execution path is unchanged:

  • Arguments are validated against the command's schema. Errors name the allowed values or flags, which is how agents complete arguments.
  • Write tools are listed only for the skill that covers them (seer, triage, or project-management); sentry api writes need triage or project-management.
  • Org and project are pinned to the session: named targets (including target positionals, every [<org>/]<id> word, <org>/<project>/<suffix> IDs, Sentry URLs including /settings/<org>/projects/<project>/, and sentry api paths under organizations/, projects/, or teams/, resolved like the request) must match it, and a bare project gets the session's org.
  • The call goes through createSentrySDK().run(), which loads only the routes it needs.
  • Output is JSON, capped at 50,000 characters, and stays valid JSON when cut. nextCursor is lifted out for paginated commands.

Hidden and stdio-only commands are not listed. localPath flags, --web, streaming flags, and status show --url (it fetches a caller-picked host) are refused. The schema is attached as metadata on permissive Zod types, so tools/list shows the catalog schema as is, the SDK fills in no defaults, and the validator's messages reach the agent.

SDK fixes this needed. SentryOptions.telemetry: false skips the SDK's own Sentry init, which otherwise closed the MCP server's client on every call. run() also reports failures again: the fake process started with exitCode: 0, and since Stricli records failures with exitCode ??= code, every failure was hidden.

Tokens (pnpm run measure-tokens):

Surface Tools Tokens
Current tool catalog 73 47,553
--commands, one tool per command (this PR) 85 37,002
--commands with search/execute meta-tools (earlier version of this PR) 3 784

QA. Over stdio, sentry mcp --commands --read-only listed 49 tools before the latest restack, none of them write tools. sentry_issue_list returned real issues from the sentry org with a nextCursor, and a sentry_api POST was refused. The slim @sentry/mcp-server bundle still stubs this path out and points users to the CLI.

Known gap. Examples in tool descriptions drop any that pipe output elsewhere or use a refused flag's long name. Short aliases such as -w still show, because the catalog doesn't record aliases.

Left for follow-ups:

--commands uses one account per session: the stored login that knows its <org>/ target, else the startup account. Calls are not routed between logins.

via @BYK.

--

View Session in Sentry

…ools

`sentry mcp --commands` replaces the hand-written tool catalog with three
tools generated from the CLI command catalog: `search_sentry` finds
commands, `execute_sentry_read` runs read commands, and
`execute_sentry_write` runs write and destructive ones. `--read-only` drops
the write tool. Calls are validated against each command's flag schema,
pinned to the session's org and project, and run through the SDK, which
loads only the routes a call needs.

The SDK can now run without its own telemetry, and `run()` reports
failures again: the fake process started with `exitCode: 0`, which hid
every error Stricli recorded with `??=`.

Closes #1890

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Nov 5, 2026 9:57pm UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Nov 3, 2026

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

A result over the size cap that is not a list was sliced mid-JSON with a
note appended, so agents could not parse it, and the text ran past the cap.
It now returns the start of the result's JSON text as a string in
`result`, with `truncated.characters` and a hint, inside the cap.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Harnesses search large tool lists themselves, so `sentry mcp --commands`
no longer adds `search_sentry`, `execute_sentry_read`, and
`execute_sentry_write`. Each exposed command in the command catalog is its
own tool, named from its path (`sentry issue list` → `sentry_issue_list`),
with the catalog's brief, examples, and `{ positional, flags }` schema.

Read commands are marked read-only and write and destructive ones
destructive. `sentry api` is one tool marked destructive, since each call
takes the class of its method; under `--read-only` it is marked read-only
and refuses every method but GET, HEAD, and OPTIONS. `--read-only` drops
the write tools.

The schema rides on permissive Zod types as metadata, so `tools/list` shows
it as is, no defaults are filled in, and the existing validator still
returns errors that name the allowed values.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@sentry-junior sentry-junior Bot changed the title feat(mcp): Serve CLI commands through search_sentry and the execute tools feat(mcp): Serve each exposed CLI command as its own MCP tool Nov 4, 2026
…cli-first-mcp-execute

# Conflicts:
#	packages/cli/src/command-manifest.ts

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Treat a `target` positional as `<org>/<project>`, check every
`[<org>/]<id>` word and a leading `<org> <id>` org, and read a nested
`organizations/<org>/projects/<project>/` path for `sentry api`. Before,
a scoped session could reach another organization or project through
these.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

`<org>/<project>/<suffix>` under a one-word title (`feedback`,
`issue`) names a project, so a project-scoped session now refuses
another project there.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
In a project-scoped session, check every --project value (repeated or
comma-separated) and pin an `<org>/` target to the session's project.
Drop nextCursor from a result cut to fit, since it points past the
items that were cut.
Comment thread packages/cli/src/lib/mcp.ts Resolved
- Check the org and project of Sentry URLs, including /settings/<org>/
  and feedbackSlug links, against the session.
- Resolve sentry api paths as the request will (dot segments), and read
  teams/<org>/ as scoped to that org.
- Refuse status show --url, which fetches a host the caller picks.
- List write commands only for the skill that covers them (seer, triage,
  or project-management), and refuse sentry api writes without one.
- Refuse the last and previous cursor aliases when hosted.
- Treat null flags and positionals as none.
- Serve --commands with the stored login that knows its <org>/ target.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

The CLI strips an api/0/ prefix with or without a leading slash, so
session pinning must too, or a valid endpoint is refused as unscoped.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
In a project session, sentry api takes only endpoints that name the
session's project, since organization-wide ones span every project.
GitHub-style (org/project#ID, project#ID) and project:ID issue
references are checked like their org/project/ID form.
sentry-junior Bot and others added 2 commits November 5, 2026 20:52
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
#1991 made `title` required on every tool. Command tools take it from
the command path, title-cased (`sentry issue list` → `Issue List`).

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

sentry-junior Bot and others added 3 commits November 5, 2026 21:08
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
sentry api sends an absolute URL to that URL's own host, so an agent
could make it fetch any host it picks, as status show --url could.
Agents pass the path relative to /api/0/ instead.
Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0b80422. Configure here.

A leading bare word of dashboard view is the organization, as for
agent-conversation view, so it must match the session's. An org/project
target under a longer title (span list acme/api, trace view acme/api
<id>, log list acme/api) names its project, so a project session checks
it.

This branch was previously deployed

1 inactive deployment
Preview — 8e4e42ae Deployed Nov 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve each exposed CLI command as its own MCP tool from sentry mcp

1 participant