Skip to content
Draft
1 change: 1 addition & 0 deletions apps/cli-docs/src/content/docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,36 +90,37 @@
│ │ │ ├── api.ts # Make an authenticated API request
│ │ │ ├── explore.ts # Query aggregate event data (Explore)
│ │ │ ├── help.ts # Help command
│ │ │ ├── info.ts # Print configuration and verify authentication
│ │ │ ├── init.ts # Initialize Sentry in your project (experimental)
│ │ │ ├── mcp.ts # Start a local Sentry MCP server
│ │ │ ├── schema.ts # Browse the Sentry API schema
│ │ │ └── wasm-split.ts# Add build ids to WebAssembly modules and split out debug data
│ │ ├── lib/ # Shared utilities
│ │ └── types/ # TypeScript types and Valibot schemas
│ ├── test/ # Test files (mirrors src/ structure)
│ ├── script/ # Build and utility scripts
│ └── plugins/ # Agent skill files
└── apps/
└── cli-docs/ # Documentation site (Astro + Starlight)
```
<!-- GENERATED:END project-structure -->

## Building

<!-- GENERATED:START build-commands -->
```bash
# Build for current platform (uses esbuild + fossilize for Node SEA packaging)
pnpm run build

# Build for all platforms
pnpm run build:all

# Create npm bundle
pnpm run bundle
```
<!-- GENERATED:END build-commands -->

## Testing

Check warning on line 123 in apps/cli-docs/src/content/docs/contributing.md

View check run for this annotation

@sentry/warden / warden: code-review

Interactive MCP auth logs into env host, not --host target

In a TTY, `sentry mcp --host=<other>` with no usable session runs OAuth against `resolveEffectiveLoginHost()` (env/default SaaS), stores those credentials, then fails host-scope checks for the MCP target.

```bash
# Run all tests
Expand Down
30 changes: 30 additions & 0 deletions apps/cli-docs/src/fragments/commands/mcp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

`sentry mcp` starts the local stdio Sentry MCP server using the active Sentry CLI session. Authenticate once with `sentry auth login`; no separate MCP login or token cache is required.

## Configure an MCP client

Point your MCP client at the installed Sentry CLI:

```json
{
"mcpServers": {
"sentry": {
"command": "sentry",
"args": ["mcp"]
}
}
}
```

For a self-hosted instance, first authenticate the CLI against that host. You can also override the target for this MCP server invocation:

```json
{
"mcpServers": {
"sentry": {
"command": "sentry",
"args": ["mcp", "--host=sentry.example.com"]
}
}
}
```
11 changes: 6 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@
"tsx": "tsx --env-file-if-exists=.env.local --import ./script/require-shim.mjs",
"cli": "tsx --env-file-if-exists=.env.local --import ./script/require-shim.mjs src/bin.ts",
"dev": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && tsx --import ./script/require-shim.mjs src/bin.ts",
"build": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts --single",
"build:all": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts",
"bundle": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/bundle.ts",
"typecheck": "pnpm run generate:docs && pnpm run generate:sdk && tsc --noEmit",
"build": "pnpm --filter @sentry/mcp-core run build && pnpm --filter @sentry/mcp-server run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts --single",
"build:all": "pnpm --filter @sentry/mcp-core run build && pnpm --filter @sentry/mcp-server run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts",
"bundle": "pnpm --filter @sentry/mcp-core run build && pnpm --filter @sentry/mcp-server run build && pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/bundle.ts",
"typecheck": "pnpm --filter @sentry/mcp-core run build && pnpm --filter @sentry/mcp-server run build && pnpm run generate:docs && pnpm run generate:sdk && tsc --noEmit",
"lint": "pnpm -w exec oxlint --report-unused-disable-directives packages/cli && pnpm -w exec oxfmt --check packages/cli",
"lint:fix": "pnpm -w exec oxlint --fix packages/cli && pnpm -w exec oxfmt --write packages/cli",
"test": "pnpm run test:unit",
"test:unit": "pnpm run generate:docs && pnpm run generate:sdk && vitest run test/lib test/commands test/types test/script --coverage",
"test:changed": "pnpm run generate:docs && pnpm run generate:sdk && vitest run --changed",
"test:e2e": "pnpm run generate:docs && pnpm run generate:sdk && vitest run test/e2e",
"test:e2e": "pnpm tsx script/prepare-e2e-bundle.ts && vitest run test/e2e",
"test:init-eval": "vitest run test/init-eval --testTimeout 600000",
"generate:parser": "pnpm tsx script/generate-parser.ts",
"generate:sdk": "pnpm tsx script/generate-sdk.ts",
Expand All @@ -92,6 +92,7 @@
"ci:policy": "pnpm run check:patches && pnpm run check:deps && pnpm run check:errors && pnpm run check:fragments && pnpm run check:docs-sections && pnpm run check:env-coverage && pnpm run check:stale-refs"
},
"devDependencies": {
"@sentry/mcp-server": "workspace:*",
"@anthropic-ai/sdk": "^0.39.0",
"@clack/prompts": "0.11.0",
"@hono/node-server": "^2.0.10",
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/plugins/sentry-cli/skills/sentry-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,14 @@ View Sentry logs

→ Full flags and examples: `references/log.md`

### Mcp

Start a local Sentry MCP server

- `sentry mcp` — Start a local Sentry MCP server

→ Full flags and examples: `references/mcp.md`

### Monitor

Work with Sentry cron monitors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: sentry-cli-mcp
version: 0.48.0-dev.0
description: Start a local Sentry MCP server
requires:
bins: ["sentry"]
auth: true
---

# Mcp Commands

Start a local Sentry MCP server

### `sentry mcp`

Start a local Sentry MCP server

All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.
19 changes: 19 additions & 0 deletions packages/cli/script/prepare-e2e-bundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { spawn } from "node:child_process";

const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const child = spawn(pnpm, ["run", "bundle"], {
env: {
...process.env,
SENTRY_CLIENT_ID: process.env.SENTRY_CLIENT_ID ?? "test-client-id",
},
stdio: "inherit",
});

Check notice on line 10 in packages/cli/script/prepare-e2e-bundle.ts

View check run for this annotation

@sentry/warden / warden: code-review

Windows prepare-e2e-bundle spawn fails without shell:true

`spawn('pnpm.cmd', …)` without `shell: true` cannot run `.cmd` shims on Windows, so `pnpm test:e2e` fails at bundle prep with ENOENT instead of building the e2e bundle.

const exitCode = await new Promise<number>((resolve, reject) => {
child.once("error", reject);
child.once("close", (code) => resolve(code ?? 1));
});

if (exitCode !== 0) {
process.exitCode = exitCode;
}
2 changes: 2 additions & 0 deletions packages/cli/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { listCommand as issueListCommand } from "./commands/issue/list.js";
import { localRoute } from "./commands/local/index.js";
import { logRoute } from "./commands/log/index.js";
import { listCommand as logListCommand } from "./commands/log/list.js";
import { mcpCommand } from "./commands/mcp.js";
import { monitorRoute } from "./commands/monitor/index.js";
import { listCommand as monitorListCommand } from "./commands/monitor/list.js";
import { orgRoute } from "./commands/org/index.js";
Expand Down Expand Up @@ -142,6 +143,7 @@ export const routes = buildRouteMap({
explore: exploreCommand,
feedback: feedbackRoute,
log: logRoute,
mcp: mcpCommand,
monitor: monitorRoute,
snapshots: snapshotsRoute,
sourcemap: sourcemapRoute,
Expand Down
82 changes: 82 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { redactCredentialText } from "./lib/credential-redaction.js";
import { getEnv } from "./lib/env.js";
import { CliError, formatError } from "./lib/errors.js";
import { buildTopLevelFlags } from "./lib/global-flags.js";
Comment thread
MathurAditya724 marked this conversation as resolved.
import { initTimezone } from "./lib/timezone.js";

/**
Expand Down Expand Up @@ -228,13 +229,94 @@ type ErrorMiddleware = (
retryArgs: string[],
) => Promise<void>;

/**
* Return MCP's arguments when it is the command after leading global flags.
*
* MCP owns stdout for JSON-RPC, so CLI-level output flags are deliberately
* ignored before it starts. Flags after `mcp` belong to the MCP server.
*/
function skipLeadingGlobalFlag(
cliArgs: readonly string[],
index: number,
): number | undefined {
const { booleanFlags, valueFlags } = buildTopLevelFlags();
const token = cliArgs[index] ?? "";
const flag = token.split("=", 1)[0] ?? token;

if (booleanFlags.has(flag)) {
return index + 1;
}
if (!valueFlags.has(flag)) {
return;
}
return token.includes("=") || cliArgs[index + 1] === undefined
? index + 1
: index + 2;
}

export function getMcpArgs(cliArgs: readonly string[]): string[] | undefined {
for (let index = 0; index < cliArgs.length;) {
const token = cliArgs[index] ?? "";
if (token === "--") {
return;
}
if (!token.startsWith("-")) {
return token === "mcp" ? cliArgs.slice(index + 1) : undefined;
}

const nextIndex = skipLeadingGlobalFlag(cliArgs, index);
if (nextIndex === undefined) {
return;
}
index = nextIndex;
}

return;
}

/** Run MCP and return whether the current invocation was handled by it. */
export async function runMcpCommand(cliArgs: string[]): Promise<boolean> {
const mcpArgs = getMcpArgs(cliArgs);
if (!mcpArgs) {
return false;
}

const [{ startMcpServer }, { getExitCode }] = await Promise.all([
import("./lib/mcp.js"),
import("./lib/errors.js"),
]);

try {
await startMcpServer(mcpArgs);
} catch (mcpError) {
process.stderr.write(`${formatError(mcpError)}\n`);
process.exitCode = getExitCode(mcpError);
// MCP setup errors are terminal, unlike a running stdio server. Clean up
// network resources only on this error path so successful servers retain
// their dispatcher and are not force-exited on macOS.
const [{ scheduleForceExit }, { closeGlobalDispatcher }] =
await Promise.all([
import("./lib/force-exit.js"),
import("./lib/close-dispatcher.js"),
]);
scheduleForceExit();
await closeGlobalDispatcher();
}
Comment thread
MathurAditya724 marked this conversation as resolved.

return true;
}

/**
* Full CLI execution with telemetry, middleware, and error recovery.
*
* All heavy imports are loaded here (not at module top level) so the
* `__complete` fast-path can skip them entirely.
*/
export async function runCli(cliArgs: string[]): Promise<void> {
if (await runMcpCommand(cliArgs)) {
return;
}

const { isatty } = await import("node:tty");
const { ExitCode, run } = await import("@stricli/core");
const { app } = await import("./app.js");
Expand Down
22 changes: 22 additions & 0 deletions packages/cli/src/commands/mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { SentryContext } from "../context.js";
import { buildCommand } from "../lib/command.js";
import { CommandOutput } from "../lib/formatters/output.js";

/** Documentation route for the stdio handoff in {@link runCli}. */
export const mcpCommand = buildCommand({
auth: false,
docs: {
brief: "Start a local Sentry MCP server",
fullDescription:
"Start the local stdio MCP server using the current Sentry CLI session. " +
"Configure an MCP client with `sentry mcp`; authenticate first with `sentry auth login`.",
},
output: { human: (message: string) => message },
parameters: {},
// biome-ignore lint/suspicious/useAwait: async generator required by buildCommand
async *func(this: SentryContext) {
yield new CommandOutput(
"The local MCP server is started by running `sentry mcp` from an MCP client configuration.",
);
},
});
Loading
Loading