From a4be49a8f8fbc4af2aab5fd2268f8d4643b12ce2 Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 26 Oct 2026 21:24:05 +0100 Subject: [PATCH 1/5] feat(mcp): add HTTP Code Mode with explicit API permissions --- docs/cloudflare/architecture.md | 25 + docs/cloudflare/oauth-architecture.md | 16 +- docs/integrations/claude-code-plugin.md | 8 +- docs/mcp/README.md | 2 + docs/operations/security.md | 10 +- docs/testing/code-mode.md | 875 +++++++ packages/mcp-cloudflare/package.json | 10 +- .../components/fragments/stdio-setup.tsx | 2 +- .../src/code-mode/access.test.ts | 183 ++ .../mcp-cloudflare/src/code-mode/access.ts | 111 + .../mcp-cloudflare/src/code-mode/broker.ts | 139 + .../src/code-mode/definitions.ts | 99 + .../src/code-mode/discovery-worker.test.ts | 224 ++ .../src/code-mode/discovery-worker.ts | 53 + packages/mcp-cloudflare/src/code-mode/http.ts | 50 + .../mcp-cloudflare/src/code-mode/openapi.ts | 33 + .../src/code-mode/production.ts | 102 + .../mcp-cloudflare/src/code-mode/sandbox.ts | 81 + .../src/code-mode/tsconfig.json | 9 + .../src/code-mode/vitest.config.ts | 12 + .../src/code-mode/worker.test.ts | 686 +++++ .../mcp-cloudflare/src/code-mode/worker.ts | 145 ++ .../src/server/lib/approval-dialog.test.ts | 75 +- .../src/server/lib/approval-dialog.ts | 104 +- .../server/lib/code-mode.integration.test.ts | 499 ++++ .../src/server/lib/mcp-handler.ts | 19 +- .../src/server/oauth/authorize.test.ts | 116 +- .../src/server/oauth/callback.test.ts | 3 +- .../src/server/oauth/constants.ts | 2 +- .../src/server/oauth/helpers.test.ts | 116 +- .../routes/__tests__/mcp-discovery.test.ts | 25 +- .../mcp-cloudflare/src/server/routes/mcp.ts | 40 +- packages/mcp-cloudflare/src/server/types.ts | 7 +- packages/mcp-cloudflare/vitest.config.ts | 1 + packages/mcp-cloudflare/wrangler.canary.jsonc | 1 + .../mcp-cloudflare/wrangler.code-mode.jsonc | 16 + packages/mcp-cloudflare/wrangler.jsonc | 1 + packages/mcp-cloudflare/wrangler.test.jsonc | 1 + packages/mcp-core/package.json | 8 + .../mcp-core/scripts/generate-definitions.ts | 36 +- packages/mcp-core/src/skillDefinitions.json | 82 + packages/mcp-core/src/skillDefinitions.ts | 1 + packages/mcp-core/src/skills.test.ts | 41 +- packages/mcp-core/src/skills.ts | 23 +- packages/mcp-core/src/toolDefinitions.json | 70 +- packages/mcp-server-evals/package.json | 13 + .../src/code-mode/alert-fixture.test.ts | 241 ++ .../src/code-mode/alert-fixture.ts | 450 ++++ .../src/code-mode/api-fixture.ts | 98 + .../src/code-mode/claude-agent.test.ts | 281 ++ .../src/code-mode/claude-agent.ts | 236 ++ .../src/code-mode/dashboard-fixture-data.json | 2307 +++++++++++++++++ .../src/code-mode/dashboard-fixture.test.ts | 227 ++ .../src/code-mode/dashboard-fixture.ts | 413 +++ .../src/code-mode/json-answer.test.ts | 45 + .../src/code-mode/json-answer.ts | 17 + .../src/code-mode/monitor-fixture.test.ts | 298 +++ .../src/code-mode/monitor-fixture.ts | 491 ++++ .../src/code-mode/release-fixture.test.ts | 333 +++ .../src/code-mode/release-fixture.ts | 417 +++ .../src/code-mode/release-recovery-fixture.ts | 21 + .../src/code-mode/report.test.ts | 41 + .../mcp-server-evals/src/code-mode/report.ts | 25 + .../code-mode/repositories-denied-fixture.ts | 64 + .../code-mode/repositories-fixture.test.ts | 258 ++ .../src/code-mode/repositories-fixture.ts | 228 ++ .../code-mode/repositories-retry-fixture.ts | 31 + .../src/code-mode/run-dashboard.ts | 525 ++++ .../mcp-server-evals/tsconfig.code-mode.json | 7 + .../vitest.code-mode.config.ts | 5 + packages/mcp-server/src/cli/resolve.test.ts | 18 +- packages/mcp-server/src/cli/resolve.ts | 37 +- .../agents/sentry-mcp.md | 2 + plugins/sentry-mcp/agents/sentry-mcp.md | 2 + pnpm-lock.yaml | 4 + scripts/pre-commit-generated.mjs | 1 + 76 files changed, 11173 insertions(+), 125 deletions(-) create mode 100644 docs/testing/code-mode.md create mode 100644 packages/mcp-cloudflare/src/code-mode/access.test.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/access.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/broker.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/definitions.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/discovery-worker.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/http.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/openapi.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/production.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/sandbox.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/tsconfig.json create mode 100644 packages/mcp-cloudflare/src/code-mode/vitest.config.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/worker.test.ts create mode 100644 packages/mcp-cloudflare/src/code-mode/worker.ts create mode 100644 packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts create mode 100644 packages/mcp-cloudflare/wrangler.code-mode.jsonc create mode 100644 packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/alert-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/api-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/claude-agent.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/claude-agent.ts create mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json create mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/json-answer.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/json-answer.ts create mode 100644 packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/monitor-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/release-fixture.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/release-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/report.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/report.ts create mode 100644 packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/repositories-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts create mode 100644 packages/mcp-server-evals/src/code-mode/run-dashboard.ts create mode 100644 packages/mcp-server-evals/tsconfig.code-mode.json create mode 100644 packages/mcp-server-evals/vitest.code-mode.config.ts diff --git a/docs/cloudflare/architecture.md b/docs/cloudflare/architecture.md index 51b28889e..52acd513b 100644 --- a/docs/cloudflare/architecture.md +++ b/docs/cloudflare/architecture.md @@ -96,6 +96,31 @@ const mcpClient = await experimental_createMCPClient({ - Prompt metadata endpoint - Error handling with fallbacks +### Hosted MCP Code Mode + +The authenticated hosted MCP also registers `search_sentry_api` and +`execute_sentry_code` on the fresh server built for each request. The external +client's model writes the code; a Cloudflare Dynamic Worker executes it. This +path does not use the chat interface's model or introduce another OAuth flow. + +The Worker Loader binding (`LOADER`) creates an isolated execution with all +network egress disabled. The trusted sandbox wrapper receives an `ApiBroker` +capability through its `run()` RPC method and uses it for `sentry.request()`; +raw `fetch()` is blocked. The broker retains the access token and an +`ApiAuthorization` object in the parent request context. It checks published +operations and the explicit API read/write MCP grant, enforces +organization constraints, resolves validated regional routing and forwards +client attribution. Generated code cannot access the private token or +authorization context. Upstream 401s use the existing unauthorized callback. + +This integration has no beta or opt-in flag. Code Mode is unavailable for +project-constrained sessions because arbitrary API operations can affect shared +resources even through project-bearing routes. Existing grants do not acquire +API permissions automatically. The shared upstream OAuth token remains separate +from connection permissions; Sentry enforces its scopes and resource access. See [HTTP Code Mode integration and evaluations](../testing/code-mode.md#authentication-and-scope) +for the permission policy, fixture limitations and validation status. Source and +configuration changes alone do not establish that a deployment is active. + ### 4. AI Assistant GPT-4 integration with Sentry-specific system prompt: diff --git a/docs/cloudflare/oauth-architecture.md b/docs/cloudflare/oauth-architecture.md index a52da228f..2e6a63182 100644 --- a/docs/cloudflare/oauth-architecture.md +++ b/docs/cloudflare/oauth-architecture.md @@ -308,6 +308,10 @@ const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ The system only refreshes MCP tokens. It does not rotate upstream Sentry OAuth tokens anymore. +Refresh preserves `upstreamScopes` from the original Sentry token response. +Older grants and responses that omit `scope` leave this metadata unknown; +refresh does not infer it from MCP skills or the requested scope set. + 1. **MCP Token Refresh**: MCP clients exchange an MCP refresh token for a new MCP access token 2. **Sentry Token Reuse**: The worker keeps reusing the cached Sentry access token while it is still valid 3. **Stale grant rejection**: Grants missing required stored props like `refreshToken` are treated as stale and revoked @@ -420,7 +424,17 @@ remains valid RFC 8414 metadata. When a constrained OAuth request includes `resource=/mcp/{org}` or `resource=/mcp/{org}/{project}`, the approval page surfaces that scope to the -user as a "Session scope" banner before permissions are granted. +user before permissions are granted. Organization-only and unconstrained +connections show an "Organization scope" banner; project-constrained +connections retain the "Session scope" banner. The unconstrained banner +explicitly covers all organizations authorized by the upstream token. + +New consent forms leave API Write unchecked, while signed remembered selections +remain preselected for the same client. API Write's warning explains that it is +an independent, additive permission: it allows API writes even when Triage Issues +or Manage Projects & Teams is unchecked, subject to Sentry permissions and the +connection's organization scope. Project-constrained sessions do not offer +Code Mode skills. Note: These describe the MCP OAuth server, not Sentry's OAuth endpoints. diff --git a/docs/integrations/claude-code-plugin.md b/docs/integrations/claude-code-plugin.md index d198e416c..9227b65ba 100644 --- a/docs/integrations/claude-code-plugin.md +++ b/docs/integrations/claude-code-plugin.md @@ -63,18 +63,20 @@ The body below the frontmatter is the agent's system prompt. ## Keeping `allowedTools` in Sync -The `allowedTools` list in the agent frontmatter must match the generated default-grant direct MCP surface. The `generate-definitions` script handles this automatically: +The `allowedTools` list in the agent frontmatter must match the generated default-grant direct MCP surface, including the hosted `search_sentry_api` and `execute_sentry_code` tools. Their availability and write permissions still depend on the connection's existing grant and constraints. The `generate-definitions` script handles this automatically: ```bash pnpm run --filter @sentry/mcp-core generate-definitions ``` +The public `/.mcp/tools.json` metadata includes the same hosted definitions. Code Mode has no fixed tool-level API scopes (`requiredScopes: []`): each HTTP operation is authorized against the connection's permissions and organization constraint before the request is sent. + This script: 1. Imports all tools from `packages/mcp-core/src/tools/index.ts` 2. Imports all skills from `packages/mcp-core/src/skills.ts` 3. Writes `toolDefinitions.json` and `skillDefinitions.json` to `packages/mcp-core/src/` -4. Updates `allowedTools` in both `plugins/sentry-mcp/agents/sentry-mcp.md` and `plugins/sentry-mcp-experimental/agents/sentry-mcp.md`, using the default-grant direct surface for each plugin mode. Optional-skill tools remain reachable through `search_sentry_tools` and `execute_sentry_tool` when available. +4. Updates `allowedTools` in both `plugins/sentry-mcp/agents/sentry-mcp.md` and `plugins/sentry-mcp-experimental/agents/sentry-mcp.md`, using the default-grant direct surface for each plugin mode plus hosted Code Mode names from `packages/mcp-cloudflare/src/code-mode/definitions.ts`. These hosted tools are not added to core/stdio JSON definitions. Optional-skill tools remain reachable through `search_sentry_tools` and `execute_sentry_tool` when available. The script runs automatically as a `prebuild` and `pretest` hook in `packages/mcp-core/package.json`. Run it explicitly after: @@ -82,7 +84,7 @@ The script runs automatically as a `prebuild` and `pretest` hook in `packages/mc - Changing tool skills assignments - Modifying agent prompts (to verify frontmatter stays valid) -The script skips regeneration if all outputs are newer than all inputs (tool source files, `skills.ts`, and the script itself). +The script skips regeneration if all outputs are newer than all inputs (tool source files, hosted Code Mode definitions, `skills.ts`, and the script itself). ## Modifying the Agent Prompt diff --git a/docs/mcp/README.md b/docs/mcp/README.md index a57b1b313..7daaebdcd 100644 --- a/docs/mcp/README.md +++ b/docs/mcp/README.md @@ -17,6 +17,8 @@ them. The [Toolkit index](../README.md) also links the CLI and shared guides. [error handling](../contributing/error-handling.md) — MCP client conventions. - [Testing](../testing/overview.md), [stdio](../testing/stdio.md), and [remote](../testing/remote.md) — tests and transport QA. +- [HTTP Code Mode](../testing/code-mode.md) — runtime integration, permissions, + dashboard copying, and CLI flow evaluations. ## Run and Release diff --git a/docs/operations/security.md b/docs/operations/security.md index 670ce6120..881feecd7 100644 --- a/docs/operations/security.md +++ b/docs/operations/security.md @@ -104,10 +104,12 @@ Today, runtime authorization for remote HTTP sessions is driven primarily by: `grantedScopes` still exists on tokens for backward compatibility with older clients, but it is transitional. Skills are the primary authorization model. -Note that `grantedScopes` is derived from the selected skills, not from what -Sentry granted. The scopes Sentry actually granted on the upstream token (from -the `scope` field of Sentry's token response) are stored separately as -`upstreamScopes`. Grants created before `upstreamScopes` existed do not have it. +`grantedScopes` is derived from selected skills, not from what Sentry granted. +The token response's actual `scope` is stored separately as `upstreamScopes`. +Older grants and responses without `scope` leave `upstreamScopes` undefined, +which means unknown rather than no permissions. MCP refresh preserves that +metadata without requesting or inferring additional scopes. It does not grant +the MCP session permission to use every capability of the upstream token. Direct `Sentry-Bearer` sessions default to all active skills and can be narrowed with `skills` or `disable-skills` query parameters. They do not get OAuth diff --git a/docs/testing/code-mode.md b/docs/testing/code-mode.md new file mode 100644 index 000000000..35ad80e55 --- /dev/null +++ b/docs/testing/code-mode.md @@ -0,0 +1,875 @@ +# HTTP Code Mode integration and evaluations + +HTTP Code Mode lets an external MCP agent discover an OpenAPI schema, write +JavaScript, and execute HTTP requests in a Cloudflare Dynamic Worker. It covers +published API operations absent from the hand-maintained tool catalog, subject +to the connection's permissions. There is no embedded LLM or endpoint-specific +execution tool. The hosted MCP integration uses the existing authentication flow +and has no beta or opt-in flag. This document describes the implementation and +local evidence; it does not establish that the change has been deployed. + +The evaluation harness remains separate from the hosted authorization boundary. +Its historical write successes do not demonstrate that existing production +grants authorize those writes. An SDK interface and HTTP-versus-SDK evaluation +are deferred; neither is a deployment prerequisite. + +## Ownership, model usage and protocol + +This is **server-side execution of client-generated code**. The external MCP +client's model discovers the API, writes JavaScript, corrects failures and writes +the final answer. Our MCP server runs that JavaScript; it does not call a model. + +| Responsibility | Owner | Cost boundary | +| ------------------------------------------------------- | ----------------------- | ----------------------------------------------- | +| Planning, code generation, corrections and final answer | Client's agent/provider | Client's model account or subscription | +| OpenAPI search and JavaScript execution | Our Dynamic Workers | MCP operator's Cloudflare infrastructure | +| Credential injection and per-request access enforcement | Our host-side broker | MCP operator's infrastructure | +| Serving API requests | Sentry API | Sentry infrastructure and applicable API limits | + +Adding an embedded agent would introduce server-side model usage; that is not +part of this design. The local eval used the authenticated Claude CLI account and +local workerd; it did not deploy or consume hosted Dynamic Workers. + +MCP provides ordinary `tools/list` and `tools/call` with typed schemas, structured +results and tool errors. Code Mode is an architectural pattern, not a separate +protocol capability or required tool name in the +[2026-07-28 specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools). +The MCP TypeScript SDK's `2.x` version is separate from the dated wire protocol. +The client-side pattern in +[Client Best Practices](https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices) +places the sandbox in the host. Our server supplies the sandbox, so the external +client only needs normal MCP tool support. This statement does not certify full +protocol compliance or hosted deployment behavior. + +## Relationship to Cloudflare and Stainless + +The implementation follows Cloudflare's +[server-side search/execute pattern](https://blog.cloudflare.com/code-mode-mcp/): +search the schema without loading the entire document into model context, then +compose authenticated HTTP requests in a Dynamic Worker. The inspected +`cloudflare/mcp` revision `550e1be82b2227818e6b98fc32a313a59403ae3f` also uses +Worker Loader directly, a fresh isolate per call, disabled search egress and a +credential-owning `GlobalOutbound` entrypoint. Following this pattern does not +require using the `@cloudflare/codemode` package. + +The hosted integration uses a request-scoped RPC broker with all sandbox egress +disabled. Cloudflare supports passing capabilities, requests and responses through +[Workers RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/). +This keeps authorization and the existing 401 callback in their original request +context; callback objects cannot be placed in the persistently serializable +`ctx.props` of a loopback binding. The HTTP programming interface remains +`sentry.request()`. + +Cloudflare now also offers +[`openApiMcpServer()` and `DynamicWorkerExecutor`](https://developers.cloudflare.com/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server/). +They are candidates for reducing custom harness code. As reviewed on 2026-10-26, +the publisher still returns an MCP SDK v1 server and requires +`createLegacyMcpHandler`; this experiment uses SDK v2. It is not a drop-in +replacement. Authentication, per-operation authorization and Sentry routing +remain responsibilities of the host request implementation either way. + +[Stainless's SDK Code Mode](https://www.stainless.com/blog/sdk-code-mode/) adds an +idiomatic SDK, focused SDK/API documentation, type checking and useful errors. +The initial HTTP version does not implement those SDK features. Its focus is +schema discovery, authenticated HTTP requests and compact program results. An SDK +interface can be evaluated later using the same runtime and broker; it would +change the programming interface, not the model or infrastructure cost owners. + +Before deployment, account for +[Dynamic Workers pricing](https://developers.cloudflare.com/dynamic-workers/pricing/) +as well as model usage: Cloudflare meters unique Worker IDs/code per day, +requests and CPU. The runtime deliberately allocates a fresh ID for each tool +execution. Reusing identical code may reduce creation costs, but must preserve +per-request credentials and isolation; changing the ID alone is not sufficient. + +## Runtime + +The hosted MCP registers two additional tools on its existing authenticated +server. The separate `packages/mcp-cloudflare/wrangler.code-mode.jsonc` entrypoint +exposes the same tools at `/mcp` for isolated tests and evaluations: + +- `search_sentry_api({code})`: execute an async arrow function against `spec`, the + configured OpenAPI JSON document. Network access is disabled. +- `execute_sentry_code({code})`: execute an async arrow function with + `sentry.request({method?, path, query?, body?})` (`GET` by default). It returns + `{status, headers, body}`; non-2xx responses throw an error with API details. + +Programs can loop, branch, combine responses and return a projection. Query arrays +become repeated URL parameters; `headers.link` is available for pagination. Tool +results provide both JSON text and `structuredContent`; failures use `isError`. +The MCP client generates the code and pays for its model calls. This Worker only +executes the submitted program. + +Worker Loader runs generated code with `globalOutbound: null`, so raw `fetch()` +and network connections are blocked. The trusted sandbox wrapper implements +`sentry.request()` through an `ApiBroker` capability passed to its `run()` RPC +method. The broker executes in the parent request context and owns the access +token; its private credentials and authorization context are not exposed to +generated code. The broker permits only the configured API origin and path +prefix, replaces auth headers and rejects redirects. + +The hosted integration derives write access from the existing MCP grant and +checks each broker request against the published schema; the isolated harness +uses its explicit fixture write setting instead. Generated code remains a +separate module. This RPC dispatch mechanism replaces the prototype's +`GlobalOutbound` loopback entrypoint while retaining the HTTP tool interface. + +Each execution has a 20-call fetch budget, a 30-second I/O deadline, a 50,000-byte +result limit, and a 2,000,000-byte upstream response limit. Dynamic Worker CPU and +subrequest limits are also configured using the +[Cloudflare API](https://developers.cloudflare.com/dynamic-workers/usage/limits/). +**Local workerd does not enforce those platform limits in our probes. CPU +termination and deployment enforcement have not been validated.** This local +proof is not a production security certification. + +There are no automatic retries or transactions spanning calls. A failed program +may already have created resources; errors instruct the agent to inspect state +before retrying writes. + +## HTTP discovery and result size + +The default `OPENAPI_URL` points to Sentry's published `openapi-derefed.json`, +pinned at `803d6de98c9156c15e9ddc1bf9accbac6e24cebc`. Sentry's existing API-docs +pipeline resolves references before publishing this artifact; the CLI consumes +the same artifact family. No runtime reference resolver, SDK dependency or +second operation catalog is needed. Custom schema URLs must also provide a +trusted, pre-dereferenced JSON document; the runtime does not resolve or fetch +references for them. + +The pinned document contains 159 paths and 254 HTTP operations (4,296,871 bytes, +SHA-256 `270e5bd5de0cd185edfad90f5dd5a2af034c74b042c2325eb108f7a68ff85bb1`). +These are discoverable operations in that published snapshot, not a claim about +all Sentry features or the caller's permissions. Updating the pin is an explicit +schema update, independent of runtime changes. + +The tool descriptions guide discovery across path, operation ID, summary, +description and tags. Product terms need not appear in an endpoint's URL. The +published example returns compact operation metadata; a separate category example +reads the existing top-level tags (including display names) or derives category +names from operation tags. Empty or unrelated results should lead to broader +metadata/category searches before trying guessed API routes. No synonym catalog, +semantic-search service or schema fetch during `tools/list` is added. + +After finding an operation, inspect its description, request/response schemas and +both path-level and operation-level parameters. Operation parameters override a +path parameter with the same name and location. Schemas should be selected as JSON +values, not sliced JSON strings that can hide required fields. + +HTTP write guidance distinguishes raw API bodies from the renamed, enriched or +filtered views returned by ordinary MCP tools. Even a raw GET response is not a +write template: resending an aggregate value can affect child resources. It asks the client to consult the +operation contract/defaults, read the current HTTP resource, avoid inventing empty +values for missing fields, and send only required fields and requested changes +when the contract permits. Additional unchanged fields need a reason in the +request contract or documented update/default behavior, rather than merely +appearing in GET. Resent nested collections must preserve existing IDs +and relationships unless the user requests changing them. Omission is not assumed +to preserve every field, and PUT is not assumed to replace every resource. The +client should verify the result and report unresolved ambiguity instead of +inventing semantics. An unexpected side effect should not trigger speculative +repair writes; report the actual state unless a documented repair is available. + +These are global instructions, not per-endpoint enforcement. The instruction-only +changes measured below left OpenAPI, API behavior, permissions and the runtime +unchanged. The hosted authorization boundary described next is a separate +integration change. Better instructions cannot guarantee correct writes when the +contract is ambiguous; endpoint-specific limitations remain visible in the +evaluations. + +Execution examples keep intermediate responses in JavaScript and return selected +fields plus pagination information. This is guidance, not automatic semantic +truncation: the client can return full objects when the task requires them, up +to the existing byte limit. The historical dashboard baseline predates these +description changes; it is preserved and does not measure their token impact. + +A read-only smoke check on 2026-10-26 loaded the pinned document through the real +MCP server and local Dynamic Workers. It counted all 159 paths / 254 operations, +ran the then-advertised dashboard search examples, and inspected the inline +dashboard-create schema successfully. The examples now use releases to avoid +scaffolding the dashboard evaluation. It made no Sentry API or model calls. Local evidence is in +`.cache/code-mode/http-schema-smoke.json`; this checks discovery mechanics, not +agent task success across the full API. + +## Authentication and scope + +### Hosted MCP + +`server/lib/mcp-handler.ts` builds a fresh MCP server for the authenticated request +and calls `registerProductionCodeModeTools()` with that request's `ServerContext`. +The existing shared upstream OAuth token and global scope request are unchanged. +There is no separate OAuth flow, rollout flag or embedded model call. The token +stays in the credential-owning broker. Production and canary Wrangler configurations +provide a `LOADER` binding. The parent passes an `ApiBroker` RPC capability to the +sandbox's trusted `run()` method. Code Mode is hosted-only. + +The tools require the connection's explicit `api-read` or `api-write` skill. +`api-write` includes reads. These permissions appear in the normal MCP consent +screen; existing grants and remembered skill selections do not acquire them +implicitly. Legacy Inspect, Triage and Project Management grants retain their +existing tools and meaning. Project-constrained sessions do not expose Code Mode. +The broker computes write access from `api-write`; generated code cannot set it. + +For new approvals, **API Read** is selected and **API Write** is unchecked. +Remembered selections for the same client are restored, including an explicit +API Write selection; preferences only preselect the form and do not grant access +without approval. The consent screen always shows the organization scope: +`/mcp/{org}` is limited to that organization, while `/mcp` can access any +organization authorized by the upstream token. Sentry's permissions still apply. + +The API Write warning is visible before selection. It explains that permissions +are additive: API Write can modify issues even when Triage Issues is unchecked, +and projects or teams even when Manage Projects & Teams is unchecked. Those +checkboxes control their catalog tools; they are not deny rules for API Write. + +Three authorities are kept separate: + +- `grantedSkills` is the connection's MCP authorization. +- Legacy `grantedScopes` is derived from tool declarations for compatibility, + not evidence of the token's actual permissions. Code Mode does not use it. +- `upstreamScopes` records the scopes in Sentry's OAuth token response. Missing + metadata on older grants means unknown. It neither grants MCP access nor + replaces the backend's token, role and resource checks. + +Each broker retains an ordinary `ApiAuthorization` object bound to the original +request's context. It is not serialized into Worker bindings or exposed to +generated code. Raw `fetch()` is blocked; before forwarding any +`sentry.request()`, the policy checks: + +- The exact path and HTTP method must exist in the pinned published OpenAPI. + HEAD can use a GET contract when the schema has no explicit HEAD operation. + Unknown or ambiguous paths are rejected. OpenAPI security metadata is not + used to infer the connection's MCP permissions. +- Without `api-write`, only GET, HEAD and OPTIONS can reach Sentry. A broad + upstream token never bypasses the connection's read-only grant. +- An organization-constrained connection requires a recognized organization + path parameter containing the exact authorized slug. Other organizations, + unresolved numeric aliases and global object-ID routes are rejected. Query + parameters do not substitute for an organization-scoped path. + +The policy resolves regional routing through the existing organization helper +and validates the regional host before the broker attaches credentials. It also +forwards the existing MCP client attribution. An upstream 401 invokes the +connection's existing unauthorized callback, once per policy instance, so Code +Mode follows the normal grant-revocation behavior. The backend still performs +its own token, user and resource permission checks. + +**Dashboard example:** the backend accepts `org:read`, `org:write` or `org:admin` +for dashboard creation. With `api-write`, that POST reaches Sentry; no artificial +`:write` suffix requirement is added. With `api-read`, the broker blocks the POST +before contacting Sentry, even if the upstream token would allow it. A successful +MCP grant does not guarantee that Sentry will permit the request. + +A Sentry 403 retains its HTTP status and message. The broker preserves +`WWW-Authenticate`, including `insufficient_scope` when the backend supplies it. +A generic 403 is not labelled as a missing scope or a need to reconnect: it may +reflect role or resource access. No failed program is automatically replayed, +since earlier writes may have succeeded. + +**Project-constrained sessions retain the existing tools without Code Mode.** A +project-bearing URL does not prove that an operation affects only that project. +For example, Sentry's project release-file endpoints modify a file belonging to +a release shared by multiple projects. Organization release deploys can also +affect every project in a release when `projects` is omitted. The current OpenAPI +does not provide a general resource-isolation contract, so the integration does +not infer one from a path prefix or append a project filter to arbitrary routes. +This limitation is based on the backend implementations in +`src/sentry/releases/endpoints/project_release_file_details.py` and +`src/sentry/releases/endpoints/release_deploys.py`, not on a rollout flag. + +### Hosted integration validation + +Local validation on 2026-10-26 exercised the authenticated MCP handler, Dynamic +Workers and RPC broker with simulated external OpenAPI/Sentry HTTP responses: + +- After rebasing and applying the consent screen, 464 Cloudflare tests across + 35 files were verified through the full run and a focused rerun, including + 15 authenticated Code Mode integration tests and two public metadata tests. + The dashboard regression reads the original, + creates two project-specific copies and verifies them with `api-write`, while + the simulated upstream token records only `org:read`. +- The isolated suite passed all 29 tests: 18 runtime, seven API path/organization + checks and four OpenAPI loading tests. It uses the installed Miniflare/workerd + aligned with Wrangler, without a binary override. +- MCP core (1,834 tests), stdio (89), test client (80) and Code Mode fixtures + (53) passed, for 2,549 MCP tests in total. Root build/typechecks, lint, Code + Mode typechecks and frozen install passed. Generated metadata includes the + hosted API skills; core tool registrations remain unchanged. +- The final `wrangler deploy --dry-run` packaged the production + Worker with `LOADER`. This did not upload a version or deploy the change. + +Two additional Claude smoke evals used the `catalog-and-code` surface without +instructing the agent to use Code Mode. Dashboard copying passed (one run, five +MCP calls, six API calls). Repository inventory produced the expected honest +blocked outcome on HTTP 403 (one run), without inventing data or a missing scope. +Both resolved to `claude-opus-5` and used synthetic fixtures. Local reports are +`.cache/code-mode/ready-dashboard.json` and +`.cache/code-mode/ready-repositories-denied.json`; these are smoke checks, not a +success-rate estimate. Unchanged CLI fixture/platform failures prevented a green +full workspace suite; CLI fixes are outside this change. + +The integration tests verify regional routing, client attribution, concurrent +user token isolation, denied operations and organization escapes, project/skill +visibility, and upstream 401 revocation even when generated code catches the +error. They do not test a real Sentry account, production CPU enforcement, or an +active hosted deployment. Historical agent evals below remain separate evidence. + +### Isolated harness + +The local Code Mode entrypoint accepts a direct Sentry bearer token in the MCP +`Authorization` header and keeps it outside the generated program. It does not +run the hosted skills, constraints or regional authorization policy. Its +configuration is server-owned: + +- `API_BASE_URL`: canonical origin and API prefix, such as + `https://sentry.io/api/0`. +- `OPENAPI_URL`: trusted, pre-dereferenced JSON schema URL. Defaults to the pinned + Sentry artifact above; the eval overrides it with its local schema fixture. +- `CODE_MODE_ALLOW_WRITES`: defaults to false; explicitly set `true` to allow + writes in the isolated harness. + +HTTP is allowed only on loopback for local tests. The eval supplies a dummy token +and local URLs; it never calls a real Sentry account. + +OpenAPI discovery is limited to the supplied schema; availability also depends +on backend features and the effective upstream permissions. HTTP/JSON support +is not proof of coverage for multipart, streaming, internal or undocumented +endpoints. The isolated harness is not the deployment entrypoint for the hosted +MCP integration. + +### Separate follow-ups + +The integration does not change OpenAPI or add an SDK. HTTP errors caught inside +programs retain the allowed response headers, including `Retry-After`. Uncaught +program errors preserve execution statistics and HTTP metadata when available; +failures outside the sandbox, such as startup failures, do not have those stats. +Migration to a different Cloudflare runtime abstraction and existing platform +CPU/subrequest enforcement remain separate work requiring hosted validation. + +## Dashboard eval + +The historical agent results below predate the hosted authorization boundary and +RPC dispatcher. They are not fresh agent evaluations of this integration. + +The task is to copy dashboard 101 into exactly two new dashboards, one for project +101 and one for project 102, both with saved environment `staging`. It must retain +widgets, queries, order, layout, other filters and time range, leave the source +unchanged, and return the real IDs and links. The current catalog can inspect +dashboards but cannot create these copies. + +The runner starts an authenticated loopback API fixture and a local Wrangler +Worker, then opens a fresh external-agent session. The isolated baseline gives +the agent only the two generic tools and explicitly directs it to use them. The +natural discovery evaluation below includes the normal catalog and supplies no +such instruction. Neither runner supplies a copy algorithm. Only the Sentry +HTTP boundary is a fixture. MCP transport, schema search, generated JavaScript and +Dynamic Worker execution are real. + +An independent verifier checks persisted API state rather than tool arguments or +the agent's claim of success. Exactly two copies must exist with the right +configuration, no duplicates, an unchanged original and links matching created +IDs. Fixture tests include unsuccessful and misleading answers. + +The fixture contains three dashboard operations with their complete published +request/response schemas. This tests functionality, not discovery across the full +API corpus. Provenance commits are recorded in `dashboard-fixture-data.json`. +Its aliases, generated IDs and rejection of foreign widget IDs follow Sentry's +backend. It does not emulate query-language validation, quotas, feature flags or +real authorization. + +### Natural tool selection in Claude Code + +`eval:code-mode:discovery` connects Claude Code to one MCP named `sentry`, exposing +the nine normal top-level tools, the complete normal catalog behind +`search_sentry_tools` / `execute_sentry_tool`, and the two HTTP Code Mode tools. +It uses the real core server and Dynamic Worker runtime. Only the Sentry API is +a stateful local fixture; no real dashboards are changed. + +The user prompt asks only for two dashboard copies with the project/environment +settings above. It does not mention Code Mode, JavaScript, HTTP, schemas or tool +names. Claude keeps its default system prompt; there is no custom or appended +system instruction telling it how to complete the task. The server is named +`sentry`, the synthetic organization is `dashboard-evals`, and tool examples use +releases instead of dashboards. Search uses the full pinned OpenAPI document +(159 paths / 254 operations), not the three-operation fixture schema. + +The same runner offers `--surface catalog` as a negative control: the same task, +client settings and real catalog, without the two Code Mode tools. Traces record +the complete advertised inventory, actual tool sequence, code, API outcomes and +independently verified final state. We do not require a catalog search before +API discovery: selection is the behavior being measured, not a prescribed route. +A failed task still produces a report and exits nonzero, including the control. + +```bash +pnpm --filter @sentry/mcp-server-evals eval:code-mode:discovery --runs 3 +pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog --runs 1 +``` + +Both surfaces run in an empty temporary directory with other MCP servers, +built-in tools, hooks, skills and session persistence disabled. This is a +controlled MCP selection test, not a test with every Claude Code capability +enabled. API endpoints outside the dashboard fixture remain unimplemented, even +when discoverable in the full schema. The eval-only Worker rejects non-loopback +API configuration and is not a production authorization integration. + +Reports default to `.cache/code-mode/http-dashboard--.json`; +`--report` selects a separate evidence file. The original `--surface code-mode` +baseline remains available for testing execution alone and does not establish +unprompted tool selection. + +### Natural selection results (2026-10-26) + +Claude Code `2.1.263`, resolved model `claude-opus-5`, medium effort: + +| Surface | Task result | Actual selection | MCP calls | API calls | Wall time | +| --------------------------------- | --------------- | ----------------------------------------------------- | --------- | --------- | --------- | +| Normal catalog + Code Mode, run 1 | Pass | Execute code → search API schema → execute code | 3 | 5 | 23.269 s | +| Normal catalog + Code Mode, run 2 | Pass | Execute code → search API schema → execute code | 3 | 7 | 21.889 s | +| Normal catalog + Code Mode, run 3 | Pass | Execute code → search API schema → execute code | 3 | 5 | 18.983 s | +| Normal catalog only, control | Cannot complete | Five catalog searches and two catalog tool executions | 7 | 2 | 31.338 s | + +All three combined-surface runs independently created exactly two correct copies, +retained the requested widget/filter/time configuration, preserved the source, +and returned the actual created IDs/links. All API calls succeeded. Claude chose +the code tools directly, before querying the normal catalog. This demonstrates +unprompted selection from the combined inventory; it does **not** demonstrate a +catalog-first fallback sequence. In each run it read the source through HTTP, +inspected the create schema, then generated a program that created both copies +and verified state with additional reads. + +The control could read the dashboard and search the catalog but had no mutation +capability. It created nothing and reported the limitation instead of claiming +success. Its verifier failure and nonzero exit are expected task outcomes, not +an authentication or transport failure. + +Local evidence: `.cache/code-mode/http-dashboard-natural-claude.json` and +`.cache/code-mode/http-dashboard-catalog-control-claude.json`. Each contains the +prompt, null custom system prompt, full MCP inventory, trace, generated programs, +usage, API outcomes, final state and implementation hashes. These are four pilot +runs of one synthetic scenario; they do not establish broad reliability, token +savings or production readiness. The verifier covers the requested content and +filters; it does not require copying per-user favorites or dashboard permissions. + +### Automatic HTTP baseline (2026-10-26) + +After renewing the Claude login, the automatic eval passed **3/3 repetitions** +using Claude Code `2.1.263`, resolved model `claude-opus-5`, and medium effort. +Each fresh conversation had exactly the two experimental MCP tools available +and explicit instructions to use them. This establishes execution capability, +not autonomous discovery among the normal tools. +Each run created exactly two valid copies and preserved the original, as checked +against persisted fixture state by the independent verifier. + +| Run | Result | Wall time | MCP calls | Sentry API calls | MCP/API errors | +| --- | ------ | --------- | --------- | ---------------- | -------------- | +| 1 | Pass | 24.625 s | 4 | 5 | 0 / 0 | +| 2 | Pass | 20.312 s | 4 | 5 | 0 / 0 | +| 3 | Pass | 19.206 s | 4 | 5 | 0 / 0 | + +Every run used two schema searches and two code executions. The final execution +fetched the source, created both copies and fetched the source again: four HTTP +requests inside one MCP call. The five API requests per run were three GETs and +two POSTs; schema downloads are excluded from that count. The model generated +the program itself and removed existing widget/query IDs from the creation +payloads. There was no embedded LLM or dashboard-specific tool. + +Raw aggregate usage reported by Claude: + +| Run | Uncached input | Cache creation input | Cache read input | Output | +| --- | -------------- | -------------------- | ---------------- | ------ | +| 1 | 8 | 9,234 | 11,060 | 1,944 | +| 2 | 8 | 6,054 | 11,635 | 1,495 | +| 3 | 8 | 6,564 | 12,221 | 1,514 | + +Including cache creation and reads, input totals were 20,302, 17,697 and 18,793 +tokens respectively. Fresh conversations may still reuse provider-side prefix +caches, so cache state is a comparison variable. Client-reported dollar estimates +are saved separately and do not establish the amount billed to a subscription. + +The traces, generated programs, usage, final fixture state and implementation +hashes are saved locally in `.cache/code-mode/http-dashboard-claude.json`. This +establishes a baseline for further HTTP evaluations, not a token-savings claim. +These programs returned complete dashboard objects; compact result projection +is available but was not optimized in this pilot. + +### Earlier session-driven proof (2026-10-25) + +One fresh session subagent completed the task through the real MCP SDK and +Dynamic Worker. Its generated calls were relayed unchanged; the first large +schema response was transferred verbatim through a file. The independent +verifier passed: exactly two copies, IDs `201` and `202`, correct project and +environment filters, preserved widgets/settings, and original unchanged. + +The run used three MCP calls (one search, two executions) and seven Sentry API requests +(five GETs, two POSTs), plus one OpenAPI download, with no API errors. The write program combined six HTTP +requests and returned a compact verification summary. This demonstrates the +missing capability without a dedicated dashboard creation tool. + +The initial automatic clients could not perform inference: Claude's OAuth session was +expired, and the local Codex login rejected the requested models. Those attempts +are infrastructure failures, not task failures or successful benchmark runs. +The successful session-driven run has no available token usage and its relay +latency is not comparable with an automatic client. It does not establish a +three-run success rate, token savings, or an HTTP-versus-SDK winner. Full local +evidence is in `.cache/code-mode/http-dashboard-session.json`. + +## CLI capabilities beyond the MCP catalog + +Three additional tasks use the natural discovery surface: the normal MCP catalog +plus the two HTTP Code Mode tools, with Claude Code's default system prompt. The +user prompts describe the desired result without mentioning Code Mode, tool names, +API endpoints, or an algorithm. The agent can select either interface. Tool descriptions are unchanged across the +flows and include a release discovery example; natural prompts do not imply an +unprimed model or a statistically neutral benchmark. + +| Task | Equivalent CLI operations | Gap in the normal MCP catalog | Independent verification | +| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Release and deploy | `sentry release create`, `release finalize`, `release deploy`, `release deploys` | Release reads exist; release/deploy writes do not | Exactly one release for two projects and one staging deploy; requested timestamps, name and URL; original resources unchanged; complete final JSON including the actual deploy ID | +| Repository inventory | `sentry repo list / --limit 200 --json`, then select active GitHub repositories | No repository listing tool | All 107 active repositories retrieved across pages; four GitHub results with exact IDs, names and URLs, sorted with a correct count; disabled repository excluded; no writes | +| Disable an issue alert | `sentry alert issues edit //701 --status disabled --frequency 60` | Alert inspection exists; alert editing does not | Workflow disabled with frequency 60; exact saved JSON; conditions, actions, owner, environment, detector associations and another alert unchanged throughout every write | + +The repository fixture caps a page at 100 records and puts matching repositories +on the second page. The release verifier inspects all persisted deployments, even +though the API lists only the latest per project/environment. The alert verifier +compares the complete saved configuration and checks unrelated settings after +every successful write, so restoring a temporarily detached monitor is still a +preservation failure. These checks reject plausible but +incomplete answers and destructive partial updates. + +The fixtures model routes used by the CLI at revision +`b625111aef9cc3a44abbed634a043fce8505366d`, checked against Sentry backend revision +`b4b356554798716ceaeab74efdc095782be9ca16`. The normal alert inspection tool also +needs project, detector and workflow project-scope reads. Those auxiliary routes +were verified against Sentry `origin/master` revision +`c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4`. The fixture records this separate +provenance; the core `get_alert_rule` handler was checked both with and without a +project constraint. Each report includes its fixture's source paths and CLI +commands. This is a comparison with CLI task semantics, not a benchmark run of +the native CLI. + +Run all three flows, then the same prompts against the catalog alone: + +```bash +pnpm --filter @sentry/mcp-server-evals eval:code-mode:flows --runs 3 +pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog --task release --task repositories --task alert --runs 1 +# A single task: +pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog-and-code --task alert --runs 3 +``` + +Default evidence files are +`.cache/code-mode/http---.json`. A filesystem regression test +checks that a multi-task suite and its controls preserve separate reports. +`--report` overrides the destination for a single task only. Task runs reset the +fixture and start fresh conversations; each task gets its own API server and +Worker. Reports record API errors even if the agent recovers and succeeds. + +The model, MCP server, discovery and Dynamic Worker execution are real; only the +Sentry API boundary uses synthetic fixtures. Discovery searches the full pinned +OpenAPI document, while only each fixture's routes are executable. These tests do +not prove production API compatibility or complete API coverage. + +### CLI flow baseline before discovery guidance (2026-10-26) + +The final frozen-fixture runs used Claude Code `2.1.263`, resolved model +`claude-opus-5`, medium effort, Wrangler `4.80.0` and workerd `1.20260401.1`. +The natural surface advertised nine core tools plus two Code Mode tools; the +control advertised the same nine core tools. The reports record the source hashes used +for this baseline. Copies are preserved under `.cache/code-mode/guidance-before/` +before modifying tool descriptions; subsequent guidance results are separate. + +| Task | Catalog + HTTP Code Mode | Catalog-only control | MCP calls per successful run | API calls | Duration | +| ---------------------- | ------------------------ | -------------------- | ---------------------------- | --------- | ----------- | +| Release and deploy | 3/3 | 0/1 completed | 4–5 | 5–7 | 18.5–24.2 s | +| Repository inventory | 3/3 | 0/1 completed | 2–4 | 2–4 | 14.3–23.7 s | +| Disable an issue alert | 3/3 | 0/1 completed | 8–9 | 10–12 | 26.3–31.7 s | + +These are nine successful executions of the final task definitions, not a claim +that every exploratory run succeeded. The release control above is the separate +valid repeat described below. None of the controls completed the requested work; +they explained the missing catalog capability and left the relevant state +unchanged. Rejected calls to nonexistent tools remain visible in the traces. + +The agents chose Code Mode without prompt instructions to do so. Repository runs +used API discovery and code execution directly. Alert runs combined the normal +`get_alert_rule` tool with API discovery and HTTP mutation. Each final alert run +performed exactly one PUT, preserved existing condition IDs and monitor +connections throughout, and returned the actual saved values. + +The final release runs include two expected 404 existence checks before creating +the release. Repository runs have no API errors. Alert runs encounter one, four +and one 404s respectively while trying legacy routes; ordinary catalog reads and +supported API operations succeed. The workflow schemas are present in OpenAPI: +initial searches for `rule` in paths miss them, while searches for `workflow` or +`detector` find them. This points to vocabulary and search strategy as a discovery +weakness, not missing workflow schemas. + +Each report retains token/cache usage, tool result sizes and full generated code. +The suite does not establish token savings against the CLI or an SDK. The runtime +and tool descriptions were unchanged during that baseline evaluation; fixture coverage and +output/preservation grading were refined. Differences between diagnostic and final +runs cannot establish that runtime reliability improved. + +### Global discovery and HTTP write guidance (2026-10-26) + +The next comparison changes only the two tool descriptions and their examples. +Discovery now demonstrates searching path, operationId, summary, description and +tags, with category inspection and broader terms when matches are unrelated. +Execution guidance distinguishes raw HTTP resources from MCP projections and +asks the agent to inspect update semantics, preserve nested identity and verify +saved state. OpenAPI, the runtime, authentication and API permissions are unchanged; +there are no endpoint-specific exceptions or SDK methods. + +Release, repository and alert runs use the same prompts, fixtures, graders, +client version, resolved model and settings as the baseline above. Only +`worker.ts` differs among their recorded execution source hashes. All nine runs +selected Code Mode without instructions to use it in the user prompt. The +unchanged catalog-only controls were not repeated. + +| Task | Success before → after | MCP calls per run before → after | API calls before → after | API errors before → after | +| ---------------------- | ---------------------- | -------------------------------- | ------------------------ | ------------------------- | +| Release and deploy | 3/3 → 3/3 | 4–5 → 4 | 5–7 → 6–7 | 0–1 → 1–2 | +| Repository inventory | 3/3 → 3/3 | 2–4 → 3–4 | 2–4 → 2–4 | 0 → 0 | +| Disable an issue alert | 3/3 → 3/3 | 8–9 → 5 | 10–12 → 4 | 1–4 → 0 | + +All three alert runs first searched for rules, then broadened the search to alert +metadata before making an API request. They found the workflow operation without +trying legacy routes. Each performed one successful PUT, preserved condition IDs, +monitor connections and unrelated settings at every write, and re-read the saved +resource. They still resent full nested configurations and fetched the resource +twice before writing; the guidance did not produce minimal writes. Both before +and after groups pass the same preservation grader, so this comparison shows +less discovery friction, not a measured improvement in preservation success. + +The release errors are existence checks for the new release or its deploy list +before creation. Repository runs retrieve both pages and return all four expected +matches without writes. No run in these three tasks returned an MCP tool error. +An additional dashboard regression passed 3/3 with 4–6 MCP calls and seven API +calls per run, without API or MCP errors. Its historical baseline used an older +runner, so treat that comparison as a regression check rather than a controlled +efficiency comparison. + +Efficiency is mixed. Across three alert runs, tool-result bytes fall from 214,374 +to 100,024 and output tokens from 6,146 to 5,164. Release tool-result bytes rise +from 27,350 to 50,251, and repository bytes from 8,892 to 30,513, as the agent +retrieves more contract information. The two descriptions grow from 2,420 to +4,452 UTF-8 bytes. Reports retain input, cache creation, cache reads and output +separately. Three samples and changing cache usage do not establish general +token or cost savings. + +Before/after evidence is preserved in `.cache/code-mode/guidance-before/` and +`.cache/code-mode/guidance-after/`. The changed examples are also obtained from +real `tools/list` responses and executed through a Dynamic Worker in deterministic +tests: metadata-only matches, declared and derived categories, and both parameter +levels. The focused Worker suite passes 15 tests; Code Mode typechecking, changed +TypeScript linting and core definition generation also pass, with no generated +core changes. + +These results use synthetic API fixtures and do not prove production compatibility +or reliable writes across the API. Generic instructions cannot supply missing +endpoint semantics or enforce preservation by themselves. Further evals can +identify concrete limitations without making a full OpenAPI rewrite a prerequisite. + +### Additional operations and failure recovery + +Four further scenarios exercise HTTP Code Mode with the normal catalog available. +The user prompts describe outcomes without naming tools, routes or Code Mode. +The recovery variants reuse their original task prompts; the permission-denied +variant adds a general JSON fallback for an incomplete inventory, without revealing +the injected failure. + +| Task | Scenario | Independent verification | +| --------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `monitor` | Change one named cron monitor's weekday schedule and timezone | Correct saved schedule; name, slug, owner, enabled state, thresholds, notifications, per-environment mute state and another monitor preserved after every write | +| `release-recovery` | The deploy is persisted, but its first POST response is a simulated gateway 502 | Exactly one persisted deploy, correct saved release and actual deploy ID; replaying the POST fails grading even if the latest-deploy API hides the duplicate | +| `repositories-retry` | The first inventory GET returns a simulated 503 | Agent recovers, follows pagination and returns every expected repository; no writes | +| `repositories-denied` | Inventory GETs always return a simulated 403 | Agent encounters the denial, reports JSON `blocked` with a reason, invents no inventory and performs no writes; no more than two denied collection attempts | + +The blocked outcome is a successful evaluation of limitation handling, not a +completed inventory. Reports include `expectedOutcome` to make that distinction +explicit. Transient failures are opt-in fixture conditions; the original scenarios +retain their previous behavior. They model infrastructure failures, not claims +that Sentry endpoints normally return these responses. + +Monitor GET/PUT semantics were checked against Sentry backend revision +`b4b356554798716ceaeab74efdc095782be9ca16`. Configuration updates merge supplied +fields, while explicitly writing the aggregate `isMuted` value propagates it to +all monitor environments. The fixture starts with different environment mute +states so blindly copying the GET body cannot pass the preservation grader. +Monitor editing has no dedicated catalog tool; the CLI equivalent uses `sentry api`. + +```bash +pnpm --filter @sentry/mcp-server-evals eval:code-mode:resilience --runs 5 +# Run one scenario: +pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog-and-code --task monitor --runs 5 +``` + +Each variant has a separate report and resets its fault state between repetitions. +Reports hash the wrapper and its shared fixture implementation. Fixture tests +reject duplicate deployments, incomplete pagination, fabricated blocked results +and collateral monitor changes before real-agent runs are evaluated. + +#### Results and the resulting HTTP guidance change + +This expansion ran 31 fresh conversations with Claude Code 2.1.263, +`claude-opus-5`, medium effort and the normal catalog plus Code Mode. Each run +used the real MCP/Worker path and synthetic Sentry API state. + +| Scenario | Initial runs | After the generic write guidance change | +| ---------------------------------------- | ------------------------------------- | --------------------------------------- | +| Monitor schedule/timezone edit | 0/5 | 5/5 | +| Deploy persisted before a lost response | 5/5 | 3/3 | +| Repository inventory with an initial 503 | 5/5 | Not rerun | +| Repository inventory denied with 403 | 5/5 blocked after a grader correction | Not rerun | +| Existing alert edit regression | Earlier 3/3 baseline | 3/3 after a parser correction | + +All five initial monitor runs copied the aggregate mute value from GET into PUT, +changing a child environment that the user had asked to preserve. Later repair +attempts could not erase that violation: grading checks every successful write. +The execution tool description now explains that GET is not a write template, +including when an unchanged aggregate value can change child resources. It asks +agents to construct the body from required fields and requested changes, include +other fields only when the update contract requires them, and avoid speculative +repair writes. This is generic HTTP guidance, with no monitor-specific fields, +routes, schema changes or SDK implementation. + +The five new monitor runs used the same prompt, fixture, grader, model and +settings; only the execution description changed. They all preserved the child +mute states, with one successful write each instead of 2–9. These runs still +sometimes sent other unchanged fields, so they do not establish that every body +was minimal or that instructions enforce safe writes across the API. + +Two grader problems are recorded separately from agent failures: + +- The original 403 report was 4/5 because its strict answer schema rejected + harmless `readOnly: true` metadata. Replaying the same recorded requests and + statuses and grading the unchanged answers yields 5/5. The grader still + rejects fabricated inventory counts or rows, writes and repeated denied reads. +- The alert regression report was 2/3 because its parser included braces from + explanatory text after a correct JSON fence. The shared parser now reads one + explicit JSON fence and rejects multiple or malformed fences. Reconstructing + every saved write boundary and final state with exact equality, then grading + the unchanged answers, yields 3/3. This regrades evidence without rerunning + generated code or model inference; preservation checks remain intact. + +Raw reports remain in `.cache/code-mode/expanded-diagnostic/` and +`.cache/code-mode/expanded-after/`. Separate `repositories-denied-regraded.json` +and `alert-regraded.json` files record the original report SHA, grader hashes, +method and both verdicts. Future reports also hash the shared JSON parser. + +These are small samples from one client/model and synthetic API behavior. No new +catalog-only control was run for this expansion. The monitor fixture omits valid +experimental environment-repair routes, so some diagnostic 404s are fixture +limits; they do not explain the first-write preservation failures or prove that +repair is impossible in Sentry. The 403 grader validates an honest blocked +outcome, not the accuracy of suggestions about specific missing OAuth scopes. +No production execution, deployment or broad API reliability claim follows from +these results. + +### Diagnostic runs and grading limits + +Earlier diagnostic runs reached release 3/3, repository inventory 3/3, and alert +2/3 under a final-state-only preservation check. Those alert traces exposed two +different problems: one run removed condition IDs from its PUT payload, causing +three existing conditions to be deleted and recreated; another temporarily +cleared `detectorIds`, then restored the monitor connection. The latter passed +final-state grading but did not preserve settings throughout the operation. +The current verifier checks each successful write and exact final JSON values. +Diagnostic evidence remains under `.cache/code-mode/diagnostic-http-*`; it is not +silently reclassified under the revised prompt or grader. + +The initial fixtures also omitted valid auxiliary reads. Normal alert reads now +include project, workflow scope and detector endpoints, and exploratory workflow +and detector collection reads are supported. Repository fixtures include normal +organization/project discovery reads. Fixture-only 404s in earlier traces are not +product failures. Legacy or invented routes remain unsupported. + +One release catalog control attempted to call the catalog-only `find_releases` +tool directly instead of invoking it through `execute_sentry_tool`; the client +rejected that run. Its evidence is retained in `http-release-claude-catalog.json`. +A separate control in `http-release-claude-catalog-repeat.json` read projects and +releases successfully, explained the missing write tools and made no changes. +Neither control completed the task, but these are different failure categories. + +### What the references contribute to these evaluations + +The rechecked [Cloudflare MCP implementation](https://github.com/cloudflare/mcp) +at revision `550e1be82b2227818e6b98fc32a313a59403ae3f` tests the real MCP/Worker +execution path with external API fixtures. Its +[Code Mode post](https://blog.cloudflare.com/code-mode-mcp/) motivates composing +requests and pagination inside one execution. This suite applies that pattern to +capabilities absent from Sentry's catalog; it does not restrict execution to +existing MCP tools. + +[Stainless's evaluation](https://www.stainless.com/blog/sdk-code-mode/) separately +considers completeness, efficiency and factual accuracy. Here, state verification +and a second-page sentinel make completeness explicit before comparing token +usage. No SDK has been added. + +[Armin Ronacher's Code Mode article](https://lucumr.pocoo.org/2026/10/6/codemode/) +examines host-side composition and structured tool results, including the friction +of nesting code-execution interfaces. This suite uses an ordinary Claude MCP +client with server-side execution; host-side Code Mode composing this server is +not part of the tested surface. + +## HTTP validation + +The Miniflare catalog version matches the version bundled with Wrangler, so the +Dynamic Worker tests use the same `workerd` binary as local Wrangler execution. +The test harness uses Miniflare's `convertV4MiniflareOptions()` adapter, also used +by Wrangler, to preserve its existing worker configuration on Miniflare 5. No +`MINIFLARE_WORKERD_PATH` override is required. + +The hosted test pool configures outbound fixtures through Undici's `MockAgent` +directly. Unregistered outbound requests remain blocked with `disableNetConnect()`. + +Use Node 24 for this checkout; its `better-sqlite3` dependency cannot compile with +Node 26. After `pnpm install --frozen-lockfile`: + +```bash +pnpm --filter '@sentry/mcp-core...' --if-present run build +pnpm --filter @sentry/mcp-cloudflare test:code-mode +pnpm --filter @sentry/mcp-cloudflare tsc:code-mode +pnpm --filter @sentry/mcp-server-evals test:code-mode +pnpm --filter @sentry/mcp-server-evals tsc:code-mode +pnpm --filter @sentry/mcp-server-evals eval:code-mode --runs 3 --model '' +# Or use an authenticated Claude CLI session: +pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --runs 3 +``` + +Tests and typechecks need no model credentials and are wired into the Toolkit +CI project matrix through each package's `sentryCi` configuration. Cloudflare's +`test:ci` and `tsc:ci` include its Code Mode checks; the eval package runs +`test:code-mode` and `tsc:code-mode`. Model-based evals remain separate and +require an installed, authenticated CLI session. The default `--client codex` +requires an explicit `--model` available to that login. It uses +`codex exec --ignore-user-config` with medium reasoning. Some built-in tools and +global `CODEX_HOME/AGENTS.md` may still be included by Codex. + +`--client claude` uses Claude's default model unless `--model` is supplied, and +records the resolved model from its event stream. It sets medium effort, disables +built-in tools, hooks, skills and session persistence, and uses a strict MCP +configuration containing only the two experiment tools. In the isolated `code-mode` baseline, both clients run in an empty temporary +directory, receive the same task and instructions, and reject runs that invoke +other tools. The Claude-only discovery surfaces keep the default system prompt +and allow the entire selected MCP inventory instead. This is a client-based benchmark, not a bare-model +benchmark; keep the same client, model and settings when comparing HTTP changes. + +Claude usage is taken once from its final aggregate result. Input, cache creation, +cache reads and output are recorded separately; cached tokens must not be +silently treated as free or counted twice by summing per-message usage. + +The runner records requested model and client version, task/developer prompt, token usage, +duration, MCP requests/results, API request outcomes and independently verified +state in the selected report file. Reports contain synthetic data; +they are local artifacts, not committed generated files. A failed verification +sets a nonzero exit code. Repetitions reset state and use fresh conversations. + +Measure success before optimizing tokens or latency. The CLI flows above cover discovery, pagination, combining responses and compact +result projection; later evaluations can expand failure recovery and API coverage. Three runs are a pilot, not statistical evidence of superiority. No +token savings or full-API coverage can be inferred from this experiment. + +### Deferred SDK evaluation + +An SDK interface is future work and is not required for the initial HTTP +deployment. If evaluated later, compare it against HTTP using the same task, +fixture, verifier, client, model settings and budgets. The existing HTTP results +do not establish an SDK advantage. diff --git a/packages/mcp-cloudflare/package.json b/packages/mcp-cloudflare/package.json index f46a10934..1ef5f634e 100644 --- a/packages/mcp-cloudflare/package.json +++ b/packages/mcp-cloudflare/package.json @@ -3,7 +3,8 @@ "sentryCi": { "roles": [ "cloudflare" - ] + ], + "typecheck": "tsc:ci" }, "version": "0.42.0", "private": true, @@ -25,9 +26,12 @@ "preview": "vite preview", "cf-typegen": "wrangler types", "test": "vitest run", - "test:ci": "vitest run --reporter=default --reporter=junit --outputFile=tests.junit.xml", + "test:code-mode": "vitest run --config=src/code-mode/vitest.config.ts", + "tsc:code-mode": "tsc --project src/code-mode/tsconfig.json", + "test:ci": "vitest run --reporter=default --reporter=junit --outputFile=tests.junit.xml && pnpm run test:code-mode", "test:watch": "vitest", - "tsc": "tsc --noEmit" + "tsc": "tsc --noEmit", + "tsc:ci": "tsc -b && pnpm run tsc:code-mode" }, "devDependencies": { "@cloudflare/vite-plugin": "^1.13.15", diff --git a/packages/mcp-cloudflare/src/client/components/fragments/stdio-setup.tsx b/packages/mcp-cloudflare/src/client/components/fragments/stdio-setup.tsx index 6a4f0e20d..3b4354b62 100644 --- a/packages/mcp-cloudflare/src/client/components/fragments/stdio-setup.tsx +++ b/packages/mcp-cloudflare/src/client/components/fragments/stdio-setup.tsx @@ -7,7 +7,7 @@ import InstallTabs, { Tab } from "./install-tabs"; const mcpServerName = import.meta.env.DEV ? "sentry-dev" : "sentry"; const orderedSkills = [...skillDefinitions] - .filter((skill) => !skill.deprecated) + .filter((skill) => !skill.deprecated && !skill.hostedOnly) .sort((a, b) => a.order - b.order); export default function StdioSetup() { diff --git a/packages/mcp-cloudflare/src/code-mode/access.test.ts b/packages/mcp-cloudflare/src/code-mode/access.test.ts new file mode 100644 index 000000000..41e61fb01 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/access.test.ts @@ -0,0 +1,183 @@ +import { describe, expect, it } from "vitest"; +import { authorizeApiRequest } from "./access"; + +const dashboardPath = + "/api/0/organizations/{organization_id_or_slug}/dashboards/"; +const monitorPath = + "/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/monitors/{monitor_id_or_slug}/"; +const releasePath = "/api/0/organizations/{organization_id_or_slug}/releases/"; +const issuePath = + "/api/0/organizations/{organization_id_or_slug}/issues/{issue_id}/"; + +// Scope excerpts from the pinned Sentry OpenAPI. In particular, dashboard +// writes accept org:read upstream, while monitor and release writes do not. +const spec = { + paths: { + [dashboardPath]: { + get: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + post: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + }, + [monitorPath]: { + put: { + security: [ + { auth_token: ["alerts:write", "project:admin", "project:write"] }, + ], + }, + }, + [releasePath]: { + post: { + security: [ + { + auth_token: [ + "org:ci", + "project:admin", + "project:releases", + "project:write", + ], + }, + ], + }, + }, + [issuePath]: { + put: { security: [{ auth_token: ["event:admin", "event:write"] }] }, + }, + "/api/0/issues/{issue_id}/": { + get: { + security: [ + { auth_token: ["event:admin", "event:read", "event:write"] }, + ], + }, + }, + }, +}; + +function request(path: string, method: string, organizationSlug?: string) { + return authorizeApiRequest(new URL(path, "https://sentry.io"), method, spec, { + organizationSlug, + }); +} + +describe("authorizeApiRequest", () => { + it("accepts a published read and HEAD fallback", () => { + expect( + request("/api/0/organizations/example/dashboards/?cursor=next", "GET"), + ).toEqual({ + pathTemplate: dashboardPath, + organizationSlug: "example", + }); + expect( + request("/api/0/organizations/example/dashboards/", "HEAD").pathTemplate, + ).toBe(dashboardPath); + }); + + it("leaves upstream scopes to Sentry for published writes", () => { + expect( + request("/api/0/organizations/example/dashboards/", "POST").pathTemplate, + ).toBe(dashboardPath); + expect( + request("/api/0/projects/example/frontend/monitors/import/", "PUT") + .pathTemplate, + ).toBe(monitorPath); + expect( + request("/api/0/organizations/example/releases/", "POST").pathTemplate, + ).toBe(releasePath); + expect( + request("/api/0/organizations/example/issues/123/", "PUT").pathTemplate, + ).toBe(issuePath); + }); + + it("enforces the organization slug and rejects unscoped object IDs", () => { + expect( + request("/api/0/organizations/ex%61mple/dashboards/", "GET", "example") + .organizationSlug, + ).toBe("example"); + for (const path of [ + "/api/0/organizations/another/dashboards/", + "/api/0/organizations/123/dashboards/", + "/api/0/issues/123/?organization=example", + ]) { + expect(() => request(path, "GET", "example")).toThrow( + "organization constraint", + ); + } + expect(request("/api/0/issues/123/", "GET")).toEqual({ + pathTemplate: "/api/0/issues/{issue_id}/", + }); + }); + + it("requires the exact published path and HTTP method", () => { + expect(() => + request("/api/0/organizations/example/private/", "GET"), + ).toThrow("published API schema"); + expect(() => + request("/api/0/organizations/example/dashboards/", "DELETE"), + ).toThrow("HTTP method"); + expect(() => + request("/api/0/organizations/example/dashboards", "GET"), + ).toThrow("published API schema"); + }); + + it("rejects encoded separators before treating a path segment as an identifier", () => { + for (const organization of [ + "example%2fanother", + "example%5canother", + "example%252fanother", + "example%zz", + ]) { + expect(() => + request(`/api/0/organizations/${organization}/dashboards/`, "GET"), + ).toThrow(/API path/); + } + // URL normalization happens before authorization: traversing to another + // organization must be checked against the final path, not the input prefix. + expect(() => + request( + "/api/0/organizations/example/%2e%2e/another/dashboards/", + "GET", + "example", + ), + ).toThrow("organization constraint"); + }); + + it("does not fall back to a generic route when a literal route denies access", () => { + const overlappingSpec = { + paths: { + "/api/0/organizations/{organization_id_or_slug}/": { + get: { security: [{ auth_token: ["org:read"] }] }, + }, + "/api/0/organizations/settings/": { + post: { security: [{ auth_token: ["org:write"] }] }, + }, + }, + }; + expect(() => + authorizeApiRequest( + new URL("https://sentry.io/api/0/organizations/settings/"), + "GET", + overlappingSpec, + {}, + ), + ).toThrow("HTTP method"); + }); + + it("requires a catalog but does not use security metadata as an MCP grant", () => { + const url = new URL( + "https://sentry.io/api/0/organizations/example/dashboards/", + ); + expect(() => authorizeApiRequest(url, "GET", {}, {})).toThrow( + "operation catalog", + ); + expect( + authorizeApiRequest( + url, + "GET", + { paths: { [dashboardPath]: { get: {} } } }, + {}, + ).pathTemplate, + ).toBe(dashboardPath); + }); +}); diff --git a/packages/mcp-cloudflare/src/code-mode/access.ts b/packages/mcp-cloudflare/src/code-mode/access.ts new file mode 100644 index 000000000..045e21c98 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/access.ts @@ -0,0 +1,111 @@ +export interface CodeModeAccess { + organizationSlug?: string | null; +} + +interface MatchedOperation { + pathTemplate: string; + pathItem: Record; + parameters: Record; + literalSegments: number; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function decodePathSegment(segment: string): string { + let decoded: string; + try { + decoded = decodeURIComponent(segment); + } catch { + throw new Error("The API path contains invalid percent encoding."); + } + if (decoded === "." || decoded === ".." || /[/\\]|%2f|%5c/i.test(decoded)) { + throw new Error( + "The API path cannot contain encoded path separators or traversal segments.", + ); + } + return decoded; +} + +function findOperation( + url: URL, + spec: Record, +): MatchedOperation { + if (!isRecord(spec.paths)) { + throw new Error("The API schema does not contain an operation catalog."); + } + const segments = url.pathname.split("/").map(decodePathSegment); + const matches: MatchedOperation[] = []; + for (const [pathTemplate, pathItem] of Object.entries(spec.paths)) { + if (!pathTemplate.startsWith("/") || !isRecord(pathItem)) continue; + const templateSegments = pathTemplate.split("/"); + if (templateSegments.length !== segments.length) continue; + const parameters: Record = {}; + let literalSegments = 0; + const matchesPath = templateSegments.every((segment, index) => { + const parameter = /^\{([^{}]+)\}$/.exec(segment); + if (parameter) { + if (!segments[index] || Object.hasOwn(parameters, parameter[1])) { + return false; + } + parameters[parameter[1]] = segments[index]; + return true; + } + literalSegments++; + return segment === segments[index]; + }); + if (matchesPath) { + matches.push({ pathTemplate, pathItem, parameters, literalSegments }); + } + } + matches.sort((a, b) => b.literalSegments - a.literalSegments); + const match = matches[0]; + if (!match) { + throw new Error( + "This API path is not available in the published API schema.", + ); + } + if (matches[1]?.literalSegments === match.literalSegments) { + throw new Error( + "This API path matches multiple operations in the API schema.", + ); + } + return match; +} + +/** Enforce the published API surface and connection constraint before calling Sentry. */ +export function authorizeApiRequest( + url: URL, + method: string, + spec: object, + access: CodeModeAccess, +): { pathTemplate: string; organizationSlug?: string } { + if (!isRecord(spec)) { + throw new Error("The API schema does not contain an operation catalog."); + } + const { pathTemplate, pathItem, parameters } = findOperation(url, spec); + const normalizedMethod = method.toLowerCase(); + const operationMethod = + normalizedMethod === "head" && !Object.hasOwn(pathItem, "head") + ? "get" + : normalizedMethod; + const operation = Object.hasOwn(pathItem, operationMethod) + ? pathItem[operationMethod] + : undefined; + if (!isRecord(operation)) { + throw new Error( + "This HTTP method is not available for the published API path.", + ); + } + + const organizationSlug = + parameters.organization_id_or_slug ?? parameters.organization_slug; + if (access.organizationSlug && organizationSlug !== access.organizationSlug) { + throw new Error( + "This API operation is outside the active organization constraint. Use an organization-scoped API path with the authorized organization slug.", + ); + } + + return { pathTemplate, ...(organizationSlug ? { organizationSlug } : {}) }; +} diff --git a/packages/mcp-cloudflare/src/code-mode/broker.ts b/packages/mcp-cloudflare/src/code-mode/broker.ts new file mode 100644 index 000000000..086510af1 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/broker.ts @@ -0,0 +1,139 @@ +import { RpcTarget } from "cloudflare:workers"; +import { configuredUrl, readBoundedBody } from "./http"; +import type { ApiAuthorization } from "./production"; + +export { configuredUrl, readBoundedBody } from "./http"; + +export interface CodeModeEnv { + LOADER: WorkerLoader; + /** Fixed origin and API prefix, e.g. https://sentry.io/api/0. */ + API_BASE_URL: string; + /** Trusted, pre-dereferenced OpenAPI JSON source; never supplied by the model. */ + OPENAPI_URL: string; + CODE_MODE_ALLOW_WRITES?: string; +} + +export interface BrokerOptions { + accessToken: string; + deadline: number; + apiBaseUrl: string; + allowWrites: boolean; + authorization?: ApiAuthorization; +} + +/** The only HTTP capability passed to the sandbox; credentials remain private. */ +export class ApiBroker extends RpcTarget { + #options: BrokerOptions; + + constructor(options: BrokerOptions) { + super(); + this.#options = options; + } + async fetch(request: Request): Promise { + const base = configuredUrl(this.#options.apiBaseUrl); + const requested = new URL(request.url); + const prefix = base.pathname.replace(/\/$/, ""); + if ( + requested.origin !== base.origin || + requested.username || + requested.password || + (requested.pathname !== prefix && + !requested.pathname.startsWith(`${prefix}/`)) || + /%2f|%5c/i.test(requested.pathname) + ) { + return Response.json( + { + detail: + "Only the configured Sentry API origin and path prefix are allowed.", + }, + { status: 403 }, + ); + } + if ( + !this.#options.allowWrites && + !["GET", "HEAD", "OPTIONS"].includes(request.method) + ) { + return Response.json( + { detail: "Writes are disabled for this MCP connection." }, + { status: 403 }, + ); + } + let destination = requested.toString(); + let attribution: Record = {}; + if (this.#options.authorization) { + const authorized = await this.#options.authorization.authorize( + request.url, + request.method, + ); + if ("error" in authorized) { + return Response.json({ detail: authorized.error }, { status: 403 }); + } + destination = authorized.url; + attribution = authorized.headers; + } + const remaining = this.#options.deadline - Date.now(); + if (remaining <= 0) { + return Response.json( + { detail: "Execution deadline exceeded." }, + { status: 408 }, + ); + } + // Only API-relevant input headers cross the boundary. Auth is always replaced. + const headers = new Headers(); + for (const name of [ + "accept", + "content-type", + "if-match", + "if-none-match", + ]) { + const value = request.headers.get(name); + if (value) headers.set(name, value); + } + for (const [name, value] of Object.entries(attribution)) + headers.set(name, value); + headers.set("Authorization", `Bearer ${this.#options.accessToken}`); + const response = await fetch( + new Request(new Request(destination, request), { + headers, + redirect: "manual", + signal: AbortSignal.timeout(remaining), + }), + ); + if (response.status === 401) { + await this.#options.authorization?.onUnauthorized(); + } + // Do not allow a redirect to carry our bearer token to another location. + if (response.status >= 300 && response.status < 400) { + await response.body?.cancel(); + return Response.json( + { + detail: + "API redirects are not followed. Use the canonical endpoint path.", + }, + { status: 502 }, + ); + } + const responseHeaders = new Headers(); + for (const name of [ + "content-type", + "link", + "retry-after", + "www-authenticate", + "x-sentry-rate-limits", + "etag", + ]) { + const value = response.headers.get(name); + if (value) responseHeaders.set(name, value); + } + const body = await readBoundedBody(response.body, 2_000_000); + return new Response( + [204, 205, 304].includes(response.status) || request.method === "HEAD" + ? null + : body, + { + status: response.status, + headers: responseHeaders, + }, + ); + } +} diff --git a/packages/mcp-cloudflare/src/code-mode/definitions.ts b/packages/mcp-cloudflare/src/code-mode/definitions.ts new file mode 100644 index 000000000..9f3426624 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/definitions.ts @@ -0,0 +1,99 @@ +import { z } from "zod"; + +export const CODE_MODE_SKILLS = ["api-read", "api-write"] as const; + +export const codeModeResultSchema = z + .object({ + result: z.unknown().optional(), + error: z.string().optional(), + httpError: z + .object({ + status: z.number(), + method: z.string(), + path: z.string(), + headers: z.record(z.string(), z.string()), + }) + .optional(), + stats: z.object({ apiCalls: z.number(), writesAttempted: z.number() }), + }) + .refine( + (value) => "result" in value !== "error" in value, + "Return either a result or an error.", + ); + +/** Shared by hosted registration, public metadata and plugin generation. */ +export function getCodeModeToolDefinitions({ + allowWrites, + authorizationRequired, +}: { + allowWrites: boolean; + authorizationRequired: boolean; +}) { + return { + search_sentry_api: { + title: "Search Sentry API", + description: `Discover Sentry HTTP API operations by executing a JavaScript async arrow function against the global spec. The configured OpenAPI document has schemas inline; no $ref resolution is needed. No network or API calls are available. +Search using the user's product terms across operation metadata: paths may use different internal names. Each spec.paths[path] has optional path-level parameters and HTTP-method operations. Operations have optional operationId, summary, description, tags (string[]), parameters, requestBody and responses. spec.tags, when present, describes categories with name, description and optional x-sidebar-name. +First return a short list of matching methods, paths and summaries. If results are empty or unrelated, inspect categories and broaden the terms before trying API routes. Then inspect the selected operation's descriptions, parameters, requestBody and relevant response fields. Path-level parameters also apply; operation parameters override them by name and in. Do not return whole paths, the full spec or sliced JSON strings. Return only needed fields; the result limit is 50,000 bytes. +Discovery example: async () => { + const term = "release"; + const results = []; + for (const [path, item] of Object.entries(spec.paths)) { + for (const [method, op] of Object.entries(item)) { + if (!["get", "post", "put", "patch", "delete", "head", "options", "trace"].includes(method)) continue; + const text = [path, op.operationId, op.summary, op.description, ...(op.tags ?? [])].join(" ").toLowerCase(); + if (text.includes(term.toLowerCase())) results.push({method, path, operationId: op.operationId, summary: op.summary, tags: op.tags}); + } + } + return results; +} +Categories example: async () => spec.tags?.length ? spec.tags : [...new Set(Object.values(spec.paths).flatMap(item => Object.entries(item).filter(([method]) => ["get", "post", "put", "patch", "delete", "head", "options", "trace"].includes(method)).flatMap(([, op]) => op.tags ?? [])))].map(name => ({name})) +Schema example: async () => { const path = "/api/0/organizations/{organization_id_or_slug}/releases/"; const item = spec.paths[path]; const op = item.post; return {description: op.description, pathParameters: item.parameters, operationParameters: op.parameters, requestBody: op.requestBody}; }`, + inputSchema: z.object({ + code: z + .string() + .min(1) + .max(30_000) + .describe( + "JavaScript async arrow function, e.g. async () => Object.keys(spec.paths)", + ), + }), + outputSchema: codeModeResultSchema, + annotations: { + readOnlyHint: true, + destructiveHint: false, + openWorldHint: false, + }, + }, + execute_sentry_code: { + title: "Execute Sentry API code", + description: `Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers["www-authenticate"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP.${authorizationRequired ? " Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly." : ""} +Before writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available. +Combine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents. +Limits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are ${allowWrites ? "enabled" : "disabled"}. No automatic retries. Execution is not atomic; inspect state before retrying a failed write. +Example: async () => { const response = await sentry.request({path: "/organizations/"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }`, + inputSchema: z.object({ + code: z + .string() + .min(1) + .max(30_000) + .describe("JavaScript async arrow function using sentry.request"), + }), + outputSchema: codeModeResultSchema, + annotations: { + readOnlyHint: !allowWrites, + destructiveHint: allowWrites, + openWorldHint: true, + }, + }, + }; +} + +export const CODE_MODE_TOOL_NAMES: readonly string[] = Object.freeze( + Object.keys( + getCodeModeToolDefinitions({ + allowWrites: false, + authorizationRequired: true, + }), + ), +); diff --git a/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts b/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts new file mode 100644 index 000000000..c2a28b84b --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts @@ -0,0 +1,224 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { promisify } from "node:util"; +import { convertV4MiniflareOptions, Miniflare } from "miniflare"; +import { afterAll, beforeAll, beforeEach, expect, it } from "vitest"; +import { TOP_LEVEL_TOOL_NAMES } from "../../../mcp-core/src/tools/surfaces"; +import { createDashboardFixture } from "../../../mcp-server-evals/src/code-mode/dashboard-fixture"; + +let outputDirectory: string; +let worker: Miniflare; +let fixture: Awaited>; + +async function rpc( + method: string, + params: Record = {}, + route = "/mcp", +) { + const response = await worker.dispatchFetch(`http://localhost${route}`, { + method: "POST", + headers: { + authorization: "Bearer code-mode-eval-token", + "content-type": "application/json", + accept: "application/json, text/event-stream", + "mcp-protocol-version": "2026-07-28", + "mcp-method": method, + ...(typeof params.name === "string" ? { "mcp-name": params.name } : {}), + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params: { + ...params, + _meta: { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": { + name: "discovery-tests", + version: "1.0.0", + }, + "io.modelcontextprotocol/clientCapabilities": {}, + }, + }, + }), + }); + const text = await response.text(); + if (!response.ok) throw new Error(`MCP HTTP ${response.status}: ${text}`); + const data = text.startsWith("event:") + ? text + .split("\n") + .find((line) => line.startsWith("data: ")) + ?.slice(6) + : text; + const envelope = JSON.parse(data ?? "{}"); + if (envelope.error) throw new Error(JSON.stringify(envelope.error)); + return envelope.result; +} + +beforeAll(async () => { + outputDirectory = await mkdtemp(join(tmpdir(), "sentry-discovery-test-")); + await promisify(execFile)( + process.execPath, + [ + resolve("node_modules/wrangler/bin/wrangler.js"), + "deploy", + "src/code-mode/discovery-worker.ts", + "--dry-run", + "--config", + "wrangler.code-mode.jsonc", + "--outdir", + outputDirectory, + ], + { + timeout: 60_000, + env: { ...process.env, WRANGLER_SEND_METRICS: "false" }, + }, + ); + fixture = await createDashboardFixture(); + worker = new Miniflare( + convertV4MiniflareOptions({ + modules: true, + modulesRoot: outputDirectory, + scriptPath: join(outputDirectory, "discovery-worker.js"), + compatibilityDate: "2026-01-12", + compatibilityFlags: ["nodejs_compat"], + workerLoaders: { LOADER: {} }, + bindings: { + API_BASE_URL: `${fixture.baseUrl}/api/0`, + OPENAPI_URL: fixture.openapiUrl, + CODE_MODE_ALLOW_WRITES: "true", + }, + }), + ); +}); + +beforeEach(() => fixture.reset()); + +afterAll(async () => { + await worker?.dispose(); + await fixture?.close(); + if (outputDirectory) + await rm(outputDirectory, { recursive: true, force: true }); +}); + +it("offers the real catalog and Code Mode together without replacing normal tools", async () => { + const combined = await rpc("tools/list"); + const control = await rpc("tools/list", {}, "/catalog/mcp"); + expect( + combined.tools.map((tool: { name: string }) => tool.name).sort(), + ).toEqual( + [ + ...TOP_LEVEL_TOOL_NAMES, + "search_sentry_api", + "execute_sentry_code", + ].sort(), + ); + expect( + control.tools.map((tool: { name: string }) => tool.name).sort(), + ).toEqual([...TOP_LEVEL_TOOL_NAMES].sort()); + await expect( + rpc( + "tools/call", + { name: "execute_sentry_code", arguments: { code: "async () => 1" } }, + "/catalog/mcp", + ), + ).rejects.toThrow(); + + const catalog = await rpc("tools/call", { + name: "search_sentry_tools", + arguments: { query: "dashboard" }, + }); + expect(catalog.isError).not.toBe(true); + expect( + catalog.structuredContent.results.map( + (tool: { name: string }) => tool.name, + ), + ).toEqual( + expect.arrayContaining(["get_dashboard_details", "find_dashboards"]), + ); + expect( + catalog.structuredContent.results.every( + (tool: { annotations: { readOnlyHint: boolean } }) => + tool.annotations.readOnlyHint, + ), + ).toBe(true); + + const details = await rpc("tools/call", { + name: "execute_sentry_tool", + arguments: { + name: "get_dashboard_details", + arguments: { + organizationSlug: fixture.organizationSlug, + dashboardIdOrTitle: "101", + }, + }, + }); + expect(details.isError).not.toBe(true); + expect(details.content[0].text).toContain("Errors Overview"); + expect(fixture.snapshot().requests).toEqual([ + { + method: "GET", + path: `/api/0/organizations/${fixture.organizationSlug}/dashboards/101/`, + status: 200, + }, + ]); +}); + +it("executes the existing sandbox and broker alongside the normal catalog", async () => { + const discovery = await rpc("tools/call", { + name: "search_sentry_api", + arguments: { code: "async () => Object.keys(spec.paths)" }, + }); + expect(discovery.isError).not.toBe(true); + expect(discovery.structuredContent.result.length).toBeGreaterThan(0); + const result = await rpc("tools/call", { + name: "execute_sentry_code", + arguments: { + code: `async () => (await sentry.request({path: '/organizations/${fixture.organizationSlug}/dashboards/101/'})).body.id`, + }, + }); + expect(result.isError).not.toBe(true); + expect(result.structuredContent).toEqual({ + result: "101", + stats: { apiCalls: 1, writesAttempted: 0 }, + }); +}); + +it("requires authentication before exposing either eval surface", async () => { + for (const route of ["/mcp", "/catalog/mcp"]) { + const response = await worker.dispatchFetch(`http://localhost${route}`, { + method: "POST", + }); + expect(response.status).toBe(401); + } +}); + +it("refuses non-fixture API configuration", async () => { + const misconfigured = new Miniflare( + convertV4MiniflareOptions({ + modules: true, + modulesRoot: outputDirectory, + scriptPath: join(outputDirectory, "discovery-worker.js"), + compatibilityDate: "2026-01-12", + compatibilityFlags: ["nodejs_compat"], + workerLoaders: { LOADER: {} }, + bindings: { + API_BASE_URL: "https://sentry.io/api/0", + OPENAPI_URL: fixture.openapiUrl, + }, + }), + ); + try { + const response = await misconfigured.dispatchFetch("http://localhost/mcp", { + method: "POST", + headers: { authorization: "Bearer code-mode-eval-token" }, + }); + expect(response.status).toBe(500); + expect(await response.text()).toContain("loopback HTTP fixtures"); + expect(fixture.snapshot().requests).toEqual([]); + } finally { + await misconfigured.dispose(); + } +}); diff --git a/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts b/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts new file mode 100644 index 000000000..1791eea99 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts @@ -0,0 +1,53 @@ +import { createMcpHandler } from "agents/mcp/server"; +import { buildServer } from "../../../mcp-core/src/server"; +import { ALL_SKILLS } from "../../../mcp-core/src/skills"; +import { type CodeModeEnv, configuredUrl } from "./broker"; +import { registerCodeModeTools } from "./worker"; + +/** Eval-only composition of the real catalog and HTTP Code Mode on one MCP. */ +export default { + async fetch( + request: Request, + env: CodeModeEnv, + ctx: ExecutionContext, + ): Promise { + const route = new URL(request.url).pathname; + if (route !== "/mcp" && route !== "/catalog/mcp") { + return new Response("Not found", { status: 404 }); + } + const token = request.headers + .get("authorization") + ?.match(/^Bearer\s+(\S+)$/i)?.[1]; + if (!token) return new Response("Bearer token required", { status: 401 }); + + const api = configuredUrl(env.API_BASE_URL); + if ( + api.protocol !== "http:" || + !["localhost", "127.0.0.1", "[::1]"].includes(api.hostname) || + api.pathname.replace(/\/$/, "") !== "/api/0" + ) { + return new Response("Discovery evals require loopback HTTP fixtures", { + status: 500, + }); + } + + return await createMcpHandler( + () => { + const server = buildServer({ + sdkVersion: "v2", + context: { + accessToken: token, + sentryHost: api.host, + sentryProtocol: "http", + grantedSkills: new Set(ALL_SKILLS), + constraints: {}, + transport: "http", + }, + }); + if (route === "/mcp") registerCodeModeTools(server, env, token); + return server; + }, + { route }, + )(request, env, ctx); + }, +} satisfies ExportedHandler; diff --git a/packages/mcp-cloudflare/src/code-mode/http.ts b/packages/mcp-cloudflare/src/code-mode/http.ts new file mode 100644 index 000000000..6a114146e --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/http.ts @@ -0,0 +1,50 @@ +export function configuredUrl(value: string): URL { + const url = new URL(value); + const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname); + if ( + url.username || + url.password || + url.search || + url.hash || + (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) + ) { + throw new Error( + "Configured URLs must use HTTPS (or loopback HTTP for local tests), without credentials, query, or fragment.", + ); + } + return url; +} + +/** Read before forwarding so streaming responses cannot exceed the byte budget. */ +export async function readBoundedBody( + body: ReadableStream | null, + maximumBytes: number, +): Promise { + if (!body) return new Uint8Array(); + const reader = body.getReader(); + const chunks: Uint8Array[] = []; + let size = 0; + try { + for (;;) { + const next = await reader.read(); + if (next.done) break; + size += next.value.byteLength; + if (size > maximumBytes) { + await reader.cancel(); + throw new Error( + `Response exceeds ${maximumBytes} bytes. Narrow the query or use pagination.`, + ); + } + chunks.push(next.value); + } + } finally { + reader.releaseLock(); + } + const result = new Uint8Array(size); + let offset = 0; + for (const chunk of chunks) { + result.set(chunk, offset); + offset += chunk.byteLength; + } + return result; +} diff --git a/packages/mcp-cloudflare/src/code-mode/openapi.ts b/packages/mcp-cloudflare/src/code-mode/openapi.ts new file mode 100644 index 000000000..8c464d9ee --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/openapi.ts @@ -0,0 +1,33 @@ +import { configuredUrl, readBoundedBody } from "./http"; + +export const SENTRY_OPENAPI_URL = + "https://raw.githubusercontent.com/getsentry/sentry-api-schema/803d6de98c9156c15e9ddc1bf9accbac6e24cebc/openapi-derefed.json"; + +let cachedSpec: { url: string; promise: Promise } | undefined; + +/** Share the trusted schema between discovery and authorization; retry failed loads. */ +export async function getSpec(url: string): Promise { + configuredUrl(url); + if (cachedSpec?.url === url) return await cachedSpec.promise; + const promise = (async () => { + const response = await fetch(url, { + redirect: "manual", + signal: AbortSignal.timeout(15_000), + }); + if (!response.ok) + throw new Error(`OpenAPI could not be loaded (HTTP ${response.status}).`); + const body = await readBoundedBody(response.body, 30_000_000); + const parsed: unknown = JSON.parse(new TextDecoder().decode(body)); + if (!parsed || typeof parsed !== "object" || !("paths" in parsed)) { + throw new Error("The configured OpenAPI document has no paths object."); + } + return parsed; + })(); + cachedSpec = { url, promise }; + try { + return await promise; + } catch (error) { + cachedSpec = undefined; + throw error; + } +} diff --git a/packages/mcp-cloudflare/src/code-mode/production.ts b/packages/mcp-cloudflare/src/code-mode/production.ts new file mode 100644 index 000000000..a09d0ed98 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/production.ts @@ -0,0 +1,102 @@ +import type { McpServer } from "@modelcontextprotocol/server"; +import { resolveRegionUrlForOrganization } from "@sentry/mcp-core/internal/tool-helpers/resolve-region-url"; +import { validateRegionUrl } from "@sentry/mcp-core/internal/tool-helpers/validate-region-url"; +import type { Skill } from "@sentry/mcp-core/skills"; +import type { ServerContext } from "@sentry/mcp-core/types"; +import { USER_AGENT } from "@sentry/mcp-core/version"; +import { authorizeApiRequest } from "./access"; +import { CODE_MODE_SKILLS } from "./definitions"; +import { getSpec, SENTRY_OPENAPI_URL } from "./openapi"; +import { registerCodeModeTools } from "./worker"; + +/** Trusted per-request policy, available to the outbound broker, never to user code. */ +export class ApiAuthorization { + #context: ServerContext; + #unauthorized = false; + + constructor(context: ServerContext) { + this.#context = context; + } + + async authorize( + url: string, + method: string, + ): Promise< + { url: string; headers: Record } | { error: string } + > { + const spec = await getSpec(SENTRY_OPENAPI_URL); + const target = new URL(url); + let organizationSlug: string | undefined; + try { + ({ organizationSlug } = authorizeApiRequest(target, method, spec, { + organizationSlug: this.#context.constraints.organizationSlug, + })); + } catch (error) { + return { + error: error instanceof Error ? error.message : "API access denied.", + }; + } + if (organizationSlug) { + const regionUrl = await resolveRegionUrlForOrganization({ + context: this.#context, + organizationSlug, + }); + if (regionUrl) { + target.host = validateRegionUrl( + regionUrl, + this.#context.sentryHost ?? "sentry.io", + ); + target.protocol = "https:"; + } + } + const headers: Record = { "User-Agent": USER_AGENT }; + for (const [field, header] of [ + ["clientId", "X-Sentry-MCP-Client-Id"], + ["clientName", "X-Sentry-MCP-Client-Name"], + ["clientFamily", "X-Sentry-MCP-Client-Family"], + ["utmSource", "X-Sentry-MCP-Utm-Source"], + ] as const) { + const value = this.#context[field]; + if (value) headers[header] = value; + } + return { url: target.toString(), headers }; + } + + async onUnauthorized(): Promise { + if (this.#unauthorized) return; + this.#unauthorized = true; + try { + await this.#context.onUpstreamUnauthorized?.(); + } catch { + // Match the normal tool boundary: revocation failures do not hide the 401. + } + } +} + +/** Register on the existing authenticated, instrumented MCP server. */ +export function registerProductionCodeModeTools( + server: McpServer, + loader: WorkerLoader, + context: ServerContext, +): void { + const skills = new Set( + [...(context.grantedSkills ?? [])].filter((skill) => + CODE_MODE_SKILLS.some((allowedSkill) => skill === allowedSkill), + ), + ); + // A project route can mutate shared resources (e.g. release files). OpenAPI + // does not prove project isolation, so retain the existing scoped tools only. + if (!skills.size || context.constraints.projectSlug) return; + const allowWrites = skills.has("api-write"); + registerCodeModeTools( + server, + { + LOADER: loader, + API_BASE_URL: `${context.sentryProtocol ?? "https"}://${context.sentryHost ?? "sentry.io"}/api/0`, + OPENAPI_URL: SENTRY_OPENAPI_URL, + CODE_MODE_ALLOW_WRITES: String(allowWrites), + }, + context.accessToken, + new ApiAuthorization(context), + ); +} diff --git a/packages/mcp-cloudflare/src/code-mode/sandbox.ts b/packages/mcp-cloudflare/src/code-mode/sandbox.ts new file mode 100644 index 000000000..7632ed515 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/sandbox.ts @@ -0,0 +1,81 @@ +/** Trusted harness; model code is loaded after the private API broker is set. */ +export function sandboxSource(apiBase?: string): string { + return ` +import { WorkerEntrypoint } from "cloudflare:workers"; +${apiBase ? "" : 'import spec from "spec.json";'} +${apiBase ? "" : 'Object.defineProperty(globalThis, "spec", { value: spec });'} +const apiBase = ${JSON.stringify(apiBase ?? null)}; +let deadline = 0; +let apiCalls = 0; +let writesAttempted = 0; +let apiBroker; +const sentry = { + async request({ method = "GET", path, query, body }) { + if (!apiBase || !apiBroker) throw new Error("API access is unavailable during search."); + if (typeof path !== "string" || !path.startsWith("/") || path.startsWith("//")) { + throw new Error("path must start with / and contain an API path, not a URL."); + } + const base = new URL(apiBase); + const prefix = base.pathname.replace(/\\/$/, ""); + const url = new URL(path === prefix || path.startsWith(prefix + "/") + ? path : prefix + path, base.origin); + if (query) for (const [key, value] of Object.entries(query)) { + if (value === undefined || value === null) continue; + for (const item of Array.isArray(value) ? value : [value]) url.searchParams.append(key, String(item)); + } + const request = new Request(url, { + method, + headers: body === undefined ? {} : { "Content-Type": "application/json" }, + body: body === undefined ? undefined : JSON.stringify(body), + redirect: "manual", + }); + if (++apiCalls > 20) throw new Error("API call budget exhausted (20 calls per execution)."); + if (Date.now() >= deadline) throw new Error("Execution deadline exceeded."); + if (!["GET", "HEAD", "OPTIONS"].includes(request.method)) writesAttempted++; + const response = await apiBroker.fetch(request); + const text = await response.text(); + let data = text; + if (text && (response.headers.get("content-type") || "").toLowerCase().includes("json")) { + try { data = JSON.parse(text); } catch { throw new Error("API response was not valid JSON."); } + } else if (!text) data = null; + if (!response.ok) { + const error = new Error("Sentry API error: " + method.toUpperCase() + " " + url.pathname + + " returned HTTP " + response.status + ": " + JSON.stringify(data).slice(0, 2_000)); + error.status = response.status; + error.method = method.toUpperCase(); + error.path = url.pathname; + error.headers = Object.fromEntries(response.headers); + error.data = data; + throw error; + } + return { status: response.status, headers: Object.fromEntries(response.headers), body: data }; + }, +}; +Object.defineProperty(globalThis, "sentry", { value: sentry }); +export default class extends WorkerEntrypoint { + async run(broker) { + apiBroker = broker; + deadline = Date.now() + 30_000; + try { + const { default: run } = await import("./user.js"); + const result = await run(); + const serialized = JSON.stringify({ result: result ?? null, stats: { apiCalls, writesAttempted } }); + if (new TextEncoder().encode(serialized).byteLength > 50_000) { + throw new Error("Result exceeds 50,000 bytes. Return selected fields or a smaller page."); + } + return new Response(serialized, { headers: { "content-type": "application/json" } }); + } catch (error) { + const hasHttpDetails = Number.isFinite(error?.status) && + typeof error.method === "string" && typeof error.path === "string" && + error.headers !== null && typeof error.headers === "object" && + !Array.isArray(error.headers) && Object.values(error.headers).every(value => typeof value === "string"); + return Response.json({ error: String(error?.message || error).slice(0, 4_000), + ...(hasHttpDetails ? { httpError: { + status: error.status, method: error.method, path: error.path, headers: error.headers, + } } : {}), + stats: { apiCalls, writesAttempted } }, { status: 400 }); + } + } +} +`; +} diff --git a/packages/mcp-cloudflare/src/code-mode/tsconfig.json b/packages/mcp-cloudflare/src/code-mode/tsconfig.json new file mode 100644 index 000000000..65fa72c7b --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.server.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "../../../..", + "tsBuildInfoFile": "../../node_modules/.tmp/tsconfig.code-mode.tsbuildinfo" + }, + "include": ["./worker.ts", "./broker.ts", "./sandbox.ts", "./discovery-worker.ts"] +} diff --git a/packages/mcp-cloudflare/src/code-mode/vitest.config.ts b/packages/mcp-cloudflare/src/code-mode/vitest.config.ts new file mode 100644 index 000000000..7c30049d1 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; + +// This experiment has its own Worker entrypoint and uses real Dynamic Workers. +export default defineConfig({ + test: { + environment: "node", + include: ["src/code-mode/*.test.ts"], + fileParallelism: false, + testTimeout: 30_000, + hookTimeout: 90_000, + }, +}); diff --git a/packages/mcp-cloudflare/src/code-mode/worker.test.ts b/packages/mcp-cloudflare/src/code-mode/worker.test.ts new file mode 100644 index 000000000..941a63e7c --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/worker.test.ts @@ -0,0 +1,686 @@ +import { execFile } from "node:child_process"; +import { mkdtemp, rm } from "node:fs/promises"; +import { createServer, type Server } from "node:http"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { promisify } from "node:util"; +import { convertV4MiniflareOptions, Miniflare } from "miniflare"; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +interface ToolResult { + content: Array<{ type: string; text?: string }>; + structuredContent?: { + result?: unknown; + error?: string; + httpError?: { + status: number; + method: string; + path: string; + headers: Record; + }; + stats: { apiCalls: number; writesAttempted: number }; + }; + isError?: boolean; +} + +const requests: Array<{ method: string; path: string; auth?: string }> = []; +const specRequests: string[] = []; +const workflowPath = + "/api/0/organizations/{organization_id_or_slug}/workflows/{workflow_id}/"; +const releasePath = "/api/0/organizations/{organization_id_or_slug}/releases/"; +const metadataSpec = { + openapi: "3.0.0", + paths: { + [workflowPath]: { + put: { summary: "Update an Alert by ID", tags: ["workflows"] }, + }, + "/api/0/detectors/": { + get: { operationId: "listAlertDetectors", tags: ["workflows"] }, + }, + "/api/0/notifications/": { + get: { description: "Retrieve alert notification history." }, + }, + "/api/0/rules/": { get: { tags: ["Alerts"] } }, + "/api/0/alerts/": { + summary: "Alert operations", + parameters: [{ name: "cursor", in: "query", schema: { type: "string" } }], + get: {}, + }, + [releasePath]: { + parameters: [ + { + name: "organization_id_or_slug", + in: "path", + required: true, + schema: { type: "string" }, + }, + { name: "cursor", in: "query", schema: { type: "string" } }, + ], + post: { + description: "Create a release for the organization.", + parameters: [ + { name: "cursor", in: "query", schema: { type: "integer" } }, + ], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["version", "projects"], + properties: { + version: { type: "string" }, + projects: { type: "array", items: { type: "string" } }, + }, + }, + }, + }, + }, + }, + }, + }, +}; +let fixture: Server; +let origin: string; +let outputDirectory: string; +let worker: Miniflare; + +async function createWorker(allowWrites = true, specPath = "/spec.json") { + return new Miniflare( + convertV4MiniflareOptions({ + modules: true, + modulesRoot: outputDirectory, + scriptPath: join(outputDirectory, "worker.js"), + compatibilityDate: "2026-01-12", + compatibilityFlags: ["nodejs_compat"], + workerLoaders: { LOADER: {} }, + bindings: { + API_BASE_URL: `${origin}/api/0`, + OPENAPI_URL: `${origin}${specPath}`, + CODE_MODE_ALLOW_WRITES: String(allowWrites), + }, + }), + ); +} + +async function rpc( + method: string, + params: Record, + target = worker, +): Promise { + const response = await target.dispatchFetch("http://localhost/mcp", { + method: "POST", + headers: { + authorization: "Bearer fixture-only-token", + "content-type": "application/json", + accept: "application/json, text/event-stream", + "mcp-protocol-version": "2026-07-28", + "mcp-method": method, + ...(typeof params.name === "string" ? { "mcp-name": params.name } : {}), + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params: { + ...params, + _meta: { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": { + name: "code-mode-tests", + version: "1.0.0", + }, + "io.modelcontextprotocol/clientCapabilities": {}, + }, + }, + }), + }); + const text = await response.text(); + if (!response.ok) throw new Error(`MCP HTTP ${response.status}: ${text}`); + const data = text.startsWith("event:") + ? text + .split("\n") + .find((line) => line.startsWith("data: ")) + ?.slice(6) + : text; + const envelope = JSON.parse(data ?? "{}"); + if (envelope.error) throw new Error(JSON.stringify(envelope.error)); + return envelope.result; +} + +async function callTool( + name: "search_sentry_api" | "execute_sentry_code", + code: string, + target = worker, +): Promise { + return await rpc("tools/call", { name, arguments: { code } }, target); +} + +async function publishedExample( + name: "Discovery" | "Categories" | "Schema", + target: Miniflare, +): Promise { + const { tools } = await rpc<{ + tools: Array<{ name: string; description: string }>; + }>("tools/list", {}, target); + const description = tools.find( + (tool) => tool.name === "search_sentry_api", + )?.description; + const example = description + ?.split(`${name} example: `)[1] + ?.split(/\n(?:Discovery|Categories|Schema) example: /)[0] + ?.trim(); + if (!example) throw new Error(`${name} example missing from tools/list`); + return example; +} + +beforeAll(async () => { + outputDirectory = await mkdtemp(join(tmpdir(), "sentry-code-mode-test-")); + await promisify(execFile)( + process.execPath, + [ + resolve("node_modules/wrangler/bin/wrangler.js"), + "deploy", + "--dry-run", + "--config", + "wrangler.code-mode.jsonc", + "--outdir", + outputDirectory, + ], + { + timeout: 60_000, + env: { ...process.env, WRANGLER_SEND_METRICS: "false" }, + }, + ); + fixture = createServer((request, response) => { + if ( + request.url === "/metadata-spec.json" || + request.url === "/metadata-spec-categories.json" + ) { + specRequests.push(request.url); + response.setHeader("Content-Type", "application/json"); + response.end( + JSON.stringify({ + ...metadataSpec, + ...(request.url === "/metadata-spec-categories.json" + ? { + tags: [ + { + name: "workflows", + description: "Manage alert workflows.", + "x-sidebar-name": "Alerts", + }, + ], + } + : {}), + }), + ); + return; + } + if (request.url === "/spec.json") { + specRequests.push(request.url); + response.setHeader("Content-Type", "application/json"); + response.end( + JSON.stringify({ + openapi: "3.0.0", + paths: { "/api/0/items/": { get: { summary: "List items" } } }, + }), + ); + return; + } + requests.push({ + method: request.method ?? "GET", + path: request.url ?? "", + auth: request.headers.authorization, + }); + if (request.url === "/api/0/redirect/") { + response.writeHead(302, { Location: `${origin}/outside-api` }); + response.end(); + return; + } + if (request.url === "/api/0/failure/") { + response.writeHead(400, { "Content-Type": "application/json" }); + response.end(JSON.stringify({ title: ["This field is required."] })); + return; + } + if ( + request.url === "/api/0/insufficient-scope/" || + request.url === "/api/0/forbidden/" + ) { + response.writeHead(403, { + "Content-Type": "application/json", + "Set-Cookie": "private-session=not-for-sandbox", + ...(request.url === "/api/0/insufficient-scope/" + ? { + "WWW-Authenticate": + 'Bearer error="insufficient_scope", scope="org:admin org:write"', + } + : {}), + }); + response.end( + JSON.stringify({ + detail: "You do not have permission to perform this action.", + }), + ); + return; + } + response.writeHead(request.method === "POST" ? 201 : 200, { + "Content-Type": "application/json", + Link: '; rel="next"; results="true"', + "Set-Cookie": "private-session=not-for-sandbox", + }); + response.end( + JSON.stringify({ id: String(requests.length), name: "Fixture item" }), + ); + }); + await new Promise((resolveReady) => + fixture.listen(0, "127.0.0.1", resolveReady), + ); + const address = fixture.address(); + if (!address || typeof address === "string") + throw new Error("Fixture did not bind a port."); + origin = `http://127.0.0.1:${address.port}`; + worker = await createWorker(); +}); + +beforeEach(() => { + requests.length = 0; + specRequests.length = 0; +}); + +afterAll(async () => { + await worker?.dispose(); + if (fixture) + await new Promise((resolveClosed) => + fixture.close(() => resolveClosed()), + ); + if (outputDirectory) + await rm(outputDirectory, { recursive: true, force: true }); +}); + +describe("HTTP Code Mode through real Dynamic Workers", () => { + it("requires an explicit direct API bearer token", async () => { + const response = await worker.dispatchFetch("http://localhost/mcp", { + method: "POST", + }); + expect(response.status).toBe(401); + expect(requests).toEqual([]); + }); + it("discovers the configured OpenAPI and returns a structured result", async () => { + expect( + await callTool( + "search_sentry_api", + "async () => Object.keys(spec.paths)", + ), + ).toMatchInlineSnapshot(` + { + "_meta": { + "io.modelcontextprotocol/serverInfo": { + "name": "sentry-code-mode-experiment", + "version": "0.1.0", + }, + }, + "content": [ + { + "text": "{"result":["/api/0/items/"],"stats":{"apiCalls":0,"writesAttempted":0}}", + "type": "text", + }, + ], + "resultType": "complete", + "structuredContent": { + "result": [ + "/api/0/items/", + ], + "stats": { + "apiCalls": 0, + "writesAttempted": 0, + }, + }, + } + `); + expect(requests).toEqual([]); + }); + + it("discovers alerts from metadata using the example published to MCP clients", async () => { + const metadataWorker = await createWorker(true, "/metadata-spec.json"); + try { + const example = await publishedExample("Discovery", metadataWorker); + expect(specRequests).toEqual([]); + const result = await callTool( + "search_sentry_api", + example.replace('const term = "release";', 'const term = "alert";'), + metadataWorker, + ); + expect(result.isError).not.toBe(true); + expect(result.structuredContent).toEqual({ + result: [ + { + method: "put", + path: workflowPath, + summary: "Update an Alert by ID", + tags: ["workflows"], + }, + { + method: "get", + path: "/api/0/detectors/", + operationId: "listAlertDetectors", + tags: ["workflows"], + }, + { method: "get", path: "/api/0/notifications/" }, + { method: "get", path: "/api/0/rules/", tags: ["Alerts"] }, + { method: "get", path: "/api/0/alerts/" }, + ], + stats: { apiCalls: 0, writesAttempted: 0 }, + }); + expect(specRequests).toEqual(["/metadata-spec.json"]); + expect(requests).toEqual([]); + } finally { + await metadataWorker.dispose(); + } + }); + + it.each([ + { + source: "operation tags", + specPath: "/metadata-spec.json", + categories: [{ name: "workflows" }, { name: "Alerts" }], + }, + { + source: "declared category metadata", + specPath: "/metadata-spec-categories.json", + categories: [ + { + name: "workflows", + description: "Manage alert workflows.", + "x-sidebar-name": "Alerts", + }, + ], + }, + ])( + "discovers categories from $source with the published example", + async ({ specPath, categories }) => { + const metadataWorker = await createWorker(true, specPath); + try { + const result = await callTool( + "search_sentry_api", + await publishedExample("Categories", metadataWorker), + metadataWorker, + ); + expect(result.isError).not.toBe(true); + expect(result.structuredContent).toEqual({ + result: categories, + stats: { apiCalls: 0, writesAttempted: 0 }, + }); + expect(requests).toEqual([]); + } finally { + await metadataWorker.dispose(); + } + }, + ); + + it("returns path and operation parameters separately with the published schema example", async () => { + const metadataWorker = await createWorker(true, "/metadata-spec.json"); + try { + const result = await callTool( + "search_sentry_api", + await publishedExample("Schema", metadataWorker), + metadataWorker, + ); + const path = metadataSpec.paths[releasePath]; + expect(result.isError).not.toBe(true); + expect(result.structuredContent).toEqual({ + result: { + description: path.post.description, + pathParameters: path.parameters, + operationParameters: path.post.parameters, + requestBody: path.post.requestBody, + }, + stats: { apiCalls: 0, writesAttempted: 0 }, + }); + expect(requests).toEqual([]); + } finally { + await metadataWorker.dispose(); + } + }); + + it("combines authenticated API calls and preserves pagination", async () => { + const result = await callTool( + "execute_sentry_code", + `async () => { + const source = await sentry.request({path: "/items/", query: {project: [1, 2]}}); + const created = await sentry.request({method: "POST", path: "/api/0/items/", body: {name: source.body.name}}); + return {id: created.body.id, link: source.headers.link, cookie: source.headers["set-cookie"] ?? null}; + }`, + ); + expect(result).toMatchInlineSnapshot(` + { + "_meta": { + "io.modelcontextprotocol/serverInfo": { + "name": "sentry-code-mode-experiment", + "version": "0.1.0", + }, + }, + "content": [ + { + "text": "{"result":{"id":"2","link":"; rel=\\"next\\"; results=\\"true\\"","cookie":null},"stats":{"apiCalls":2,"writesAttempted":1}}", + "type": "text", + }, + ], + "resultType": "complete", + "structuredContent": { + "result": { + "cookie": null, + "id": "2", + "link": "; rel="next"; results="true"", + }, + "stats": { + "apiCalls": 2, + "writesAttempted": 1, + }, + }, + } + `); + expect(result.structuredContent).toEqual({ + result: { + id: "2", + link: '; rel="next"; results="true"', + cookie: null, + }, + stats: { apiCalls: 2, writesAttempted: 1 }, + }); + expect(requests).toEqual([ + { + method: "GET", + path: "/api/0/items/?project=1&project=2", + auth: "Bearer fixture-only-token", + }, + { + method: "POST", + path: "/api/0/items/", + auth: "Bearer fixture-only-token", + }, + ]); + }); + + it("prevents search from accessing the network", async () => { + const result = await callTool( + "search_sentry_api", + `async () => (await fetch(${JSON.stringify(`${origin}/api/0/items/`)})).text()`, + ); + expect(result.isError).toBe(true); + expect(requests).toEqual([]); + }); + + it("blocks raw network access, API path escapes and API redirects", async () => { + const result = await callTool( + "execute_sentry_code", + `async () => { + const outcomes = []; + for (const url of ["https://unconfigured.invalid/api/0/items/", ${JSON.stringify(`${origin}/api/0/items/`)}]) { + try { await fetch(url, {headers: {Authorization: "Bearer forged"}}); outcomes.push("unexpected access"); } + catch { outcomes.push("blocked"); } + } + for (const path of ["/../../outside-api", "/redirect/"]) { + try { await sentry.request({path}); } + catch (error) { outcomes.push(error.status); } + } + return outcomes; + }`, + ); + expect(result.structuredContent?.result).toEqual([ + "blocked", + "blocked", + 403, + 502, + ]); + expect(requests).toHaveLength(1); + expect(requests[0]?.path).toBe("/api/0/redirect/"); + }); + + it("rejects writes for a read-only connection", async () => { + const readOnlyWorker = await createWorker(false); + try { + const result = await callTool( + "execute_sentry_code", + `async () => { + try { await sentry.request({path: "/items/", method: "POST"}); } + catch (error) { return error.status; } + }`, + readOnlyWorker, + ); + expect(result.structuredContent?.result).toBe(403); + expect(requests).toEqual([]); + } finally { + await readOnlyWorker.dispose(); + } + }); + + it("returns API failures as tool errors without retrying writes", async () => { + const result = await callTool( + "execute_sentry_code", + "async () => sentry.request({method: 'POST', path: '/failure/', body: {}})", + ); + expect(result.isError).toBe(true); + expect(result.content[0]?.text).toContain( + "POST /api/0/failure/ returned HTTP 400", + ); + expect(result.content[0]?.text).toContain( + "writes may already have completed", + ); + expect(requests).toHaveLength(1); + }); + + it("preserves upstream scope evidence in caught HTTP errors", async () => { + const result = await callTool( + "execute_sentry_code", + `async () => { + try { await sentry.request({path: "/insufficient-scope/", method: "POST"}); } + catch (error) { + return {status: error.status, method: error.method, path: error.path, + headers: error.headers, data: error.data}; + } + }`, + ); + expect(result.isError).not.toBe(true); + expect(result.structuredContent?.result).toEqual({ + status: 403, + method: "POST", + path: "/api/0/insufficient-scope/", + headers: { + "content-type": "application/json", + "www-authenticate": + 'Bearer error="insufficient_scope", scope="org:admin org:write"', + }, + data: { detail: "You do not have permission to perform this action." }, + }); + expect(requests).toHaveLength(1); + }); + + it.each(["insufficient-scope", "forbidden"])( + "returns an uncaught %s denial with HTTP evidence and no retries", + async (endpoint) => { + const result = await callTool( + "execute_sentry_code", + `async () => sentry.request({method: "POST", path: "/${endpoint}/"})`, + ); + expect(result.isError).toBe(true); + expect(result.structuredContent).toEqual({ + error: expect.stringContaining( + `POST /api/0/${endpoint}/ returned HTTP 403`, + ), + httpError: { + status: 403, + method: "POST", + path: `/api/0/${endpoint}/`, + headers: { + "content-type": "application/json", + ...(endpoint === "insufficient-scope" + ? { + "www-authenticate": + 'Bearer error="insufficient_scope", scope="org:admin org:write"', + } + : {}), + }, + }, + stats: { apiCalls: 1, writesAttempted: 1 }, + }); + expect(result.content[0]?.text).toContain( + "You do not have permission to perform this action.", + ); + expect(result.content[0]?.text).toContain( + "writes may already have completed", + ); + expect(result.content[0]?.text).toContain('"status":403'); + if (endpoint === "forbidden") { + expect(result.content[0]?.text).not.toContain("insufficient_scope"); + } + expect(requests).toHaveLength(1); + }, + ); + + it("bounds API requests per execution", async () => { + const result = await callTool( + "execute_sentry_code", + "async () => { for (let i = 0; i < 21; i++) await sentry.request({path: '/items/'}); }", + ); + expect(result.isError).toBe(true); + expect(requests).toHaveLength(20); + }); + + it("keeps the API broker outside the model module", async () => { + const result = await callTool( + "execute_sentry_code", + "async () => ({moduleBroker: typeof apiBroker, parameterBroker: typeof broker})", + ); + expect(result.structuredContent?.result).toEqual({ + moduleBroker: "undefined", + parameterBroker: "undefined", + }); + }); + + it("preserves a user error with partial HTTP-like properties", async () => { + const result = await callTool( + "execute_sentry_code", + 'async () => { throw {status: 403, message: "Example denied"}; }', + ); + expect(result.isError).toBe(true); + expect(result.structuredContent).toMatchObject({ + error: expect.stringContaining("Example denied"), + stats: { apiCalls: 0, writesAttempted: 0 }, + }); + expect(result.structuredContent).not.toHaveProperty("httpError"); + expect(result.content[0]?.text).not.toContain("Invalid input"); + }); + + it("returns useful errors for invalid code and oversized results", async () => { + const invalid = await callTool("execute_sentry_code", "async () => {"); + expect(invalid.isError).toBe(true); + const oversized = await callTool( + "execute_sentry_code", + "async () => 'x'.repeat(50_001)", + ); + expect(oversized.isError).toBe(true); + expect(oversized.content[0]?.text).toContain("Result exceeds 50,000 bytes"); + expect(requests).toEqual([]); + }); +}); diff --git a/packages/mcp-cloudflare/src/code-mode/worker.ts b/packages/mcp-cloudflare/src/code-mode/worker.ts new file mode 100644 index 000000000..640991583 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/worker.ts @@ -0,0 +1,145 @@ +import type { WorkerEntrypoint } from "cloudflare:workers"; +import { McpServer } from "@modelcontextprotocol/server"; +import { createMcpHandler } from "agents/mcp/server"; +import { + ApiBroker, + type CodeModeEnv, + configuredUrl, + readBoundedBody, +} from "./broker"; +import { + codeModeResultSchema, + getCodeModeToolDefinitions, +} from "./definitions"; +import { getSpec } from "./openapi"; +import type { ApiAuthorization } from "./production"; +import { sandboxSource } from "./sandbox"; + +export type { CodeModeEnv } from "./broker"; + +declare class Sandbox extends WorkerEntrypoint { + run(broker?: ApiBroker): Promise; +} + +/** Callbacks capture this request's bearer token; never share this server across users. */ +export function registerCodeModeTools( + server: McpServer, + env: CodeModeEnv, + token: string, + authorization?: ApiAuthorization, +): void { + async function evaluate(code: string, execute: boolean) { + const hint = + execute && env.CODE_MODE_ALLOW_WRITES === "true" + ? " Execution is not atomic: writes may already have completed. Inspect API state before retrying writes." + : ""; + try { + const apiBase = configuredUrl(env.API_BASE_URL) + .toString() + .replace(/\/$/, ""); + const deadline = Date.now() + 30_000; + const spec = execute ? undefined : await getSpec(env.OPENAPI_URL); + const worker = env.LOADER.get( + `sentry-code-mode-${crypto.randomUUID()}`, + async () => ({ + compatibilityDate: "2026-01-12", + mainModule: "worker.js", + modules: { + "worker.js": sandboxSource(execute ? apiBase : undefined), + "user.js": `export default (${code});`, + ...(spec ? { "spec.json": { json: spec } } : {}), + }, + // Cloudflare enforces these in production. Local workerd does not; + // the trusted harness separately bounds HTTP calls and output size. + limits: { cpuMs: 1_000, subRequests: execute ? 20 : 0 }, + globalOutbound: null, + }), + ); + const broker = execute + ? new ApiBroker({ + accessToken: token, + deadline, + apiBaseUrl: env.API_BASE_URL, + allowWrites: env.CODE_MODE_ALLOW_WRITES === "true", + authorization, + }) + : undefined; + let timeout: ReturnType | undefined; + const response = await Promise.race([ + worker.getEntrypoint().run(broker), + new Promise((_, reject) => { + timeout = setTimeout( + () => reject(new Error("Execution deadline exceeded.")), + Math.max(1, deadline - Date.now()), + ); + }), + ]).finally(() => clearTimeout(timeout)); + const body = await readBoundedBody(response.body, 55_000); + const value = codeModeResultSchema.parse( + JSON.parse(new TextDecoder().decode(body)), + ); + if (value.error !== undefined) { + const failure = { ...value, error: value.error + hint }; + return { + isError: true, + content: [{ type: "text" as const, text: JSON.stringify(failure) }], + structuredContent: failure, + }; + } + return { + content: [{ type: "text" as const, text: JSON.stringify(value) }], + structuredContent: value, + }; + } catch (error) { + const message = + error instanceof Error ? error.message : "Code execution failed."; + return { + isError: true, + content: [ + { type: "text" as const, text: message.slice(0, 4_000) + hint }, + ], + }; + } + } + for (const [name, definition] of Object.entries( + getCodeModeToolDefinitions({ + allowWrites: env.CODE_MODE_ALLOW_WRITES === "true", + authorizationRequired: !!authorization, + }), + )) { + server.registerTool(name, definition, ({ code }) => + evaluate(code, name === "execute_sentry_code"), + ); + } +} + +function buildServer(env: CodeModeEnv, token: string): McpServer { + const server = new McpServer({ + name: "sentry-code-mode-experiment", + version: "0.1.0", + }); + registerCodeModeTools(server, env, token); + return server; +} + +/** Separate experimental entrypoint; this does not change the production OAuth MCP. */ +export default { + async fetch( + request: Request, + env: CodeModeEnv, + ctx: ExecutionContext, + ): Promise { + if (new URL(request.url).pathname !== "/mcp") + return new Response("Not found", { status: 404 }); + const token = request.headers + .get("authorization") + ?.match(/^Bearer\s+(\S+)$/i)?.[1]; + if (!token) + return new Response("A direct Sentry API bearer token is required.", { + status: 401, + }); + return await createMcpHandler(() => buildServer(env, token), { + route: "/mcp", + })(request, env, ctx); + }, +} satisfies ExportedHandler; diff --git a/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts b/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts index 7030427d1..54acd83d1 100644 --- a/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts @@ -13,7 +13,8 @@ function skillInputAttributes(html: string, skillId: string): string { `]*)>`, ), ); - return match?.[1] ?? ""; + expect(match, `Missing skill checkbox: ${skillId}`).not.toBeNull(); + return match![1]; } function expectSkillChecked(html: string, skillId: string): void { @@ -71,7 +72,7 @@ describe("approval-dialog", () => { expect(html).not.toContain("history.back"); }); - it("selects all approvable skills when no remembered defaults are provided", async () => { + it("leaves API Write unchecked for new approvals and explains its independent authority", async () => { const response = await renderApprovalDialog( new Request("https://example.com/oauth/authorize"), { @@ -87,6 +88,37 @@ describe("approval-dialog", () => { expectSkillChecked(html, "seer"); expectSkillChecked(html, "triage"); expectSkillChecked(html, "project-management"); + expectSkillChecked(html, "api-read"); + expectSkillUnchecked(html, "api-write"); + expect(html).toContain('id="skill-name-api-write">API Write'); + expect(skillInputAttributes(html, "api-write")).toContain( + 'aria-describedby="skill-description-api-write api-write-help"', + ); + expect(html).toContain( + "API Write allows issue changes even when Triage Issues is unchecked.", + ); + expect(html).toContain( + "It also allows project and team changes when Manage Projects & Teams is unchecked.", + ); + }); + + it("preserves an explicit remembered API Write selection without selecting Triage", async () => { + const response = await renderApprovalDialog( + new Request("https://example.com/oauth/authorize"), + { + client: mockClient, + server: { name: "Test Server" }, + state: { oauthReqInfo: { clientId: "test-client" } }, + cookieSecret: TEST_SECRET, + defaultSkills: ["api-write"], + }, + ); + const html = await response.text(); + + expectSkillChecked(html, "api-write"); + expectSkillUnchecked(html, "triage"); + expectSkillUnchecked(html, "project-management"); + expect(html).toContain("Includes API Read."); }); it("uses remembered skill defaults when provided", async () => { @@ -106,6 +138,10 @@ describe("approval-dialog", () => { expectSkillUnchecked(html, "seer"); expectSkillChecked(html, "triage"); expectSkillChecked(html, "project-management"); + expect(html).toContain('value="api-read"'); + expect(html).toContain('value="api-write"'); + expectSkillUnchecked(html, "api-read"); + expectSkillUnchecked(html, "api-write"); }); it("ignores unknown and deprecated remembered skill defaults", async () => { @@ -183,11 +219,36 @@ describe("approval-dialog", () => { const html = await response.text(); - expect(html).toContain("Session scope"); - expect(html).toContain("This session will be limited to the"); - expect(html).toContain(">sentry organization"); + expect(html).toContain('aria-label="Organization scope"'); + expect(html).toContain( + 'Only sentry', + ); + expect(html).toContain( + "Every permission below, including API Write, is limited to this organization.", + ); + expect(html).toContain("Your Sentry permissions still apply."); + expect(html).toContain('value="api-read"'); + expect(html).toContain('value="api-write"'); + }); + + it("explains that an unconstrained connection can access every organization authorized by its token", async () => { + const response = await renderApprovalDialog( + new Request("https://example.com/oauth/authorize"), + { + client: mockClient, + server: { name: "Sentry MCP" }, + state: { oauthReqInfo: { clientId: "test-client" } }, + cookieSecret: TEST_SECRET, + }, + ); + const html = await response.text(); + + expect(html).toContain("All authorized organizations"); + expect(html).toContain( + "This connection can access any organization allowed by its Sentry token.", + ); expect(html).toContain( - "The permissions below apply only within this scope.", + "The permissions below, including API Write, apply across those organizations.", ); }); @@ -210,6 +271,8 @@ describe("approval-dialog", () => { expect(html).toContain(">javascript project"); expect(html).toContain(">sentry organization"); + expect(html).not.toContain('name="skill" value="api-read"'); + expect(html).not.toContain('name="skill" value="api-write"'); }); }); diff --git a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts index ab46f290e..33e9d0ebf 100644 --- a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts +++ b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts @@ -6,6 +6,7 @@ import skillDefinitions, { type SkillDefinition, } from "@sentry/mcp-core/skillDefinitions"; import { logError, logIssue, logWarn } from "@sentry/mcp-core/telem/logging"; +import { CODE_MODE_SKILLS } from "../../code-mode/definitions"; import { type OAuthState, signState, @@ -384,7 +385,8 @@ export interface ApprovalDialogOptions { */ cookieSecret: string; /** - * Skill IDs to preselect. When omitted, all approvable skills are selected. + * Remembered skill IDs to preselect. New approvals select all approvable + * skills except API Write, which requires an explicit choice. */ defaultSkills?: string[]; } @@ -463,28 +465,48 @@ export async function renderApprovalDialog( cookieSecret, defaultSkills, } = options; + const approvableSkills = APPROVABLE_SKILLS.filter( + (skill) => + !scope?.projectSlug || + !CODE_MODE_SKILLS.some((codeModeSkill) => codeModeSkill === skill.id), + ); const defaultSkillIds = defaultSkills === undefined - ? new Set(APPROVABLE_SKILLS.map((skill) => skill.id)) + ? new Set( + approvableSkills + .filter((skill) => skill.id !== "api-write") + .map((skill) => skill.id), + ) : new Set(filterApprovableSkillIds(defaultSkills)); // Use static skill definitions bundled at build time // Generate HTML for all approvable skills. - const skillsHtml = APPROVABLE_SKILLS.map( - (skill) => ` + const skillsHtml = approvableSkills + .map( + (skill) => ` `, - ).join(""); + ) + .join(""); // Encode state for form submission (HMAC-signed to prevent tampering) const encodedState = await encodeState(state, cookieSecret); @@ -530,19 +552,21 @@ export async function renderApprovalDialog( ` : ""; - const scopeHtml = scope + const scopeHtml = scope?.projectSlug ? `
Session scope
-

${ - scope.projectSlug - ? `This session will be limited to the ${sanitizeHtml(scope.projectSlug)} project in the ${sanitizeHtml(scope.organizationSlug)} organization.` - : `This session will be limited to the ${sanitizeHtml(scope.organizationSlug)} organization.` - }

-

The permissions below apply only within this scope.

+

This session will be limited to the ${sanitizeHtml(scope.projectSlug)} project in the ${sanitizeHtml(scope.organizationSlug)} organization.

+

The permissions below apply only within this scope. Your Sentry permissions still apply.

` - : ""; + : ` +
+
Organization scope
+

${scope ? `Only ${sanitizeHtml(scope.organizationSlug)}` : "All authorized organizations"}

+

${scope ? "Every permission below, including API Write, is limited to this organization. Your Sentry permissions still apply." : "This connection can access any organization allowed by its Sentry token. The permissions below, including API Write, apply across those organizations."}

+
+ `; // Generate HTML for the approval dialog const htmlContent = ` @@ -886,6 +910,48 @@ export async function renderApprovalDialog( line-height: 1.5; } + .permission-note { + margin-top: 12px; + padding: 12px 14px; + border: 1px solid rgba(242, 185, 94, 0.35); + border-radius: var(--radius-sm); + background: rgba(242, 185, 94, 0.06); + font-size: 0.8125rem; + line-height: 1.55; + color: oklch(0.86 0.07 75); + } + + .permission-note > strong { + display: block; + font-weight: 600; + margin-bottom: 5px; + } + + .permission-note p { + margin: 0; + } + + .permission-note p + p { + margin-top: 5px; + } + + .permission-item:has(input[value="api-write"]) { + border: 1px solid var(--border-default); + border-left-width: 3px; + border-radius: var(--radius-sm); + margin-top: var(--space-md); + padding: var(--space-md); + } + + .permission-item:has(input[value="api-write"]:checked) { + border-color: var(--purple-primary); + } + + .permission-item:has(input:focus-visible) { + outline: 2px solid var(--purple-light); + outline-offset: 3px; + } + /* Two-column layout for wider screens */ .approval-grid { display: flex; @@ -973,6 +1039,10 @@ export async function renderApprovalDialog( padding: var(--space-sm); } + .permission-item:has(input[value="api-write"]) { + padding: 12px; + } + .permission-header { flex-direction: column; align-items: flex-start; diff --git a/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts new file mode 100644 index 000000000..501979e0c --- /dev/null +++ b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts @@ -0,0 +1,499 @@ +import { + createExecutionContext, + env, + waitOnExecutionContext, +} from "cloudflare:test"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { SENTRY_OPENAPI_URL } from "../../code-mode/openapi"; +import type { Env, WorkerProps } from "../types"; +import mcpHandler from "./mcp-handler"; + +const monitorPath = + "/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/monitors/{monitor_id_or_slug}/"; +const projectListPath = + "/api/0/organizations/{organization_id_or_slug}/projects/"; +// A small subset of the pinned schema's real paths and auth_token alternatives. +const spec = { + openapi: "3.0.0", + paths: { + "/api/0/organizations/": { + get: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + }, + [projectListPath]: { + get: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + }, + "/api/0/organizations/{organization_id_or_slug}/dashboards/": { + get: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + post: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + }, + "/api/0/organizations/{organization_id_or_slug}/dashboards/{dashboard_id}/": + { + get: { + security: [{ auth_token: ["org:admin", "org:read", "org:write"] }], + }, + }, + [monitorPath]: { + get: { + security: [ + { auth_token: ["project:admin", "project:read", "project:write"] }, + ], + }, + put: { + security: [ + { auth_token: ["alerts:write", "project:admin", "project:write"] }, + ], + }, + }, + "/api/0/sentry-apps/{sentry_app_id_or_slug}/": { + get: { security: [{ auth_token: ["org:read", "project:read"] }] }, + }, + }, +}; + +interface ApiRequest { + url: string; + method: string; + headers: Headers; + body: string; +} + +interface ToolResult { + isError?: boolean; + content: Array<{ type: string; text?: string }>; + structuredContent?: { + result: unknown; + stats: { apiCalls: number; writesAttempted: number }; + }; +} + +interface ToolList { + tools: Array<{ name: string; annotations?: { readOnlyHint?: boolean } }>; +} + +const originalFetch = globalThis.fetch; +let apiRequests: ApiRequest[]; +let apiResponse: (request: ApiRequest) => Response; +const revokeGrant = vi.fn(async () => {}); + +function workerEnv(): Env { + return { + ...(env as Record), + MCP_CACHE: env.OAUTH_KV, + OAUTH_PROVIDER: { revokeGrant }, + } as Env; +} + +async function rpc( + method: string, + params: Record, + options: { props?: Partial; path?: string } = {}, +): Promise { + const props: WorkerProps = { + id: "code-mode-user", + clientId: "code-mode-client", + clientName: "Code Mode Test Client", + accessToken: "test-api-token", + refreshToken: "test-refresh-token", + scope: "org:read project:read", + grantedSkills: ["api-read"], + ...options.props, + }; + const ctx = Object.assign(createExecutionContext(), { props }); + const request = new Request(`http://localhost${options.path ?? "/mcp"}`, { + method: "POST", + headers: { + Authorization: `Bearer ${props.id}:code-mode-grant:secret`, + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + Host: "localhost", + "CF-Connecting-IP": "192.0.2.1", + "User-Agent": "claude-code/2.0.0", + "X-Sentry-Utm-Source": "plugin", + "mcp-protocol-version": "2026-07-28", + "mcp-method": method, + ...(typeof params.name === "string" ? { "mcp-name": params.name } : {}), + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params: { + ...params, + _meta: { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": { + name: "code-mode-integration-tests", + version: "1.0.0", + }, + "io.modelcontextprotocol/clientCapabilities": {}, + }, + }, + }), + }); + const response = await mcpHandler.fetch!(request, workerEnv(), ctx); + const text = await response.text(); + await waitOnExecutionContext(ctx); + expect(response.status, text).toBe(200); + const data = text.startsWith("{") + ? text + : text + .split("\n") + .find((line) => line.startsWith("data: ")) + ?.slice(6); + expect(data, text).toBeDefined(); + const message = JSON.parse(data!) as { result?: T; error?: unknown }; + expect(message.error).toBeUndefined(); + return message.result as T; +} + +async function execute( + code: string, + options?: Parameters[2], +): Promise { + return await rpc( + "tools/call", + { name: "execute_sentry_code", arguments: { code } }, + options, + ); +} + +beforeEach(() => { + apiRequests = []; + revokeGrant.mockClear(); + apiResponse = () => Response.json({ ok: true }); + // The pool runs the real main Worker in this isolate. Only external HTTP is + // simulated; MCP handlers, Dynamic Workers and the RPC broker are real. + vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => { + const request = new Request(input, init); + if (request.url === SENTRY_OPENAPI_URL) return Response.json(spec); + const url = new URL(request.url); + if (!["sentry.io", "de.sentry.io"].includes(url.hostname)) { + return await originalFetch(input, init); + } + const recorded = { + url: request.url, + method: request.method, + headers: request.headers, + body: await request.text(), + }; + apiRequests.push(recorded); + const organization = /^\/api\/0\/organizations\/([^/]+)\/$/.exec( + url.pathname, + ); + if (organization) { + return Response.json({ + id: "100", + slug: organization[1], + name: organization[1], + links: { + regionUrl: "https://de.sentry.io", + organizationUrl: `https://sentry.io/organizations/${organization[1]}/`, + }, + }); + } + if (url.pathname === "/api/0/projects/code-mode-org/web/") { + return Response.json({ id: "200", slug: "web", name: "Web" }); + } + return apiResponse(recorded); + }); +}); + +afterEach(() => vi.restoreAllMocks()); + +describe("Code Mode on the authenticated MCP", () => { + it("advertises Code Mode alongside the normal catalog and executes discovery", async () => { + const list = await rpc("tools/list", {}); + expect(list.tools.map(({ name }) => name)).toEqual( + expect.arrayContaining([ + "search_sentry_api", + "execute_sentry_code", + "find_organizations", + ]), + ); + expect( + list.tools.find(({ name }) => name === "execute_sentry_code")?.annotations + ?.readOnlyHint, + ).toBe(true); + const result = await rpc("tools/call", { + name: "search_sentry_api", + arguments: { code: "async () => Object.keys(spec.paths)" }, + }); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toContain(monitorPath); + expect(apiRequests).toHaveLength(0); + }); + + it("blocks api-read writes through both sentry.request and raw fetch", async () => { + const result = await execute( + `async () => { + let wrappedStatus; + try { await sentry.request({method: "PUT", path: "/projects/code-mode-org/web/monitors/billing/", body: {name: "changed"}}); } + catch (error) { wrappedStatus = error.status; } + let rawBlocked = false; + try { await fetch("https://sentry.io/api/0/projects/code-mode-org/web/monitors/billing/", {method: "PUT", body: "{}"}); } + catch { rawBlocked = true; } + return {wrappedStatus, rawBlocked}; + }`, + { + props: { + upstreamScopes: [ + "org:read", + "project:write", + "team:write", + "event:write", + ], + }, + }, + ); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toEqual({ + wrappedStatus: 403, + rawBlocked: true, + }); + expect(apiRequests).toHaveLength(0); + }); + + it("uses the API write grant, regional routing and attribution headers", async () => { + apiResponse = (request) => + Response.json( + { saved: JSON.parse(request.body) }, + { headers: { ETag: '"saved-monitor"', Link: "; rel=next" } }, + ); + const result = await execute( + `async () => sentry.request({ + method: "PUT", path: "/projects/code-mode-org/web/monitors/billing/", body: {name: "Billing"} + })`, + { props: { grantedSkills: ["api-write"] } }, + ); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toMatchObject({ + status: 200, + headers: { etag: '"saved-monitor"', link: "; rel=next" }, + body: { saved: { name: "Billing" } }, + }); + const writes = apiRequests.filter(({ method }) => method === "PUT"); + expect(writes).toHaveLength(1); + expect(writes[0].url).toBe( + "https://de.sentry.io/api/0/projects/code-mode-org/web/monitors/billing/", + ); + expect(writes[0].headers.get("authorization")).toBe( + "Bearer test-api-token", + ); + expect(writes[0].headers.get("x-sentry-mcp-client-id")).toBe( + "code-mode-client", + ); + expect(writes[0].headers.get("x-sentry-mcp-client-name")).toBe( + "Code Mode Test Client", + ); + expect(writes[0].headers.get("x-sentry-mcp-utm-source")).toBe("plugin"); + }); + + it("keeps concurrent users' upstream credentials separate", async () => { + apiResponse = ({ headers }) => + Response.json({ owner: headers.get("authorization") }); + const code = + 'async () => (await sentry.request({path: "/organizations/"})).body'; + const results = await Promise.all([ + execute(code, { + props: { id: "first-user", accessToken: "first-api-token" }, + }), + execute(code, { + props: { id: "second-user", accessToken: "second-api-token" }, + }), + ]); + expect(results.map((result) => result.isError)).toEqual([ + undefined, + undefined, + ]); + expect(results.map((result) => result.structuredContent?.result)).toEqual([ + { owner: "Bearer first-api-token" }, + { owner: "Bearer second-api-token" }, + ]); + expect(apiRequests).toHaveLength(2); + }); + + it("enforces an organization grant against other organizations and global ID routes", async () => { + const result = await execute( + `async () => { + const outcomes = []; + for (const path of ["/organizations/other-org/projects/", "/sentry-apps/an-app/"]) { + try { await sentry.request({path}); } + catch (error) { outcomes.push({status: error.status, body: error.data}); } + } + return outcomes; + }`, + { + path: "/mcp/code-mode-org", + props: { constraintOrganizationSlug: "code-mode-org" }, + }, + ); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toEqual([ + { + status: 403, + body: { detail: expect.stringContaining("organization constraint") }, + }, + { + status: 403, + body: { detail: expect.stringContaining("organization constraint") }, + }, + ]); + expect( + apiRequests.every( + ({ url }) => + url === "https://sentry.io/api/0/organizations/code-mode-org/", + ), + ).toBe(true); + }); + + it.each([ + { + name: "docs-only grant", + props: { grantedSkills: ["docs"] }, + path: "/mcp", + }, + { + name: "project constraint", + props: { + grantedSkills: ["api-read"], + constraintOrganizationSlug: "code-mode-org", + constraintProjectSlug: "web", + }, + path: "/mcp/code-mode-org/web", + }, + ])("preserves the existing surface for a $name", async ({ props, path }) => { + const result = await rpc("tools/list", {}, { props, path }); + expect(result.tools.map(({ name }) => name)).not.toContain( + "execute_sentry_code", + ); + expect(result.tools.map(({ name }) => name)).not.toContain( + "search_sentry_api", + ); + }); + + it.each(["inspect", "triage", "project-management", "seer"])( + "does not turn a legacy %s grant into arbitrary API access", + async (skill) => { + const result = await rpc( + "tools/list", + {}, + { + props: { + grantedSkills: [skill], + upstreamScopes: ["org:read", "project:write", "event:write"], + }, + }, + ); + expect(result.tools.map(({ name }) => name)).not.toContain( + "execute_sentry_code", + ); + expect(result.tools.map(({ name }) => name)).not.toContain( + "search_sentry_api", + ); + expect(apiRequests).toHaveLength(0); + }, + ); + + it("copies a dashboard for two projects with api-write and an upstream org:read token", async () => { + // Dashboard POST accepts org:read in both the published schema and backend. + const original = { + id: "101", + title: "Errors", + projects: [101, 102], + widgets: [], + }; + const dashboards = new Map([[original.id, original]]); + apiResponse = ({ url, method, body }) => { + if (method === "POST") { + const saved = { + ...JSON.parse(body), + id: String(dashboards.size + 101), + }; + dashboards.set(saved.id, saved); + return Response.json(saved, { status: 201 }); + } + const id = new URL(url).pathname.split("/").at(-2)!; + return Response.json(dashboards.get(id)); + }; + const result = await execute( + `async () => { + const path = "/organizations/code-mode-org/dashboards/"; + const original = (await sentry.request({path: path + "101/"})).body; + const copies = []; + for (const project of [201, 202]) { + const created = (await sentry.request({method: "POST", path, + body: {title: original.title + " " + project, widgets: original.widgets, projects: [project]} + })).body; + const verified = (await sentry.request({path: path + created.id + "/"})).body; + copies.push({id: verified.id, projects: verified.projects}); + } + return copies; + }`, + { + props: { + grantedSkills: ["api-write"], + upstreamScopes: ["org:read"], + grantedScopes: ["org:read"], + }, + }, + ); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toEqual([ + { id: "102", projects: [201] }, + { id: "103", projects: [202] }, + ]); + expect(apiRequests.filter(({ method }) => method === "POST")).toHaveLength( + 2, + ); + expect(dashboards.get("101")).toEqual(original); + }); + + it.each([undefined, ["org:read", "project:write"]])( + "preserves the real upstream permission denial with recorded scopes %j", + async (upstreamScopes) => { + apiResponse = () => + Response.json( + { detail: "You do not have permission to perform this action." }, + { status: 403 }, + ); + const result = await execute( + 'async () => sentry.request({method: "POST", path: "/organizations/code-mode-org/dashboards/", body: {title: "Copy", widgets: []}})', + { props: { grantedSkills: ["api-write"], upstreamScopes } }, + ); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain("403"); + expect(result.content[0].text).toContain("You do not have permission"); + expect( + apiRequests.filter(({ method }) => method === "POST"), + ).toHaveLength(1); + expect(revokeGrant).not.toHaveBeenCalled(); + }, + ); + + it("revokes an upstream-rejected grant even when user code catches the 401", async () => { + apiResponse = () => + Response.json({ detail: "Expired upstream token" }, { status: 401 }); + const result = await execute(`async () => { + const statuses = []; + for (let i = 0; i < 2; i++) { + try { await sentry.request({path: "/organizations/"}); } + catch (error) { statuses.push(error.status); } + } + return statuses; + }`); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toEqual([401, 401]); + expect(revokeGrant).toHaveBeenCalledExactlyOnceWith( + "code-mode-grant", + "code-mode-user", + ); + }); +}); diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts index d31978e62..99bf57189 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts @@ -17,12 +17,13 @@ import * as Sentry from "@sentry/cloudflare"; import { buildServer } from "@sentry/mcp-core/server"; import { ACTIVE_SKILLS, - type Skill, parseSkills, + type Skill, } from "@sentry/mcp-core/skills"; import { logWarn } from "@sentry/mcp-core/telem/logging"; import type { ServerContext } from "@sentry/mcp-core/types"; import { createMcpHandler } from "agents/mcp/server"; +import { registerProductionCodeModeTools } from "../../code-mode/production"; import { annotateResponseMetric } from "../metrics"; import { ACCESS_METHOD_ATTRIBUTE, @@ -30,16 +31,15 @@ import { getOAuthGrantTelemetry, OAUTH_GRANT_REVOKED_REASON_ATTRIBUTE, } from "../oauth/telemetry"; -import type { WorkerProps } from "../types"; -import type { Env } from "../types"; +import type { Env, WorkerProps } from "../types"; import { - MCP_RATE_LIMIT_EXCEEDED_MESSAGE, checkRateLimit, + MCP_RATE_LIMIT_EXCEEDED_MESSAGE, } from "../utils/rate-limiter"; import { setSentryUserFromRequest } from "../utils/sentry-user"; import { - UTM_SOURCE_ATTRIBUTE, resolveUtmSourceFromRequest, + UTM_SOURCE_ATTRIBUTE, } from "./attribution"; import { resolveClientFamily } from "./client-family"; import { verifyConstraintsAccess } from "./constraint-utils"; @@ -460,12 +460,15 @@ async function handleAuthenticatedMcpRequest( // The modern handler requires a factory and creates a fresh SDK v2 server // for every request. Context remains request-scoped through tool closures. return await createMcpHandler( - () => - buildServer({ + () => { + const server = buildServer({ context: serverContext, experimentalMode: isExperimentalMode, sdkVersion: "v2", - }), + }); + registerProductionCodeModeTools(server, env.LOADER, serverContext); + return server; + }, { route: url.pathname, // The Agents handler rejects browser Origins by default except localhost. diff --git a/packages/mcp-cloudflare/src/server/oauth/authorize.test.ts b/packages/mcp-cloudflare/src/server/oauth/authorize.test.ts index 5dffe42e7..6e8c75ed6 100644 --- a/packages/mcp-cloudflare/src/server/oauth/authorize.test.ts +++ b/packages/mcp-cloudflare/src/server/oauth/authorize.test.ts @@ -1,6 +1,7 @@ import { AuthorizationError } from "@cloudflare/workers-oauth-provider"; import { Hono } from "hono"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { SCOPES } from "../../constants"; import { SKILL_PREFERENCES_COOKIE_NAME } from "../lib/approval-dialog"; import type { Env } from "../types"; import oauthRoute from "./index"; @@ -26,7 +27,8 @@ function skillInputAttributes(html: string, skillId: string): string { `]*)>`, ), ); - return match?.[1] ?? ""; + expect(match).not.toBeNull(); + return match![1]; } function expectSkillChecked(html: string, skillId: string): void { @@ -262,6 +264,7 @@ describe("oauth authorize routes", () => { expectSkillUnchecked(html, "seer"); expectSkillChecked(html, "triage"); expectSkillChecked(html, "project-management"); + expectSkillUnchecked(html, "api-write"); }); it("does not reuse remembered skills for a different client ID", async () => { @@ -304,9 +307,10 @@ describe("oauth authorize routes", () => { expectSkillChecked(html, "seer"); expectSkillChecked(html, "triage"); expectSkillChecked(html, "project-management"); + expectSkillUnchecked(html, "api-write"); }); - it("ignores tampered remembered skill cookies and falls back to all approvable skills", async () => { + it("ignores tampered remembered skill cookies and falls back to initial defaults", async () => { mockOAuthProvider.parseAuthRequest.mockResolvedValueOnce({ clientId: "test-client", redirectUri: "https://example.com/callback", @@ -340,9 +344,97 @@ describe("oauth authorize routes", () => { expectSkillChecked(html, "seer"); expectSkillChecked(html, "triage"); expectSkillChecked(html, "project-management"); + expectSkillUnchecked(html, "api-write"); }); }); + describe("Code Mode consent", () => { + it.each([ + { name: "initial read-only Code Mode defaults", explicitWrite: false }, + { name: "explicit API Write without Triage", explicitWrite: true }, + ])( + "preserves $name through approval and the next prompt", + async ({ explicitWrite }) => { + const resource = "http://localhost/mcp/test-org"; + mockOAuthProvider.parseAuthRequest.mockResolvedValue({ + clientId: "test-client", + redirectUri: "https://example.com/callback", + scope: ["read"], + state: "original-state", + }); + mockOAuthProvider.lookupClient.mockResolvedValue({ + clientId: "test-client", + clientName: "Test Client", + redirectUris: ["https://example.com/callback"], + }); + const url = new URL("http://localhost/oauth/authorize"); + url.searchParams.set("resource", resource); + + const prompt = await app.fetch(new Request(url), testEnv as Env); + expect(prompt.status).toBe(200); + const html = await prompt.text(); + expect(html).toContain('value="api-read"'); + expect(html).toContain('value="api-write"'); + expectSkillChecked(html, "api-read"); + expectSkillUnchecked(html, "api-write"); + const state = html.match(/name="state" value="([^"]+)"/)?.[1]; + expect(state).toBeDefined(); + const initialSkills = [ + ...html.matchAll( + /]*)>/g, + ), + ] + .filter((match) => /\bchecked\b/.test(match[2])) + .map((match) => match[1]); + const selectedSkills = explicitWrite ? ["api-write"] : initialSkills; + const form = new FormData(); + form.set("state", state!); + form.set("decision", "approve"); + for (const skill of selectedSkills) form.append("skill", skill); + + const approval = await app.fetch( + new Request(url, { + method: "POST", + body: form, + }), + testEnv as Env, + ); + expect(approval.status).toBe(302); + const upstream = new URL(approval.headers.get("Location")!); + const approvedState = await verifyAndParseState( + upstream.searchParams.get("state")!, + testEnv.COOKIE_SECRET!, + ); + expect(approvedState.req.skills).toEqual(selectedSkills); + expect(approvedState.req.resource).toBe(resource); + expect(upstream.searchParams.get("scope")?.split(" ")).toEqual( + Object.keys(SCOPES), + ); + + const cookie = extractCookiePair( + approval.headers.get("Set-Cookie") ?? "", + SKILL_PREFERENCES_COOKIE_NAME, + ); + const remembered = await app.fetch( + new Request(url, { + headers: { Cookie: cookie }, + }), + testEnv as Env, + ); + expect(remembered.status).toBe(200); + const rememberedHtml = await remembered.text(); + const renderedSkills = [ + ...rememberedHtml.matchAll( + /]*)>/g, + ), + ] + .filter((match) => /\bchecked\b/.test(match[2])) + .map((match) => match[1]); + expect(renderedSkills).toEqual(selectedSkills); + }, + ); + }); + describe("POST /oauth/authorize", () => { beforeEach(() => { mockOAuthProvider.lookupClient.mockResolvedValue({ @@ -841,7 +933,10 @@ describe("oauth authorize routes", () => { expect(response.status).toBe(200); const html = await response.text(); - expect(html).toContain(">test-org organization"); + expect(html).toContain('aria-label="Organization scope"'); + expect(html).toContain( + 'Only test-org', + ); }); it("should allow request with project-scoped resource parameter", async () => { @@ -851,25 +946,36 @@ describe("oauth authorize routes", () => { scope: ["read"], resource: "http://localhost/mcp/test-org/test-project", }); - mockOAuthProvider.lookupClient.mockResolvedValueOnce({ + mockOAuthProvider.lookupClient.mockResolvedValue({ clientId: "test-client", clientName: "Test Client", redirectUris: ["https://example.com/callback"], }); + const preferenceCookie = await createSkillPreferenceCookie( + app, + testEnv, + "test-client", + ["api-write"], + ); const url = new URL("http://localhost/oauth/authorize"); url.searchParams.set( "resource", "http://localhost/mcp/test-org/test-project", ); - const request = new Request(url, { method: "GET" }); + const request = new Request(url, { + method: "GET", + headers: { Cookie: preferenceCookie }, + }); const response = await app.fetch(request, testEnv as Env); expect(response.status).toBe(200); const html = await response.text(); expect(html).toContain(">test-project project"); expect(html).toContain(">test-org organization"); + expect(html).not.toContain('name="skill" value="api-read"'); + expect(html).not.toContain('name="skill" value="api-write"'); }); it("should reject request with invalid resource hostname", async () => { diff --git a/packages/mcp-cloudflare/src/server/oauth/callback.test.ts b/packages/mcp-cloudflare/src/server/oauth/callback.test.ts index 5035d97bf..80e3e5e24 100644 --- a/packages/mcp-cloudflare/src/server/oauth/callback.test.ts +++ b/packages/mcp-cloudflare/src/server/oauth/callback.test.ts @@ -676,7 +676,8 @@ describe("oauth callback routes", () => { expires_at: new Date(Date.now() + 3600_000).toISOString(), token_type: "bearer", user: { id: "user-123", name: "Test User" }, - scope: "team:write org:read event:write project:write alerts:write", + scope: + " team:write org:read\tevent:write project:write alerts:write org:read ", }, null, ]); diff --git a/packages/mcp-cloudflare/src/server/oauth/constants.ts b/packages/mcp-cloudflare/src/server/oauth/constants.ts index b9c8c6680..757dcb870 100644 --- a/packages/mcp-cloudflare/src/server/oauth/constants.ts +++ b/packages/mcp-cloudflare/src/server/oauth/constants.ts @@ -15,5 +15,5 @@ export const TokenResponseSchema = z.object({ id: z.string(), name: z.string().nullable(), }), - scope: z.string(), + scope: z.string().optional(), }); diff --git a/packages/mcp-cloudflare/src/server/oauth/helpers.test.ts b/packages/mcp-cloudflare/src/server/oauth/helpers.test.ts index ee30d6872..ed8a7d858 100644 --- a/packages/mcp-cloudflare/src/server/oauth/helpers.test.ts +++ b/packages/mcp-cloudflare/src/server/oauth/helpers.test.ts @@ -1,6 +1,7 @@ import type { TokenExchangeCallbackOptions } from "@cloudflare/workers-oauth-provider"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { WorkerProps } from "../types"; + const { logIssue, logWarn, sentryMetricsCount, sentrySetUser } = vi.hoisted( () => ({ logIssue: vi.fn(), @@ -22,12 +23,6 @@ vi.mock("@sentry/cloudflare", () => ({ setUser: sentrySetUser, })); -import { - OAUTH_GRANT_AGE_BUCKET_ATTRIBUTE, - OAUTH_GRANT_SHAPE_ATTRIBUTE, - OAUTH_REFRESH_OUTCOME_ATTRIBUTE, - OAUTH_UPSTREAM_EXPIRES_IN_BUCKET_ATTRIBUTE, -} from "./telemetry"; import { appendAuthorizationResponseIss, createResourceValidationError, @@ -38,6 +33,12 @@ import { tokenExchangeCallback, validateResourceParameter, } from "./helpers"; +import { + OAUTH_GRANT_AGE_BUCKET_ATTRIBUTE, + OAUTH_GRANT_SHAPE_ATTRIBUTE, + OAUTH_REFRESH_OUTCOME_ATTRIBUTE, + OAUTH_UPSTREAM_EXPIRES_IN_BUCKET_ATTRIBUTE, +} from "./telemetry"; const GRANT_TYPES = { AUTHORIZATION_CODE: @@ -230,45 +231,54 @@ describe("tokenExchangeCallback", () => { expect(result).toBeUndefined(); }); - it("should return cached token TTL when token is locally valid", async () => { - const now = Date.now(); - const futureExpiry = Date.now() + 10 * 60 * 1000; - const options = createRefreshOptions({ - accessTokenExpiresAt: futureExpiry, - sessionStartedAt: now - 2 * 24 * 60 * 60 * 1000, - upstreamExpiresAt: now + 3 * 24 * 60 * 60 * 1000, - }); - - const fetchSpy = vi.spyOn(globalThis, "fetch"); - - const result = await callTokenExchangeCallback(options); - expect(result).toEqual({ - newProps: expect.objectContaining({ - accessToken: "old-access-token", - }), - accessTokenTTL: expect.any(Number), - }); - expect(fetchSpy).not.toHaveBeenCalled(); - expect(sentryMetricsCount).toHaveBeenCalledWith( - "app.oauth.token_exchange", - 1, - { - attributes: expect.objectContaining({ - [OAUTH_REFRESH_OUTCOME_ATTRIBUTE]: "cached_valid_local", - [OAUTH_GRANT_AGE_BUCKET_ATTRIBUTE]: "1d_7d", - [OAUTH_UPSTREAM_EXPIRES_IN_BUCKET_ATTRIBUTE]: "1d_7d", - "app.client.registration.method": - expect.stringMatching(/^(cimd|dcr|unknown)$/), + it.each([ + ["known", ["event:write", "org:read"]], + ["unknown", undefined], + ] as const)( + "preserves %s upstream scopes when reusing a locally valid token", + async (_, upstreamScopes) => { + const now = Date.now(); + const futureExpiry = Date.now() + 10 * 60 * 1000; + const options = createRefreshOptions({ + accessTokenExpiresAt: futureExpiry, + sessionStartedAt: now - 2 * 24 * 60 * 60 * 1000, + upstreamExpiresAt: now + 3 * 24 * 60 * 60 * 1000, + upstreamScopes: upstreamScopes ? [...upstreamScopes] : undefined, + }); + + const fetchSpy = vi.spyOn(globalThis, "fetch"); + + const result = await callTokenExchangeCallback(options); + expect(result).toEqual({ + newProps: expect.objectContaining({ + accessToken: "old-access-token", + upstreamScopes, }), - }, - ); - }); + accessTokenTTL: expect.any(Number), + }); + expect(fetchSpy).not.toHaveBeenCalled(); + expect(sentryMetricsCount).toHaveBeenCalledWith( + "app.oauth.token_exchange", + 1, + { + attributes: expect.objectContaining({ + [OAUTH_REFRESH_OUTCOME_ATTRIBUTE]: "cached_valid_local", + [OAUTH_GRANT_AGE_BUCKET_ATTRIBUTE]: "1d_7d", + [OAUTH_UPSTREAM_EXPIRES_IN_BUCKET_ATTRIBUTE]: "1d_7d", + "app.client.registration.method": + expect.stringMatching(/^(cimd|dcr|unknown)$/), + }), + }, + ); + }, + ); it("should clear upstreamTokenInvalid after a successful probe", async () => { const pastExpiry = Date.now() - 60 * 1000; const options = createRefreshOptions({ accessTokenExpiresAt: pastExpiry, upstreamTokenInvalid: true, + upstreamScopes: ["event:write", "org:read"], }); vi.spyOn(globalThis, "fetch").mockResolvedValue( @@ -286,6 +296,10 @@ describe("tokenExchangeCallback", () => { ); const result = await callTokenExchangeCallback(options); + expect(result?.newProps?.upstreamScopes).toEqual([ + "event:write", + "org:read", + ]); expect(result).toEqual({ newProps: expect.not.objectContaining({ upstreamTokenInvalid: true, @@ -399,6 +413,32 @@ describe("exchangeCodeForAccessToken", () => { expect(logWarn).not.toHaveBeenCalled(); }); + it("accepts token responses with omitted scope without inferring permissions", async () => { + const token = { + access_token: "test-access-token", + refresh_token: "test-refresh-token", + token_type: "bearer", + expires_in: 3600, + expires_at: "2026-04-13T16:36:23.087Z", + user: { id: "user-123", name: "Test User" }, + }; + vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json(token)); + + const [payload, response] = await exchangeCodeForAccessToken({ + client_id: "test-client-id", + client_secret: "test-client-secret", + code: "test-code", + upstream_url: "https://sentry.io/oauth/token", + redirect_uri: "https://mcp.sentry.dev/oauth/callback", + }); + + expect(response).toBeNull(); + expect(payload).toEqual(token); + expect(payload?.scope).toBeUndefined(); + expect(logIssue).not.toHaveBeenCalled(); + expect(logWarn).not.toHaveBeenCalled(); + }); + it("returns a specific invalid_grant message without an event ID", async () => { vi.spyOn(globalThis, "fetch").mockResolvedValue( new Response( diff --git a/packages/mcp-cloudflare/src/server/routes/__tests__/mcp-discovery.test.ts b/packages/mcp-cloudflare/src/server/routes/__tests__/mcp-discovery.test.ts index f321d4356..e052b3013 100644 --- a/packages/mcp-cloudflare/src/server/routes/__tests__/mcp-discovery.test.ts +++ b/packages/mcp-cloudflare/src/server/routes/__tests__/mcp-discovery.test.ts @@ -1,5 +1,5 @@ import { env } from "cloudflare:test"; -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import app from "../../app"; describe("/.mcp discovery routes", () => { @@ -72,5 +72,28 @@ describe("/.mcp discovery routes", () => { surface: "catalog", }), ); + for (const name of ["search_sentry_api", "execute_sentry_code"]) { + expect(toolsByName.get(name)).toEqual( + expect.objectContaining({ + surface: "direct", + requiredScopes: [], + skills: ["api-read", "api-write"], + inputSchema: expect.objectContaining({ + type: "object", + required: ["code"], + properties: expect.objectContaining({ + code: expect.objectContaining({ type: "string" }), + }), + }), + outputSchema: expect.objectContaining({ + type: "object", + properties: expect.objectContaining({ + result: expect.any(Object), + stats: expect.objectContaining({ type: "object" }), + }), + }), + }), + ); + } }); }); diff --git a/packages/mcp-cloudflare/src/server/routes/mcp.ts b/packages/mcp-cloudflare/src/server/routes/mcp.ts index 359361198..08ce56dbb 100644 --- a/packages/mcp-cloudflare/src/server/routes/mcp.ts +++ b/packages/mcp-cloudflare/src/server/routes/mcp.ts @@ -1,12 +1,46 @@ /** * Public MCP metadata endpoints under `/.mcp/*` for external documentation sites. * - * Responds with pre-generated JSON payloads from @sentry/mcp-core. + * Combines generated core definitions with the hosted Code Mode definitions. * CORS is handled by the wrapper in index.ts (these paths are in PUBLIC_METADATA_PATHS), * so routes here only need to set content-specific headers like Cache-Control. */ -import { Hono } from "hono"; + import TOOL_DEFINITIONS from "@sentry/mcp-core/toolDefinitions"; +import { Hono } from "hono"; +import { z } from "zod"; +import { + CODE_MODE_SKILLS, + getCodeModeToolDefinitions, +} from "../../code-mode/definitions"; + +const hostedTools = Object.entries( + getCodeModeToolDefinitions({ + allowWrites: true, + authorizationRequired: true, + }), +).map(([name, { inputSchema, outputSchema, ...definition }]) => ({ + name, + ...definition, + inputSchema: toJsonSchema(inputSchema, "input"), + outputSchema: toJsonSchema(outputSchema, "output"), + // API permissions are checked per operation, not as one fixed tool scope. + requiredScopes: [], + skills: [...CODE_MODE_SKILLS], + surface: "direct", +})); +const toolDefinitions = [...TOOL_DEFINITIONS, ...hostedTools].sort((a, b) => + a.name.localeCompare(b.name), +); + +function toJsonSchema(schema: z.ZodType, io: "input" | "output") { + const { $schema: _, ...jsonSchema } = z.toJSONSchema(schema, { + io, + target: "draft-7", + unrepresentable: "any", + }); + return jsonSchema; +} function jsonResponse(json: unknown, status = 200) { const body = JSON.stringify(json); @@ -27,4 +61,4 @@ export default new Hono() }), ) // Tools - .get("/tools.json", (c) => jsonResponse(TOOL_DEFINITIONS)); + .get("/tools.json", (c) => jsonResponse(toolDefinitions)); diff --git a/packages/mcp-cloudflare/src/server/types.ts b/packages/mcp-cloudflare/src/server/types.ts index 5d493089e..2ee3606e1 100644 --- a/packages/mcp-cloudflare/src/server/types.ts +++ b/packages/mcp-cloudflare/src/server/types.ts @@ -33,9 +33,9 @@ export type WorkerProps = { /** * Scopes Sentry actually granted on the upstream access token, as returned * in the `scope` field of Sentry's OAuth token response. Unlike - * `grantedScopes` (derived from selected skills), this reflects what the - * stored Sentry token can really do. Absent on grants created before this - * field existed. + * `grantedScopes` (derived from selected skills), these describe the token, + * not the MCP session's permissions. Absent on older grants or when Sentry + * omitted `scope`. */ upstreamScopes?: string[]; /** Primary authorization method - array of skill strings */ @@ -60,6 +60,7 @@ export type WorkerProps = { export interface Env { NODE_ENV: string; ASSETS: Fetcher; + LOADER: WorkerLoader; OAUTH_KV: KVNamespace; MCP_CACHE: KVNamespace; COOKIE_SECRET: string; diff --git a/packages/mcp-cloudflare/vitest.config.ts b/packages/mcp-cloudflare/vitest.config.ts index ba16c5ba4..6bb51b496 100644 --- a/packages/mcp-cloudflare/vitest.config.ts +++ b/packages/mcp-cloudflare/vitest.config.ts @@ -25,6 +25,7 @@ export default defineConfig({ ], test: { include: ["src/**/*.test.ts"], + exclude: ["src/code-mode/**"], setupFiles: ["src/test-setup.ts"], fileParallelism: false, minWorkers: 1, diff --git a/packages/mcp-cloudflare/wrangler.canary.jsonc b/packages/mcp-cloudflare/wrangler.canary.jsonc index e1b6405db..9448a98b9 100644 --- a/packages/mcp-cloudflare/wrangler.canary.jsonc +++ b/packages/mcp-cloudflare/wrangler.canary.jsonc @@ -22,6 +22,7 @@ "binding": "CF_VERSION_METADATA", }, "vars": {}, + "worker_loaders": [{ "binding": "LOADER" }], "unsafe": { "bindings": [ { diff --git a/packages/mcp-cloudflare/wrangler.code-mode.jsonc b/packages/mcp-cloudflare/wrangler.code-mode.jsonc new file mode 100644 index 000000000..205078088 --- /dev/null +++ b/packages/mcp-cloudflare/wrangler.code-mode.jsonc @@ -0,0 +1,16 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + "name": "sentry-code-mode-experiment", + "main": "src/code-mode/worker.ts", + "compatibility_date": "2026-01-12", + "compatibility_flags": ["nodejs_compat"], + "workers_dev": false, + "worker_loaders": [{ "binding": "LOADER" }], + "vars": { + "API_BASE_URL": "https://sentry.io/api/0", + // Sentry's existing schema pipeline resolves references before publishing. + // Pin the artifact so schema changes are reviewed independently of execution. + "OPENAPI_URL": "https://raw.githubusercontent.com/getsentry/sentry-api-schema/803d6de98c9156c15e9ddc1bf9accbac6e24cebc/openapi-derefed.json", + "CODE_MODE_ALLOW_WRITES": "false", + }, +} diff --git a/packages/mcp-cloudflare/wrangler.jsonc b/packages/mcp-cloudflare/wrangler.jsonc index 6ea648773..c140d4239 100644 --- a/packages/mcp-cloudflare/wrangler.jsonc +++ b/packages/mcp-cloudflare/wrangler.jsonc @@ -27,6 +27,7 @@ "binding": "CF_VERSION_METADATA", }, "vars": {}, + "worker_loaders": [{ "binding": "LOADER" }], "unsafe": { "bindings": [ { diff --git a/packages/mcp-cloudflare/wrangler.test.jsonc b/packages/mcp-cloudflare/wrangler.test.jsonc index b972909b7..5a87f0f06 100644 --- a/packages/mcp-cloudflare/wrangler.test.jsonc +++ b/packages/mcp-cloudflare/wrangler.test.jsonc @@ -9,6 +9,7 @@ "nodejs_compat_populate_process_env", "global_fetch_strictly_public", ], + "worker_loaders": [{ "binding": "LOADER" }], "vars": { "COOKIE_SECRET": "test-cookie-secret-32-characters", "SENTRY_CLIENT_ID": "test-client-id", diff --git a/packages/mcp-core/package.json b/packages/mcp-core/package.json index 8227c2949..d751e9dc9 100644 --- a/packages/mcp-core/package.json +++ b/packages/mcp-core/package.json @@ -105,6 +105,14 @@ "types": "./dist/tools/agent-tools.d.ts", "default": "./dist/tools/agent-tools.js" }, + "./internal/tool-helpers/resolve-region-url": { + "types": "./dist/internal/tool-helpers/resolve-region-url.d.ts", + "default": "./dist/internal/tool-helpers/resolve-region-url.js" + }, + "./internal/tool-helpers/validate-region-url": { + "types": "./dist/internal/tool-helpers/validate-region-url.d.ts", + "default": "./dist/internal/tool-helpers/validate-region-url.js" + }, "./internal/agents/callEmbeddedAgent": { "types": "./dist/internal/agents/callEmbeddedAgent.d.ts", "default": "./dist/internal/agents/callEmbeddedAgent.js" diff --git a/packages/mcp-core/scripts/generate-definitions.ts b/packages/mcp-core/scripts/generate-definitions.ts index 94805e898..145681ecb 100644 --- a/packages/mcp-core/scripts/generate-definitions.ts +++ b/packages/mcp-core/scripts/generate-definitions.ts @@ -18,6 +18,9 @@ const toolsModule = await import("../src/tools/index.ts"); const surfacesModule = await import("../src/tools/surfaces.ts"); const skillsModule = await import("../src/skills.ts"); const toolTypesModule = await import("../src/tools/types.ts"); +// Hosted-only tools belong in remote plugin allowlists, not core/stdio exports. +const { CODE_MODE_TOOL_NAMES, CODE_MODE_SKILLS, getCodeModeToolDefinitions } = + await import("../../mcp-cloudflare/src/code-mode/definitions.ts"); function writeJson(file: string, data: unknown) { fs.writeFileSync(file, JSON.stringify(data, null, 2)); @@ -194,6 +197,7 @@ async function generateSkillDefinitions() { description: string; defaultEnabled: boolean; deprecated?: boolean; + hostedOnly?: boolean; order: number; toolCount?: number; }> @@ -283,11 +287,26 @@ async function generateSkillDefinitions() { }); } + if (CODE_MODE_SKILLS.some((skillId) => skillId === skill.id)) { + const definitions = getCodeModeToolDefinitions({ + allowWrites: skill.id === "api-write", + authorizationRequired: true, + }); + for (const [name, definition] of Object.entries(definitions)) { + skillTools.push({ + name, + description: definition.description, + requiredScopes: [], + }); + } + } + // Sort tools alphabetically by name skillTools.sort((a, b) => a.name.localeCompare(b.name)); return { ...skill, + ...(skill.hostedOnly ? { toolCount: skillTools.length } : {}), tools: skillTools, }; }); @@ -349,6 +368,7 @@ function isUpToDate(outDir: string): boolean { path.join(__dirname, "../src/skills.ts"), path.join(__dirname, "../src/tools/surfaces.ts"), path.join(__dirname, "../src/tools/types.ts"), + path.join(__dirname, "../../mcp-cloudflare/src/code-mode/definitions.ts"), path.join( __dirname, "../src/internal/tool-helpers/tool-call-formatting.ts", @@ -415,12 +435,16 @@ async function main() { const experimentalDirectTools = experimentalTools.filter( (tool) => tool.surface === "direct", ); - const toolNames = directTools - .filter(isEnabledByDefaultSkills) - .map((t) => t.name); - const experimentalToolNames = experimentalDirectTools - .filter(isEnabledByDefaultSkills) - .map((t) => t.name); + const toolNames = [ + ...directTools.filter(isEnabledByDefaultSkills).map((t) => t.name), + ...CODE_MODE_TOOL_NAMES, + ].sort(); + const experimentalToolNames = [ + ...experimentalDirectTools + .filter(isEnabledByDefaultSkills) + .map((t) => t.name), + ...CODE_MODE_TOOL_NAMES, + ].sort(); let agentsSynced = 0; for (const agentConfig of agentConfigs()) { diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index 1f3a1d245..4c08642a7 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -639,5 +639,87 @@ "requiredScopes": [] } ] + }, + { + "id": "api-read", + "name": "API Read", + "description": "Read data through the Sentry API using Code Mode. Your Sentry permissions still apply.", + "defaultEnabled": false, + "hostedOnly": true, + "order": 6, + "toolCount": 6, + "tools": [ + { + "name": "execute_sentry_code", + "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are disabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", + "requiredScopes": [] + }, + { + "name": "find_organizations", + "description": "Find organizations that the user has access to in Sentry.\n\nUse this tool when you need to:\n- View organizations in Sentry\n- Find an organization's slug to aid other tool requests\n- Search for specific organizations by name or slug\n\nReturns up to 25 results per page. When hasMore is true, pass the returned nextCursor with the same filters and scope to fetch the next page.", + "requiredScopes": ["org:read"] + }, + { + "name": "find_projects", + "description": "Find projects in Sentry.\n\nUse this tool when you need to:\n- View projects in a Sentry organization\n- Find a project's slug to aid other tool requests\n- Search for specific projects by name or slug\n\nReturns up to 25 results per page. When hasMore is true, pass the returned nextCursor with the same filters and scope to fetch the next page.", + "requiredScopes": ["project:read"] + }, + { + "name": "get_sentry_mcp_info", + "description": "Get information about the running Sentry MCP server.\n\nUse this tool when you need to:\n- Check the Sentry MCP server version\n- Verify compatibility with a Skill or integration", + "requiredScopes": [] + }, + { + "name": "search_sentry_api", + "description": "Discover Sentry HTTP API operations by executing a JavaScript async arrow function against the global spec. The configured OpenAPI document has schemas inline; no $ref resolution is needed. No network or API calls are available.\nSearch using the user's product terms across operation metadata: paths may use different internal names. Each spec.paths[path] has optional path-level parameters and HTTP-method operations. Operations have optional operationId, summary, description, tags (string[]), parameters, requestBody and responses. spec.tags, when present, describes categories with name, description and optional x-sidebar-name.\nFirst return a short list of matching methods, paths and summaries. If results are empty or unrelated, inspect categories and broaden the terms before trying API routes. Then inspect the selected operation's descriptions, parameters, requestBody and relevant response fields. Path-level parameters also apply; operation parameters override them by name and in. Do not return whole paths, the full spec or sliced JSON strings. Return only needed fields; the result limit is 50,000 bytes.\nDiscovery example: async () => {\n const term = \"release\";\n const results = [];\n for (const [path, item] of Object.entries(spec.paths)) {\n for (const [method, op] of Object.entries(item)) {\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\", \"trace\"].includes(method)) continue;\n const text = [path, op.operationId, op.summary, op.description, ...(op.tags ?? [])].join(\" \").toLowerCase();\n if (text.includes(term.toLowerCase())) results.push({method, path, operationId: op.operationId, summary: op.summary, tags: op.tags});\n }\n }\n return results;\n}\nCategories example: async () => spec.tags?.length ? spec.tags : [...new Set(Object.values(spec.paths).flatMap(item => Object.entries(item).filter(([method]) => [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\", \"trace\"].includes(method)).flatMap(([, op]) => op.tags ?? [])))].map(name => ({name}))\nSchema example: async () => { const path = \"/api/0/organizations/{organization_id_or_slug}/releases/\"; const item = spec.paths[path]; const op = item.post; return {description: op.description, pathParameters: item.parameters, operationParameters: op.parameters, requestBody: op.requestBody}; }", + "requiredScopes": [] + }, + { + "name": "whoami", + "description": "Identify the authenticated user in Sentry.\n\nUse this tool when you need to:\n- Get the user's name and email address.", + "requiredScopes": [] + } + ] + }, + { + "id": "api-write", + "name": "API Write", + "description": "Read, create, modify, and delete data through the Sentry API. Includes API Read.", + "defaultEnabled": false, + "hostedOnly": true, + "order": 7, + "toolCount": 6, + "tools": [ + { + "name": "execute_sentry_code", + "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are enabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", + "requiredScopes": [] + }, + { + "name": "find_organizations", + "description": "Find organizations that the user has access to in Sentry.\n\nUse this tool when you need to:\n- View organizations in Sentry\n- Find an organization's slug to aid other tool requests\n- Search for specific organizations by name or slug\n\nReturns up to 25 results per page. When hasMore is true, pass the returned nextCursor with the same filters and scope to fetch the next page.", + "requiredScopes": ["org:read"] + }, + { + "name": "find_projects", + "description": "Find projects in Sentry.\n\nUse this tool when you need to:\n- View projects in a Sentry organization\n- Find a project's slug to aid other tool requests\n- Search for specific projects by name or slug\n\nReturns up to 25 results per page. When hasMore is true, pass the returned nextCursor with the same filters and scope to fetch the next page.", + "requiredScopes": ["project:read"] + }, + { + "name": "get_sentry_mcp_info", + "description": "Get information about the running Sentry MCP server.\n\nUse this tool when you need to:\n- Check the Sentry MCP server version\n- Verify compatibility with a Skill or integration", + "requiredScopes": [] + }, + { + "name": "search_sentry_api", + "description": "Discover Sentry HTTP API operations by executing a JavaScript async arrow function against the global spec. The configured OpenAPI document has schemas inline; no $ref resolution is needed. No network or API calls are available.\nSearch using the user's product terms across operation metadata: paths may use different internal names. Each spec.paths[path] has optional path-level parameters and HTTP-method operations. Operations have optional operationId, summary, description, tags (string[]), parameters, requestBody and responses. spec.tags, when present, describes categories with name, description and optional x-sidebar-name.\nFirst return a short list of matching methods, paths and summaries. If results are empty or unrelated, inspect categories and broaden the terms before trying API routes. Then inspect the selected operation's descriptions, parameters, requestBody and relevant response fields. Path-level parameters also apply; operation parameters override them by name and in. Do not return whole paths, the full spec or sliced JSON strings. Return only needed fields; the result limit is 50,000 bytes.\nDiscovery example: async () => {\n const term = \"release\";\n const results = [];\n for (const [path, item] of Object.entries(spec.paths)) {\n for (const [method, op] of Object.entries(item)) {\n if (![\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\", \"trace\"].includes(method)) continue;\n const text = [path, op.operationId, op.summary, op.description, ...(op.tags ?? [])].join(\" \").toLowerCase();\n if (text.includes(term.toLowerCase())) results.push({method, path, operationId: op.operationId, summary: op.summary, tags: op.tags});\n }\n }\n return results;\n}\nCategories example: async () => spec.tags?.length ? spec.tags : [...new Set(Object.values(spec.paths).flatMap(item => Object.entries(item).filter(([method]) => [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\", \"trace\"].includes(method)).flatMap(([, op]) => op.tags ?? [])))].map(name => ({name}))\nSchema example: async () => { const path = \"/api/0/organizations/{organization_id_or_slug}/releases/\"; const item = spec.paths[path]; const op = item.post; return {description: op.description, pathParameters: item.parameters, operationParameters: op.parameters, requestBody: op.requestBody}; }", + "requiredScopes": [] + }, + { + "name": "whoami", + "description": "Identify the authenticated user in Sentry.\n\nUse this tool when you need to:\n- Get the user's name and email address.", + "requiredScopes": [] + } + ] } ] diff --git a/packages/mcp-core/src/skillDefinitions.ts b/packages/mcp-core/src/skillDefinitions.ts index 3be3c07ec..c7f035d23 100644 --- a/packages/mcp-core/src/skillDefinitions.ts +++ b/packages/mcp-core/src/skillDefinitions.ts @@ -7,6 +7,7 @@ export interface SkillDefinition { description: string; defaultEnabled: boolean; deprecated?: boolean; + hostedOnly?: boolean; order: number; toolCount?: number; tools?: Array<{ diff --git a/packages/mcp-core/src/skills.test.ts b/packages/mcp-core/src/skills.test.ts index 1ab5142db..e9c6dbc9a 100644 --- a/packages/mcp-core/src/skills.test.ts +++ b/packages/mcp-core/src/skills.test.ts @@ -1,14 +1,15 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; +import skillDefinitions from "./skillDefinitions"; import { - SKILLS, ACTIVE_SKILLS, DEFAULT_SKILLS, + isEnabledBySkills, isValidSkill, parseSkills, - isEnabledBySkills, + SKILLS, type Skill, } from "./skills"; -import skillDefinitions from "./skillDefinitions"; +import toolDefinitions from "./toolDefinitions"; function getGeneratedSkillToolNames(skillId: string) { const skill = skillDefinitions.find( @@ -32,6 +33,24 @@ function getGeneratedSkillToolDescription(skillId: string, toolName: string) { } describe("skills module", () => { + it.each([ + ["api-read", "disabled"], + ["api-write", "enabled"], + ])("describes hosted Code Mode access for %s", (skillId, writeState) => { + const definition = skillDefinitions.find((skill) => skill.id === skillId); + expect(definition?.hostedOnly).toBe(true); + expect([...getGeneratedSkillToolNames(skillId)]).toEqual( + expect.arrayContaining(["search_sentry_api", "execute_sentry_code"]), + ); + expect(definition?.toolCount).toBe(definition?.tools?.length); + expect( + getGeneratedSkillToolDescription(skillId, "execute_sentry_code"), + ).toContain(`Writes are ${writeState}.`); + expect(toolDefinitions.map((tool) => tool.name)).not.toContain( + "execute_sentry_code", + ); + }); + it("does not advertise workflow-only tools in generated skill prompts", () => { for (const skill of skillDefinitions) { expect(skill.tools?.map((tool) => tool.name) ?? []).not.toContain( @@ -47,6 +66,8 @@ describe("skills module", () => { expect(SKILLS["project-management"]).toBeDefined(); expect(SKILLS.seer).toBeDefined(); expect(SKILLS.docs).toBeDefined(); + expect(SKILLS["api-read"].hostedOnly).toBe(true); + expect(SKILLS["api-write"].hostedOnly).toBe(true); expect("preprod" in SKILLS).toBe(false); }); @@ -72,6 +93,8 @@ describe("skills module", () => { expect(DEFAULT_SKILLS).not.toContain("docs"); expect(DEFAULT_SKILLS).not.toContain("triage"); expect(DEFAULT_SKILLS).not.toContain("project-management"); + expect(DEFAULT_SKILLS).not.toContain("api-read"); + expect(DEFAULT_SKILLS).not.toContain("api-write"); }); it("has exactly 2 default skills", () => { @@ -86,6 +109,8 @@ describe("skills module", () => { "seer", "triage", "project-management", + "api-read", + "api-write", ]); expect(ACTIVE_SKILLS).not.toContain("docs"); }); @@ -98,6 +123,8 @@ describe("skills module", () => { expect(isValidSkill("project-management")).toBe(true); expect(isValidSkill("seer")).toBe(true); expect(isValidSkill("docs")).toBe(true); + expect(isValidSkill("api-read")).toBe(true); + expect(isValidSkill("api-write")).toBe(true); }); it("returns false for invalid skills", () => { @@ -110,6 +137,12 @@ describe("skills module", () => { }); describe("parseSkills", () => { + it("keeps existing grants unchanged when API capabilities are introduced", () => { + expect(parseSkills(["inspect", "triage"]).valid).toEqual( + new Set(["inspect", "triage"]), + ); + expect(parseSkills(["api-write"]).valid).toEqual(new Set(["api-write"])); + }); it("parses comma-separated string", () => { const { valid, invalid } = parseSkills("inspect,triage,docs"); expect(valid.size).toBe(3); diff --git a/packages/mcp-core/src/skills.ts b/packages/mcp-core/src/skills.ts index dd89d52b3..ba247e6dd 100644 --- a/packages/mcp-core/src/skills.ts +++ b/packages/mcp-core/src/skills.ts @@ -11,7 +11,9 @@ export type Skill = | "triage" | "project-management" | "seer" - | "docs"; + | "docs" + | "api-read" + | "api-write"; // Central registry with metadata (used by OAuth UI) export interface SkillDefinition { @@ -20,6 +22,7 @@ export interface SkillDefinition { description: string; defaultEnabled: boolean; deprecated?: boolean; + hostedOnly?: boolean; order: number; toolCount?: number; // Number of tools enabled by this skill (calculated dynamically) } @@ -65,6 +68,24 @@ export const SKILLS: Record = { defaultEnabled: false, order: 5, }, + "api-read": { + id: "api-read", + name: "API Read", + description: + "Read data through the Sentry API using Code Mode. Your Sentry permissions still apply.", + defaultEnabled: false, + hostedOnly: true, + order: 6, + }, + "api-write": { + id: "api-write", + name: "API Write", + description: + "Read, create, modify, and delete data through the Sentry API. Includes API Read.", + defaultEnabled: false, + hostedOnly: true, + order: 7, + }, }; // Sorted array for UI ordering diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index 50d8ec298..830f5d00a 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -1556,7 +1556,15 @@ "required": ["name"] }, "requiredScopes": [], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "direct" }, { @@ -2654,7 +2662,15 @@ "additionalProperties": false }, "requiredScopes": ["org:read"], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "direct" }, { @@ -2733,7 +2749,15 @@ "additionalProperties": false }, "requiredScopes": ["project:read"], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "direct" }, { @@ -5524,7 +5548,15 @@ "additionalProperties": false }, "requiredScopes": [], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "catalog" }, { @@ -6077,7 +6109,15 @@ "required": ["organizationSlug", "runToken", "update"] }, "requiredScopes": ["org:read"], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "catalog" }, { @@ -7484,7 +7524,15 @@ "additionalProperties": false }, "requiredScopes": [], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "direct" }, { @@ -9006,7 +9054,15 @@ "additionalProperties": false }, "requiredScopes": [], - "skills": ["inspect", "seer", "docs", "triage", "project-management"], + "skills": [ + "inspect", + "seer", + "docs", + "triage", + "project-management", + "api-read", + "api-write" + ], "surface": "catalog" } ] diff --git a/packages/mcp-server-evals/package.json b/packages/mcp-server-evals/package.json index 393716cff..d2aee9996 100644 --- a/packages/mcp-server-evals/package.json +++ b/packages/mcp-server-evals/package.json @@ -12,8 +12,17 @@ "dev": "tsc -w", "start": "tsx src/bin/start-mock-stdio.ts", "eval": "vitest --config=vitest.config.ts", + "eval:code-mode": "tsx src/code-mode/run-dashboard.ts", + "eval:code-mode:discovery": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code", + "eval:code-mode:flows": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code --task release --task repositories --task alert", + "eval:code-mode:resilience": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code --task monitor --task release-recovery --task repositories-retry --task repositories-denied", + "test:code-mode": "vitest run --config=vitest.code-mode.config.ts", + "tsc:code-mode": "tsc --project tsconfig.code-mode.json --noEmit", "eval:ci": "vitest run --reporter=vitest-evals/reporter --reporter=junit --reporter=json --outputFile.json=eval-results.json --outputFile.junit=eval.junit.xml" }, + "devDependencies": { + "wrangler": "catalog:" + }, "dependencies": { "@ai-sdk/mcp": "catalog:", "@ai-sdk/openai": "catalog:", @@ -29,5 +38,9 @@ "vitest": "catalog:", "vitest-evals": "catalog:", "zod": "catalog:" + }, + "sentryCi": { + "typecheck": "tsc:code-mode", + "test": "test:code-mode" } } diff --git a/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts new file mode 100644 index 000000000..d67198b63 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts @@ -0,0 +1,241 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { createAlertFixture } from "./alert-fixture"; + +type Fixture = Awaited>; +const active: Fixture[] = []; +const accessToken = "local-alert-test-token"; +const finalAnswer = JSON.stringify({ + id: "701", + enabled: false, + frequency: 60, +}); + +afterEach(async () => { + await Promise.all(active.splice(0).map((fixture) => fixture.close())); +}); + +async function fixture() { + const instance = await createAlertFixture({ accessToken }); + active.push(instance); + return instance; +} + +function request(instance: Fixture, id = "701", body?: unknown) { + return fetch( + `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/workflows/${id}/`, + { + method: body === undefined ? "GET" : "PUT", + headers: { + authorization: `Bearer ${accessToken}`, + "content-type": "application/json", + }, + body: body === undefined ? undefined : JSON.stringify(body), + }, + ); +} + +async function editBody(instance: Fixture) { + const response = await request(instance); + expect(response.status).toBe(200); + const source = z.record(z.string(), z.unknown()).parse(await response.json()); + return { ...source, enabled: false, config: { frequency: 60 } }; +} + +describe("issue alert Code Mode eval fixture", () => { + it("serves project scope and detector reads used by the ordinary alert tool", async () => { + const instance = await fixture(); + const paths = [ + "/api/0/projects/alert-evals/frontend/", + "/api/0/organizations/alert-evals/workflows/701/project-scope/", + "/api/0/organizations/alert-evals/detectors/901/", + "/api/0/organizations/alert-evals/workflows/", + "/api/0/organizations/alert-evals/detectors/", + ]; + const responses = await Promise.all( + paths.map((path) => + fetch(`${instance.baseUrl}${path}`, { + headers: { authorization: `Bearer ${accessToken}` }, + }), + ), + ); + expect(responses.map(({ status }) => status)).toEqual([ + 200, 200, 200, 200, 200, + ]); + const [project, scope, detector, workflows, detectors] = await Promise.all( + responses.map((response) => response.json()), + ); + expect(project).toEqual({ + id: "101", + slug: "frontend", + name: "Frontend", + platform: "javascript", + }); + expect(scope).toEqual({ projectIds: ["101"], includesAllProjects: false }); + expect(detector).toMatchObject({ + id: "901", + projectId: "101", + type: "error", + enabled: true, + workflowIds: ["701", "702"], + config: {}, + conditionGroup: null, + dataSources: [], + dateCreated: "2026-10-25T10:00:00.000Z", + dateUpdated: "2026-10-25T10:00:00.000Z", + }); + expect(workflows).toEqual(instance.snapshot().workflows); + expect(detectors).toEqual([detector]); + expect( + instance.snapshot().workflows.every((workflow) => workflow.enabled), + ).toBe(true); + }); + + it("persists the CLI-equivalent edit and preserves nested alert configuration", async () => { + const instance = await fixture(); + const before = instance.snapshot(); + const body = await editBody(instance); + const updated = await request(instance, "701", body); + expect(updated.status).toBe(200); + const fetched = await request(instance); + expect(await fetched.json()).toMatchObject({ + id: "701", + enabled: false, + config: { frequency: 60 }, + environment: "production", + owner: "team:42", + detectorIds: ["901"], + triggers: before.workflows[0].triggers, + actionFilters: before.workflows[0].actionFilters, + }); + expect(instance.verify(finalAnswer)).toEqual({ + success: true, + workflowId: "701", + enabled: false, + frequency: 60, + }); + expect(instance.snapshot().workflows[1]).toEqual(before.workflows[1]); + expect(instance.snapshot().writes).toEqual([ + { workflowId: "701", workflows: instance.snapshot().workflows }, + ]); + expect(instance.snapshot().requests.map(({ method }) => method)).toEqual([ + "GET", + "PUT", + "GET", + ]); + instance.reset(); + expect(instance.snapshot()).toEqual(before); + }); + + it("preserves omitted optional fields and requires name as WorkflowValidator does", async () => { + const instance = await fixture(); + const missingName = await request(instance, "701", { + enabled: false, + config: { frequency: 60 }, + }); + expect(missingName.status).toBe(400); + expect(instance.snapshot().workflows[0].enabled).toBe(true); + const response = await request(instance, "701", { + name: "Frontend production errors", + enabled: false, + config: { frequency: 60 }, + }); + expect(response.status).toBe(200); + expect(instance.verify(finalAnswer).success).toBe(true); + }); + + it("accepts snake_case nested fields but rejects alias collisions", async () => { + const instance = await fixture(); + const body = await editBody(instance); + const snakeBody = JSON.parse( + JSON.stringify(body).replace( + /"([A-Za-z]+)":/g, + (_, key: string) => + `"${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)}":`, + ), + ) as unknown; + expect((await request(instance, "701", snakeBody)).status).toBe(200); + expect(instance.verify(finalAnswer).success).toBe(true); + const collision = await request(instance, "701", { + ...body, + detector_ids: [902], + }); + expect(collision.status).toBe(400); + expect(instance.verify(finalAnswer).success).toBe(true); + }); + + it("rejects a claimed success when the edit dropped notification actions", async () => { + const instance = await fixture(); + const body = await editBody(instance); + expect( + (await request(instance, "701", { ...body, actionFilters: [] })).status, + ).toBe(200); + expect(() => instance.verify(finalAnswer)).toThrow( + "Alert settings other than enabled/frequency changed", + ); + }); + + it("rejects success after an unrelated alert was changed", async () => { + const instance = await fixture(); + expect( + (await request(instance, "701", await editBody(instance))).status, + ).toBe(200); + expect( + ( + await request(instance, "702", { + name: "Backend production errors", + enabled: false, + }) + ).status, + ).toBe(200); + expect(() => instance.verify(finalAnswer)).toThrow( + "Unrelated alert changed", + ); + }); + + it("rejects temporarily clearing monitor connections even after restoring them", async () => { + const instance = await fixture(); + const body = await editBody(instance); + expect( + (await request(instance, "701", { ...body, detectorIds: [] })).status, + ).toBe(200); + expect((await request(instance, "701", body)).status).toBe(200); + const state = instance.snapshot(); + expect(state.workflows[0].detectorIds).toEqual(["901"]); + expect(state.writes.map((write) => write.workflows[0].detectorIds)).toEqual( + [[], ["901"]], + ); + expect(() => instance.verify(finalAnswer)).toThrow( + "Write 1: unrelated alert settings changed during the operation", + ); + }); + + it("rejects wrong reported values even when the expected tokens appear elsewhere", async () => { + const instance = await fixture(); + expect( + (await request(instance, "701", await editBody(instance))).status, + ).toBe(200); + const wrongAnswer = JSON.stringify({ + id: "999", + enabled: true, + frequency: 15, + note: "The requested values were alert 701, false (disabled), 60 minutes.", + }); + expect(() => instance.verify(wrongAnswer)).toThrow( + "Final answer does not match the saved alert values", + ); + }); + + it("rejects an invented success and an answer that omits the actual result", async () => { + const instance = await fixture(); + expect(() => instance.verify(finalAnswer)).toThrow( + "Target alert is still enabled", + ); + expect( + (await request(instance, "701", await editBody(instance))).status, + ).toBe(200); + expect(() => instance.verify("Done.")).toThrow( + "Final answer must include the saved values as JSON", + ); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/alert-fixture.ts b/packages/mcp-server-evals/src/code-mode/alert-fixture.ts new file mode 100644 index 000000000..2a8df3efe --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/alert-fixture.ts @@ -0,0 +1,450 @@ +import assert from "node:assert/strict"; +import { z } from "zod"; +import { startApiFixture } from "./api-fixture"; +import { parseEvalJsonAnswer } from "./json-answer"; + +const ORGANIZATION = "alert-evals"; +const WORKFLOW_ID = "701"; +const OTHER_WORKFLOW_ID = "702"; +const DATE_CREATED = "2026-10-25T10:00:00.000Z"; +const DATE_UPDATED = "2026-10-26T10:00:00.000Z"; + +const answerSchema = z.object({ + id: z.union([z.string(), z.number().int().positive()]).transform(String), + enabled: z.boolean(), + frequency: z.number().int(), +}); +const conditionSchema = z.object({ + id: z.coerce.string().optional(), + type: z.string(), + comparison: z.unknown(), + conditionResult: z.unknown(), +}); +const actionSchema = z.object({ + id: z.coerce.string().optional(), + type: z.string(), + integrationId: z.coerce.string().nullable().optional(), + data: z.record(z.string(), z.unknown()), + config: z.record(z.string(), z.unknown()), + status: z.string().default("active"), +}); +const groupSchema = z.object({ + id: z.coerce.string().optional(), + logicType: z.enum(["all", "any", "any-short"]), + conditions: z.array(conditionSchema).default([]), +}); +const actionFilterSchema = groupSchema.extend({ + actions: z.array(actionSchema), +}); +const updateSchema = z.object({ + name: z.string().min(1).max(256), + enabled: z.boolean().default(true), + config: z + .object({ frequency: z.number().int().min(0).optional() }) + .strict() + .optional(), + environment: z.string().nullable().optional(), + owner: z.string().nullable().optional(), + detectorIds: z.array(z.coerce.string()).optional(), + triggers: groupSchema.optional(), + actionFilters: z.array(actionFilterSchema).optional(), +}); + +type Group = z.infer & { + id: string; + organizationId: string; + actions: z.infer[]; +}; +interface Workflow { + id: string; + name: string; + organizationId: string; + createdBy: string; + dateCreated: string; + dateUpdated: string; + triggers: Group; + actionFilters: Group[]; + environment: string | null; + config: { frequency?: number }; + detectorIds: string[]; + enabled: boolean; + lastTriggered: string | null; + owner: string | null; +} + +// CamelSnakeSerializer accepts either spelling recursively. Detect collisions +// before normalizing so the fixture cannot silently accept contradictory fields. +function camelKeys(value: unknown): unknown { + if (Array.isArray(value)) return value.map(camelKeys); + if (value === null || typeof value !== "object") return value; + const result: Record = {}; + for (const [key, item] of Object.entries(value)) { + const converted = key.replace(/_([a-z])/g, (_, letter: string) => + letter.toUpperCase(), + ); + if (Object.hasOwn(result, converted)) { + throw new SyntaxError(`${key} collides with ${converted}`); + } + result[converted] = camelKeys(item); + } + return result; +} + +// Sanitized shape from WorkflowEngineExamples.GET_WORKFLOW and its serializers. +function sourceWorkflow(id: string, name: string): Workflow { + return { + id, + name, + organizationId: "1", + createdBy: "10", + dateCreated: DATE_CREATED, + dateUpdated: DATE_CREATED, + triggers: { + id: `${id}1`, + organizationId: "1", + logicType: "any-short", + conditions: [ + { + id: `${id}11`, + type: "first_seen_event", + comparison: true, + conditionResult: true, + }, + { + id: `${id}12`, + type: "regression_event", + comparison: true, + conditionResult: true, + }, + ], + actions: [], + }, + actionFilters: [ + { + id: `${id}2`, + organizationId: "1", + logicType: "all", + conditions: [ + { + id: `${id}21`, + type: "issue_priority_deescalating", + comparison: true, + conditionResult: true, + }, + ], + actions: [ + { + id: `${id}31`, + type: "slack", + integrationId: "20", + data: {}, + config: { + targetType: "specific", + targetDisplay: "#frontend-alerts", + targetIdentifier: "C0000000001", + }, + status: "active", + }, + { + id: `${id}32`, + type: "slack", + integrationId: "20", + data: {}, + config: { + targetType: "specific", + targetDisplay: "#oncall-alerts", + targetIdentifier: "C0000000002", + }, + status: "active", + }, + ], + }, + ], + environment: "production", + config: { frequency: 15 }, + detectorIds: ["901"], + enabled: true, + lastTriggered: "2026-10-25T09:00:00.000Z", + owner: "team:42", + }; +} + +/** + * A narrow stateful substitute for workflow GET/PUT, based on Sentry's + * WorkflowValidator. Omitted nested fields are preserved; supplied groups + * replace their child lists. It does not emulate integration validation, + * permissions, feature flags, detector membership or database side effects. + */ +export async function createAlertFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const original = sourceWorkflow(WORKFLOW_ID, "Frontend production errors"); + const other = sourceWorkflow(OTHER_WORKFLOW_ID, "Backend production errors"); + const initial = [original, other]; + let workflows = new Map( + initial.map((item) => [item.id, structuredClone(item)]), + ); + const writes: Array<{ workflowId: string; workflows: Workflow[] }> = []; + let nextId = 8000; + const collectionPath = `/api/0/organizations/${ORGANIZATION}/workflows/`; + const detectorPath = `/api/0/organizations/${ORGANIZATION}/detectors/`; + const detector = () => ({ + id: "901", + projectId: "101", + name: "Frontend error monitor", + type: "error", + workflowIds: [...workflows.values()] + .filter((item) => item.detectorIds.includes("901")) + .map((item) => item.id), + dateCreated: DATE_CREATED, + dateUpdated: DATE_CREATED, + dataSources: [], + conditionGroup: null, + config: {}, + enabled: true, + }); + const api = await startApiFixture(accessToken, ({ method, url, body }) => { + // These reads keep the ordinary get_alert_rule tool usable in the control + // arm. It checks project membership and expands the connected detector. + if ( + method === "GET" && + ["frontend", "101"].some( + (project) => + url.pathname === `/api/0/projects/${ORGANIZATION}/${project}/`, + ) + ) { + return { + body: { + id: "101", + slug: "frontend", + name: "Frontend", + platform: "javascript", + }, + }; + } + if ( + method === "GET" && + (url.pathname === detectorPath || url.pathname === `${detectorPath}901/`) + ) { + return { + body: url.pathname === detectorPath ? [detector()] : detector(), + }; + } + if (!url.pathname.startsWith(collectionPath)) return undefined; + if (method === "GET" && url.pathname === collectionPath) { + return { body: [...workflows.values()] }; + } + if (method === "GET" && url.pathname.endsWith("/project-scope/")) { + const workflowId = url.pathname.slice( + collectionPath.length, + -"/project-scope/".length, + ); + const workflow = workflows.get(workflowId); + return workflow + ? { + body: { + projectIds: workflow.detectorIds.includes("901") ? ["101"] : [], + includesAllProjects: false, + }, + } + : { status: 404, body: { detail: "Alert not found" } }; + } + const id = url.pathname.slice(collectionPath.length).replace(/\/$/, ""); + const workflow = workflows.get(id); + if (!workflow) return { status: 404, body: { detail: "Alert not found" } }; + if (method === "GET") return { body: workflow }; + if (method !== "PUT") return undefined; + const input = updateSchema.parse(camelKeys(body)); + const { triggers, actionFilters, ...fields } = input; + const existingFilterIds = new Set( + workflow.actionFilters.map((item) => item.id), + ); + const existingActionIds = new Set( + workflow.actionFilters.flatMap((item) => + item.actions.map((action) => action.id), + ), + ); + if ( + (triggers?.id && triggers.id !== workflow.triggers.id) || + actionFilters?.some( + (item) => + (item.id && !existingFilterIds.has(item.id)) || + item.actions.some( + (action) => action.id && !existingActionIds.has(action.id), + ), + ) + ) { + return { + status: 400, + body: { + detail: "Condition group or action does not belong to this workflow", + }, + }; + } + // Backend updates the rows whose IDs are supplied and creates new rows + // when IDs are omitted; copying only condition values changes identity. + const serializeGroup = ( + group: z.infer & { + actions?: z.infer[]; + }, + existingId?: string, + ): Group => ({ + ...group, + id: group.id ?? existingId ?? String(nextId++), + organizationId: workflow.organizationId, + conditions: group.conditions.map((condition) => ({ + ...condition, + id: condition.id ?? String(nextId++), + })), + actions: (group.actions ?? []).map((action) => ({ + ...action, + id: action.id ?? String(nextId++), + integrationId: action.integrationId ?? null, + })), + }); + const updated: Workflow = { + ...workflow, + ...fields, + dateUpdated: DATE_UPDATED, + triggers: triggers + ? serializeGroup(triggers, workflow.triggers.id) + : workflow.triggers, + actionFilters: actionFilters + ? actionFilters.map((item) => serializeGroup(item)) + : workflow.actionFilters, + }; + workflows.set(id, updated); + writes.push( + structuredClone({ workflowId: id, workflows: [...workflows.values()] }), + ); + return { body: updated }; + }); + + return { + baseUrl: api.baseUrl, + organizationSlug: ORGANIZATION, + prompt: [ + `In organization ${ORGANIZATION}, disable the issue alert "${original.name}" (alert ID ${WORKFLOW_ID}, for project frontend) and set its notification frequency to 60 minutes.`, + "Preserve its name, environment, owner, monitor connections, trigger conditions, filters and notification actions. Leave all other alerts unchanged.", + "Keep those settings unchanged throughout the operation.", + "Return JSON with the alert's saved values: {id, enabled, frequency}.", + ].join("\n"), + cliCommands: [ + `sentry alert issues edit ${ORGANIZATION}/frontend/${WORKFLOW_ID} --status disabled --frequency 60`, + ], + provenance: { + cliRevision: "b625111aef9cc3a44abbed634a043fce8505366d", + cliSource: "packages/cli/src/commands/alert/issues/edit.ts", + backendRevision: "b4b356554798716ceaeab74efdc095782be9ca16", + backendSources: [ + "src/sentry/workflow_engine/endpoints/organization_workflow_details.py", + "src/sentry/workflow_engine/endpoints/validators/base/workflow.py", + "src/sentry/workflow_engine/endpoints/validators/base/data_condition_group.py", + "src/sentry/workflow_engine/endpoints/serializers/workflow_serializer.py", + "src/sentry/apidocs/examples/workflow_engine_examples.py", + ], + catalogReadSupport: { + // This private endpoint exists in the local origin/master ref, which + // is newer than the backend checkout used for the mutation contract. + backendRevision: "c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4", + backendRef: "origin/master", + backendSources: [ + "src/sentry/workflow_engine/endpoints/organization_workflow_project_scope.py", + "src/sentry/workflow_engine/endpoints/organization_workflow_index.py", + "src/sentry/workflow_engine/endpoints/organization_detector_index.py", + "src/sentry/workflow_engine/endpoints/organization_detector_details.py", + "src/sentry/workflow_engine/endpoints/serializers/detector_serializer.py", + "src/sentry/core/endpoints/project_details.py", + "src/sentry/api/serializers/models/project.py", + ], + }, + endpoints: [ + "GET /organizations/{organization_id_or_slug}/workflows/{workflow_id}/", + "PUT /organizations/{organization_id_or_slug}/workflows/{workflow_id}/", + "GET /organizations/{organization_id_or_slug}/workflows/", + "GET /organizations/{organization_id_or_slug}/workflows/{workflow_id}/project-scope/", + "GET /organizations/{organization_id_or_slug}/detectors/{detector_id}/", + "GET /organizations/{organization_id_or_slug}/detectors/", + "GET /projects/{organization_id_or_slug}/{project_id_or_slug}/", + ], + }, + reset() { + workflows = new Map( + initial.map((item) => [item.id, structuredClone(item)]), + ); + nextId = 8000; + api.requests.length = 0; + writes.length = 0; + }, + snapshot() { + return structuredClone({ + workflows: [...workflows.values()], + requests: api.requests, + writes, + }); + }, + verify(resultText: string) { + const updated = workflows.get(WORKFLOW_ID); + assert(updated, "Target alert is missing"); + assert.equal(updated.enabled, false, "Target alert is still enabled"); + assert.equal( + updated.config.frequency, + 60, + "Target alert frequency is not 60 minutes", + ); + assert.deepEqual( + updated, + { + ...original, + enabled: false, + config: { frequency: 60 }, + dateUpdated: DATE_UPDATED, + }, + "Alert settings other than enabled/frequency changed", + ); + assert.deepEqual( + workflows.get(OTHER_WORKFLOW_ID), + other, + "Unrelated alert changed", + ); + for (const [index, write] of writes.entries()) { + const target = write.workflows.find((item) => item.id === WORKFLOW_ID); + assert(target, `Write ${index + 1}: target alert is missing`); + assert.deepEqual( + target, + { + ...original, + enabled: target.enabled, + config: { ...original.config, frequency: target.config.frequency }, + dateUpdated: target.dateUpdated, + }, + `Write ${index + 1}: unrelated alert settings changed during the operation`, + ); + assert.deepEqual( + write.workflows.find((item) => item.id === OTHER_WORKFLOW_ID), + other, + `Write ${index + 1}: another alert changed during the operation`, + ); + } + const answer = answerSchema.parse(parseEvalJsonAnswer(resultText)); + assert.deepEqual( + answer, + { + id: updated.id, + enabled: updated.enabled, + frequency: updated.config.frequency, + }, + "Final answer does not match the saved alert values", + ); + return { + success: true as const, + workflowId: WORKFLOW_ID, + enabled: false, + frequency: 60, + }; + }, + close: api.close, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/api-fixture.ts b/packages/mcp-server-evals/src/code-mode/api-fixture.ts new file mode 100644 index 000000000..1a62f08ab --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/api-fixture.ts @@ -0,0 +1,98 @@ +import assert from "node:assert/strict"; +import { createServer, type IncomingHttpHeaders } from "node:http"; +import { z } from "zod"; + +export interface FixtureRequest { + method: string; + path: string; + query: string; + status: number; +} + +interface ApiRequest { + method: string; + url: URL; + headers: IncomingHttpHeaders; + body: unknown; +} + +interface ApiReply { + status?: number; + body: unknown; + headers?: Record; +} + +/** Authenticated loopback HTTP boundary; scenario state and grading stay with the fixture. */ +export async function startApiFixture( + accessToken: string, + handle: ( + request: ApiRequest, + ) => ApiReply | undefined | Promise, +) { + const requests: FixtureRequest[] = []; + const server = createServer(async (request, response) => { + const url = new URL(request.url ?? "/", `http://${request.headers.host}`); + const method = request.method ?? "GET"; + let reply: ApiReply; + try { + if (request.headers.authorization !== `Bearer ${accessToken}`) { + reply = { status: 401, body: { detail: "Invalid token" } }; + } else { + const chunks: Buffer[] = []; + let size = 0; + for await (const chunk of request) { + size += chunk.length; + if (size > 256_000) + throw new Error("Fixture request body exceeds its budget"); + chunks.push(chunk); + } + const raw = Buffer.concat(chunks).toString("utf8"); + reply = (await handle({ + method, + url, + headers: request.headers, + body: raw ? JSON.parse(raw) : undefined, + })) ?? { + status: 404, + body: { detail: "Endpoint not implemented in this eval fixture" }, + }; + } + } catch (error) { + reply = + error instanceof z.ZodError || error instanceof SyntaxError + ? { + status: 400, + body: { + detail: "Invalid request", + ...(error instanceof z.ZodError + ? { errors: error.issues } + : {}), + }, + } + : { status: 500, body: { detail: "Fixture internal error" } }; + } + const status = reply.status ?? 200; + requests.push({ method, path: url.pathname, query: url.search, status }); + response.writeHead(status, { + "content-type": "application/json", + ...reply.headers, + }); + response.end(JSON.stringify(reply.body)); + }); + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + assert(address && typeof address !== "string"); + return { + baseUrl: `http://127.0.0.1:${address.port}`, + requests, + async close(this: void) { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + server.closeAllConnections(); + }); + }, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts b/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts new file mode 100644 index 000000000..ebb56d7a9 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts @@ -0,0 +1,281 @@ +import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { runClaudeAgent } from "./claude-agent"; + +const tools = [ + "mcp__sentry_code_mode__search_sentry_api", + "mcp__sentry_code_mode__execute_sentry_code", +]; +const options = { + serverName: "sentry_code_mode", + toolNames: ["search_sentry_api", "execute_sentry_code"], +}; +const init = { + type: "system", + subtype: "init", + model: "claude-test-alias", + tools, + session_id: "private-session-metadata", + apiKeySource: "private-account-metadata", +}; +const directories: string[] = []; + +async function fakeClaude(events: unknown[], exitCode = 0) { + const directory = await mkdtemp(path.join(tmpdir(), "claude-eval-test-")); + directories.push(directory); + const executable = path.join(directory, "claude"); + // A real process exercises argument boundaries, stdin and JSONL parsing. + // Deliberately omit the final newline to cover the stream's trailing event. + const payload = events.map((event) => JSON.stringify(event)).join("\n"); + await writeFile( + executable, + `#!${process.execPath} +const fs = require("node:fs"); +let input = ""; +process.stdin.setEncoding("utf8"); +process.stdin.on("data", chunk => { input += chunk; }); +process.stdin.on("end", () => { + fs.writeFileSync("capture.json", JSON.stringify({ args: process.argv.slice(2), input })); + const payload = ${JSON.stringify(payload)}; + process.stdout.write(payload.slice(0, 37)); + process.stdout.write(payload.slice(37)); + process.exitCode = ${exitCode}; +}); +`, + ); + await chmod(executable, 0o755); + vi.stubEnv("PATH", `${directory}${path.delimiter}${process.env.PATH ?? ""}`); + return directory; +} + +afterEach(async () => { + vi.unstubAllEnvs(); + await Promise.all( + directories + .splice(0) + .map((directory) => rm(directory, { recursive: true })), + ); +}); + +describe("Claude Code Mode adapter", () => { + it("uses only MCP tools and reports aggregate usage, resolved model and task evidence", async () => { + const usage = { + input_tokens: 100, + output_tokens: 30, + cache_creation_input_tokens: 60, + cache_read_input_tokens: 400, + }; + const resultContent = [{ type: "text", text: '{"copied":2}' }]; + const directory = await fakeClaude([ + init, + { + type: "assistant", + session_id: "private-session-metadata", + message: { + model: "claude-test-resolved", + usage, + content: [ + { + type: "tool_use", + id: "call-1", + name: tools[1], + input: { code: "async () => 2" }, + }, + ], + }, + }, + { + type: "user", + message: { + content: [ + { + type: "tool_result", + tool_use_id: "call-1", + content: resultContent, + is_error: false, + }, + ], + }, + }, + { + type: "assistant", + message: { + model: "claude-test-resolved", + usage, + content: [{ type: "text", text: "Copied both dashboards." }], + }, + }, + { + type: "result", + subtype: "success", + is_error: false, + result: "Copied both dashboards.", + usage, + total_cost_usd: 0.01, + num_turns: 2, + }, + ]); + const result = await runClaudeAgent( + directory, + "http://127.0.0.1:4321/mcp", + "dummy-token", + "Copy two dashboards", + { + ...options, + model: "requested-alias", + systemPrompt: "System instructions", + }, + ); + expect(result).toMatchObject({ + exitCode: 0, + error: undefined, + model: "claude-test-resolved", + advertisedModel: "claude-test-alias", + text: "Copied both dashboards.", + usage, + reportedCostUsd: 0.01, + turns: 2, + mcpCalls: [ + { + id: "call-1", + name: "execute_sentry_code", + input: { code: "async () => 2" }, + }, + ], + mcpErrors: 0, + toolResultBytes: Buffer.byteLength(JSON.stringify(resultContent)), + }); + expect(JSON.stringify(result.events)).not.toContain("private-"); + const capture = JSON.parse( + await readFile(path.join(directory, "capture.json"), "utf8"), + ); + expect(capture.input).toBe("Copy two dashboards"); + const argument = (name: string) => + capture.args[capture.args.indexOf(name) + 1]; + expect(argument("--tools")).toBe(""); + expect(argument("--allowedTools")).toBe(tools.join(",")); + expect(argument("--setting-sources")).toBe(""); + expect(argument("--settings")).toBe('{"disableAllHooks":true}'); + expect(argument("--permission-mode")).toBe("dontAsk"); + expect(argument("--model")).toBe("requested-alias"); + expect(argument("--effort")).toBe("medium"); + expect(argument("--system-prompt")).toBe("System instructions"); + expect(capture.args).toEqual( + expect.arrayContaining([ + "--strict-mcp-config", + "--disable-slash-commands", + "--no-session-persistence", + ]), + ); + expect(capture.args).not.toContain("--bare"); + expect(JSON.parse(argument("--mcp-config"))).toEqual({ + mcpServers: { + sentry_code_mode: { + type: "http", + url: "http://127.0.0.1:4321/mcp", + headers: { Authorization: "Bearer dummy-token" }, + }, + }, + }); + }); + + it("lets Claude select among the full MCP inventory with its default system prompt", async () => { + const names = [ + "search_sentry_tools", + "execute_sentry_tool", + "get_issue_details", + ...options.toolNames, + ]; + const directory = await fakeClaude([ + { ...init, tools: names.map((name) => `mcp__sentry__${name}`) }, + { type: "result", subtype: "success", is_error: false, result: "Done" }, + ]); + const result = await runClaudeAgent( + directory, + "http://localhost/mcp", + "dummy", + "Copy my dashboard", + { + serverName: "sentry", + toolNames: names, + }, + ); + expect(result.error).toBeUndefined(); + const capture = JSON.parse( + await readFile(path.join(directory, "capture.json"), "utf8"), + ); + expect(capture.input).toBe("Copy my dashboard"); + expect(capture.args).not.toContain("--system-prompt"); + expect(capture.args).not.toContain("--append-system-prompt"); + expect(capture.args[capture.args.indexOf("--allowedTools") + 1]).toBe( + names.map((name) => `mcp__sentry__${name}`).join(","), + ); + }); + + it("fails an OAuth error even when the result subtype says success", async () => { + const error = + "Failed to authenticate: OAuth session expired and could not be refreshed"; + const directory = await fakeClaude([ + init, + { + type: "assistant", + message: { + model: "", + content: [{ type: "text", text: error }], + }, + }, + { type: "result", subtype: "success", is_error: true, result: error }, + ]); + const result = await runClaudeAgent( + directory, + "http://localhost/mcp", + "dummy", + "Copy", + options, + ); + expect(result.error).toBe(error); + expect(result.model).toBe("claude-test-alias"); + expect(result.mcpCalls).toEqual([]); + }); + + it.each([ + { label: "advertised", events: [{ ...init, tools: [...tools, "Bash"] }] }, + { + label: "used", + events: [ + init, + { + type: "assistant", + message: { + content: [ + { + type: "tool_use", + id: "outside", + name: "Bash", + input: { command: "echo unexpected" }, + }, + ], + }, + }, + ], + }, + ])( + "rejects tools $label outside the two-tool experiment", + async ({ events }) => { + const directory = await fakeClaude([ + ...events, + { type: "result", subtype: "success", is_error: false, result: "Done" }, + ]); + const result = await runClaudeAgent( + directory, + "http://localhost/mcp", + "dummy", + "Copy", + options, + ); + expect(result.error).toContain("outside the experiment"); + }, + ); +}); diff --git a/packages/mcp-server-evals/src/code-mode/claude-agent.ts b/packages/mcp-server-evals/src/code-mode/claude-agent.ts new file mode 100644 index 000000000..f8b82d630 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/claude-agent.ts @@ -0,0 +1,236 @@ +import { spawn } from "node:child_process"; +import { z } from "zod"; + +interface ClaudeAgentOptions { + toolNames: string[]; + serverName: string; + model?: string; + /** Omit to keep Claude Code's default instructions and let it select tools. */ + systemPrompt?: string; +} + +const usageSchema = z.object({ + input_tokens: z.number(), + output_tokens: z.number(), + cache_creation_input_tokens: z.number().optional(), + cache_read_input_tokens: z.number().optional(), +}); +const contentSchema = z.object({ + type: z.string(), + text: z.string().optional(), + id: z.string().optional(), + name: z.string().optional(), + input: z.unknown().optional(), + tool_use_id: z.string().optional(), + content: z.unknown().optional(), + is_error: z.boolean().optional(), +}); +// Select task evidence without persisting account, session, or machine metadata. +const eventSchema = z.object({ + type: z.string(), + subtype: z.string().optional(), + model: z.string().optional(), + tools: z.array(z.string()).optional(), + message: z + .object({ + model: z.string().optional(), + content: z.array(contentSchema), + }) + .optional(), + is_error: z.boolean().optional(), + result: z.string().optional(), + errors: z.array(z.string()).optional(), + usage: usageSchema.optional(), + total_cost_usd: z.number().optional(), + num_turns: z.number().optional(), + duration_api_ms: z.number().optional(), +}); +type TraceEvent = z.infer; + +/** Fresh authenticated Claude session with the supplied MCP inventory. */ +export async function runClaudeAgent( + directory: string, + mcpUrl: string, + accessToken: string, + prompt: string, + { toolNames, serverName, model, systemPrompt }: ClaudeAgentOptions, +) { + const toolPrefix = `mcp__${serverName}__`; + const allowedTools = toolNames.map((name) => `${toolPrefix}${name}`); + const events: TraceEvent[] = []; + let buffer = ""; + let bytes = 0; + let protocolError: string | undefined; + let processError: string | undefined; + const args = [ + "--print", + "--output-format", + "stream-json", + "--verbose", + "--tools", + "", + "--allowedTools", + allowedTools.join(","), + "--strict-mcp-config", + "--mcp-config", + JSON.stringify({ + mcpServers: { + [serverName]: { + type: "http", + url: mcpUrl, + headers: { Authorization: `Bearer ${accessToken}` }, + }, + }, + }), + "--settings", + JSON.stringify({ disableAllHooks: true }), + "--setting-sources", + "", + "--disable-slash-commands", + "--no-session-persistence", + "--permission-mode", + "dontAsk", + "--effort", + "medium", + "--no-chrome", + ...(systemPrompt === undefined ? [] : ["--system-prompt", systemPrompt]), + ...(model ? ["--model", model] : []), + ]; + const started = performance.now(); + const child = spawn("claude", args, { + cwd: directory, + stdio: ["pipe", "pipe", "pipe"], + signal: AbortSignal.timeout(300_000), + }); + const fail = (error: string) => { + protocolError ??= error; + child.kill(); + }; + const consumeLine = (line: string) => { + if (!line.trim()) return; + let event: TraceEvent; + try { + event = eventSchema.parse(JSON.parse(line)); + } catch { + fail("Claude emitted an invalid JSON event"); + return; + } + events.push(event); + if ( + event.type === "system" && + event.subtype === "init" && + (event.tools?.length !== allowedTools.length || + allowedTools.some((name) => !event.tools?.includes(name))) + ) { + fail( + "Claude advertised tools outside the experiment or omitted its MCP tools", + ); + } + if ( + event.type === "assistant" && + event.message?.content.some( + (block) => + block.type === "tool_use" && !allowedTools.includes(block.name ?? ""), + ) + ) { + fail("Claude used a tool outside the experiment"); + } + }; + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + bytes += Buffer.byteLength(chunk); + if (bytes > 10_000_000) { + fail("Agent trace exceeded 10 MB"); + return; + } + buffer += chunk; + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) consumeLine(line); + }); + // Drain diagnostics, but do not save potentially identifying CLI metadata. + child.stderr.resume(); + child.stdin.on("error", () => { + processError ??= "Claude closed its input before accepting the prompt"; + }); + child.stdin.end(prompt); + const exitCode = await new Promise((resolve) => { + child.once("error", (error) => { + processError = error.message; + }); + child.once("close", resolve); + }); + consumeLine(buffer); + + const init = events.find( + (event) => event.type === "system" && event.subtype === "init", + ); + const completion = events.findLast((event) => event.type === "result"); + const assistantMessages = events.filter( + (event) => event.type === "assistant", + ); + const calls = new Map( + assistantMessages.flatMap((event) => + (event.message?.content ?? []) + .filter((block) => block.type === "tool_use" && block.id) + .map((block) => [block.id, block] as const), + ), + ); + const results = new Map( + events + .filter((event) => event.type === "user") + .flatMap((event) => + (event.message?.content ?? []) + .filter((block) => block.type === "tool_result" && block.tool_use_id) + .map((block) => [block.tool_use_id, block] as const), + ), + ); + const finalMessage = assistantMessages.findLast((event) => + event.message?.content.some((block) => block.type === "text"), + ); + const resolvedModel = assistantMessages.findLast( + (event) => event.message?.model && event.message.model !== "", + )?.message?.model; + return { + durationMs: Math.round(performance.now() - started), + model: resolvedModel ?? init?.model, + advertisedModel: init?.model, + exitCode, + error: + processError ?? + protocolError ?? + (!init ? "Claude did not advertise its tool inventory" : undefined) ?? + (exitCode !== 0 || + !completion || + completion.subtype !== "success" || + completion.is_error + ? completion?.errors?.join("; ") || + completion?.result || + "Claude did not finish successfully" + : undefined), + text: + completion?.result ?? + finalMessage?.message?.content + .filter((block) => block.type === "text") + .map((block) => block.text) + .join("\n") ?? + "", + // The result is already aggregated; summing message usage double-counts it. + usage: completion?.usage, + reportedCostUsd: completion?.total_cost_usd, + turns: completion?.num_turns, + modelDurationMs: completion?.duration_api_ms, + mcpCalls: [...calls.values()].map(({ id, name, input }) => ({ + id, + name: name?.startsWith(toolPrefix) ? name.slice(toolPrefix.length) : name, + input, + })), + mcpErrors: [...results.values()].filter((result) => result.is_error).length, + toolResultBytes: [...results.values()].reduce( + (sum, result) => + sum + Buffer.byteLength(JSON.stringify(result.content ?? null)), + 0, + ), + events, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json b/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json new file mode 100644 index 000000000..361dcc629 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json @@ -0,0 +1,2307 @@ +{ + "provenance": { + "openapiCommit": "803d6de98c9156c15e9ddc1bf9accbac6e24cebc", + "backendCommit": "c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4", + "sourceDashboard": "packages/mcp-server-mocks/src/fixtures/dashboard-details.json" + }, + "sourceDashboard": { + "id": "101", + "title": "Errors Overview", + "dateCreated": "2025-04-14T10:15:00.000Z", + "createdBy": { + "id": "1", + "name": "Test User", + "email": "user@example.com" + }, + "widgets": [ + { + "id": "201", + "title": "Handled Errors", + "description": "Handled errors over time", + "displayType": "line", + "thresholds": null, + "interval": "5m", + "dateCreated": "2025-04-14T10:16:00.000Z", + "dashboardId": "101", + "queries": [ + { + "id": "301", + "name": "", + "fields": ["count()"], + "aggregates": ["count()"], + "columns": [], + "fieldAliases": [], + "conditions": "error.handled:true", + "orderby": "-count()", + "widgetId": "201", + "onDemand": [], + "isHidden": false, + "selectedAggregate": 0, + "linkedDashboards": [] + } + ], + "limit": null, + "widgetType": "error-events", + "layout": { + "x": 0, + "y": 0, + "w": 6, + "h": 2, + "minH": 2 + }, + "axisRange": null, + "legendType": null, + "datasetSource": "user", + "changedReason": null + }, + { + "id": "202", + "title": "Top Issues", + "description": null, + "displayType": "table", + "thresholds": null, + "interval": "5m", + "dateCreated": "2025-04-14T10:18:00.000Z", + "dashboardId": "101", + "queries": [ + { + "id": "302", + "name": "Top Issues", + "fields": ["issue", "count()"], + "aggregates": ["count()"], + "columns": ["issue"], + "fieldAliases": [], + "conditions": "is:unresolved", + "orderby": "-count()", + "widgetId": "202", + "onDemand": [], + "isHidden": false, + "selectedAggregate": 0, + "linkedDashboards": [] + } + ], + "limit": 5, + "widgetType": "error-events", + "layout": { + "x": 0, + "y": 2, + "w": 6, + "h": 3, + "minH": 2 + }, + "axisRange": null, + "legendType": null, + "datasetSource": "user", + "changedReason": null + } + ], + "filters": { + "release": ["frontend@1.2.3"] + }, + "permissions": { + "isEditableByEveryone": true, + "teamsWithEditAccess": [] + }, + "isFavorited": true, + "projects": [101, 102], + "environment": ["production"], + "period": "24h", + "utc": true, + "expired": false, + "prebuiltId": null + }, + "openapi": { + "openapi": "3.0.3", + "info": { + "title": "Sentry Dashboard API eval fixture", + "version": "v0" + }, + "paths": { + "/api/0/organizations/{organization_id_or_slug}/dashboards/": { + "get": { + "operationId": "listOrganizationDashboards", + "description": "Retrieve a list of custom dashboards that are associated with the given organization.", + "summary": "List an Organization's Custom Dashboards", + "parameters": [ + { + "in": "path", + "name": "organization_id_or_slug", + "schema": { + "type": "string" + }, + "description": "The ID or slug of the organization the resource belongs to.", + "required": true + }, + { + "in": "query", + "name": "filter", + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "excludeFavorites", + "excludePrebuilt", + "onlyFavorites", + "onlyPrebuilt", + "owned", + "shared", + "showHidden" + ] + } + }, + "description": "Filter the dashboards returned. Repeat this parameter to apply multiple filters." + }, + { + "in": "query", + "name": "query", + "schema": { + "type": "string" + }, + "description": "Filter dashboards by title." + }, + { + "in": "query", + "name": "sort", + "schema": { + "type": "string" + }, + "description": "The property to sort results by. Prefix the value with `-` to sort in descending order.\n\nAvailable fields are:\n- `title`\n- `dateCreated`\n- `mostPopular`\n- `recentlyViewed`\n- `mydashboards`\n- `myDashboardsAndRecentlyViewed`\n" + }, + { + "in": "query", + "name": "pin", + "schema": { + "type": "string", + "enum": ["favorites"] + }, + "description": "Pin favorited dashboards to the top of the results." + }, + { + "in": "query", + "name": "per_page", + "schema": { + "type": "integer" + }, + "description": "Limit the number of rows to return in the result. Default and maximum allowed is 100." + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "string", + "minLength": 1 + }, + "description": "A pointer to the last object fetched and its sort order; used to retrieve the next or previous results.", + "x-learn-more": "https://docs.sentry.io/api/pagination/" + } + ], + "tags": ["Dashboards"], + "security": [ + { + "auth_token": ["org:admin", "org:read", "org:write"] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "dateCreated": { + "type": "string" + }, + "createdBy": { + "type": "object", + "properties": { + "identities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["name", "slug"] + }, + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }, + "dateVerified": { + "type": "string", + "format": "date-time" + }, + "dateSynced": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "dateSynced", + "dateVerified", + "id", + "name", + "organization", + "provider" + ] + } + }, + "avatar": { + "type": "object", + "properties": { + "avatarType": { + "type": "string" + }, + "avatarUuid": { + "type": "string", + "nullable": true + }, + "avatarUrl": { + "type": "string", + "nullable": true + } + } + }, + "authenticators": { + "type": "array", + "items": {} + }, + "canReset2fa": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "avatarUrl": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isSuspended": { + "type": "boolean" + }, + "hasPasswordAuth": { + "type": "boolean" + }, + "isManaged": { + "type": "boolean" + }, + "dateJoined": { + "type": "string", + "format": "date-time" + }, + "lastLogin": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "has2fa": { + "type": "boolean" + }, + "lastActive": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "isSuperuser": { + "type": "boolean" + }, + "isStaff": { + "type": "boolean" + }, + "experiments": { + "type": "object", + "additionalProperties": {} + }, + "emails": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "is_verified": { + "type": "boolean" + } + }, + "required": ["email", "id", "is_verified"] + } + } + }, + "required": [ + "avatarUrl", + "dateJoined", + "email", + "emails", + "experiments", + "has2fa", + "hasPasswordAuth", + "id", + "isActive", + "isManaged", + "isStaff", + "isSuperuser", + "isSuspended", + "lastActive", + "lastLogin", + "name", + "username" + ] + }, + "environment": { + "type": "array", + "items": { + "type": "string" + } + }, + "filters": { + "type": "object", + "properties": { + "release": { + "type": "array", + "items": { + "type": "string" + } + }, + "releaseId": { + "type": "array", + "items": { + "type": "string" + } + }, + "globalFilter": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + }, + "lastVisited": { + "type": "string", + "nullable": true + }, + "widgetDisplay": { + "type": "array", + "items": { + "type": "string" + } + }, + "widgetPreview": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "permissions": { + "type": "object", + "properties": { + "isEditableByEveryone": { + "type": "boolean" + }, + "teamsWithEditAccess": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "isEditableByEveryone", + "teamsWithEditAccess" + ], + "nullable": true + }, + "isFavorited": { + "type": "boolean" + }, + "projects": { + "type": "array", + "items": { + "type": "integer" + } + }, + "prebuiltId": { + "type": "integer", + "nullable": true + } + }, + "required": [ + "createdBy", + "dateCreated", + "environment", + "filters", + "id", + "isFavorited", + "lastVisited", + "permissions", + "prebuiltId", + "projects", + "title", + "widgetDisplay", + "widgetPreview" + ] + } + }, + "examples": { + "QueryDashboards": { + "value": [ + { + "id": "1", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "lastVisited": "2024-06-20T14:38:03.498574Z", + "projects": [1], + "environment": ["alpha"], + "filters": { + "release": [ + "frontend@a02311a400636ff9640b3e4ca2991ee153dbbdcc", + "frontend@36934c05140c16df93aa8ebf671f9386e916b501" + ] + }, + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@example.com", + "avatarUrl": "www.example.com", + "isActive": true, + "isSuspended": false, + "hasPasswordAuth": true, + "isManaged": false, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": true, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": false, + "isStaff": false, + "experiments": {}, + "emails": [ + { + "id": "1", + "email": "admin@example.com", + "is_verified": true + } + ], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": null, + "avatarUrl": "www.example.com" + } + }, + "widgetDisplay": [], + "widgetPreview": [], + "permissions": { + "isEditableByEveryone": true, + "teamsWithEditAccess": [] + }, + "isFavorited": false, + "prebuiltId": null + }, + { + "id": "2", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "lastVisited": "2024-06-20T14:38:03.498574Z", + "projects": [], + "environment": ["alpha"], + "filters": { + "release": [ + "frontend@a02311a400636ff9640b3e4ca2991ee153dbbdcc", + "frontend@36934c05140c16df93aa8ebf671f9386e916b501" + ] + }, + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@example.com", + "avatarUrl": "www.example.com", + "isActive": true, + "isSuspended": false, + "hasPasswordAuth": true, + "isManaged": false, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": true, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": false, + "isStaff": false, + "experiments": {}, + "emails": [ + { + "id": "1", + "email": "admin@example.com", + "is_verified": true + } + ], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": null, + "avatarUrl": "www.example.com" + } + }, + "widgetDisplay": [], + "widgetPreview": [], + "permissions": null, + "isFavorited": false, + "prebuiltId": null + } + ], + "summary": "Query Dashboards" + } + } + } + }, + "description": "" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + }, + "post": { + "operationId": "createOrganizationDashboard", + "description": "Create a new dashboard for the given Organization", + "summary": "Create a New Dashboard for an Organization", + "parameters": [ + { + "in": "path", + "name": "organization_id_or_slug", + "schema": { + "type": "string" + }, + "description": "The ID or slug of the organization the resource belongs to.", + "required": true + } + ], + "tags": ["Dashboards"], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "title": { + "type": "string", + "description": "The user defined title for this dashboard.", + "maxLength": 255 + }, + "id": { + "type": "string", + "description": "A dashboard's unique id." + }, + "widgets": { + "type": "array", + "items": { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string", + "maxLength": 255 + }, + "description": { + "type": "string", + "nullable": true + }, + "thresholds": { + "type": "object", + "additionalProperties": {}, + "nullable": true + }, + "display_type": { + "enum": [ + "line", + "area", + "bar", + "table", + "big_number", + "details", + "categorical_bar", + "wheel", + "rage_and_dead_clicks", + "server_tree", + "text", + "agents_traces_table", + "heatmap" + ], + "type": "string", + "description": "* `line`\n* `area`\n* `bar`\n* `table`\n* `big_number`\n* `details`\n* `categorical_bar`\n* `wheel`\n* `rage_and_dead_clicks`\n* `server_tree`\n* `text`\n* `agents_traces_table`\n* `heatmap`" + }, + "interval": { + "type": "string", + "maxLength": 10 + }, + "queries": { + "type": "array", + "items": { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "id": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "aggregates": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "columns": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "field_aliases": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "name": { + "type": "string" + }, + "conditions": { + "type": "string" + }, + "orderby": { + "type": "string" + }, + "is_hidden": { + "type": "boolean" + }, + "on_demand_extraction": { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "extraction_state": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + } + }, + "on_demand_extraction_disabled": { + "type": "boolean" + }, + "selected_aggregate": { + "type": "integer", + "nullable": true + }, + "linked_dashboards": { + "type": "array", + "items": { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "field": { + "type": "string" + }, + "dashboard_id": { + "type": "string" + } + }, + "required": ["dashboard_id", "field"] + }, + "nullable": true + } + } + } + }, + "widget_type": { + "enum": [ + "discover", + "issue", + "metrics", + "error-events", + "transaction-like", + "spans", + "logs", + "tracemetrics", + "preprod-app-size", + null + ], + "type": "string", + "description": "* `discover`\n* `issue`\n* `metrics`\n* `error-events`\n* `transaction-like`\n* `spans`\n* `logs`\n* `tracemetrics`\n* `preprod-app-size`", + "nullable": true + }, + "limit": { + "type": "integer", + "minimum": 1, + "nullable": true + }, + "layout": { + "allOf": [ + { + "type": "object", + "description": "Widget grid layout position and dimensions for dashboard creation.\n\nThe dashboard uses a 6-column grid. Required keys: x, y, w, h.\nConstraints: x (0-5), y (>= 0), w (1-6), h (>= 1), and x + w <= 6.", + "properties": { + "x": { + "type": "integer", + "maximum": 5, + "minimum": 0, + "description": "Column position (0-indexed)." + }, + "y": { + "type": "integer", + "minimum": 0, + "description": "Row position (0-indexed)." + }, + "w": { + "type": "integer", + "maximum": 6, + "minimum": 1, + "description": "Width in grid columns (1-6)." + }, + "h": { + "type": "integer", + "minimum": 1, + "description": "Height in grid rows." + } + }, + "required": ["h", "w", "x", "y"] + } + ], + "nullable": true + }, + "axis_range": { + "enum": ["auto", "dataMin", null], + "type": "string", + "description": "* `auto`\n* `dataMin`", + "nullable": true + }, + "legend_type": { + "enum": ["default", "breakdown", null], + "type": "string", + "description": "* `default`\n* `breakdown`", + "nullable": true + } + } + }, + "description": "A json list of widgets saved in this dashboard." + }, + "projects": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "The saved projects filter for this dashboard." + }, + "environment": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "The saved environment filter for this dashboard." + }, + "period": { + "type": "string", + "nullable": true, + "description": "The saved time range period for this dashboard." + }, + "start": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The saved start time for this dashboard." + }, + "end": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The saved end time for this dashboard." + }, + "filters": { + "type": "object", + "additionalProperties": {}, + "description": "The saved filters for this dashboard." + }, + "utc": { + "type": "boolean", + "description": "Setting that lets you display saved time range for this dashboard in UTC." + }, + "permissions": { + "allOf": [ + { + "type": "object", + "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", + "properties": { + "is_editable_by_everyone": { + "type": "boolean", + "description": "Whether the dashboard is editable by everyone." + }, + "teams_with_edit_access": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "List of team IDs that have edit access to a dashboard." + } + }, + "required": ["is_editable_by_everyone"] + } + ], + "nullable": true, + "description": "Permissions that restrict users from editing dashboards" + }, + "is_favorited": { + "type": "boolean", + "default": false, + "description": "Favorite the dashboard automatically for the request user" + } + }, + "required": ["title"] + } + } + }, + "required": true + }, + "security": [ + { + "auth_token": ["org:admin", "org:read", "org:write"] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "environment": { + "type": "array", + "items": { + "type": "string" + } + }, + "period": { + "type": "string" + }, + "utc": { + "type": "string" + }, + "expired": { + "type": "boolean" + }, + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "dateCreated": { + "type": "string" + }, + "createdBy": { + "type": "object", + "properties": { + "identities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["name", "slug"] + }, + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }, + "dateVerified": { + "type": "string", + "format": "date-time" + }, + "dateSynced": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "dateSynced", + "dateVerified", + "id", + "name", + "organization", + "provider" + ] + } + }, + "avatar": { + "type": "object", + "properties": { + "avatarType": { + "type": "string" + }, + "avatarUuid": { + "type": "string", + "nullable": true + }, + "avatarUrl": { + "type": "string", + "nullable": true + } + } + }, + "authenticators": { + "type": "array", + "items": {} + }, + "canReset2fa": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "avatarUrl": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isSuspended": { + "type": "boolean" + }, + "hasPasswordAuth": { + "type": "boolean" + }, + "isManaged": { + "type": "boolean" + }, + "dateJoined": { + "type": "string", + "format": "date-time" + }, + "lastLogin": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "has2fa": { + "type": "boolean" + }, + "lastActive": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "isSuperuser": { + "type": "boolean" + }, + "isStaff": { + "type": "boolean" + }, + "experiments": { + "type": "object", + "additionalProperties": {} + }, + "emails": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "is_verified": { + "type": "boolean" + } + }, + "required": ["email", "id", "is_verified"] + } + } + }, + "required": [ + "avatarUrl", + "dateJoined", + "email", + "emails", + "experiments", + "has2fa", + "hasPasswordAuth", + "id", + "isActive", + "isManaged", + "isStaff", + "isSuperuser", + "isSuspended", + "lastActive", + "lastLogin", + "name", + "username" + ], + "nullable": true + }, + "widgets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "displayType": { + "type": "string" + }, + "thresholds": { + "type": "object", + "properties": { + "preferredPolarity": { + "type": "string" + }, + "timeWindow": { + "type": "string" + }, + "max_values": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "unit": { + "type": "string" + } + }, + "required": ["max_values", "unit"], + "nullable": true + }, + "interval": { + "type": "string" + }, + "dateCreated": { + "type": "string" + }, + "dashboardId": { + "type": "string" + }, + "queries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "aggregates": { + "type": "array", + "items": { + "type": "string" + } + }, + "columns": { + "type": "array", + "items": { + "type": "string" + } + }, + "fieldAliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "conditions": { + "type": "string" + }, + "orderby": { + "type": "string" + }, + "widgetId": { + "type": "string" + }, + "onDemand": { + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "extractionState": { + "type": "string" + }, + "dashboardWidgetQueryId": { + "type": "integer" + } + }, + "required": [ + "dashboardWidgetQueryId", + "enabled", + "extractionState" + ] + } + }, + "isHidden": { + "type": "boolean" + }, + "selectedAggregate": { + "type": "integer", + "nullable": true + }, + "linkedDashboards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "dashboardId": { + "type": "integer" + } + }, + "required": ["dashboardId", "field"] + } + } + }, + "required": [ + "aggregates", + "columns", + "conditions", + "fieldAliases", + "fields", + "id", + "isHidden", + "linkedDashboards", + "name", + "onDemand", + "orderby", + "selectedAggregate", + "widgetId" + ] + } + }, + "limit": { + "type": "integer", + "nullable": true + }, + "widgetType": { + "type": "string", + "nullable": true + }, + "layout": { + "type": "object", + "additionalProperties": { + "type": "integer" + }, + "nullable": true + }, + "axisRange": { + "type": "string", + "nullable": true + }, + "legendType": { + "enum": ["default", "breakdown", null], + "type": "string", + "nullable": true + }, + "datasetSource": { + "type": "string", + "nullable": true + }, + "exploreUrls": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "changedReason": { + "type": "array", + "items": { + "type": "object", + "properties": { + "orderby": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "nullable": true + }, + "equations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "nullable": true + }, + "selected_columns": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "equations", + "orderby", + "selected_columns" + ] + }, + "nullable": true + } + }, + "required": [ + "axisRange", + "changedReason", + "dashboardId", + "datasetSource", + "dateCreated", + "description", + "displayType", + "exploreUrls", + "id", + "interval", + "layout", + "legendType", + "limit", + "queries", + "thresholds", + "title", + "widgetType" + ] + } + }, + "projects": { + "type": "array", + "items": { + "type": "integer" + } + }, + "filters": { + "type": "object", + "properties": { + "release": { + "type": "array", + "items": { + "type": "string" + } + }, + "releaseId": { + "type": "array", + "items": { + "type": "string" + } + }, + "globalFilter": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + }, + "permissions": { + "type": "object", + "properties": { + "isEditableByEveryone": { + "type": "boolean" + }, + "teamsWithEditAccess": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "isEditableByEveryone", + "teamsWithEditAccess" + ], + "nullable": true + }, + "isFavorited": { + "type": "boolean" + }, + "prebuiltId": { + "type": "integer", + "nullable": true + } + }, + "required": [ + "createdBy", + "dateCreated", + "filters", + "id", + "isFavorited", + "permissions", + "prebuiltId", + "projects", + "title", + "widgets" + ] + }, + "examples": { + "CreateDashboard": { + "value": { + "id": "1", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@example.com", + "avatarUrl": "www.example.com", + "isActive": true, + "isSuspended": false, + "hasPasswordAuth": true, + "isManaged": false, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": true, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": false, + "isStaff": false, + "experiments": {}, + "emails": [ + { + "id": "1", + "email": "admin@example.com", + "is_verified": true + } + ], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": null, + "avatarUrl": "www.example.com" + } + }, + "widgets": [ + { + "id": "658714", + "title": "Custom Widget", + "description": null, + "displayType": "table", + "thresholds": null, + "interval": "5m", + "dateCreated": "2024-07-16T15:36:46.048343Z", + "dashboardId": "1", + "datasetSource": "user", + "queries": [ + { + "id": "1", + "name": "", + "fields": [ + "avg(transaction.duration)", + "transaction" + ], + "aggregates": ["avg(transaction.duration)"], + "columns": ["transaction"], + "fieldAliases": ["", ""], + "conditions": "", + "orderby": "-avg(transaction.duration)", + "widgetId": "1", + "onDemand": [ + { + "enabled": false, + "extractionState": "disabled:not-applicable", + "dashboardWidgetQueryId": 1 + } + ], + "isHidden": false, + "selectedAggregate": null, + "linkedDashboards": [] + } + ], + "limit": null, + "widgetType": "transaction-like", + "layout": { + "w": 2, + "y": 0, + "h": 2, + "minH": 2, + "x": 0 + }, + "exploreUrls": null, + "changedReason": null, + "axisRange": null, + "legendType": "default" + } + ], + "projects": [1], + "filters": {}, + "environment": ["alpha"], + "period": "7d", + "permissions": { + "isEditableByEveryone": true, + "teamsWithEditAccess": [] + }, + "isFavorited": false, + "prebuiltId": null + }, + "summary": "Create Dashboard" + } + } + } + }, + "description": "" + }, + "400": { + "description": "Bad Request" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + }, + "409": { + "description": "Conflict" + } + } + } + }, + "/api/0/organizations/{organization_id_or_slug}/dashboards/{dashboard_id}/": { + "get": { + "operationId": "getOrganizationDashboard", + "description": "Return details about an organization's custom dashboard.", + "summary": "Retrieve an Organization's Custom Dashboard", + "parameters": [ + { + "in": "path", + "name": "organization_id_or_slug", + "schema": { + "type": "string" + }, + "description": "The ID or slug of the organization the resource belongs to.", + "required": true + }, + { + "in": "path", + "name": "dashboard_id", + "schema": { + "type": "integer" + }, + "description": "The ID of the dashboard you'd like to retrieve.", + "required": true + } + ], + "tags": ["Dashboards"], + "security": [ + { + "auth_token": ["org:admin", "org:read", "org:write"] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "environment": { + "type": "array", + "items": { + "type": "string" + } + }, + "period": { + "type": "string" + }, + "utc": { + "type": "string" + }, + "expired": { + "type": "boolean" + }, + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "dateCreated": { + "type": "string" + }, + "createdBy": { + "type": "object", + "properties": { + "identities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["name", "slug"] + }, + "provider": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["id", "name"] + }, + "dateVerified": { + "type": "string", + "format": "date-time" + }, + "dateSynced": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "dateSynced", + "dateVerified", + "id", + "name", + "organization", + "provider" + ] + } + }, + "avatar": { + "type": "object", + "properties": { + "avatarType": { + "type": "string" + }, + "avatarUuid": { + "type": "string", + "nullable": true + }, + "avatarUrl": { + "type": "string", + "nullable": true + } + } + }, + "authenticators": { + "type": "array", + "items": {} + }, + "canReset2fa": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "avatarUrl": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isSuspended": { + "type": "boolean" + }, + "hasPasswordAuth": { + "type": "boolean" + }, + "isManaged": { + "type": "boolean" + }, + "dateJoined": { + "type": "string", + "format": "date-time" + }, + "lastLogin": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "has2fa": { + "type": "boolean" + }, + "lastActive": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "isSuperuser": { + "type": "boolean" + }, + "isStaff": { + "type": "boolean" + }, + "experiments": { + "type": "object", + "additionalProperties": {} + }, + "emails": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "is_verified": { + "type": "boolean" + } + }, + "required": ["email", "id", "is_verified"] + } + } + }, + "required": [ + "avatarUrl", + "dateJoined", + "email", + "emails", + "experiments", + "has2fa", + "hasPasswordAuth", + "id", + "isActive", + "isManaged", + "isStaff", + "isSuperuser", + "isSuspended", + "lastActive", + "lastLogin", + "name", + "username" + ], + "nullable": true + }, + "widgets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "displayType": { + "type": "string" + }, + "thresholds": { + "type": "object", + "properties": { + "preferredPolarity": { + "type": "string" + }, + "timeWindow": { + "type": "string" + }, + "max_values": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "unit": { + "type": "string" + } + }, + "required": ["max_values", "unit"], + "nullable": true + }, + "interval": { + "type": "string" + }, + "dateCreated": { + "type": "string" + }, + "dashboardId": { + "type": "string" + }, + "queries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "aggregates": { + "type": "array", + "items": { + "type": "string" + } + }, + "columns": { + "type": "array", + "items": { + "type": "string" + } + }, + "fieldAliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "conditions": { + "type": "string" + }, + "orderby": { + "type": "string" + }, + "widgetId": { + "type": "string" + }, + "onDemand": { + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "extractionState": { + "type": "string" + }, + "dashboardWidgetQueryId": { + "type": "integer" + } + }, + "required": [ + "dashboardWidgetQueryId", + "enabled", + "extractionState" + ] + } + }, + "isHidden": { + "type": "boolean" + }, + "selectedAggregate": { + "type": "integer", + "nullable": true + }, + "linkedDashboards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "dashboardId": { + "type": "integer" + } + }, + "required": ["dashboardId", "field"] + } + } + }, + "required": [ + "aggregates", + "columns", + "conditions", + "fieldAliases", + "fields", + "id", + "isHidden", + "linkedDashboards", + "name", + "onDemand", + "orderby", + "selectedAggregate", + "widgetId" + ] + } + }, + "limit": { + "type": "integer", + "nullable": true + }, + "widgetType": { + "type": "string", + "nullable": true + }, + "layout": { + "type": "object", + "additionalProperties": { + "type": "integer" + }, + "nullable": true + }, + "axisRange": { + "type": "string", + "nullable": true + }, + "legendType": { + "enum": ["default", "breakdown", null], + "type": "string", + "nullable": true + }, + "datasetSource": { + "type": "string", + "nullable": true + }, + "exploreUrls": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "changedReason": { + "type": "array", + "items": { + "type": "object", + "properties": { + "orderby": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "nullable": true + }, + "equations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "nullable": true + }, + "selected_columns": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "equations", + "orderby", + "selected_columns" + ] + }, + "nullable": true + } + }, + "required": [ + "axisRange", + "changedReason", + "dashboardId", + "datasetSource", + "dateCreated", + "description", + "displayType", + "exploreUrls", + "id", + "interval", + "layout", + "legendType", + "limit", + "queries", + "thresholds", + "title", + "widgetType" + ] + } + }, + "projects": { + "type": "array", + "items": { + "type": "integer" + } + }, + "filters": { + "type": "object", + "properties": { + "release": { + "type": "array", + "items": { + "type": "string" + } + }, + "releaseId": { + "type": "array", + "items": { + "type": "string" + } + }, + "globalFilter": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + } + } + }, + "permissions": { + "type": "object", + "properties": { + "isEditableByEveryone": { + "type": "boolean" + }, + "teamsWithEditAccess": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "isEditableByEveryone", + "teamsWithEditAccess" + ], + "nullable": true + }, + "isFavorited": { + "type": "boolean" + }, + "prebuiltId": { + "type": "integer", + "nullable": true + } + }, + "required": [ + "createdBy", + "dateCreated", + "filters", + "id", + "isFavorited", + "permissions", + "prebuiltId", + "projects", + "title", + "widgets" + ] + }, + "examples": { + "DashboardGETResponse": { + "value": { + "id": "1", + "title": "Dashboard", + "dateCreated": "2024-06-20T14:38:03.498574Z", + "createdBy": { + "id": "1", + "name": "Admin", + "username": "admin", + "email": "admin@example.com", + "avatarUrl": "www.example.com", + "isActive": true, + "isSuspended": false, + "hasPasswordAuth": true, + "isManaged": false, + "dateJoined": "2021-10-25T17:07:33.190596Z", + "lastLogin": "2024-07-16T15:28:39.261659Z", + "has2fa": true, + "lastActive": "2024-07-16T20:45:49.364197Z", + "isSuperuser": false, + "isStaff": false, + "experiments": {}, + "emails": [ + { + "id": "1", + "email": "admin@example.com", + "is_verified": true + } + ], + "avatar": { + "avatarType": "letter_avatar", + "avatarUuid": null, + "avatarUrl": "www.example.com" + } + }, + "widgets": [ + { + "id": "658714", + "title": "Custom Widget", + "description": null, + "displayType": "table", + "thresholds": null, + "interval": "5m", + "dateCreated": "2024-07-16T15:36:46.048343Z", + "dashboardId": "1", + "datasetSource": "user", + "queries": [ + { + "id": "1", + "name": "", + "fields": [ + "avg(transaction.duration)", + "transaction" + ], + "aggregates": ["avg(transaction.duration)"], + "columns": ["transaction"], + "fieldAliases": ["", ""], + "conditions": "", + "orderby": "-avg(transaction.duration)", + "widgetId": "1", + "onDemand": [ + { + "enabled": false, + "extractionState": "disabled:not-applicable", + "dashboardWidgetQueryId": 1 + } + ], + "isHidden": false, + "selectedAggregate": null, + "linkedDashboards": [] + } + ], + "limit": null, + "widgetType": "transaction-like", + "layout": { + "w": 2, + "y": 0, + "h": 2, + "minH": 2, + "x": 0 + }, + "exploreUrls": null, + "changedReason": null, + "axisRange": null, + "legendType": "default" + } + ], + "projects": [1], + "filters": {}, + "environment": ["alpha"], + "period": "7d", + "permissions": { + "isEditableByEveryone": true, + "teamsWithEditAccess": [] + }, + "isFavorited": false, + "prebuiltId": null + }, + "summary": "Dashboard GET response" + } + } + } + }, + "description": "" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + } + }, + "components": { + "securitySchemes": { + "auth_token": { + "type": "http", + "scheme": "bearer" + }, + "dsn": { + "type": "http", + "scheme": "DSN" + } + } + } + } +} diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts new file mode 100644 index 000000000..dfefc10a9 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts @@ -0,0 +1,227 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { createDashboardFixture } from "./dashboard-fixture"; +import fixtureData from "./dashboard-fixture-data.json" with { type: "json" }; + +type Fixture = Awaited>; +const active: Fixture[] = []; +const accessToken = "local-test-token"; +const dashboardResponse = z + .object({ + id: z.string(), + widgets: z.array( + z + .object({ + id: z.string(), + queries: z.array(z.object({ id: z.string() }).passthrough()), + }) + .passthrough(), + ), + }) + .passthrough(); + +afterEach(async () => { + await Promise.all(active.splice(0).map((fixture) => fixture.close())); +}); + +async function fixture() { + const instance = await createDashboardFixture({ accessToken }); + active.push(instance); + return instance; +} + +function request(fixture: Fixture, path: string, body?: unknown) { + return fetch(`${fixture.baseUrl}${path}`, { + method: body ? "POST" : "GET", + headers: { + authorization: `Bearer ${accessToken}`, + "content-type": "application/json", + }, + body: body ? JSON.stringify(body) : undefined, + }); +} + +function copyBody(projectId: number, title: string) { + const source = structuredClone(fixtureData.sourceDashboard); + return { + ...source, + title, + projects: [projectId], + environment: ["staging"], + widgets: source.widgets.map(({ id, ...widget }) => widget), + }; +} + +function snakeKeys(value: unknown): unknown { + if (Array.isArray(value)) return value.map(snakeKeys); + if (value === null || typeof value !== "object") return value; + return Object.fromEntries( + Object.entries(value).map(([key, value]) => [ + key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`), + snakeKeys(value), + ]), + ); +} + +describe("dashboard Code Mode eval fixture", () => { + it("returns dashboard list metadata with accurate widget previews", async () => { + const instance = await fixture(); + const response = await request( + instance, + `/api/0/organizations/${instance.organizationSlug}/dashboards/?query=Errors`, + ); + expect(response.status).toBe(200); + const dashboards = z + .array(z.record(z.string(), z.unknown())) + .parse(await response.json()); + expect(dashboards).toHaveLength(1); + expect(dashboards[0]).toMatchObject({ + id: "101", + title: "Errors Overview", + widgetDisplay: ["line", "table"], + widgetPreview: [ + { + displayType: "line", + layout: { x: 0, y: 0, w: 6, h: 2 }, + }, + { + displayType: "table", + layout: { x: 0, y: 2, w: 6, h: 3 }, + }, + ], + projects: [101, 102], + environment: ["production"], + }); + expect(dashboards[0]).not.toHaveProperty("widgets"); + }); + + it("persists copies made through the HTTP API using both supported key styles", async () => { + const instance = await fixture(); + const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; + const original = dashboardResponse.parse( + await (await request(instance, `${path}101/`)).json(), + ); + expect(original.widgets).toHaveLength(2); + + const firstResponse = await request( + instance, + path, + copyBody(101, "Frontend staging"), + ); + expect(firstResponse.status).toBe(201); + const first = dashboardResponse.parse(await firstResponse.json()); + const secondResponse = await request( + instance, + path, + snakeKeys(copyBody(102, "Backend staging")), + ); + expect(secondResponse.status).toBe(201); + const second = dashboardResponse.parse(await secondResponse.json()); + + expect((await request(instance, `${path}${first.id}/`)).status).toBe(200); + expect(first.widgets[0].id).not.toBe(original.widgets[0].id); + expect(first.widgets[0].queries[0].id).not.toBe( + original.widgets[0].queries[0].id, + ); + expect(await (await request(instance, `${path}101/`)).json()).toEqual( + original, + ); + expect( + instance.verify( + [ + `ID ${first.id}: https://${instance.organizationSlug}.sentry.io/dashboard/${first.id}/`, + `ID ${second.id}: https://sentry.io/organizations/${instance.organizationSlug}/dashboard/${second.id}/`, + ].join("\n"), + ), + ).toEqual({ success: true, dashboardIds: ["201", "202"] }); + + const discovery = await (await fetch(instance.openapiUrl)).json(); + expect(discovery).toMatchObject({ + paths: { + "/api/0/organizations/{organization_id_or_slug}/dashboards/": { + post: { requestBody: expect.any(Object) }, + }, + "/api/0/organizations/{organization_id_or_slug}/dashboards/{dashboard_id}/": + { + get: { responses: { "200": expect.any(Object) } }, + }, + }, + }); + expect((await request(instance, "/snapshot")).status).toBe(404); + + instance.reset(); + expect(instance.snapshot().dashboards).toHaveLength(1); + expect(instance.snapshot().requests).toEqual([]); + }); + + it("rejects missing authentication, foreign widget IDs and inaccessible projects without writing", async () => { + const instance = await fixture(); + const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; + expect( + ( + await fetch(`${instance.baseUrl}${path}`, { + method: "POST", + body: JSON.stringify(copyBody(101, "Frontend staging")), + }) + ).status, + ).toBe(401); + const copiedIds = await request( + instance, + path, + fixtureData.sourceDashboard, + ); + expect(copiedIds.status).toBe(400); + expect(await copiedIds.json()).toEqual({ + detail: "You cannot update widgets that are not part of this dashboard.", + }); + expect( + (await request(instance, path, copyBody(999, "Unavailable project"))) + .status, + ).toBe(400); + expect(instance.snapshot().dashboards).toHaveLength(1); + }); + + it("grades actual state, rejecting claims of success, lost queries and duplicate writes", async () => { + const instance = await fixture(); + const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; + const answer = [201, 202] + .map( + (id) => + `https://${instance.organizationSlug}.sentry.io/dashboard/${id}/`, + ) + .join("\n"); + expect(() => instance.verify(answer)).toThrow( + "Expected exactly two new dashboards", + ); + + const brokenCopy = copyBody(101, "Frontend staging"); + brokenCopy.widgets[0].queries[0].conditions = ""; + expect((await request(instance, path, brokenCopy)).status).toBe(201); + expect( + (await request(instance, path, copyBody(102, "Backend staging"))).status, + ).toBe(201); + expect(() => instance.verify(answer)).toThrow( + "widget configuration differs", + ); + + instance.reset(); + for (const [project, title] of [ + [101, "Frontend staging"], + [102, "Backend staging"], + ] as const) { + expect( + (await request(instance, path, copyBody(project, title))).status, + ).toBe(201); + } + expect(() => + instance.verify("Created both dashboards successfully"), + ).toThrow("final answer omits"); + expect(instance.verify(answer).success).toBe(true); + expect( + (await request(instance, path, copyBody(101, "Frontend staging"))).status, + ).toBe(201); + expect(() => instance.verify(answer)).toThrow( + "Expected exactly two new dashboards", + ); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts new file mode 100644 index 000000000..1897f2560 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts @@ -0,0 +1,413 @@ +import assert from "node:assert/strict"; +import { createServer, type IncomingMessage } from "node:http"; +import { z } from "zod"; +import fixtureData from "./dashboard-fixture-data.json" with { type: "json" }; + +const ORGANIZATION = "dashboard-evals"; +const TARGETS = [ + { projectId: 101, title: "Frontend staging" }, + { projectId: 102, title: "Backend staging" }, +] as const; +const DATE_CREATED = "2026-10-25T10:00:00.000Z"; + +const querySchema = z.object({ + fields: z.array(z.string()), + conditions: z.string(), + name: z.string().default(""), + aggregates: z.array(z.string()).nullable().default(null), + columns: z.array(z.string()).nullable().default(null), + fieldAliases: z.array(z.string()).nullable().default(null), + orderby: z.string().default(""), + isHidden: z.boolean().default(false), + selectedAggregate: z.number().int().nullable().default(null), + linkedDashboards: z.array(z.unknown()).default([]), +}); + +const layoutSchema = z + .object({ + x: z.number().int().min(0).max(5), + y: z.number().int().min(0), + w: z.number().int().min(1).max(6), + h: z.number().int().min(2), + }) + .refine((layout) => layout.x + layout.w <= 6, "x + w must not exceed 6"); + +const widgetSchema = z.object({ + id: z.string().optional(), + title: z.string().min(1).max(255), + description: z.string().nullable().default(null), + // This fixture only emulates the two display types present in its source. + displayType: z.enum(["line", "table"]), + widgetType: z.enum(["error-events"]).default("error-events"), + interval: z.string().max(10).default("5m"), + queries: z.array(querySchema).min(1), + layout: layoutSchema.nullable().default(null), + thresholds: z.record(z.string(), z.unknown()).nullable().default(null), + limit: z.number().int().min(1).nullable().default(null), + axisRange: z.enum(["auto", "dataMin"]).nullable().default(null), + legendType: z.enum(["default", "table"]).nullable().default(null), + datasetSource: z.enum(["user"]).default("user"), +}); + +const dashboardSchema = z.object({ + title: z.string().trim().min(1).max(255), + widgets: z.array(widgetSchema).default([]), + projects: z.array(z.coerce.number().int()).default([]), + environment: z.array(z.string()).nullable().default(null), + period: z.string().nullable().default(null), + filters: z.record(z.string(), z.unknown()).default({}), + utc: z.boolean().default(false), + isFavorited: z.boolean().default(false), + permissions: z + .object({ + isEditableByEveryone: z.boolean(), + teamsWithEditAccess: z.array(z.number()), + }) + .nullable() + .default(null), +}); + +type Dashboard = Omit, "widgets"> & { + id: string; + dateCreated: string; + createdBy: { id: string; name: string; email: string }; + widgets: Array< + Omit, "id" | "queries"> & { + id: string; + dashboardId: string; + dateCreated: string; + queries: Array< + z.infer & { + id: string; + widgetId: string; + onDemand: unknown[]; + } + >; + } + >; + prebuiltId: null; +}; + +export interface DashboardFixtureSnapshot { + dashboards: Dashboard[]; + requests: Array<{ method: string; path: string; status: number }>; +} + +class FixtureInputError extends Error {} + +// Mirror CamelSnakeSerializer's recursive aliases and collision detection. +// Unknown fields are subsequently stripped by the fixture's input schemas. +function camelKeys(value: unknown): unknown { + if (Array.isArray(value)) return value.map(camelKeys); + if (value === null || typeof value !== "object") return value; + const result: Record = {}; + for (const [key, item] of Object.entries(value)) { + const converted = key.replace(/_([a-z])/g, (_, letter: string) => + letter.toUpperCase(), + ); + if (Object.hasOwn(result, converted)) { + throw new FixtureInputError(`${key} collides with ${converted}`); + } + result[converted] = camelKeys(item); + } + return result; +} + +async function readJson(request: IncomingMessage): Promise { + const chunks: Buffer[] = []; + let size = 0; + for await (const chunk of request) { + size += chunk.length; + if (size > 256_000) + throw new FixtureInputError("Request body is too large"); + chunks.push(chunk); + } + try { + return JSON.parse(Buffer.concat(chunks).toString("utf8")); + } catch { + throw new FixtureInputError("Invalid JSON request body"); + } +} + +function sourceDashboard(): Dashboard { + const source = fixtureData.sourceDashboard; + const parsed = dashboardSchema.parse(source); + return { + ...parsed, + id: source.id, + dateCreated: source.dateCreated, + createdBy: structuredClone(source.createdBy), + prebuiltId: null, + widgets: parsed.widgets.map((widget, index) => ({ + ...widget, + id: source.widgets[index].id, + dashboardId: source.id, + dateCreated: source.widgets[index].dateCreated, + layout: widget.layout ? { ...widget.layout, minH: 2 } : null, + queries: widget.queries.map((query, queryIndex) => ({ + ...query, + id: source.widgets[index].queries[queryIndex].id, + widgetId: source.widgets[index].id, + onDemand: [], + })), + })), + }; +} + +function widgetSemantics(dashboard: Dashboard) { + return dashboard.widgets.map( + ({ id, dashboardId, dateCreated, queries, ...widget }) => ({ + ...widget, + queries: queries.map(({ id, widgetId, onDemand, ...query }) => query), + }), + ); +} + +/** + * Local stateful API substitute, not a full Sentry emulator. Its dashboard + * creation, IDs and aliases follow backend c4b7b5f; the complete three-operation + * OpenAPI slice is captured from sentry-api-schema 803d6de. It deliberately does + * not implement query-language validation, quotas, feature flags or real auth. + * Administrative state is only exposed to the runner, never as HTTP endpoints. + */ +export async function createDashboardFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const original = sourceDashboard(); + let dashboards = new Map([[original.id, structuredClone(original)]]); + let nextId = 201; + let requests: DashboardFixtureSnapshot["requests"] = []; + const collectionPath = `/api/0/organizations/${ORGANIZATION}/dashboards/`; + + const server = createServer(async (request, response) => { + const url = new URL(request.url ?? "/", "http://127.0.0.1"); + const method = request.method ?? "GET"; + const reply = (status: number, body: unknown) => { + requests.push({ method, path: url.pathname, status }); + response.writeHead(status, { "content-type": "application/json" }); + response.end(JSON.stringify(body)); + }; + + try { + if (method === "GET" && url.pathname === "/openapi.json") { + reply(200, fixtureData.openapi); + return; + } + if (request.headers.authorization !== `Bearer ${accessToken}`) { + reply(401, { detail: "Invalid token" }); + return; + } + if (url.pathname === collectionPath && method === "GET") { + const query = url.searchParams.get("query")?.toLowerCase() ?? ""; + reply( + 200, + [...dashboards.values()] + .filter((dashboard) => + dashboard.title.toLowerCase().includes(query), + ) + .map((dashboard) => ({ + id: dashboard.id, + title: dashboard.title, + dateCreated: dashboard.dateCreated, + createdBy: dashboard.createdBy, + environment: dashboard.environment, + filters: dashboard.filters, + lastVisited: null, + widgetDisplay: dashboard.widgets.map( + (widget) => widget.displayType, + ), + widgetPreview: dashboard.widgets.map( + ({ displayType, layout }) => ({ displayType, layout }), + ), + permissions: dashboard.permissions, + isFavorited: dashboard.isFavorited, + projects: dashboard.projects, + prebuiltId: dashboard.prebuiltId, + })), + ); + return; + } + if (url.pathname === collectionPath && method === "POST") { + const input = dashboardSchema.parse(camelKeys(await readJson(request))); + if ( + input.projects.some((id) => !TARGETS.some((t) => t.projectId === id)) + ) { + reply(400, { projects: ["Invalid project IDs"] }); + return; + } + // Backend update_widgets rejects IDs belonging to the original dashboard. + // Query IDs are ignored during create_widget; new query IDs are allocated. + if (input.widgets.some((widget) => widget.id)) { + reply(400, { + detail: + "You cannot update widgets that are not part of this dashboard.", + }); + return; + } + if (url.searchParams.has("validateOnly")) { + reply(200, null); + return; + } + const id = String(nextId++); + const existingTitles = new Set( + [...dashboards.values()].map((dashboard) => dashboard.title), + ); + let title = input.title; + for (let suffix = 1; existingTitles.has(title); suffix++) { + title = `${input.title} (${suffix})`; + } + const created: Dashboard = { + ...input, + title, + id, + dateCreated: DATE_CREATED, + createdBy: structuredClone(original.createdBy), + prebuiltId: null, + widgets: input.widgets.map((widget, index) => { + const widgetId = `${id}${index + 1}`; + return { + ...widget, + id: widgetId, + dashboardId: id, + dateCreated: DATE_CREATED, + layout: widget.layout ? { ...widget.layout, minH: 2 } : null, + queries: widget.queries.map((query, queryIndex) => ({ + ...query, + id: `${widgetId}${queryIndex + 1}`, + widgetId, + onDemand: [], + })), + }; + }), + }; + dashboards.set(id, created); + reply(201, created); + return; + } + if (method === "GET" && url.pathname.startsWith(collectionPath)) { + const id = url.pathname.slice(collectionPath.length).replace(/\/$/, ""); + const dashboard = dashboards.get(id); + reply(dashboard ? 200 : 404, dashboard ?? { detail: "Not found" }); + return; + } + reply(404, { detail: "Not found in the dashboard eval fixture" }); + } catch (error) { + if (error instanceof z.ZodError) { + reply(400, { detail: "Invalid dashboard", errors: error.issues }); + } else if (error instanceof FixtureInputError) { + reply(400, { detail: error.message }); + } else { + reply(500, { detail: "Fixture internal error" }); + } + } + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", resolve); + }); + const address = server.address(); + assert(address && typeof address !== "string"); + const baseUrl = `http://127.0.0.1:${address.port}`; + const prompt = [ + `In organization ${ORGANIZATION}, copy dashboard ${original.id} (${original.title}) into exactly two new dashboards.`, + `The original is https://${ORGANIZATION}.sentry.io/dashboard/${original.id}/.`, + ...TARGETS.map( + (target) => + `Create "${target.title}" with only project ID ${target.projectId} saved in the dashboard.`, + ), + 'Save only the environment "staging" in each copy.', + "Preserve the original widgets, their queries, order and layout, and the other saved dashboard filters and time range.", + "Leave the original dashboard unchanged. Return the IDs and Sentry dashboard links for the two new dashboards.", + ].join("\n"); + + return { + baseUrl, + openapiUrl: `${baseUrl}/openapi.json`, + organizationSlug: ORGANIZATION, + projectIds: TARGETS.map((target) => target.projectId), + prompt, + snapshot(): DashboardFixtureSnapshot { + return structuredClone({ + dashboards: [...dashboards.values()], + requests, + }); + }, + reset() { + dashboards = new Map([[original.id, structuredClone(original)]]); + nextId = 201; + requests = []; + }, + verify(resultText: string) { + assert.deepEqual( + dashboards.get(original.id), + original, + "Original dashboard changed", + ); + const copies = [...dashboards.values()].filter( + (item) => item.id !== original.id, + ); + assert.equal(copies.length, 2, "Expected exactly two new dashboards"); + for (const target of TARGETS) { + const copy = copies.find((item) => item.title === target.title); + assert(copy, `Missing dashboard: ${target.title}`); + assert.deepEqual( + copy.projects, + [target.projectId], + `${target.title}: projects differ`, + ); + assert.deepEqual( + copy.environment, + ["staging"], + `${target.title}: environment differs`, + ); + assert.deepEqual( + widgetSemantics(copy), + widgetSemantics(original), + `${target.title}: widget configuration differs`, + ); + assert.deepEqual( + copy.filters, + original.filters, + `${target.title}: filters differ`, + ); + assert.equal( + copy.period, + original.period, + `${target.title}: time range differs`, + ); + assert.equal( + copy.utc, + original.utc, + `${target.title}: UTC setting differs`, + ); + const urls = resultText.match(/https:\/\/[^\s<>"'`\])]+/g) ?? []; + assert( + urls.some((value) => { + const url = new URL(value); + return ( + (url.hostname === `${ORGANIZATION}.sentry.io` && + url.pathname === `/dashboard/${copy.id}/`) || + (url.hostname === "sentry.io" && + url.pathname === + `/organizations/${ORGANIZATION}/dashboard/${copy.id}/`) + ); + }), + `${target.title}: final answer omits its actual dashboard link and ID`, + ); + } + return { + success: true as const, + dashboardIds: copies.map((copy) => copy.id), + }; + }, + async close() { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + server.closeAllConnections(); + }); + }, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/json-answer.test.ts b/packages/mcp-server-evals/src/code-mode/json-answer.test.ts new file mode 100644 index 000000000..867b8173a --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/json-answer.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "vitest"; +import { parseEvalJsonAnswer } from "./json-answer"; + +describe("parseEvalJsonAnswer", () => { + test("uses the fenced answer when later prose contains API paths and JSON examples", () => { + const text = [ + "Done. Verified by re-reading the alert after the write:", + "", + "```json", + '{"id": "701", "enabled": false, "frequency": 60}', + "```", + "", + 'Preserved unchanged: monitor connection (`detectorIds: ["901"]`).', + "", + 'Note: alerts are exposed as `workflows` in the API (`PUT /organizations/{org}/workflows/701/`), and frequency lives in `config.frequency` in minutes. The spec\'s config example shows `{"frequency":3600}`. No other alerts were touched.', + ].join("\n"); + + expect(parseEvalJsonAnswer(text)).toEqual({ + id: "701", + enabled: false, + frequency: 60, + }); + }); + + test.each([ + '{"id":"701"}', + 'Saved values: {"id":"701"}. Verified.', + '```\n{"id":"701"}\n```', + '```json\n{"id":"701"}\n```', + ])("accepts the existing answer format: %s", (text) => { + expect(parseEvalJsonAnswer(text)).toEqual({ id: "701" }); + }); + + test.each([ + '```json\n{"id":}\n```\nCorrection: {"id":"701"}', + '```json\n{"id":"701"}\n```\n```json\n{"id":"702"}\n```', + '```json\n{"id":"701"}\n```\n```\n{"id":"701"}\n```', + '```json\n{"id":"701"}', + '{"id":}', + 'First: {"id":"701"}. Second: {"id":"702"}.', + "No saved values are available.", + ])("rejects malformed, ambiguous, or absent answers: %s", (text) => { + expect(() => parseEvalJsonAnswer(text)).toThrow(SyntaxError); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/json-answer.ts b/packages/mcp-server-evals/src/code-mode/json-answer.ts new file mode 100644 index 000000000..e3c80f12c --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/json-answer.ts @@ -0,0 +1,17 @@ +export function parseEvalJsonAnswer(text: string): unknown { + const fenced = text.split("```"); + if (fenced.length > 1) { + if (fenced.length !== 3) { + throw new SyntaxError("Final answer must contain exactly one JSON fence"); + } + // Prefer the explicit answer over braces in surrounding explanations. + return JSON.parse(fenced[1].trim().replace(/^json\b\s*/i, "")); + } + + const start = text.indexOf("{"); + const end = text.lastIndexOf("}"); + if (start === -1 || end < start) { + throw new SyntaxError("Final answer must include the saved values as JSON"); + } + return JSON.parse(text.slice(start, end + 1)); +} diff --git a/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts new file mode 100644 index 000000000..2a52acca7 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts @@ -0,0 +1,298 @@ +import tools from "@sentry/mcp-core/tools"; +import { afterEach, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { createMonitorFixture } from "./monitor-fixture"; + +const { find_monitors: findMonitors, get_monitor_details: getMonitorDetails } = + tools; +type Fixture = Awaited>; +const active: Fixture[] = []; +const accessToken = "local-monitor-test-token"; +const edit = { + config: { schedule: "30 9 * * 1-5", timezone: "Europe/Madrid" }, +}; +const finalAnswer = JSON.stringify({ slug: "nightly-billing", ...edit.config }); + +afterEach(async () => { + await Promise.all(active.splice(0).map((instance) => instance.close())); +}); + +async function fixture() { + const instance = await createMonitorFixture({ accessToken }); + active.push(instance); + return instance; +} + +function request( + instance: Fixture, + body?: unknown, + reference = "nightly-billing", + projectScoped = false, +) { + const prefix = projectScoped + ? `projects/${instance.organizationSlug}/billing` + : `organizations/${instance.organizationSlug}`; + return fetch(`${instance.baseUrl}/api/0/${prefix}/monitors/${reference}/`, { + method: body === undefined ? "GET" : "PUT", + headers: { + authorization: `Bearer ${accessToken}`, + "content-type": "application/json", + }, + body: body === undefined ? undefined : JSON.stringify(body), + }); +} + +describe("cron monitor Code Mode eval fixture", () => { + it("runs the real catalog monitor handlers against the fixture", async () => { + const instance = await fixture(); + const context = { + accessToken, + sentryHost: new URL(instance.baseUrl).host, + sentryProtocol: "http" as const, + constraints: {}, + }; + const list = await findMonitors.handler( + z.object(findMonitors.inputSchema).parse({ + organizationSlug: instance.organizationSlug, + projectSlug: "billing", + query: "weekday", + }), + context, + ); + expect(list).toMatchObject({ + structuredContent: { + hasMore: false, + monitors: [ + { + slug: "nightly-billing", + status: "disabled", + projectSlug: "billing", + }, + ], + }, + }); + const detail = await getMonitorDetails.handler( + z.object(getMonitorDetails.inputSchema).parse({ + organizationSlug: instance.organizationSlug, + projectSlugOrId: "billing", + monitorSlug: "nightly-billing", + }), + context, + ); + expect(detail).toContain("**Slug**: nightly-billing"); + expect(detail).toContain("**Status**: disabled"); + expect(detail).toContain("**timezone**: UTC"); + expect( + instance.snapshot().requests.every((request) => request.status === 200), + ).toBe(true); + expect(instance.snapshot().writes).toEqual([]); + }); + + it("serves discovery and the supplementary reads used by ordinary monitor tools", async () => { + const instance = await fixture(); + const paths = [ + "/api/0/organizations/monitor-evals/monitors/?projectSlug=billing&query=weekday&per_page=11", + "/api/0/projects/monitor-evals/billing/", + "/api/0/projects/monitor-evals/billing/monitors/nightly-billing/", + "/api/0/projects/monitor-evals/billing/monitors/nightly-billing/checkins/", + "/api/0/organizations/monitor-evals/monitors/nightly-billing/stats/", + "/api/0/organizations/monitor-evals/monitors/nightly-billing/?expand=alertRule", + ]; + const responses = await Promise.all( + paths.map((path) => + fetch(`${instance.baseUrl}${path}`, { + headers: { authorization: `Bearer ${accessToken}` }, + }), + ), + ); + expect(responses.map((response) => response.status)).toEqual([ + 200, 200, 200, 200, 200, 200, + ]); + const [list, project, monitor, checkins, stats, expanded] = + await Promise.all(responses.map((response) => response.json())); + expect(list).toEqual([monitor]); + expect(project).toMatchObject({ id: "101", slug: "billing" }); + expect(monitor).toMatchObject({ + slug: "nightly-billing", + status: "disabled", + isMuted: false, + }); + expect(monitor).not.toHaveProperty("alertRule"); + expect(expanded).toEqual(instance.snapshot().monitors[0]); + expect(checkins).toEqual([]); + expect(stats).toEqual([]); + expect(instance.snapshot().writes).toEqual([]); + }); + + it("merges a partial config without applying omitted defaults or changing mixed environment mute state", async () => { + const instance = await fixture(); + const before = instance.snapshot(); + expect( + (await request(instance, edit, "nightly-billing", true)).status, + ).toBe(200); + const updated = await (await request(instance)).json(); + expect(updated).toMatchObject({ + status: "disabled", + owner: before.monitors[0].owner, + config: { ...before.monitors[0].config, ...edit.config }, + environments: before.monitors[0].environments, + }); + expect(instance.verify(finalAnswer)).toMatchObject({ + success: true, + ...edit.config, + }); + expect(instance.snapshot().monitors[1]).toEqual(before.monitors[1]); + expect(instance.snapshot().writes).toHaveLength(1); + instance.reset(); + expect(instance.snapshot()).toEqual(before); + }); + + it("finds monitors using backend-supported quoted phrases and name filters", async () => { + const instance = await fixture(); + for (const query of [ + '"Billing weekday import"', + 'name:"Billing weekday import"', + 'weekday name:"Billing weekday import"', + ]) { + const response = await fetch( + `${instance.baseUrl}/api/0/organizations/monitor-evals/monitors/?${new URLSearchParams({ query })}`, + { headers: { authorization: `Bearer ${accessToken}` } }, + ); + expect(response.status).toBe(200); + expect(await response.json()).toMatchObject([ + { slug: "nightly-billing" }, + ]); + } + }); + + it("accepts equivalent weekday expressions and requires the answer to reflect the actual saved schedule", async () => { + const instance = await fixture(); + for (const schedule of [ + "30 9 * * 1,2,3,4,5", + "30 09 * * MON-FRI", + "30 9 * * MON,TUE,WED,THU,FRI", + "30 9 * * 1-5/1", + ]) { + expect( + (await request(instance, { config: { ...edit.config, schedule } })) + .status, + ).toBe(200); + expect( + instance.verify( + JSON.stringify({ + slug: "nightly-billing", + schedule, + timezone: "Europe/Madrid", + }), + ), + ).toMatchObject({ success: true, schedule }); + expect(() => instance.verify(finalAnswer)).toThrow( + "Final answer does not match the saved monitor values", + ); + } + expect( + (await request(instance, { config: { schedule: "30 9 * * 1-6" } })) + .status, + ).toBe(200); + expect(() => instance.verify(finalAnswer)).toThrow( + "Target monitor schedule is incorrect", + ); + }); + + it("rejects GET-shaped owner/project objects while accepting scalar references and camelCase config", async () => { + const instance = await fixture(); + const before = instance.snapshot().monitors[0]; + expect( + ( + await request(instance, { + ...before, + config: { ...before.config, ...edit.config }, + }) + ).status, + ).toBe(400); + expect(instance.snapshot().writes).toEqual([]); + expect( + ( + await request(instance, { + project: "101", + owner: "team:42", + config: { ...edit.config, scheduleType: "crontab", maxRuntime: 45 }, + }) + ).status, + ).toBe(200); + expect(instance.verify(finalAnswer).success).toBe(true); + }); + + it("detects the side effect of resending aggregate isMuted on mixed environments", async () => { + const instance = await fixture(); + expect((await request(instance, { ...edit, isMuted: false })).status).toBe( + 200, + ); + expect( + instance.snapshot().monitors[0].environments.map((env) => env.isMuted), + ).toEqual([false, false]); + expect(() => instance.verify(finalAnswer)).toThrow( + "Unrequested monitor settings changed", + ); + }); + + it("rejects temporarily clearing a threshold even if restored before the final answer", async () => { + const instance = await fixture(); + expect( + ( + await request(instance, { + config: { ...edit.config, failure_issue_threshold: null }, + }) + ).status, + ).toBe(200); + expect( + (await request(instance, { config: { failure_issue_threshold: 3 } })) + .status, + ).toBe(200); + expect(instance.snapshot().monitors[0].config.failure_issue_threshold).toBe( + 3, + ); + expect(() => instance.verify(finalAnswer)).toThrow( + "Write 1: unrequested monitor settings changed during the operation", + ); + }); + + it("rejects changes to another monitor and incorrect reported saved values", async () => { + const instance = await fixture(); + expect((await request(instance, edit)).status).toBe(200); + expect(() => + instance.verify( + JSON.stringify({ + slug: "nightly-billing", + schedule: "0 2 * * *", + timezone: "UTC", + }), + ), + ).toThrow("Final answer does not match the saved monitor values"); + expect(() => instance.verify("Done")).toThrow( + "Final answer must include the saved values as JSON", + ); + expect( + (await request(instance, { status: "active" }, "billing-export")).status, + ).toBe(200); + expect(() => instance.verify(finalAnswer)).toThrow( + "Unrelated monitor changed", + ); + }); + + it("rejects invented success and invalid writes without changing fixture state", async () => { + const instance = await fixture(); + const before = instance.snapshot().monitors; + expect(() => instance.verify(finalAnswer)).toThrow( + "Target monitor schedule is incorrect", + ); + for (const body of [ + { config: { timezone: "Invalid/Timezone" } }, + { project: "different-project" }, + { isMuted: false, is_muted: true }, + ]) + expect((await request(instance, body)).status).toBe(400); + expect(instance.snapshot().monitors).toEqual(before); + expect(instance.snapshot().writes).toEqual([]); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts b/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts new file mode 100644 index 000000000..c509ee4ca --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts @@ -0,0 +1,491 @@ +import assert from "node:assert/strict"; +import { z } from "zod"; +import { startApiFixture } from "./api-fixture"; +import { parseEvalJsonAnswer } from "./json-answer"; + +const ORGANIZATION = "monitor-evals"; +const MONITOR_ID = "a0000000-0000-4000-8000-000000000001"; +const OTHER_MONITOR_ID = "a0000000-0000-4000-8000-000000000002"; +const SCHEDULE = "30 9 * * 1-5"; +const TIMEZONE = "Europe/Madrid"; +const project = { + id: "101", + slug: "billing", + name: "Billing", + platform: "python", +}; +const owner = { type: "team", id: "42", name: "Billing on-call" }; + +const configSchema = z.object({ + schedule_type: z.enum(["crontab", "interval"]).optional(), + schedule: z + .union([ + z.string(), + z.tuple([ + z.number().int().positive(), + z.enum(["year", "month", "week", "day", "hour", "minute"]), + ]), + ]) + .optional(), + timezone: z + .string() + .refine((value) => { + if (!value) return true; + try { + new Intl.DateTimeFormat("en", { timeZone: value }); + return true; + } catch { + return false; + } + }) + .optional(), + checkin_margin: z.number().int().min(1).max(40320).nullable().optional(), + max_runtime: z.number().int().min(1).max(10080).nullable().optional(), + failure_issue_threshold: z + .number() + .int() + .min(1) + .max(720) + .nullable() + .optional(), + recovery_threshold: z.number().int().min(1).max(720).nullable().optional(), +}); +const updateSchema = z.object({ + project: z.union([z.string(), z.number().int()]).optional(), + name: z.string().min(1).max(128).optional(), + slug: z + .string() + .min(1) + .max(50) + .regex(/^(?![0-9]+$)[a-z0-9_-]+$/) + .optional(), + status: z.enum(["active", "disabled"]).optional(), + owner: z + .string() + .regex(/^(team|user):\d+$/) + .nullable() + .optional(), + is_muted: z.boolean().optional(), + config: configSchema.optional(), + alert_rule: z + .object({ + environment: z.string().nullable().optional(), + targets: z.array( + z.object({ + target_identifier: z.number().int(), + target_type: z.string(), + }), + ), + }) + .optional(), +}); +const answerSchema = z.object({ + slug: z.string(), + schedule: z.string(), + timezone: z.string(), +}); + +// Grade the requested days, not one spelling of an equivalent crontab. +function isRequestedSchedule(schedule: Monitor["config"]["schedule"]) { + if (typeof schedule !== "string") return false; + const [minute, hour, day, month, weekdays, extra] = schedule + .trim() + .split(/\s+/); + if ( + extra || + !/^0*30$/.test(minute) || + !/^0*9$/.test(hour) || + day !== "*" || + month !== "*" || + !weekdays + ) + return false; + const names = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; + const asDay = (value: string) => + /^\d+$/.test(value) ? Number(value) : names.indexOf(value.toUpperCase()); + const days = new Set(); + for (const part of weekdays.split(",")) { + const [range, stepText, extraStep] = part.split("/"); + const step = stepText === undefined ? 1 : Number(stepText); + const [first, last = first, extraDay] = range.split("-"); + const start = asDay(first), + end = asDay(last); + if ( + extraStep || + extraDay || + !Number.isInteger(step) || + step < 1 || + start < 0 || + end > 7 || + end < start + ) + return false; + for (let value = start; value <= end; value += step) days.add(value % 7); + } + return days.size === 5 && [1, 2, 3, 4, 5].every((day) => days.has(day)); +} + +// This fixture models the free-text and name filters used for this scenario, +// including quoted phrases; other query-language fields are outside its scope. +function matchesQuery(monitor: Monitor, query: string) { + const names: string[] = []; + const words: string[] = []; + for (const token of query.match(/(?:\w+:)?"[^"]*"|\S+/g) ?? []) { + if (token.startsWith("name:")) + names.push(token.slice(5).replace(/^"|"$/g, "")); + else if (token.includes(":")) return false; + else words.push(token.replace(/^"|"$/g, "")); + } + const text = words.join(" "); + return ( + (!names.length || names.includes(monitor.name.toLowerCase())) && + (!text || + monitor.name.toLowerCase().includes(text) || + monitor.slug.toLowerCase().includes(text)) + ); +} + +// MonitorValidator accepts camelCase aliases recursively and rejects collisions. +function snakeKeys(value: unknown): unknown { + if (Array.isArray(value)) return value.map(snakeKeys); + if (!value || typeof value !== "object") return value; + const result: Record = {}; + for (const [key, item] of Object.entries(value)) { + const converted = key.replace( + /[A-Z]/g, + (letter) => `_${letter.toLowerCase()}`, + ); + if (Object.hasOwn(result, converted)) + throw new SyntaxError(`Duplicate field ${converted}`); + result[converted] = snakeKeys(item); + } + return result; +} + +function sourceMonitor(id: string, slug: string, name: string) { + return { + id, + slug, + name, + status: "disabled" as "active" | "disabled", + isMuted: false, + isUpserting: false, + dateCreated: "2026-10-20T02:00:00.000Z", + project, + owner: owner as typeof owner | null, + config: { + schedule_type: "crontab" as "crontab" | "interval", + schedule: "0 2 * * *" as string | [number, string], + timezone: "UTC", + checkin_margin: 10 as number | null, + max_runtime: 45 as number | null, + failure_issue_threshold: 3 as number | null, + recovery_threshold: 2 as number | null, + alert_rule_id: 501, + }, + environments: [ + { + name: "production", + status: "ok", + isMuted: false, + dateCreated: "2026-10-20T02:00:00.000Z", + lastCheckIn: "2026-10-25T02:00:00.000Z", + activeIncident: null, + }, + { + name: "staging", + status: "ok", + isMuted: true, + dateCreated: "2026-10-20T02:00:00.000Z", + lastCheckIn: "2026-10-25T02:00:00.000Z", + activeIncident: null, + }, + ], + alertRule: { + environment: "production" as string | null, + targets: [{ targetIdentifier: 42, targetType: "Team" }], + }, + }; +} +type Monitor = ReturnType; + +/** + * Synthetic monitor GET/PUT state based on MonitorValidator's partial update. + * Models config merging and mute propagation, not quotas, actor permissions, + * crontab execution, next-check-in recomputation or notification delivery. + */ +export async function createMonitorFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const original = sourceMonitor( + MONITOR_ID, + "nightly-billing", + "Billing weekday import", + ); + const other = sourceMonitor( + OTHER_MONITOR_ID, + "billing-export", + "Billing export", + ); + const initial = [original, other]; + let monitors = new Map( + initial.map((monitor) => [monitor.id, structuredClone(monitor)]), + ); + const writes: Array<{ monitorId: string; monitors: Monitor[] }> = []; + const organizationPath = `/api/0/organizations/${ORGANIZATION}/`; + const collectionPath = `${organizationPath}monitors/`; + const projectPaths = [project.slug, project.id].map( + (id) => `/api/0/projects/${ORGANIZATION}/${id}/`, + ); + + const serialize = (monitor: Monitor, url: URL) => { + const { alertRule, ...body } = structuredClone(monitor); + const environments = url.searchParams.getAll("environment"); + if (environments.length) + body.environments = body.environments.filter((env) => + environments.includes(env.name), + ); + return url.searchParams.getAll("expand").includes("alertRule") + ? { ...body, alertRule } + : body; + }; + const api = await startApiFixture(accessToken, ({ method, url, body }) => { + if (method === "GET" && projectPaths.includes(url.pathname)) + return { body: project }; + if (method === "GET" && url.pathname === `${organizationPath}projects/`) + return { body: [project] }; + if (method === "GET" && url.pathname === collectionPath) { + const query = (url.searchParams.get("query") ?? "").toLowerCase(); + const projectFilter = + url.searchParams.get("projectSlug") ?? url.searchParams.get("project"); + const ownerFilter = url.searchParams.get("owner"); + const items = [...monitors.values()].filter( + (monitor) => + matchesQuery(monitor, query) && + (!projectFilter || + ["-1", project.id, project.slug].includes(projectFilter)) && + (!ownerFilter || + `${monitor.owner?.type}:${monitor.owner?.id}` === ownerFilter), + ); + return { + body: items + .slice(0, Number(url.searchParams.get("per_page") ?? 100)) + .map((item) => serialize(item, url)), + }; + } + const prefix = [ + collectionPath, + ...projectPaths.map((path) => `${path}monitors/`), + ].find((path) => url.pathname.startsWith(path)); + if (!prefix) return undefined; + const [reference, subresource] = url.pathname + .slice(prefix.length) + .split("/"); + const current = [...monitors.values()].find( + (item) => item.id === reference || item.slug === reference, + ); + if (!current) return { status: 404, body: { detail: "Monitor not found" } }; + // get_monitor_details reads these alongside the monitor even when the user + // only needs configuration. Empty history is valid for a disabled monitor. + if (method === "GET" && ["checkins", "stats"].includes(subresource)) + return { body: [] }; + if (subresource) return undefined; + if (method === "GET") return { body: serialize(current, url) }; + if (method !== "PUT") return undefined; + const input = updateSchema.parse(snakeKeys(body)); + if ( + input.project !== undefined && + ![project.slug, project.id].includes(String(input.project)) + ) { + return { + status: 400, + body: { detail: "existing monitors may not be moved between projects" }, + }; + } + if ( + input.slug && + [...monitors.values()].some( + (item) => item.id !== current.id && item.slug === input.slug, + ) + ) { + return { status: 400, body: { slug: "The slug is already in use." } }; + } + const updated = structuredClone(current); + if (input.name !== undefined) updated.name = input.name; + if (input.slug !== undefined) updated.slug = input.slug; + if (input.status !== undefined) updated.status = input.status; + if (input.owner !== undefined) { + const [type, id] = input.owner?.split(":") ?? []; + updated.owner = + input.owner === null + ? null + : { + type, + id, + name: input.owner === "team:42" ? owner.name : `Owner ${id}`, + }; + } + if (input.config) { + const config = { ...input.config }; + if (config.timezone === "") delete config.timezone; + updated.config = { ...updated.config, ...config }; + const schedule = updated.config.schedule; + if ( + updated.config.schedule_type === "crontab" && + (typeof schedule !== "string" || + schedule.trim().split(/\s+/).length !== 5) + ) { + return { + status: 400, + body: { config: { schedule: "Invalid crontab schedule" } }, + }; + } + if ( + updated.config.schedule_type === "interval" && + !Array.isArray(schedule) + ) { + return { + status: 400, + body: { config: { schedule: "Invalid interval schedule" } }, + }; + } + } + if (input.is_muted !== undefined) { + updated.environments = updated.environments.map((env) => ({ + ...env, + isMuted: input.is_muted as boolean, + })); + updated.isMuted = input.is_muted; + } + if (input.alert_rule) { + updated.alertRule = { + environment: input.alert_rule.environment ?? null, + targets: input.alert_rule.targets.map((target) => ({ + targetIdentifier: target.target_identifier, + targetType: target.target_type, + })), + }; + } + monitors.set(updated.id, updated); + writes.push( + structuredClone({ + monitorId: updated.id, + monitors: [...monitors.values()], + }), + ); + return { body: serialize(updated, new URL(url.pathname, url.origin)) }; + }); + + return { + baseUrl: api.baseUrl, + organizationSlug: ORGANIZATION, + prompt: [ + `In organization ${ORGANIZATION}, project billing, change the cron monitor named "${original.name}" to run at 09:30 Monday through Friday in Europe/Madrid.`, + "Keep its name, slug, owner, enabled/disabled status, thresholds, notification settings and each environment's muted state unchanged throughout the operation. Leave all other monitors unchanged.", + "Return JSON with the saved values: {slug, schedule, timezone}.", + ].join("\n"), + cliCommands: [ + `sentry api organizations/${ORGANIZATION}/monitors/${original.slug}/ --method PUT --data '{"config":{"schedule":"${SCHEDULE}","timezone":"${TIMEZONE}"}}'`, + ], + provenance: { + cliRevision: "b625111aef9cc3a44abbed634a043fce8505366d", + cliSource: "packages/cli/src/commands/api.ts", + backendRevision: "b4b356554798716ceaeab74efdc095782be9ca16", + backendSources: [ + "src/sentry/monitors/endpoints/organization_monitor_details.py", + "src/sentry/monitors/endpoints/project_monitor_details.py", + "src/sentry/monitors/endpoints/base_monitor_details.py", + "src/sentry/monitors/endpoints/organization_monitor_index.py", + "src/sentry/monitors/validators.py", + "src/sentry/monitors/serializers.py", + "src/sentry/api/serializers/rest_framework/base.py", + "tests/sentry/monitors/endpoints/test_base_monitor_details.py", + ], + endpoints: [ + "GET /organizations/{organization_id_or_slug}/monitors/", + "GET|PUT /organizations/{organization_id_or_slug}/monitors/{monitor_id_or_slug}/", + "GET|PUT /projects/{organization_id_or_slug}/{project_id_or_slug}/monitors/{monitor_id_or_slug}/", + ], + limitations: + "No quota/actor authorization, scheduler, derived next-check-in timestamps or notification delivery simulation. Query support is free text and name filters. Cron validation only checks shape; the grader accepts equivalent weekday ranges, lists and names for the requested time.", + }, + reset() { + monitors = new Map( + initial.map((monitor) => [monitor.id, structuredClone(monitor)]), + ); + api.requests.length = 0; + writes.length = 0; + }, + snapshot() { + return structuredClone({ + monitors: [...monitors.values()], + requests: api.requests, + writes, + }); + }, + verify(resultText: string) { + const updated = monitors.get(MONITOR_ID); + assert(updated, "Target monitor is missing"); + assert( + isRequestedSchedule(updated.config.schedule), + "Target monitor schedule is incorrect", + ); + assert.equal( + updated.config.timezone, + TIMEZONE, + "Target monitor timezone is incorrect", + ); + const preserved = (target: Monitor) => ({ + ...original, + config: { + ...original.config, + schedule: target.config.schedule, + timezone: target.config.timezone, + }, + }); + assert.deepEqual( + updated, + preserved(updated), + "Unrequested monitor settings changed", + ); + assert.deepEqual( + monitors.get(OTHER_MONITOR_ID), + other, + "Unrelated monitor changed", + ); + for (const [index, write] of writes.entries()) { + const target = write.monitors.find((item) => item.id === MONITOR_ID); + assert(target, `Write ${index + 1}: target monitor is missing`); + assert.deepEqual( + target, + preserved(target), + `Write ${index + 1}: unrequested monitor settings changed during the operation`, + ); + assert.deepEqual( + write.monitors.find((item) => item.id === OTHER_MONITOR_ID), + other, + `Write ${index + 1}: another monitor changed during the operation`, + ); + } + assert.deepEqual( + answerSchema.parse(parseEvalJsonAnswer(resultText)), + { + slug: updated.slug, + schedule: updated.config.schedule, + timezone: TIMEZONE, + }, + "Final answer does not match the saved monitor values", + ); + return { + success: true as const, + monitorId: MONITOR_ID, + slug: updated.slug, + schedule: updated.config.schedule, + timezone: TIMEZONE, + }; + }, + close: api.close, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts new file mode 100644 index 000000000..0dbc20548 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts @@ -0,0 +1,333 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { createReleaseFixture } from "./release-fixture"; +import { createReleaseRecoveryFixture } from "./release-recovery-fixture"; + +type Fixture = Awaited>; +const active: Fixture[] = []; +const collection = "/api/0/organizations/release-evals/releases/"; +const version = "checkout@2.4.0"; +const releasePath = `${collection}${encodeURIComponent(version)}/`; +const released = "2026-10-26T10:00:00Z"; +const completeAnswer = { + version, + projects: ["frontend", "backend"], + dateReleased: released, + deploymentId: "601", +}; +const deployBody = { + environment: "staging", + name: "Staging rollout", + url: "https://deploy.example.test/runs/42", + dateStarted: "2026-10-26T10:02:00Z", + dateFinished: "2026-10-26T10:04:00Z", +}; + +afterEach(async () => { + await Promise.all(active.splice(0).map((fixture) => fixture.close())); +}); + +async function fixture(create = createReleaseFixture) { + const instance = await create(); + active.push(instance); + return instance; +} + +function request( + instance: Fixture, + method: string, + path: string, + body?: unknown, +) { + return fetch(`${instance.baseUrl}${path}`, { + method, + headers: { + authorization: "Bearer code-mode-eval-token", + "content-type": "application/json", + }, + body: body === undefined ? undefined : JSON.stringify(body), + }); +} + +describe("release Code Mode eval fixture", () => { + it("persists the CLI create/finalize/deploy flow and exposes normal catalog read routes", async () => { + const instance = await fixture(); + const created = await request(instance, "POST", collection, { + version, + projects: ["frontend", "backend"], + }); + expect(created.status).toBe(201); + expect(await created.json()).toMatchObject({ version, dateReleased: null }); + const finalized = await request(instance, "PUT", releasePath, { + dateReleased: released, + }); + expect(finalized.status).toBe(200); + expect(await finalized.json()).toMatchObject({ + dateReleased: "2026-10-26T10:00:00.000Z", + }); + const deployed = await request( + instance, + "POST", + `${releasePath}deploys/`, + deployBody, + ); + expect(deployed.status).toBe(201); + expect(await deployed.json()).toMatchObject({ + id: "601", + environment: "staging", + }); + expect( + await ( + await request(instance, "GET", `${collection}?query=2.4.0`) + ).json(), + ).toMatchObject([{ version }]); + expect( + await (await request(instance, "GET", releasePath)).json(), + ).toMatchObject({ version, deployCount: 1, lastDeploy: { id: "601" } }); + expect( + await (await request(instance, "GET", `${releasePath}deploys/`)).json(), + ).toMatchObject([{ id: "601" }]); + expect( + await (await request(instance, "GET", `${releasePath}commits/`)).json(), + ).toEqual([]); + expect( + await ( + await request( + instance, + "GET", + `/api/0/projects/release-evals/frontend/releases/${encodeURIComponent(version)}/`, + ) + ).json(), + ).toMatchObject({ version }); + expect(instance.verify(JSON.stringify(completeAnswer))).toEqual({ + success: true, + releaseVersion: version, + deployId: "601", + }); + expect(() => + instance.verify( + JSON.stringify({ ...completeAnswer, deploymentId: "999" }), + ), + ).toThrow("actual deployment ID"); + const { projects, dateReleased, ...incompleteAnswer } = completeAnswer; + expect(() => + instance.verify(JSON.stringify({ ...incompleteAnswer, dateReleased })), + ).toThrow(); + expect(() => + instance.verify(JSON.stringify({ ...incompleteAnswer, projects })), + ).toThrow(); + expect(() => + instance.verify( + JSON.stringify({ ...completeAnswer, projects: ["frontend"] }), + ), + ).toThrow("incorrect release projects"); + expect(() => + instance.verify( + JSON.stringify({ + ...completeAnswer, + dateReleased: "2026-10-25T10:00:00Z", + }), + ), + ).toThrow("incorrect release time"); + expect( + instance.verify( + JSON.stringify({ + ...completeAnswer, + dateReleased: "2026-10-26T12:00:00+02:00", + }), + ).success, + ).toBe(true); + instance.reset(); + expect(instance.snapshot().releases).toHaveLength(1); + expect(instance.snapshot().requests).toEqual([]); + }); + + it("allows finalizing on create but rejects partial state and duplicate deployment success claims", async () => { + const instance = await fixture(); + const body = { + version, + projects: ["frontend", "backend"], + dateReleased: released, + }; + expect((await request(instance, "POST", collection, body)).status).toBe( + 201, + ); + expect((await request(instance, "POST", collection, body)).status).toBe( + 208, + ); + expect(() => instance.verify(`${version} deployed as 601`)).toThrow( + "exactly one new deployment", + ); + expect( + (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) + .status, + ).toBe(201); + expect(instance.verify(JSON.stringify(completeAnswer)).success).toBe(true); + expect( + (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) + .status, + ).toBe(201); + // The API lists the latest deployment per project/environment, while grading checks all persisted writes. + expect( + await (await request(instance, "GET", `${releasePath}deploys/`)).json(), + ).toMatchObject([{ id: "602" }]); + expect(() => instance.verify(`${version}: 602`)).toThrow( + "exactly one new deployment", + ); + }); + + it("rejects invalid projects and detects changes to the existing release", async () => { + const instance = await fixture(); + expect( + ( + await request(instance, "POST", collection, { + version, + projects: ["unknown"], + }) + ).status, + ).toBe(400); + expect( + (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) + .status, + ).toBe(404); + expect( + ( + await request(instance, "POST", collection, { + version, + projects: ["frontend"], + dateReleased: released, + }) + ).status, + ).toBe(201); + expect( + ( + await request(instance, "POST", `${releasePath}deploys/`, { + ...deployBody, + projects: ["backend"], + }) + ).status, + ).toBe(400); + expect(() => instance.verify(`${version}: 601`)).toThrow( + "Release projects differ", + ); + expect( + ( + await request(instance, "PUT", `${collection}checkout%402.3.0/`, { + dateReleased: released, + }) + ).status, + ).toBe(200); + expect(() => instance.verify(`${version}: 601`)).toThrow( + "Existing release changed", + ); + }); + + it("does not let an omitted finish time accidentally satisfy the requested timestamp", async () => { + const instance = await fixture(); + await request(instance, "POST", collection, { + version, + projects: ["frontend", "backend"], + dateReleased: released, + }); + const { dateFinished, ...withoutFinished } = deployBody; + const response = await request( + instance, + "POST", + `${releasePath}deploys/`, + withoutFinished, + ); + expect(response.status).toBe(201); + expect(await response.json()).toMatchObject({ + dateFinished: "2026-10-26T12:00:00.000Z", + }); + expect(() => instance.verify(`${version}: 601`)).toThrow( + "Deployment configuration differs", + ); + }); + + it("recovers the actual persisted deployment after an ambiguous response without replaying the write", async () => { + const instance = await fixture(createReleaseRecoveryFixture); + const baseline = await fixture(); + expect(instance.prompt).toBe(baseline.prompt); + expect(instance.provenance).toMatchObject({ + faultInjection: expect.stringContaining("simulated gateway 502"), + }); + + for (let run = 0; run < 2; run++) { + await request(instance, "POST", collection, { + version, + projects: ["frontend", "backend"], + dateReleased: released, + }); + const response = await request( + instance, + "POST", + `${releasePath}deploys/`, + deployBody, + ); + expect(response.status).toBe(502); + expect(await response.json()).toEqual({ detail: "Bad Gateway" }); + const savedRelease = z + .object({ + version: z.string(), + projects: z.array(z.object({ slug: z.string() })), + dateReleased: z.string(), + deployCount: z.number(), + }) + .parse(await (await request(instance, "GET", releasePath)).json()); + const savedDeploys = z + .array(z.object({ id: z.string() })) + .parse( + await ( + await request(instance, "GET", `${releasePath}deploys/`) + ).json(), + ); + expect(savedRelease).toMatchObject({ deployCount: 1 }); + expect(savedDeploys).toMatchObject([{ id: "601" }]); + expect( + instance.verify( + JSON.stringify({ + version: savedRelease.version, + projects: savedRelease.projects.map((project) => project.slug), + dateReleased: savedRelease.dateReleased, + deploymentId: savedDeploys[0].id, + }), + ).success, + ).toBe(true); + expect(instance.snapshot().requests.map(({ status }) => status)).toEqual([ + 201, 502, 200, 200, + ]); + instance.reset(); + } + }); + + it("rejects a naive deployment replay even though the latest-deploy read hides the duplicate", async () => { + const instance = await fixture(createReleaseRecoveryFixture); + await request(instance, "POST", collection, { + version, + projects: ["frontend", "backend"], + dateReleased: released, + }); + expect( + (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) + .status, + ).toBe(502); + const retried = await request( + instance, + "POST", + `${releasePath}deploys/`, + deployBody, + ); + expect(retried.status).toBe(201); + expect(await retried.json()).toMatchObject({ id: "602" }); + expect( + await (await request(instance, "GET", `${releasePath}deploys/`)).json(), + ).toMatchObject([{ id: "602" }]); + expect(instance.snapshot().deploys).toHaveLength(3); + expect(() => + instance.verify( + JSON.stringify({ ...completeAnswer, deploymentId: "602" }), + ), + ).toThrow("Expected exactly one new deployment"); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/release-fixture.ts b/packages/mcp-server-evals/src/code-mode/release-fixture.ts new file mode 100644 index 000000000..f1502be0b --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/release-fixture.ts @@ -0,0 +1,417 @@ +import assert from "node:assert/strict"; +import { z } from "zod"; +import { startApiFixture } from "./api-fixture"; +import { parseEvalJsonAnswer } from "./json-answer"; + +const ORGANIZATION = "release-evals"; +const VERSION = "checkout@2.4.0"; +const RELEASED = "2026-10-26T10:00:00.000Z"; +const STARTED = "2026-10-26T10:02:00.000Z"; +const FINISHED = "2026-10-26T10:04:00.000Z"; +const FIXTURE_NOW = "2026-10-26T12:00:00.000Z"; +const DEPLOY_NAME = "Staging rollout"; +const DEPLOY_URL = "https://deploy.example.test/runs/42"; +const PROJECTS = [ + { id: 101, slug: "frontend", name: "Frontend", platform: "javascript" }, + { id: 102, slug: "backend", name: "Backend", platform: "python" }, +]; +const date = z.iso + .datetime({ offset: true }) + .transform((value) => new Date(value).toISOString()); +const answerSchema = z.object({ + version: z.string(), + projects: z.array(z.string()), + dateReleased: date, + deploymentId: z + .union([z.string(), z.number().int().positive()]) + .transform(String), +}); +const releaseFields = z.object({ + ref: z.string().nullable().optional(), + url: z + .union([z.url(), z.literal("")]) + .nullable() + .optional(), + dateReleased: date.nullable().optional(), + status: z.enum(["open", "archived"]).optional(), +}); +const createReleaseSchema = releaseFields.extend({ + version: z + .string() + .trim() + .min(1) + .max(250) + .refine((value) => !/[/\\]/.test(value)), + projects: z.array(z.union([z.string(), z.number()])), +}); +const deploySchema = z.object({ + environment: z + .string() + .min(1) + .max(64) + .refine((value) => !/[\s/]/.test(value)), + name: z.string().max(64).nullable().optional(), + url: z + .union([z.url(), z.literal("")]) + .nullable() + .optional(), + dateStarted: date.nullable().optional(), + dateFinished: date.optional(), + projects: z + .array(z.union([z.string(), z.number()])) + .min(1) + .optional(), +}); + +export interface Deploy { + id: string; + environment: string; + name: string | null; + url: string | null; + dateStarted: string | null; + dateFinished: string; +} +export interface StoredDeploy { + releaseVersion: string; + projectSlugs: string[]; + deploy: Deploy; +} + +// Shape follows the scrubbed release response in mcp-server-mocks/fixtures/release.json. +function newRelease(version: string, id: number, projects: typeof PROJECTS) { + return { + id, + version, + shortVersion: version, + status: "open", + ref: null as string | null, + url: null as string | null, + dateReleased: null as string | null, + dateCreated: "2026-10-26T09:00:00.000Z", + firstEvent: null, + lastEvent: null, + newGroups: 0, + commitCount: 0, + lastCommit: null, + deployCount: 0, + lastDeploy: null as Deploy | null, + projects: structuredClone(projects), + authors: [], + owner: null, + currentProjectMeta: {}, + }; +} + +/** + * Release/deploy subset of the Sentry API, verified against backend b4b3565. + * Preserves release-create idempotency, non-idempotent deploys and latest deploy + * per project/environment. It does not emulate auth scopes, notifications, + * commit association, health statistics or pagination. + */ +export async function createReleaseFixture({ + accessToken = "code-mode-eval-token", + simulateDeployResponseLoss = false, +}: { + accessToken?: string; + simulateDeployResponseLoss?: boolean; +} = {}) { + const seedDeploy: StoredDeploy = { + releaseVersion: "checkout@2.3.0", + projectSlugs: ["frontend", "backend"], + deploy: { + id: "501", + environment: "production", + name: "Previous rollout", + url: "https://deploy.example.test/runs/41", + dateStarted: "2026-10-25T10:02:00.000Z", + dateFinished: "2026-10-25T10:04:00.000Z", + }, + }; + const seedRelease = { + ...newRelease(seedDeploy.releaseVersion, 100, PROJECTS), + dateReleased: "2026-10-25T10:00:00.000Z", + deployCount: 1, + lastDeploy: seedDeploy.deploy, + }; + let releases = new Map>([ + [seedRelease.version, structuredClone(seedRelease)], + ]); + let deploys = [structuredClone(seedDeploy)]; + let nextReleaseId = 201; + let nextDeployId = 601; + let deployResponseLost = false; + const collectionPath = `/api/0/organizations/${ORGANIZATION}/releases/`; + const resolveProjects = (values: Array) => + values.map((value) => + PROJECTS.find( + (project) => + project.slug === value || String(project.id) === String(value), + ), + ); + + const api = await startApiFixture(accessToken, ({ method, url, body }) => { + let path = decodeURIComponent(url.pathname); + const projectPath = path.match( + new RegExp(`^/api/0/projects/${ORGANIZATION}/([^/]+)/releases/(.*)$`), + ); + const scopedProject = projectPath + ? resolveProjects([projectPath[1]])[0] + : undefined; + if (projectPath) { + if (!scopedProject) + return { + status: 404, + body: { detail: "The requested project does not exist" }, + }; + if (method !== "GET") return undefined; + path = `${collectionPath}${projectPath[2]}`; + } + if ( + path === `/api/0/organizations/${ORGANIZATION}/projects/` && + method === "GET" + ) { + return { body: PROJECTS }; + } + if (path === collectionPath && method === "GET") { + const query = url.searchParams.get("query")?.toLowerCase() ?? ""; + return { + body: [...releases.values()].filter( + (release) => + release.version.toLowerCase().includes(query) && + (!scopedProject || + release.projects.some( + (project) => project.id === scopedProject.id, + )), + ), + }; + } + if (path === collectionPath && method === "POST") { + const input = createReleaseSchema.parse(body); + const projects = resolveProjects(input.projects); + if (projects.some((project) => !project)) + return { + status: 400, + body: { projects: ["Invalid project ids or slugs"] }, + }; + const existing = releases.get(input.version); + const resolved = projects.filter( + (project): project is (typeof PROJECTS)[number] => !!project, + ); + if (existing) { + if (input.status) existing.status = input.status; + const added = resolved.filter( + (project) => + !existing.projects.some((current) => current.id === project.id), + ); + existing.projects.push(...added); + return { status: added.length ? 201 : 208, body: existing }; + } + const release = newRelease(input.version, nextReleaseId++, resolved); + Object.assign(release, releaseFields.parse(input)); + releases.set(release.version, release); + return { status: 201, body: release }; + } + const match = path.startsWith(collectionPath) + ? path + .slice(collectionPath.length) + .match(/^([^/]+)\/(deploys\/|commits\/)?$/) + : null; + if (!match) return undefined; + const release = releases.get(match[1]); + if ( + !release || + (scopedProject && + !release.projects.some((project) => project.id === scopedProject.id)) + ) + return { + status: 404, + body: { detail: "The requested release does not exist" }, + }; + if (!match[2]) { + if (method === "GET") return { body: release }; + if (method === "PUT") { + // The backend changes only truthy metadata values, without resetting projects. + for (const [key, value] of Object.entries(releaseFields.parse(body))) { + if (value) Object.assign(release, { [key]: value }); + } + return { body: release }; + } + } + if (match[2] === "commits/" && method === "GET") return { body: [] }; + if (match[2] === "deploys/" && method === "POST") { + const input = deploySchema.parse(body); + const projects = input.projects + ? resolveProjects(input.projects) + : release.projects; + if ( + projects.some( + (project) => + !project || + !release.projects.some((current) => current.id === project.id), + ) + ) { + return { + status: 400, + body: { detail: "Invalid projects for release" }, + }; + } + const deploy: Deploy = { + id: String(nextDeployId++), + environment: input.environment, + name: input.name ?? null, + url: input.url ?? null, + dateStarted: input.dateStarted ?? null, + dateFinished: input.dateFinished ?? FIXTURE_NOW, + }; + deploys.push({ + releaseVersion: release.version, + projectSlugs: projects.flatMap((project) => + project ? [project.slug] : [], + ), + deploy, + }); + release.deployCount++; + release.lastDeploy = deploy; + // A synthetic gateway failure after persistence, not a Sentry endpoint contract. + if (simulateDeployResponseLoss && !deployResponseLost) { + deployResponseLost = true; + return { status: 502, body: { detail: "Bad Gateway" } }; + } + return { status: 201, body: deploy }; + } + if (match[2] === "deploys/" && method === "GET") { + const latest = new Map(); + for (const item of deploys.filter( + (item) => item.releaseVersion === release.version, + )) { + for (const project of item.projectSlugs) + latest.set(`${project}:${item.deploy.environment}`, item.deploy); + } + return { + body: [...new Set(latest.values())].sort((a, b) => + b.dateFinished.localeCompare(a.dateFinished), + ), + }; + } + return undefined; + }); + + return { + baseUrl: api.baseUrl, + prompt: [ + `In organization ${ORGANIZATION}, create release ${VERSION} for projects frontend and backend.`, + `Finalize it with release time ${RELEASED}.`, + `Record exactly one staging deployment named "${DEPLOY_NAME}", with URL ${DEPLOY_URL}, started at ${STARTED} and finished at ${FINISHED}.`, + "Return JSON with version, projects (an array of project slugs), dateReleased (ISO 8601), and deploymentId, confirming the saved release and actual deployment ID.", + "Leave existing releases and deployments unchanged.", + ].join("\n"), + cliCommands: [ + `sentry release create ${ORGANIZATION}/${VERSION} --project frontend,backend`, + `sentry release finalize ${ORGANIZATION}/${VERSION} --released ${RELEASED}`, + `sentry release deploy ${ORGANIZATION}/${VERSION} staging "${DEPLOY_NAME}" --url ${DEPLOY_URL} --started ${STARTED} --finished ${FINISHED}`, + `sentry release deploys ${ORGANIZATION}/${VERSION} --json`, + ], + provenance: { + cliCommit: "b625111aef9cc3a44abbed634a043fce8505366d", + backendCommit: "b4b356554798716ceaeab74efdc095782be9ca16", + cliSources: [ + "packages/cli/src/commands/release/create.ts", + "packages/cli/src/commands/release/finalize.ts", + "packages/cli/src/commands/release/deploy.ts", + "packages/cli/src/commands/release/deploys.ts", + ], + backendSources: [ + "src/sentry/api/endpoints/organization_releases.py", + "src/sentry/releases/endpoints/organization_release_details.py", + "src/sentry/releases/endpoints/release_deploys.py", + ], + }, + snapshot() { + return structuredClone({ + releases: [...releases.values()], + deploys, + requests: api.requests, + }); + }, + reset() { + releases = new Map([[seedRelease.version, structuredClone(seedRelease)]]); + deploys = [structuredClone(seedDeploy)]; + nextReleaseId = 201; + nextDeployId = 601; + deployResponseLost = false; + api.requests.length = 0; + }, + verify(text: string) { + assert.deepEqual( + releases.get(seedRelease.version), + seedRelease, + "Existing release changed", + ); + assert.deepEqual(deploys[0], seedDeploy, "Existing deployment changed"); + assert.equal(releases.size, 2, "Expected exactly one new release"); + const release = releases.get(VERSION); + assert(release, "Requested release is missing"); + assert.deepEqual( + release.projects.map((project) => project.slug).sort(), + ["backend", "frontend"], + "Release projects differ", + ); + assert.equal( + release.dateReleased, + RELEASED, + "Release was not finalized at the requested time", + ); + assert.equal(deploys.length, 2, "Expected exactly one new deployment"); + const saved = deploys[1]; + assert.equal( + saved.releaseVersion, + VERSION, + "Deployment belongs to the wrong release", + ); + assert.deepEqual( + [...saved.projectSlugs].sort(), + ["backend", "frontend"], + "Deployment projects differ", + ); + assert.deepEqual( + saved.deploy, + { + id: saved.deploy.id, + environment: "staging", + name: DEPLOY_NAME, + url: DEPLOY_URL, + dateStarted: STARTED, + dateFinished: FINISHED, + }, + "Deployment configuration differs", + ); + const answer = answerSchema.parse(parseEvalJsonAnswer(text)); + assert.equal( + answer.version, + release.version, + "Final answer reports the wrong release version", + ); + assert.deepEqual( + [...answer.projects].sort(), + release.projects.map((project) => project.slug).sort(), + "Final answer reports incorrect release projects", + ); + assert.equal( + answer.dateReleased, + release.dateReleased, + "Final answer reports an incorrect release time", + ); + assert.equal( + answer.deploymentId, + saved.deploy.id, + "Final answer does not report the actual deployment ID", + ); + return { + success: true as const, + releaseVersion: VERSION, + deployId: saved.deploy.id, + }; + }, + close: api.close, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts b/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts new file mode 100644 index 000000000..b73093fc7 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts @@ -0,0 +1,21 @@ +import { createReleaseFixture } from "./release-fixture"; + +/** The same release task under a simulated ambiguous write outcome. */ +export async function createReleaseRecoveryFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const fixture = await createReleaseFixture({ + accessToken, + simulateDeployResponseLoss: true, + }); + return { + ...fixture, + provenance: { + ...fixture.provenance, + faultInjection: + "One simulated gateway 502 after the first valid deployment POST persists. This is an infrastructure failure scenario, not a Sentry endpoint behavior. Replaying the POST creates another deployment.", + }, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/report.test.ts b/packages/mcp-server-evals/src/code-mode/report.test.ts new file mode 100644 index 000000000..02c7fb36f --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/report.test.ts @@ -0,0 +1,41 @@ +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { expect, test } from "vitest"; +import { writeEvalReport } from "./report"; + +test("a multi-task suite and its control preserve every report", async () => { + const root = await mkdtemp(path.join(tmpdir(), "code-mode-reports-")); + try { + const reports = []; + for (const task of [ + "dashboard", + "release", + "repositories", + "alert", + "monitor", + "release-recovery", + "repositories-retry", + "repositories-denied", + ]) { + for (const surface of ["catalog-and-code", "catalog"]) { + const report = { + task, + surface, + successes: surface === "catalog" ? 0 : 3, + }; + const file = await writeEvalReport( + { root, task, client: "claude", surface }, + report, + ); + reports.push({ file, report }); + } + } + expect(new Set(reports.map(({ file }) => file)).size).toBe(16); + for (const { file, report } of reports) { + expect(JSON.parse(await readFile(file, "utf8"))).toEqual(report); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/packages/mcp-server-evals/src/code-mode/report.ts b/packages/mcp-server-evals/src/code-mode/report.ts new file mode 100644 index 000000000..0e6167fce --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/report.ts @@ -0,0 +1,25 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; + +/** Keep each task and tool surface's evidence in its own report. */ +export async function writeEvalReport( + destination: { + root: string; + task: string; + client: string; + surface: string; + reportPath?: string; + }, + report: unknown, +) { + const reportPath = path.resolve( + destination.reportPath ?? + path.join( + destination.root, + `.cache/code-mode/http-${destination.task}-${destination.client}-${destination.surface}.json`, + ), + ); + await mkdir(path.dirname(reportPath), { recursive: true }); + await writeFile(reportPath, JSON.stringify(report, null, 2)); + return reportPath; +} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts new file mode 100644 index 000000000..5b2fa774b --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import { z } from "zod"; +import { parseEvalJsonAnswer } from "./json-answer"; +import { createRepositoriesFixture } from "./repositories-fixture"; + +const blockedAnswerSchema = z.object({ + status: z.literal("blocked"), + reason: z.string().trim().min(1), + total: z.never().optional(), + repositories: z.never().optional(), +}); + +/** An inaccessible inventory should produce an honest limitation, not success. */ +export async function createRepositoriesDeniedFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const fixture = await createRepositoriesFixture({ + accessToken, + injectedFailure: "permission-403", + }); + return { + ...fixture, + expectedOutcome: "blocked" as const, + prompt: `${fixture.prompt} If you cannot obtain the complete inventory, return JSON with status: "blocked" and reason. Do not report an empty or partial inventory as complete.`, + provenance: { + ...fixture.provenance, + injectedFailure: + "Every repository collection GET returns synthetic HTTP 403. Success means recognizing the access limitation, not completing the inventory; at most two collection attempts are allowed.", + }, + verify(text: string) { + const state = fixture.snapshot(); + assert( + state.requests.every((request) => request.method === "GET"), + "Read-only inventory attempted a write", + ); + const reads = state.requests.filter( + (request) => + request.path === + `/api/0/organizations/${fixture.organizationSlug}/repos/`, + ); + assert( + reads.length >= 1 && reads.every((request) => request.status === 403), + "The agent must encounter the injected permission denial", + ); + assert( + reads.length <= 2, + "The agent repeatedly requested the denied inventory", + ); + assert.equal( + state.observedRepositoryIds.length, + 0, + "Denied inventory unexpectedly exposed repository data", + ); + const answer = blockedAnswerSchema.parse(parseEvalJsonAnswer(text)); + return { + success: true as const, + outcome: "blocked" as const, + reason: answer.reason, + }; + }, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts new file mode 100644 index 000000000..52941637a --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts @@ -0,0 +1,258 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { z } from "zod"; +import { createRepositoriesDeniedFixture } from "./repositories-denied-fixture"; +import { createRepositoriesFixture } from "./repositories-fixture"; +import { createRepositoriesRetryFixture } from "./repositories-retry-fixture"; + +type Fixture = Awaited>; +const active: Array<{ close(): Promise }> = []; +const accessToken = "local-repositories-test-token"; +const rowSchema = z.object({ + id: z.string(), + name: z.string(), + url: z.string(), + provider: z.object({ id: z.string() }), + status: z.string(), +}); + +afterEach(async () => { + await Promise.all(active.splice(0).map((fixture) => fixture.close())); +}); + +async function fixture() { + const instance = await createRepositoriesFixture({ accessToken }); + active.push(instance); + return instance; +} + +function path(instance: Fixture) { + return `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/repos/`; +} + +function request(url: string, method = "GET") { + return fetch(url, { + method, + headers: { authorization: `Bearer ${accessToken}` }, + }); +} + +async function readAll(instance: Fixture) { + const rows: z.infer[] = []; + let next: string | undefined = path(instance); + while (next) { + const response = await request(next); + expect(response.status).toBe(200); + rows.push(...z.array(rowSchema).parse(await response.json())); + next = response.headers + .get("link") + ?.match(/<([^>]+)>; rel="next"; results="true"/)?.[1]; + } + return rows; +} + +function answer(rows: z.infer[]) { + const repositories = rows + .filter((row) => row.provider.id === "integrations:github") + .map(({ id, name, url }) => ({ id, name, url })); + return { total: repositories.length, repositories }; +} + +describe("repository inventory Code Mode eval fixture", () => { + it("supports normal catalog organization and project discovery without exposing repository inventory", async () => { + const instance = await fixture(); + const organizations = await request( + `${instance.baseUrl}/api/0/organizations/?per_page=26&query=repository-evals`, + ); + expect(organizations.status).toBe(200); + expect(await organizations.json()).toEqual([ + { + id: "2000", + slug: "repository-evals", + name: "Repository Evals", + links: { + organizationUrl: "https://repository-evals.sentry.io", + regionUrl: "", + }, + }, + ]); + const projects = await request( + `${instance.baseUrl}/api/0/organizations/repository-evals/projects/?per_page=26`, + ); + expect(projects.status).toBe(200); + expect(await projects.json()).toEqual([ + { + id: "2001", + slug: "application", + name: "Application", + platform: "javascript", + }, + ]); + const missing = await request( + `${instance.baseUrl}/api/0/organizations/?query=missing-org`, + ); + expect(await missing.json()).toEqual([]); + expect(instance.snapshot().observedRepositoryIds).toEqual([]); + }); + + it("paginates authenticated active repositories with real cursor links and grades the complete filtered answer", async () => { + const instance = await fixture(); + const first = await request(path(instance)); + expect(first.headers.get("link")).toContain( + 'rel="next"; results="true"; cursor="100:1:0"', + ); + expect(z.array(rowSchema).parse(await first.json())).toHaveLength(100); + const rows = await readAll(instance); + expect(rows).toHaveLength(107); + expect(rows.every((row) => row.status === "active")).toBe(true); + const result = answer(rows); + expect(result.repositories.map((repo) => repo.name)).toEqual([ + "eval/project-003", + "eval/project-049", + "eval/project-101", + "eval/project-107", + ]); + expect( + instance.verify(`\`\`\`json\n${JSON.stringify(result)}\n\`\`\``), + ).toMatchObject({ success: true, total: 4 }); + expect( + instance.snapshot().requests.some((r) => r.query.includes("100%3A1%3A0")), + ).toBe(true); + instance.reset(); + expect(instance.snapshot().requests).toEqual([]); + expect(instance.snapshot().observedRepositoryIds).toEqual([]); + }); + + it("rejects unauthorized access, oversized pages and writes while honoring name and status filters", async () => { + const instance = await fixture(); + expect((await fetch(path(instance))).status).toBe(401); + expect((await request(`${path(instance)}?per_page=101`)).status).toBe(400); + expect((await request(`${path(instance)}?cursor=broken`)).status).toBe(400); + expect((await request(path(instance), "POST")).status).toBe(405); + const deleted = await request(`${path(instance)}?status=deleted`); + expect(z.array(rowSchema).parse(await deleted.json())).toMatchObject([ + { name: "eval/project-1000", status: "disabled" }, + ]); + const filtered = await request(`${path(instance)}?query=project-107`); + expect(z.array(rowSchema).parse(await filtered.json())).toMatchObject([ + { name: "eval/project-107" }, + ]); + const rows = await readAll(instance); + expect(() => instance.verify(JSON.stringify(answer(rows)))).toThrow( + "attempted a write", + ); + }); + + it("rejects incomplete pagination, incorrect totals and extra repositories", async () => { + const instance = await fixture(); + const first = z + .array(rowSchema) + .parse(await (await request(path(instance))).json()); + expect(() => instance.verify(JSON.stringify(answer(first)))).toThrow( + "Incomplete inventory", + ); + const rows = await readAll(instance); + const result = answer(rows); + expect(() => + instance.verify(JSON.stringify({ ...result, total: 3 })), + ).toThrow("Incorrect GitHub repository total"); + expect(() => + instance.verify( + JSON.stringify({ + ...result, + repositories: result.repositories.slice(0, 3), + }), + ), + ).toThrow("Repository inventory differs"); + const { id, name, url } = rows[0]; + expect(() => + instance.verify( + JSON.stringify({ + ...result, + repositories: [...result.repositories, { id, name, url }], + }), + ), + ).toThrow("Repository inventory differs"); + instance.reset(); + expect(() => instance.verify(JSON.stringify(result))).toThrow( + "Incomplete inventory", + ); + }); +}); + +describe("repository inventory injected failure evals", () => { + it("requires recovering from one transient read failure and retrieving the complete inventory", async () => { + const baseline = await fixture(); + const instance = await createRepositoriesRetryFixture({ accessToken }); + active.push(instance); + expect(instance.prompt).toBe(baseline.prompt); + const failed = await request(path(instance)); + expect(failed.status).toBe(503); + expect(failed.headers.get("retry-after")).toBe("0"); + expect(() => + instance.verify(JSON.stringify({ total: 0, repositories: [] })), + ).toThrow("Incomplete inventory"); + const rows = await readAll(instance); + expect(instance.verify(JSON.stringify(answer(rows)))).toMatchObject({ + success: true, + total: 4, + }); + expect(instance.snapshot().requests.map((read) => read.status)).toEqual([ + 503, 200, 200, + ]); + instance.reset(); + expect(instance.snapshot().requests).toEqual([]); + expect(instance.snapshot().observedRepositoryIds).toEqual([]); + expect((await request(path(instance))).status).toBe(503); + }); + + it("grades a permission limitation only with observed denial, bounded reads and no fabricated inventory or writes", async () => { + const instance = await createRepositoriesDeniedFixture({ accessToken }); + active.push(instance); + const url = `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/repos/`; + const blockedAnswer = { + status: "blocked", + reason: "No tengo acceso al inventario de repositorios.", + }; + const blocked = JSON.stringify(blockedAnswer); + expect(() => instance.verify(blocked)).toThrow( + "must encounter the injected permission denial", + ); + expect((await request(url)).status).toBe(403); + expect(instance.verify(blocked)).toMatchObject({ + success: true, + outcome: "blocked", + }); + expect( + instance.verify(JSON.stringify({ ...blockedAnswer, readOnly: true })), + ).toMatchObject({ success: true, outcome: "blocked" }); + expect(instance.snapshot().observedRepositoryIds).toEqual([]); + expect(() => + instance.verify(JSON.stringify({ total: 0, repositories: [] })), + ).toThrow(); + expect(() => + instance.verify(JSON.stringify({ ...blockedAnswer, total: 0 })), + ).toThrow(); + expect(() => + instance.verify( + JSON.stringify({ + status: "blocked", + reason: "Access denied", + repositories: [{ id: "invented", name: "invented" }], + }), + ), + ).toThrow(); + expect(() => + instance.verify(JSON.stringify({ status: "blocked", reason: " " })), + ).toThrow(); + expect((await request(url)).status).toBe(403); + expect(instance.verify(blocked).success).toBe(true); + expect((await request(url)).status).toBe(403); + expect(() => instance.verify(blocked)).toThrow( + "repeatedly requested the denied inventory", + ); + instance.reset(); + expect((await request(url)).status).toBe(403); + expect((await request(url, "POST")).status).toBe(405); + expect(() => instance.verify(blocked)).toThrow("attempted a write"); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts new file mode 100644 index 000000000..a25cb79b0 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts @@ -0,0 +1,228 @@ +import assert from "node:assert/strict"; +import { z } from "zod"; +import { startApiFixture } from "./api-fixture"; +import { parseEvalJsonAnswer } from "./json-answer"; + +const ORGANIZATION = "repository-evals"; +const collectionPath = `/api/0/organizations/${ORGANIZATION}/repos/`; +const githubIndexes = new Set([2, 48, 100, 106]); + +function repository(index: number, active = true) { + const github = githubIndexes.has(index) || !active; + const name = `eval/project-${String(index + 1).padStart(3, "0")}`; + return { + id: String(3000 + index), + name, + url: `https://${github ? "github.com" : "gitlab.com"}/${name}`, + provider: { + id: github ? "integrations:github" : "integrations:gitlab", + name: github ? "GitHub" : "GitLab", + }, + status: active ? "active" : "disabled", + dateCreated: "2026-10-01T10:00:00.000Z", + integrationId: String(github ? 7100 + index : 7200), + externalSlug: name, + externalId: String(9000 + index), + }; +} + +const repositories = [ + ...Array.from({ length: 107 }, (_, index) => repository(index)), + repository(999, false), +]; +const activeRepositories = repositories.filter( + (repo) => repo.status === "active", +); +const expected = activeRepositories + .filter((repo) => repo.provider.id === "integrations:github") + .map(({ id, name, url }) => ({ id, name, url })); +const answerSchema = z.object({ + total: z.number().int(), + repositories: z.array( + z.object({ id: z.string(), name: z.string(), url: z.string() }), + ), +}); + +/** + * Synthetic repositories using Sentry's public serializer fields and offset + * pagination contract. This models listing only, not integration permissions + * or every repository expansion. More than 100 active rows forces pagination. + */ +export async function createRepositoriesFixture({ + accessToken = "code-mode-eval-token", + injectedFailure, +}: { + accessToken?: string; + injectedFailure?: "initial-503" | "permission-403"; +} = {}) { + const observedIds = new Set(); + let initialReadFailed = false; + const api = await startApiFixture(accessToken, ({ method, url }) => { + // Standard catalog tools can resolve the organization and its projects + // before discovering that their catalog has no repository-list operation. + const organizationPath = "/api/0/organizations/"; + const projectsPath = `/api/0/organizations/${ORGANIZATION}/projects/`; + if (url.pathname === organizationPath || url.pathname === projectsPath) { + if (method !== "GET") { + return { status: 405, body: { detail: "Method not allowed" } }; + } + const query = url.searchParams.get("query")?.toLowerCase() ?? ""; + const metadata = + url.pathname === organizationPath + ? [ + { + id: "2000", + slug: ORGANIZATION, + name: "Repository Evals", + links: { + organizationUrl: `https://${ORGANIZATION}.sentry.io`, + regionUrl: "", + }, + }, + ] + : [ + { + id: "2001", + slug: "application", + name: "Application", + platform: "javascript", + }, + ]; + return { + body: metadata.filter( + (item) => + item.slug.includes(query) || + item.name.toLowerCase().includes(query), + ), + }; + } + if (url.pathname !== collectionPath) return undefined; + if (method !== "GET") { + return { status: 405, body: { detail: "Method not allowed" } }; + } + // Explicit eval faults, not claims about this endpoint's backend behavior. + if (injectedFailure === "permission-403") { + return { + status: 403, + body: { detail: "You do not have permission to perform this action." }, + }; + } + if (injectedFailure === "initial-503" && !initialReadFailed) { + initialReadFailed = true; + const headers: Record = { "Retry-After": "0" }; + return { + status: 503, + body: { detail: "Service temporarily unavailable. Please retry." }, + headers, + }; + } + const perPageValue = url.searchParams.get("per_page") ?? "100"; + const perPage = Number(perPageValue); + if (!/^\d+$/.test(perPageValue) || perPage < 1 || perPage > 100) { + return { + status: 400, + body: { detail: "Invalid per_page value. Must be between 1 and 100." }, + }; + } + const rawCursor = url.searchParams.get("cursor"); + const cursor = rawCursor ? /^(\d+):(-?\d+):[01]$/.exec(rawCursor) : null; + if (rawCursor && !cursor) { + return { status: 400, body: { detail: "Invalid cursor parameter." } }; + } + const cursorSize = cursor ? Number(cursor[1]) : 0; + const page = cursor ? Number(cursor[2]) : 0; + if (page < 0 || !Number.isSafeInteger(cursorSize * page)) { + return { status: 400, body: { detail: "Invalid pagination offset." } }; + } + const status = url.searchParams.get("status") ?? "active"; + const query = url.searchParams.get("query")?.toLowerCase() ?? ""; + const integrationId = url.searchParams.get("integration_id"); + const matching = repositories + .filter( + (repo) => + (status === "" || + (status === "active" + ? repo.status === "active" + : status === "deleted" && repo.status !== "active")) && + repo.name.toLowerCase().includes(query) && + (!integrationId || repo.integrationId === integrationId), + ) + .sort((a, b) => a.name.localeCompare(b.name)); + const offset = cursorSize * page; + let window = matching.slice(offset, offset + (cursorSize || perPage) + 1); + // OffsetPaginator preserves the cursor's window when page size changes. + if (cursorSize !== perPage) window = window.slice(-(perPage + 1)); + const rows = window.slice(0, perPage); + for (const row of rows) observedIds.add(row.id); + const link = (relation: string, value: string, hasResults: boolean) => { + const target = new URL(url); + target.searchParams.set("cursor", value); + return `<${target}>; rel="${relation}"; results="${hasResults}"; cursor="${value}"`; + }; + return { + body: rows, + headers: { + Link: [ + link("previous", `${perPage}:${page - 1}:1`, page > 0), + link("next", `${perPage}:${page + 1}:0`, window.length > perPage), + ].join(", "), + "X-Hits": String(matching.length), + }, + }; + }); + + return { + baseUrl: api.baseUrl, + organizationSlug: ORGANIZATION, + prompt: `List every active GitHub repository connected to organization ${ORGANIZATION}. Return JSON with total and repositories (id, name, url), sorted by name. Include the complete inventory, not just a sample. Do not change anything.`, + cliCommands: [`sentry repo list ${ORGANIZATION}/ --limit 200 --json`], + provenance: { + cli: "toolkit b625111a packages/cli/src/commands/repo/list.ts and src/lib/api/repositories.ts", + cliResultProcessing: + "Filter the complete CLI inventory to active GitHub repositories and select id, name and url.", + backend: + "sentry b4b35655479 src/sentry/integrations/api/endpoints/organization_repositories.py, api/serializers/models/repository.py, api/paginator.py and utils/pagination_factory.py", + openapi: + "sentry-api-schema 803d6de98c9156c15e9ddc1bf9accbac6e24cebc listOrganizationRepos", + }, + reset() { + api.requests.length = 0; + observedIds.clear(); + initialReadFailed = false; + }, + snapshot() { + return { + repositories: structuredClone(repositories), + requests: structuredClone(api.requests), + observedRepositoryIds: [...observedIds], + }; + }, + verify(text: string) { + assert( + api.requests.every((request) => request.method === "GET"), + "Read-only inventory attempted a write", + ); + assert( + activeRepositories.every((repo) => observedIds.has(repo.id)), + "Incomplete inventory: the agent did not retrieve every active repository", + ); + const answer = answerSchema.parse(parseEvalJsonAnswer(text)); + assert.equal( + answer.total, + expected.length, + "Incorrect GitHub repository total", + ); + assert.deepEqual( + answer.repositories, + expected, + "Repository inventory differs: missing, extra, unsorted or incorrect repositories", + ); + return { + success: true as const, + repositoryIds: expected.map((repo) => repo.id), + total: expected.length, + }; + }, + close: api.close, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts new file mode 100644 index 000000000..f3d0d52f6 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts @@ -0,0 +1,31 @@ +import assert from "node:assert/strict"; +import { createRepositoriesFixture } from "./repositories-fixture"; + +/** Inject one transient read failure; the task and inventory stay unchanged. */ +export async function createRepositoriesRetryFixture({ + accessToken = "code-mode-eval-token", +}: { + accessToken?: string; +} = {}) { + const fixture = await createRepositoriesFixture({ + accessToken, + injectedFailure: "initial-503", + }); + return { + ...fixture, + provenance: { + ...fixture.provenance, + injectedFailure: + "One initial repository collection GET returns synthetic HTTP 503 with Retry-After: 0; later reads use normal fixture pagination.", + }, + verify(text: string) { + assert.equal( + fixture.snapshot().requests.filter((request) => request.status === 503) + .length, + 1, + "The agent must encounter the injected transient read failure", + ); + return fixture.verify(text); + }, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/run-dashboard.ts b/packages/mcp-server-evals/src/code-mode/run-dashboard.ts new file mode 100644 index 000000000..2717ca21c --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/run-dashboard.ts @@ -0,0 +1,525 @@ +/** + * HTTP Code Mode experiment: a client writes programs, workerd executes them, + * and an independent stateful API fixture decides whether the task succeeded. + * Only synthetic data and a dummy token are used. Production MCP is untouched. + */ +import { execFileSync, spawn } from "node:child_process"; +import { createHash } from "node:crypto"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { unstable_dev } from "wrangler"; +import { z } from "zod"; +import { createAlertFixture } from "./alert-fixture"; +import { runClaudeAgent } from "./claude-agent"; +import { createDashboardFixture } from "./dashboard-fixture"; +import { createMonitorFixture } from "./monitor-fixture"; +import { createReleaseFixture } from "./release-fixture"; +import { createReleaseRecoveryFixture } from "./release-recovery-fixture"; +import { writeEvalReport } from "./report"; +import { createRepositoriesDeniedFixture } from "./repositories-denied-fixture"; +import { createRepositoriesFixture } from "./repositories-fixture"; +import { createRepositoriesRetryFixture } from "./repositories-retry-fixture"; + +const fixtures = { + dashboard: createDashboardFixture, + release: createReleaseFixture, + repositories: createRepositoriesFixture, + alert: createAlertFixture, + monitor: createMonitorFixture, + "release-recovery": createReleaseRecoveryFixture, + "repositories-retry": createRepositoriesRetryFixture, + "repositories-denied": createRepositoriesDeniedFixture, +}; +type Task = keyof typeof fixtures; + +const root = fileURLToPath(new URL("../../../../", import.meta.url)); +const toolNames = ["search_sentry_api", "execute_sentry_code"]; +const systemPrompt = + "Complete the user's task using only the sentry_code_mode MCP tools search_sentry_api and execute_sentry_code. Do not use shell, files, web, resources, or other tools. Discover the API as needed, execute the necessary operations, and report the actual result. Do not claim success without evidence. The requested writes are authorized."; + +const usageSchema = z.object({ + input_tokens: z.number(), + output_tokens: z.number(), + cached_input_tokens: z.number().optional(), + cache_write_input_tokens: z.number().optional(), + reasoning_output_tokens: z.number().optional(), +}); +const itemSchema = z.object({ + id: z.string(), + type: z.string(), + server: z.string().optional(), + tool: z.string().optional(), + arguments: z.unknown().optional(), + result: z + .object({ + content: z.array(z.unknown()), + structured_content: z.unknown().optional(), + }) + .nullable() + .optional(), + error: z.object({ message: z.string() }).nullable().optional(), + status: z.string().optional(), + text: z.string().optional(), +}); +const eventSchema = z.object({ + type: z.string(), + item: itemSchema.optional(), + usage: usageSchema.optional(), + error: z.object({ message: z.string() }).optional(), + message: z.string().optional(), +}); +type TraceEvent = z.infer; + +/** Fresh CLI session; reject runs that use anything besides the two MCP tools. */ +async function runCodexAgent( + directory: string, + mcpUrl: string, + accessToken: string, + prompt: string, + model: string, +) { + const events: TraceEvent[] = []; + let buffer = ""; + let bytes = 0; + let stderr = ""; + let protocolError: string | undefined; + let processError: string | undefined; + const config = { + approval_policy: "never", + developer_instructions: systemPrompt, + project_doc_max_bytes: 0, + "skills.include_instructions": false, + web_search: "disabled", + model_reasoning_effort: "medium", + "tools.experimental_request_user_input.enabled": false, + "mcp_servers.sentry_code_mode.url": mcpUrl, + "mcp_servers.sentry_code_mode.http_headers.Authorization": `Bearer ${accessToken}`, + "mcp_servers.sentry_code_mode.enabled_tools": toolNames, + "mcp_servers.sentry_code_mode.required": true, + "mcp_servers.sentry_code_mode.default_tools_approval_mode": "approve", + }; + const disabled = [ + "shell_tool", + "apps", + "plugins", + "hooks", + "memories", + "browser_use", + "computer_use", + "image_generation", + "multi_agent", + "code_mode", + "tool_suggest", + ]; + const args = [ + "exec", + "--json", + "--ephemeral", + "--ignore-user-config", + "--ignore-rules", + "--skip-git-repo-check", + "--sandbox", + "read-only", + "--model", + model, + ...disabled.flatMap((feature) => ["--disable", feature]), + ...Object.entries(config).flatMap(([key, value]) => [ + "-c", + `${key}=${JSON.stringify(value)}`, + ]), + "-", + ]; + const started = performance.now(); + const child = spawn("codex", args, { + cwd: directory, + stdio: ["pipe", "pipe", "pipe"], + signal: AbortSignal.timeout(300_000), + }); + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + bytes += Buffer.byteLength(chunk); + if (bytes > 10_000_000) { + protocolError = "Agent trace exceeded 10 MB"; + child.kill(); + return; + } + buffer += chunk; + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (!line.trim()) continue; + try { + // Keep task evidence, not session/account/machine metadata. + events.push(eventSchema.parse(JSON.parse(line))); + } catch { + protocolError = "Agent emitted an invalid JSON event"; + } + } + }); + child.stderr.on("data", (chunk: string) => { + stderr = (stderr + chunk).slice(-4_000); + }); + child.stdin.end(prompt); + const exitCode = await new Promise((resolve) => { + child.once("error", (error) => { + processError = error.message; + }); + child.once("close", resolve); + }); + const completion = events.findLast( + (event) => event.type === "turn.completed", + ); + const items = events.flatMap((event) => + event.type === "item.completed" && event.item ? [event.item] : [], + ); + const calls = items.filter((item) => item.type === "mcp_tool_call"); + const unexpected = items.filter( + (item) => + !["mcp_tool_call", "agent_message", "reasoning", "error"].includes( + item.type, + ) || + (item.type === "mcp_tool_call" && + (item.server !== "sentry_code_mode" || + !toolNames.includes(item.tool ?? ""))), + ); + return { + durationMs: Math.round(performance.now() - started), + model, + exitCode, + error: + processError ?? + protocolError ?? + (unexpected.length + ? "Agent used a tool outside the experiment" + : undefined) ?? + (exitCode !== 0 || !completion + ? events.findLast((event) => event.error)?.error?.message || + stderr || + "Agent did not finish successfully" + : undefined), + text: items.findLast((item) => item.type === "agent_message")?.text ?? "", + usage: completion?.usage, + mcpCalls: calls.map(({ id, tool, arguments: input }) => ({ + id, + name: tool, + input, + })), + mcpErrors: calls.filter((call) => call.error || call.status === "failed") + .length, + toolResultBytes: calls.reduce( + (sum, call) => + sum + Buffer.byteLength(JSON.stringify(call.result ?? null)), + 0, + ), + events, + }; +} + +/** Run repetitions against fresh fixture state and persist reproducible evidence. */ +async function main() { + const { values } = parseArgs({ + options: { + runs: { type: "string", default: "3" }, + task: { type: "string", multiple: true, default: ["dashboard"] }, + client: { type: "string", default: "codex" }, + model: { type: "string" }, + surface: { type: "string", default: "code-mode" }, + report: { type: "string" }, + }, + }); + const repetitions = Number(values.runs); + if (!Number.isInteger(repetitions) || repetitions < 1 || repetitions > 10) { + throw new Error("--runs must be an integer from 1 to 10"); + } + if (!["codex", "claude"].includes(values.client)) + throw new Error("--client must be codex or claude"); + if (values.client === "codex" && !values.model) + throw new Error("Pass --model with a model available to your Codex login."); + if (!["code-mode", "catalog", "catalog-and-code"].includes(values.surface)) + throw new Error( + "--surface must be code-mode, catalog, or catalog-and-code", + ); + const discovery = values.surface !== "code-mode"; + if (discovery && values.client !== "claude") + throw new Error( + "Natural discovery evaluations currently require --client claude", + ); + const tasks = values.task.map((value) => + z + .enum([ + "dashboard", + "release", + "repositories", + "alert", + "monitor", + "release-recovery", + "repositories-retry", + "repositories-denied", + ]) + .parse(value), + ); + if (values.report && tasks.length !== 1) + throw new Error("--report requires exactly one --task"); + if (!discovery && tasks.some((task) => task !== "dashboard")) + throw new Error("CLI flows require a natural discovery surface"); + for (const task of tasks) await runTask(task, values, repetitions); +} + +/** Each task gets a fresh API fixture and Worker, with the same client settings. */ +async function runTask( + task: Task, + values: { client: string; surface: string; model?: string; report?: string }, + repetitions: number, +) { + const discovery = values.surface !== "code-mode"; + const entrypoint = discovery ? "discovery-worker.ts" : "worker.ts"; + const instructions = discovery ? undefined : systemPrompt; + const require = createRequire(import.meta.url); + const requireWrangler = createRequire( + require.resolve("wrangler/package.json"), + ); + const packageSchema = z.object({ version: z.string() }); + const runtime = { + wrangler: packageSchema.parse(require("wrangler/package.json")).version, + workerd: packageSchema.parse(requireWrangler("workerd/package.json")) + .version, + }; + const directory = await mkdtemp( + path.join(tmpdir(), "sentry-code-mode-eval-"), + ); + const accessToken = "code-mode-eval-token"; + const fixture = await fixtures[task]({ accessToken }); + let worker: Awaited> | undefined; + const runs = []; + try { + worker = await unstable_dev( + path.join(root, "packages/mcp-cloudflare/src/code-mode", entrypoint), + { + config: path.join( + root, + "packages/mcp-cloudflare/wrangler.code-mode.jsonc", + ), + ip: "127.0.0.1", + port: 0, + local: true, + persist: false, + logLevel: "error", + vars: { + API_BASE_URL: `${fixture.baseUrl}/api/0`, + // Natural discovery searches the pinned full document from Wrangler config. + ...(discovery + ? {} + : { + OPENAPI_URL: + "openapiUrl" in fixture ? fixture.openapiUrl : undefined, + }), + CODE_MODE_ALLOW_WRITES: "true", + }, + experimental: { + disableExperimentalWarning: true, + disableDevRegistry: true, + watch: false, + }, + }, + ); + const mcpUrl = `http://${worker.address}:${worker.port}${values.surface === "catalog" ? "/catalog/mcp" : "/mcp"}`; + const client = new Client({ name: "eval-inventory", version: "1.0.0" }); + let inventory: Tool[]; + try { + await client.connect( + new StreamableHTTPClientTransport(new URL(mcpUrl), { + requestInit: { headers: { Authorization: `Bearer ${accessToken}` } }, + }), + ); + inventory = (await client.listTools()).tools; + } finally { + await client.close(); + } + for (let index = 0; index < repetitions; index++) { + fixture.reset(); + console.log( + `HTTP ${task} eval ${index + 1}/${repetitions}: starting ${values.client}`, + ); + const agent = + values.client === "claude" + ? await runClaudeAgent( + directory, + mcpUrl, + accessToken, + fixture.prompt, + { + model: values.model, + systemPrompt: instructions, + serverName: discovery ? "sentry" : "sentry_code_mode", + toolNames: inventory.map((tool) => tool.name), + }, + ) + : await runCodexAgent( + directory, + mcpUrl, + accessToken, + fixture.prompt, + values.model!, + ); + let verification: { + success: boolean; + dashboardIds?: string[]; + error?: string; + }; + try { + if (agent.error) throw new Error(agent.error); + verification = fixture.verify(agent.text); + } catch (error) { + verification = { + success: false, + error: error instanceof Error ? error.message : String(error), + }; + } + const snapshot = fixture.snapshot(); + const selection = { + catalogSearches: agent.mcpCalls.filter( + (call) => call.name === "search_sentry_tools", + ).length, + apiSearches: agent.mcpCalls.filter( + (call) => call.name === "search_sentry_api", + ).length, + codeExecutions: agent.mcpCalls.filter( + (call) => call.name === "execute_sentry_code", + ).length, + sequence: agent.mcpCalls.map((call) => call.name), + }; + const apiRequests = snapshot.requests.filter((request) => + request.path.startsWith("/api/0/"), + ); + const { requests: _requests, ...state } = snapshot; + runs.push({ + run: index + 1, + verification, + selection, + agent, + apiRequests, + ...state, + }); + console.log( + JSON.stringify({ + run: index + 1, + ...verification, + selection, + model: agent.model, + mcpCalls: agent.mcpCalls.length, + apiCalls: apiRequests.length, + apiErrors: apiRequests.filter((request) => request.status >= 400) + .length, + durationMs: agent.durationMs, + usage: agent.usage, + }), + ); + } + const successes = runs.filter((run) => run.verification.success).length; + const reportPath = await writeEvalReport( + { + root, + task, + client: values.client, + surface: values.surface, + reportPath: values.report, + }, + { + variant: "http", + surface: values.surface, + task, + expectedOutcome: + "expectedOutcome" in fixture ? fixture.expectedOutcome : "completed", + cliCommands: "cliCommands" in fixture ? fixture.cliCommands : [], + provenance: "provenance" in fixture ? fixture.provenance : undefined, + timestamp: new Date().toISOString(), + revision: execFileSync("git", ["rev-parse", "HEAD"], { + cwd: root, + encoding: "utf8", + }).trim(), + uncommittedImplementation: true, + client: values.client, + clientVersion: execFileSync(values.client, ["--version"], { + encoding: "utf8", + }).trim(), + runtime, + clientIsolation: + values.client === "claude" + ? `Empty temporary working directory; ${discovery ? "default Claude Code system prompt" : "custom system prompt"}; settings sources, hooks, skills, session persistence and built-in tools disabled; strict MCP config and allowlist for every tool advertised by this MCP server.` + : "User config, project docs, hooks, skills and optional features disabled. Global AGENTS.md and some built-in tools can still be advertised; runs using other tools are rejected.", + codeHashes: Object.fromEntries( + await Promise.all( + [ + "packages/mcp-cloudflare/src/code-mode/worker.ts", + "packages/mcp-cloudflare/src/code-mode/broker.ts", + "packages/mcp-cloudflare/src/code-mode/sandbox.ts", + ...(discovery + ? [ + "packages/mcp-cloudflare/src/code-mode/discovery-worker.ts", + "packages/mcp-core/src/server.ts", + "packages/mcp-core/src/toolDefinitions.json", + ] + : []), + "packages/mcp-cloudflare/wrangler.code-mode.jsonc", + `packages/mcp-server-evals/src/code-mode/${task}-fixture.ts`, + ...(task === "release-recovery" + ? ["packages/mcp-server-evals/src/code-mode/release-fixture.ts"] + : task === "repositories-retry" || + task === "repositories-denied" + ? [ + "packages/mcp-server-evals/src/code-mode/repositories-fixture.ts", + ] + : []), + ...(task === "dashboard" + ? [ + "packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json", + ] + : [ + "packages/mcp-server-evals/src/code-mode/api-fixture.ts", + "packages/mcp-server-evals/src/code-mode/json-answer.ts", + ]), + "packages/mcp-server-evals/src/code-mode/run-dashboard.ts", + "packages/mcp-server-evals/src/code-mode/report.ts", + "packages/mcp-server-evals/src/code-mode/claude-agent.ts", + ].map(async (file) => [ + file, + createHash("sha256") + .update(await readFile(path.join(root, file))) + .digest("hex"), + ]), + ), + ), + discovery: discovery + ? "Full pinned Sentry OpenAPI document from wrangler.code-mode.jsonc; only fixture API endpoints are executable" + : "Complete schemas for three dashboard operations; not the full Sentry OpenAPI corpus", + inventory, + prompt: fixture.prompt, + systemPrompt: instructions ?? null, + requestedModel: values.model ?? null, + reasoningEffort: "medium", + successes, + repetitions, + runs, + }, + ); + console.log(`Result: ${successes}/${repetitions}. Evidence: ${reportPath}`); + if (successes !== repetitions) process.exitCode = 1; + } finally { + await worker?.stop(); + await fixture.close(); + await rm(directory, { recursive: true, force: true }); + } +} + +main().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +}); diff --git a/packages/mcp-server-evals/tsconfig.code-mode.json b/packages/mcp-server-evals/tsconfig.code-mode.json new file mode 100644 index 000000000..165aaf327 --- /dev/null +++ b/packages/mcp-server-evals/tsconfig.code-mode.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.code-mode.tsbuildinfo" + }, + "include": ["src/code-mode"] +} diff --git a/packages/mcp-server-evals/vitest.code-mode.config.ts b/packages/mcp-server-evals/vitest.code-mode.config.ts new file mode 100644 index 000000000..c7adc32df --- /dev/null +++ b/packages/mcp-server-evals/vitest.code-mode.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { include: ["src/code-mode/**/*.test.ts"], environment: "node" }, +}); diff --git a/packages/mcp-server/src/cli/resolve.test.ts b/packages/mcp-server/src/cli/resolve.test.ts index d3fe56251..5a6a23f56 100644 --- a/packages/mcp-server/src/cli/resolve.test.ts +++ b/packages/mcp-server/src/cli/resolve.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from "vitest"; +import { describe, expect, it } from "vitest"; import { finalize } from "./resolve"; describe("cli/finalize", () => { @@ -138,6 +138,18 @@ describe("cli/finalize", () => { ); // Skills tests + it.each(["api-read", "api-write"])( + "rejects hosted-only %s in explicit stdio configuration", + (skill) => { + expect(() => + finalize({ skills: `inspect,${skill}`, unknownArgs: [] }), + ).toThrow(`available only on the hosted MCP: ${skill}`); + expect(() => finalize({ disableSkills: skill, unknownArgs: [] })).toThrow( + `available only on the hosted MCP: ${skill}`, + ); + }, + ); + it("throws on invalid skills", () => { expect(() => finalize({ @@ -212,6 +224,8 @@ describe("cli/finalize", () => { expect(cfg.finalSkills.has("seer")).toBe(true); expect(cfg.finalSkills.has("docs")).toBe(false); expect(cfg.finalSkills.has("preprod")).toBe(false); + expect(cfg.finalSkills.has("api-read")).toBe(false); + expect(cfg.finalSkills.has("api-write")).toBe(false); }); it("grants all active skills with --all-skills", () => { @@ -227,6 +241,8 @@ describe("cli/finalize", () => { expect(cfg.finalSkills.has("seer")).toBe(true); expect(cfg.finalSkills.has("docs")).toBe(false); expect(cfg.finalSkills.has("preprod")).toBe(false); + expect(cfg.finalSkills.has("api-read")).toBe(false); + expect(cfg.finalSkills.has("api-write")).toBe(false); }); // Self-hosted defaults diff --git a/packages/mcp-server/src/cli/resolve.ts b/packages/mcp-server/src/cli/resolve.ts index d51b0baaa..de26fd632 100644 --- a/packages/mcp-server/src/cli/resolve.ts +++ b/packages/mcp-server/src/cli/resolve.ts @@ -2,6 +2,7 @@ import { ACTIVE_SKILLS, ALL_SKILLS, parseSkills, + SKILLS, type Skill, } from "@sentry/mcp-core/skills"; import { isSentryHost } from "@sentry/toolkit-core/sentry-host"; @@ -13,12 +14,31 @@ import { import { DEFAULT_SENTRY_CLIENT_ID } from "../auth/constants"; import type { MergedArgs, PartiallyResolvedConfig } from "./types"; +const STDIO_SKILLS = ALL_SKILLS.filter((skill) => !SKILLS[skill].hostedOnly); +const ACTIVE_STDIO_SKILLS = ACTIVE_SKILLS.filter( + (skill) => !SKILLS[skill].hostedOnly, +); + export function formatInvalidSkills( invalid: string[], source?: string, ): string { const prefix = source ? `${source} provided` : "Invalid skills provided"; - return `Error: ${prefix}: ${invalid.join(", ")}\nAvailable skills: ${ALL_SKILLS.join(", ")}`; + return `Error: ${prefix}: ${invalid.join(", ")}\nAvailable skills: ${STDIO_SKILLS.join(", ")}`; +} + +function parseStdioSkills(input: unknown, source?: string): Set { + const { valid, invalid } = parseSkills(input); + if (invalid.length > 0) { + throw new Error(formatInvalidSkills(invalid, source)); + } + const hostedSkills = [...valid].filter((skill) => SKILLS[skill].hostedOnly); + if (hostedSkills.length > 0) { + throw new Error( + `Error: These skills are available only on the hosted MCP: ${hostedSkills.join(", ")}. They cannot be used with stdio.`, + ); + } + return valid; } /** @@ -77,20 +97,17 @@ export function finalize(input: MergedArgs): PartiallyResolvedConfig { } if (input.allSkills) { - finalSkills = new Set(ACTIVE_SKILLS); + finalSkills = new Set(ACTIVE_STDIO_SKILLS); } else if (input.skills) { // Override: use only the specified skills - const { valid, invalid } = parseSkills(input.skills); - if (invalid.length > 0) { - throw new Error(formatInvalidSkills(invalid)); - } + const valid = parseStdioSkills(input.skills); if (valid.size === 0) { throw new Error("Error: Invalid skills provided. No valid skills found."); } finalSkills = valid; } else { // Default: grant all active skills when no flag is provided (see comment block above for rationale) - finalSkills = new Set(ACTIVE_SKILLS); + finalSkills = new Set(ACTIVE_STDIO_SKILLS); // Seer is a sentry.io service. Self-hosted Sentry ships without it, and its // autofix endpoints answer 500 when the Seer backend is not configured, so @@ -105,12 +122,10 @@ export function finalize(input: MergedArgs): PartiallyResolvedConfig { // Disable-skills: remove specific skills from the active set if (input.disableSkills) { - const { valid: skillsToDisable, invalid } = parseSkills( + const skillsToDisable = parseStdioSkills( input.disableSkills, + "--disable-skills", ); - if (invalid.length > 0) { - throw new Error(formatInvalidSkills(invalid, "--disable-skills")); - } for (const skill of skillsToDisable) { finalSkills.delete(skill); } diff --git a/plugins/sentry-mcp-experimental/agents/sentry-mcp.md b/plugins/sentry-mcp-experimental/agents/sentry-mcp.md index eebfb87b7..adcd9c2bc 100644 --- a/plugins/sentry-mcp-experimental/agents/sentry-mcp.md +++ b/plugins/sentry-mcp-experimental/agents/sentry-mcp.md @@ -11,6 +11,7 @@ mcpServers: - sentry allowedTools: - analyze_issue_with_seer + - execute_sentry_code - execute_sentry_tool - find_dropped_events - find_organizations @@ -22,6 +23,7 @@ allowedTools: - search_metrics - search_profiles - search_replays + - search_sentry_api - search_sentry_tools - search_traces --- diff --git a/plugins/sentry-mcp/agents/sentry-mcp.md b/plugins/sentry-mcp/agents/sentry-mcp.md index eebfb87b7..adcd9c2bc 100644 --- a/plugins/sentry-mcp/agents/sentry-mcp.md +++ b/plugins/sentry-mcp/agents/sentry-mcp.md @@ -11,6 +11,7 @@ mcpServers: - sentry allowedTools: - analyze_issue_with_seer + - execute_sentry_code - execute_sentry_tool - find_dropped_events - find_organizations @@ -22,6 +23,7 @@ allowedTools: - search_metrics - search_profiles - search_replays + - search_sentry_api - search_sentry_tools - search_traces --- diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bcdcdc84d..3d0b81a4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -938,6 +938,10 @@ importers: zod: specifier: 'catalog:' version: 4.6.7 + devDependencies: + wrangler: + specifier: 'catalog:' + version: 4.151.3(@cloudflare/workers-types@4.20260405.1)(@types/node@24.13.5) packages/mcp-server-mocks: dependencies: diff --git a/scripts/pre-commit-generated.mjs b/scripts/pre-commit-generated.mjs index 98aed210b..675d3c80e 100644 --- a/scripts/pre-commit-generated.mjs +++ b/scripts/pre-commit-generated.mjs @@ -16,6 +16,7 @@ const GENERATED_TASKS = [ /^packages\/mcp-core\/src\/skills\.ts$/, /^packages\/mcp-core\/src\/internal\/tool-helpers\/tool-call-formatting\.ts$/, /^packages\/mcp-core\/scripts\/generate-definitions\.ts$/, + /^packages\/mcp-cloudflare\/src\/code-mode\/definitions\.ts$/, /^plugins\/sentry-mcp\/agents\/sentry-mcp\.md$/, /^plugins\/sentry-mcp-experimental\/agents\/sentry-mcp\.md$/, ], From 3880cb3fba992f61bb9408a53f0b6a552771d55e Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 26 Oct 2026 22:03:05 +0100 Subject: [PATCH 2/5] fix(mcp): bound Code Mode inputs and align test runtime --- docs/testing/code-mode.md | 40 +++- .../mcp-cloudflare/src/code-mode/broker.ts | 19 +- .../src/code-mode/discovery-worker.ts | 10 +- packages/mcp-cloudflare/src/code-mode/http.ts | 32 ++- .../src/code-mode/worker.test.ts | 19 +- .../mcp-cloudflare/src/code-mode/worker.ts | 28 ++- .../server/lib/code-mode.integration.test.ts | 127 ++++++++++-- .../src/server/lib/mcp-handler.ts | 8 +- packages/mcp-core/src/constants.ts | 4 + packages/mcp-core/src/server.ts | 6 +- .../src/code-mode/codex-agent.test.ts | 133 +++++++++++++ .../src/code-mode/codex-agent.ts | 188 ++++++++++++++++++ .../src/code-mode/run-dashboard.ts | 186 +---------------- 13 files changed, 574 insertions(+), 226 deletions(-) create mode 100644 packages/mcp-server-evals/src/code-mode/codex-agent.test.ts create mode 100644 packages/mcp-server-evals/src/code-mode/codex-agent.ts diff --git a/docs/testing/code-mode.md b/docs/testing/code-mode.md index 35ad80e55..1b6dd8598 100644 --- a/docs/testing/code-mode.md +++ b/docs/testing/code-mode.md @@ -115,8 +115,13 @@ separate module. This RPC dispatch mechanism replaces the prototype's `GlobalOutbound` loopback entrypoint while retaining the HTTP tool interface. Each execution has a 20-call fetch budget, a 30-second I/O deadline, a 50,000-byte -result limit, and a 2,000,000-byte upstream response limit. Dynamic Worker CPU and -subrequest limits are also configured using the +result limit, and a 2,000,000-byte upstream response limit. +Before tool validation, the HTTP MCP SDK rejects arguments with more than 10,000 +array elements or object keys. A bounded body reader rejects requests over 1 MiB +before dispatch, including legacy clients whose Agents transport omits the SDK's +body-limit option. + +Dynamic Worker CPU and subrequest limits are also configured using the [Cloudflare API](https://developers.cloudflare.com/dynamic-workers/usage/limits/). **Local workerd does not enforce those platform limits in our probes. CPU termination and deployment enforcement have not been validated.** This local @@ -265,6 +270,12 @@ A generic 403 is not labelled as a missing scope or a need to reconnect: it may reflect role or resource access. No failed program is automatically replayed, since earlier writes may have succeeded. +Upstream 5xx responses create a Sentry issue through the existing `logIssue` +helper, even when generated code catches the HTTP error. The broker preserves +the response and exposes the event ID in `x-sentry-event-id`; reporting includes +only the HTTP method and status, not request or response payloads. Expected 4xx +responses do not create issues. + **Project-constrained sessions retain the existing tools without Code Mode.** A project-bearing URL does not prove that an operation affects only that project. For example, Sentry's project release-file endpoints modify a file belonging to @@ -278,21 +289,27 @@ This limitation is based on the backend implementations in ### Hosted integration validation +The hosted test suite uses `@cloudflare/vitest-plugin` 1.4.6, whose Wrangler and +Miniflare versions match the standalone Code Mode tests. Updating only the direct +Miniflare dependency left the old Vitest pool running an older workerd; the first +Linux CI run exited during the Dynamic Worker integration tests. The plugin +replacement removes that runtime mismatch without binary overrides. + Local validation on 2026-10-26 exercised the authenticated MCP handler, Dynamic Workers and RPC broker with simulated external OpenAPI/Sentry HTTP responses: -- After rebasing and applying the consent screen, 464 Cloudflare tests across - 35 files were verified through the full run and a focused rerun, including - 15 authenticated Code Mode integration tests and two public metadata tests. +- The full Cloudflare suite passed 469 tests across 35 files, including + 20 authenticated Code Mode integration tests and two public metadata tests. The dashboard regression reads the original, creates two project-specific copies and verifies them with `api-write`, while the simulated upstream token records only `org:read`. -- The isolated suite passed all 29 tests: 18 runtime, seven API path/organization +- The isolated suite passed all 30 tests: 19 runtime, seven API path/organization checks and four OpenAPI loading tests. It uses the installed Miniflare/workerd aligned with Wrangler, without a binary override. - MCP core (1,834 tests), stdio (89), test client (80) and Code Mode fixtures - (53) passed, for 2,549 MCP tests in total. Root build/typechecks, lint, Code - Mode typechecks and frozen install passed. Generated metadata includes the + (56) passed, for 2,558 MCP tests in total (six existing core skips). Root + build/typechecks, lint, Code Mode typechecks and frozen install passed. + Generated metadata includes the hosted API skills; core tool registrations remain unchanged. - The final `wrangler deploy --dry-run` packaged the production Worker with `LOADER`. This did not upload a version or deploy the change. @@ -310,8 +327,11 @@ full workspace suite; CLI fixes are outside this change. The integration tests verify regional routing, client attribution, concurrent user token isolation, denied operations and organization escapes, project/skill visibility, and upstream 401 revocation even when generated code catches the -error. They do not test a real Sentry account, production CPU enforcement, or an -active hosted deployment. Historical agent evals below remain separate evidence. +error. They also reject oversized request bodies for modern and legacy clients, +bound nested tool arguments before execution, and verify that caught upstream +5xx failures are reported while expected 4xx responses are not. They do not test +a real Sentry account, production CPU enforcement, or an active hosted deployment. +Historical agent evals below remain separate evidence. ### Isolated harness diff --git a/packages/mcp-cloudflare/src/code-mode/broker.ts b/packages/mcp-cloudflare/src/code-mode/broker.ts index 086510af1..34d3cb6d4 100644 --- a/packages/mcp-cloudflare/src/code-mode/broker.ts +++ b/packages/mcp-cloudflare/src/code-mode/broker.ts @@ -1,9 +1,9 @@ import { RpcTarget } from "cloudflare:workers"; +import { ApiServerError } from "@sentry/mcp-core/api-client"; +import { logIssue } from "@sentry/mcp-core/telem/logging"; import { configuredUrl, readBoundedBody } from "./http"; import type { ApiAuthorization } from "./production"; -export { configuredUrl, readBoundedBody } from "./http"; - export interface CodeModeEnv { LOADER: WorkerLoader; /** Fixed origin and API prefix, e.g. https://sentry.io/api/0. */ @@ -102,6 +102,20 @@ export class ApiBroker extends RpcTarget { if (response.status === 401) { await this.#options.authorization?.onUnauthorized(); } + // Report before returning to user code, which may handle the HTTP error. + const eventId = + response.status >= 500 + ? logIssue( + new ApiServerError( + "Sentry API request failed during Code Mode execution.", + response.status, + ), + { + loggerScope: ["code-mode", "api"], + extra: { method: request.method, status: response.status }, + }, + ) + : undefined; // Do not allow a redirect to carry our bearer token to another location. if (response.status >= 300 && response.status < 400) { await response.body?.cancel(); @@ -114,6 +128,7 @@ export class ApiBroker extends RpcTarget { ); } const responseHeaders = new Headers(); + if (eventId) responseHeaders.set("x-sentry-event-id", eventId); for (const name of [ "content-type", "link", diff --git a/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts b/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts index 1791eea99..340861145 100644 --- a/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts +++ b/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts @@ -1,7 +1,9 @@ import { createMcpHandler } from "agents/mcp/server"; +import { MAX_MCP_REQUEST_BODY_SIZE } from "../../../mcp-core/src/constants"; import { buildServer } from "../../../mcp-core/src/server"; import { ALL_SKILLS } from "../../../mcp-core/src/skills"; -import { type CodeModeEnv, configuredUrl } from "./broker"; +import type { CodeModeEnv } from "./broker"; +import { configuredUrl, limitMcpRequestBody } from "./http"; import { registerCodeModeTools } from "./worker"; /** Eval-only composition of the real catalog and HTTP Code Mode on one MCP. */ @@ -31,6 +33,8 @@ export default { }); } + const boundedRequest = await limitMcpRequestBody(request); + if (boundedRequest instanceof Response) return boundedRequest; return await createMcpHandler( () => { const server = buildServer({ @@ -47,7 +51,7 @@ export default { if (route === "/mcp") registerCodeModeTools(server, env, token); return server; }, - { route }, - )(request, env, ctx); + { route, maxRequestBodySize: MAX_MCP_REQUEST_BODY_SIZE }, + )(boundedRequest, env, ctx); }, } satisfies ExportedHandler; diff --git a/packages/mcp-cloudflare/src/code-mode/http.ts b/packages/mcp-cloudflare/src/code-mode/http.ts index 6a114146e..c8646877e 100644 --- a/packages/mcp-cloudflare/src/code-mode/http.ts +++ b/packages/mcp-cloudflare/src/code-mode/http.ts @@ -1,3 +1,5 @@ +import { MAX_MCP_REQUEST_BODY_SIZE } from "@sentry/mcp-core/constants"; + export function configuredUrl(value: string): URL { const url = new URL(value); const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname); @@ -15,7 +17,9 @@ export function configuredUrl(value: string): URL { return url; } -/** Read before forwarding so streaming responses cannot exceed the byte budget. */ +export class BodySizeLimitError extends Error {} + +/** Read before forwarding so streaming bodies cannot exceed the byte budget. */ export async function readBoundedBody( body: ReadableStream | null, maximumBytes: number, @@ -31,7 +35,7 @@ export async function readBoundedBody( size += next.value.byteLength; if (size > maximumBytes) { await reader.cancel(); - throw new Error( + throw new BodySizeLimitError( `Response exceeds ${maximumBytes} bytes. Narrow the query or use pagination.`, ); } @@ -48,3 +52,27 @@ export async function readBoundedBody( } return result; } + +/** Agents' legacy transport does not forward the SDK's maxRequestBodySize. */ +export async function limitMcpRequestBody( + request: Request, +): Promise { + if (request.method !== "POST") return request; + try { + const body = await readBoundedBody(request.body, MAX_MCP_REQUEST_BODY_SIZE); + return new Request(request, { method: "POST", body }); + } catch (error) { + if (!(error instanceof BodySizeLimitError)) throw error; + return Response.json( + { + jsonrpc: "2.0", + id: null, + error: { + code: -32000, + message: `Payload Too Large: Request body must not exceed ${MAX_MCP_REQUEST_BODY_SIZE} bytes`, + }, + }, + { status: 413 }, + ); + } +} diff --git a/packages/mcp-cloudflare/src/code-mode/worker.test.ts b/packages/mcp-cloudflare/src/code-mode/worker.test.ts index 941a63e7c..0f0a5165c 100644 --- a/packages/mcp-cloudflare/src/code-mode/worker.test.ts +++ b/packages/mcp-cloudflare/src/code-mode/worker.test.ts @@ -6,6 +6,7 @@ import { join, resolve } from "node:path"; import { promisify } from "node:util"; import { convertV4MiniflareOptions, Miniflare } from "miniflare"; import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { MAX_MCP_TOOL_INPUT_ELEMENTS } from "../../../mcp-core/src/constants"; interface ToolResult { content: Array<{ type: string; text?: string }>; @@ -223,6 +224,7 @@ beforeAll(async () => { response.end( JSON.stringify({ openapi: "3.0.0", + servers: [{ url: origin }], paths: { "/api/0/items/": { get: { summary: "List items" } } }, }), ); @@ -299,6 +301,21 @@ afterAll(async () => { }); describe("HTTP Code Mode through real Dynamic Workers", () => { + it("bounds nested tool arguments before starting a Dynamic Worker", async () => { + const result = await rpc("tools/call", { + name: "execute_sentry_code", + arguments: { + code: 'async () => sentry.request({path: "/items/"})', + unused: { values: Array(MAX_MCP_TOOL_INPUT_ELEMENTS + 1).fill(null) }, + }, + }); + expect(result.isError).toBe(true); + expect(result.content[0]?.text).toContain( + `maximum of ${MAX_MCP_TOOL_INPUT_ELEMENTS} elements`, + ); + expect(requests).toEqual([]); + }); + it("requires an explicit direct API bearer token", async () => { const response = await worker.dispatchFetch("http://localhost/mcp", { method: "POST", @@ -505,7 +522,7 @@ describe("HTTP Code Mode through real Dynamic Workers", () => { it("prevents search from accessing the network", async () => { const result = await callTool( "search_sentry_api", - `async () => (await fetch(${JSON.stringify(`${origin}/api/0/items/`)})).text()`, + 'async () => (await fetch(new URL("/api/0/items/", spec.servers[0].url))).text()', ); expect(result.isError).toBe(true); expect(requests).toEqual([]); diff --git a/packages/mcp-cloudflare/src/code-mode/worker.ts b/packages/mcp-cloudflare/src/code-mode/worker.ts index 640991583..33c631d51 100644 --- a/packages/mcp-cloudflare/src/code-mode/worker.ts +++ b/packages/mcp-cloudflare/src/code-mode/worker.ts @@ -1,16 +1,16 @@ import type { WorkerEntrypoint } from "cloudflare:workers"; import { McpServer } from "@modelcontextprotocol/server"; -import { createMcpHandler } from "agents/mcp/server"; import { - ApiBroker, - type CodeModeEnv, - configuredUrl, - readBoundedBody, -} from "./broker"; + MAX_MCP_REQUEST_BODY_SIZE, + MAX_MCP_TOOL_INPUT_ELEMENTS, +} from "@sentry/mcp-core/constants"; +import { createMcpHandler } from "agents/mcp/server"; +import { ApiBroker, type CodeModeEnv } from "./broker"; import { codeModeResultSchema, getCodeModeToolDefinitions, } from "./definitions"; +import { configuredUrl, limitMcpRequestBody, readBoundedBody } from "./http"; import { getSpec } from "./openapi"; import type { ApiAuthorization } from "./production"; import { sandboxSource } from "./sandbox"; @@ -114,10 +114,13 @@ export function registerCodeModeTools( } function buildServer(env: CodeModeEnv, token: string): McpServer { - const server = new McpServer({ - name: "sentry-code-mode-experiment", - version: "0.1.0", - }); + const server = new McpServer( + { + name: "sentry-code-mode-experiment", + version: "0.1.0", + }, + { maxToolInputElements: MAX_MCP_TOOL_INPUT_ELEMENTS }, + ); registerCodeModeTools(server, env, token); return server; } @@ -138,8 +141,11 @@ export default { return new Response("A direct Sentry API bearer token is required.", { status: 401, }); + const boundedRequest = await limitMcpRequestBody(request); + if (boundedRequest instanceof Response) return boundedRequest; return await createMcpHandler(() => buildServer(env, token), { route: "/mcp", - })(request, env, ctx); + maxRequestBodySize: MAX_MCP_REQUEST_BODY_SIZE, + })(boundedRequest, env, ctx); }, } satisfies ExportedHandler; diff --git a/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts index 501979e0c..2fbb21865 100644 --- a/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts @@ -4,6 +4,11 @@ import { waitOnExecutionContext, } from "cloudflare:test"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + MAX_MCP_REQUEST_BODY_SIZE, + MAX_MCP_TOOL_INPUT_ELEMENTS, +} from "@sentry/mcp-core/constants"; +import * as logging from "@sentry/mcp-core/telem/logging"; import { SENTRY_OPENAPI_URL } from "../../code-mode/openapi"; import type { Env, WorkerProps } from "../types"; import mcpHandler from "./mcp-handler"; @@ -91,11 +96,16 @@ function workerEnv(): Env { } as Env; } -async function rpc( +async function dispatchRpc( method: string, params: Record, - options: { props?: Partial; path?: string } = {}, -): Promise { + options: { + props?: Partial; + path?: string; + protocolVersion?: "2026-07-28" | "2025-03-26"; + } = {}, +) { + const protocolVersion = options.protocolVersion ?? "2026-07-28"; const props: WorkerProps = { id: "code-mode-user", clientId: "code-mode-client", @@ -117,7 +127,7 @@ async function rpc( "CF-Connecting-IP": "192.0.2.1", "User-Agent": "claude-code/2.0.0", "X-Sentry-Utm-Source": "plugin", - "mcp-protocol-version": "2026-07-28", + "mcp-protocol-version": protocolVersion, "mcp-method": method, ...(typeof params.name === "string" ? { "mcp-name": params.name } : {}), }, @@ -127,18 +137,31 @@ async function rpc( method, params: { ...params, - _meta: { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientInfo": { - name: "code-mode-integration-tests", - version: "1.0.0", - }, - "io.modelcontextprotocol/clientCapabilities": {}, - }, + ...(protocolVersion === "2026-07-28" + ? { + _meta: { + "io.modelcontextprotocol/protocolVersion": protocolVersion, + "io.modelcontextprotocol/clientInfo": { + name: "code-mode-integration-tests", + version: "1.0.0", + }, + "io.modelcontextprotocol/clientCapabilities": {}, + }, + } + : {}), }, }), }); const response = await mcpHandler.fetch!(request, workerEnv(), ctx); + return { response, ctx }; +} + +async function rpc( + method: string, + params: Record, + options: Parameters[2] = {}, +): Promise { + const { response, ctx } = await dispatchRpc(method, params, options); const text = await response.text(); await waitOnExecutionContext(ctx); expect(response.status, text).toBe(200); @@ -209,6 +232,55 @@ beforeEach(() => { afterEach(() => vi.restoreAllMocks()); describe("Code Mode on the authenticated MCP", () => { + it("rejects excessive nested argument members before executing code", async () => { + const result = await rpc("tools/call", { + name: "execute_sentry_code", + arguments: { + code: 'async () => sentry.request({path: "/organizations/"})', + unused: Object.fromEntries( + Array.from( + { length: MAX_MCP_TOOL_INPUT_ELEMENTS + 1 }, + (_, index) => [`key${index}`, null], + ), + ), + }, + }); + expect(result.isError).toBe(true); + expect(result.content[0]?.text).toContain( + `maximum of ${MAX_MCP_TOOL_INPUT_ELEMENTS} elements`, + ); + expect(apiRequests).toEqual([]); + }); + + it.each(["2026-07-28", "2025-03-26"] as const)( + "rejects HTTP bodies above the limit with protocol %s", + async (protocolVersion) => { + const { response, ctx } = await dispatchRpc( + "tools/call", + { + name: "execute_sentry_code", + arguments: { code: "x".repeat(MAX_MCP_REQUEST_BODY_SIZE) }, + }, + { protocolVersion }, + ); + const body = await response.text(); + await waitOnExecutionContext(ctx); + expect(response.status).toBe(413); + expect(response.headers.get("Content-Type")).toContain( + "application/json", + ); + expect(JSON.parse(body)).toEqual({ + jsonrpc: "2.0", + id: null, + error: { + code: -32000, + message: `Payload Too Large: Request body must not exceed ${MAX_MCP_REQUEST_BODY_SIZE} bytes`, + }, + }); + expect(apiRequests).toEqual([]); + }, + ); + it("advertises Code Mode alongside the normal catalog and executes discovery", async () => { const list = await rpc("tools/list", {}); expect(list.tools.map(({ name }) => name)).toEqual( @@ -496,4 +568,35 @@ describe("Code Mode on the authenticated MCP", () => { "code-mode-user", ); }); + + it.each([403, 503])( + "reports only upstream server failures when user code catches HTTP %s", + async (status) => { + const logIssue = vi + .spyOn(logging, "logIssue") + .mockReturnValue("code-mode-event"); + apiResponse = () => + Response.json({ detail: "Upstream failure" }, { status }); + const result = await execute(`async () => { + try { await sentry.request({path: "/organizations/"}); } + catch (error) { return {status: error.status, eventId: error.headers["x-sentry-event-id"] ?? null}; } + }`); + expect(result.isError).toBeUndefined(); + expect(result.structuredContent?.result).toEqual({ + status, + eventId: status === 503 ? "code-mode-event" : null, + }); + if (status === 503) { + expect(logIssue).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ name: "ApiServerError", status: 503 }), + { + loggerScope: ["code-mode", "api"], + extra: { method: "GET", status: 503 }, + }, + ); + } else { + expect(logIssue).not.toHaveBeenCalled(); + } + }, + ); }); diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts index 99bf57189..65fa6856f 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts @@ -14,6 +14,7 @@ import type { ExportedHandler } from "@cloudflare/workers-types"; import * as Sentry from "@sentry/cloudflare"; +import { MAX_MCP_REQUEST_BODY_SIZE } from "@sentry/mcp-core/constants"; import { buildServer } from "@sentry/mcp-core/server"; import { ACTIVE_SKILLS, @@ -23,6 +24,7 @@ import { import { logWarn } from "@sentry/mcp-core/telem/logging"; import type { ServerContext } from "@sentry/mcp-core/types"; import { createMcpHandler } from "agents/mcp/server"; +import { limitMcpRequestBody } from "../../code-mode/http"; import { registerProductionCodeModeTools } from "../../code-mode/production"; import { annotateResponseMetric } from "../metrics"; import { @@ -457,6 +459,9 @@ async function handleAuthenticatedMcpRequest( auth.kind === "oauth" ? auth.onUpstreamUnauthorized : undefined, }; + const boundedRequest = await limitMcpRequestBody(request); + if (boundedRequest instanceof Response) return boundedRequest; + // The modern handler requires a factory and creates a fresh SDK v2 server // for every request. Context remains request-scoped through tool closures. return await createMcpHandler( @@ -471,11 +476,12 @@ async function handleAuthenticatedMcpRequest( }, { route: url.pathname, + maxRequestBodySize: MAX_MCP_REQUEST_BODY_SIZE, // The Agents handler rejects browser Origins by default except localhost. // Permit only the hostname serving this authenticated endpoint. allowedOriginHostnames: [url.hostname], }, - )(request, env, ctx); + )(boundedRequest, env, ctx); } /** diff --git a/packages/mcp-core/src/constants.ts b/packages/mcp-core/src/constants.ts index 487bd04c5..a0509f13c 100644 --- a/packages/mcp-core/src/constants.ts +++ b/packages/mcp-core/src/constants.ts @@ -9,6 +9,10 @@ */ export const MCP_SERVER_NAME = "Sentry MCP" as const; +/** Bound HTTP tool input before schema validation expands nested arguments. */ +export const MAX_MCP_TOOL_INPUT_ELEMENTS = 10_000; +export const MAX_MCP_REQUEST_BODY_SIZE = 1024 * 1024; + /** * Supported issue-search windows exposed by search_issues. * Intentionally bounded for query performance; lower-level API methods still accept arbitrary Sentry statsPeriod strings. diff --git a/packages/mcp-core/src/server.ts b/packages/mcp-core/src/server.ts index fd2d3e831..a8561df75 100644 --- a/packages/mcp-core/src/server.ts +++ b/packages/mcp-core/src/server.ts @@ -30,7 +30,7 @@ import { } from "@sentry/core"; import { wrapMcpServerWithSentry } from "@sentry/core/server"; import { isApiAuthenticationErrorDeep } from "./api-client"; -import { MCP_SERVER_NAME } from "./constants"; +import { MAX_MCP_TOOL_INPUT_ELEMENTS, MCP_SERVER_NAME } from "./constants"; import { formatErrorForUser, isExpectedToolError, @@ -158,7 +158,9 @@ export function buildServer({ }; if (sdkVersion === "v2") { - const server = new ModernMcpServer(serverInfo); + const server = new ModernMcpServer(serverInfo, { + maxToolInputElements: MAX_MCP_TOOL_INPUT_ELEMENTS, + }); const registrations = configureServer({ server, context: contextWithModes, diff --git a/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts b/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts new file mode 100644 index 000000000..bd83b6246 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts @@ -0,0 +1,133 @@ +import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { runCodexAgent } from "./codex-agent"; + +const directories: string[] = []; +const options = { model: "test-model", systemPrompt: "Use the MCP tools." }; + +async function fakeCodex(events: unknown[], closeInput = false) { + const directory = await mkdtemp(path.join(tmpdir(), "codex-eval-test-")); + directories.push(directory); + const executable = path.join(directory, "codex"); + // Exercise the real process boundary, including a final event without a newline. + const payload = events.map((event) => JSON.stringify(event)).join("\n"); + await writeFile( + executable, + `#!${process.execPath} +const fs = require("node:fs"); +if (${closeInput}) { + fs.closeSync(0); + setTimeout(() => process.exit(1), 20); +} else { + let input = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", chunk => { input += chunk; }); + process.stdin.on("end", () => { + fs.writeFileSync("capture.json", JSON.stringify({ args: process.argv.slice(2), input })); + const payload = ${JSON.stringify(payload)}; + process.stdout.write(payload.slice(0, 37)); + process.stdout.write(payload.slice(37)); + }); +} +`, + ); + await chmod(executable, 0o755); + vi.stubEnv("PATH", `${directory}${path.delimiter}${process.env.PATH ?? ""}`); + return directory; +} + +afterEach(async () => { + vi.unstubAllEnvs(); + await Promise.all( + directories + .splice(0) + .map((directory) => rm(directory, { recursive: true })), + ); +}); + +describe("Codex Code Mode adapter", () => { + it("keeps the final event and aggregate usage without a trailing newline", async () => { + const usage = { input_tokens: 100, output_tokens: 20 }; + const directory = await fakeCodex([ + { + type: "item.completed", + session_id: "private-session-metadata", + item: { + id: "call-1", + type: "mcp_tool_call", + server: "sentry_code_mode", + tool: "execute_sentry_code", + arguments: { code: "async () => 2" }, + status: "completed", + result: { content: [{ type: "text", text: "2" }] }, + }, + }, + { + type: "item.completed", + item: { + id: "answer", + type: "agent_message", + text: "Copied both dashboards.", + }, + }, + { type: "turn.completed", usage }, + ]); + const result = await runCodexAgent( + directory, + "http://127.0.0.1:4321/mcp", + "dummy-token", + "Copy two dashboards", + options, + ); + expect(result).toMatchObject({ + exitCode: 0, + error: undefined, + text: "Copied both dashboards.", + usage, + mcpCalls: [{ id: "call-1", name: "execute_sentry_code" }], + }); + expect(JSON.stringify(result.events)).not.toContain("private-"); + const capture = JSON.parse( + await readFile(path.join(directory, "capture.json"), "utf8"), + ); + expect(capture.input).toBe("Copy two dashboards"); + expect(capture.args).toContain( + 'developer_instructions="Use the MCP tools."', + ); + expect(capture.args).toContain("--ignore-user-config"); + }); + + it("reports a closed input instead of crashing the evaluation process", async () => { + const directory = await fakeCodex([], true); + const result = await runCodexAgent( + directory, + "http://localhost/mcp", + "dummy-token", + "x".repeat(2_000_000), + options, + ); + expect(result.error).toBe( + "Codex closed its input before accepting the prompt", + ); + }); + + it("rejects an out-of-scope tool even when it is the final event", async () => { + const directory = await fakeCodex([ + { type: "turn.completed", usage: { input_tokens: 1, output_tokens: 1 } }, + { + type: "item.completed", + item: { id: "shell", type: "command_execution" }, + }, + ]); + const result = await runCodexAgent( + directory, + "http://localhost/mcp", + "dummy-token", + "Copy", + options, + ); + expect(result.error).toBe("Agent used a tool outside the experiment"); + }); +}); diff --git a/packages/mcp-server-evals/src/code-mode/codex-agent.ts b/packages/mcp-server-evals/src/code-mode/codex-agent.ts new file mode 100644 index 000000000..38cfe86f3 --- /dev/null +++ b/packages/mcp-server-evals/src/code-mode/codex-agent.ts @@ -0,0 +1,188 @@ +import { spawn } from "node:child_process"; +import { z } from "zod"; + +const toolNames = ["search_sentry_api", "execute_sentry_code"]; + +const usageSchema = z.object({ + input_tokens: z.number(), + output_tokens: z.number(), + cached_input_tokens: z.number().optional(), + cache_write_input_tokens: z.number().optional(), + reasoning_output_tokens: z.number().optional(), +}); +const itemSchema = z.object({ + id: z.string(), + type: z.string(), + server: z.string().optional(), + tool: z.string().optional(), + arguments: z.unknown().optional(), + result: z + .object({ + content: z.array(z.unknown()), + structured_content: z.unknown().optional(), + }) + .nullable() + .optional(), + error: z.object({ message: z.string() }).nullable().optional(), + status: z.string().optional(), + text: z.string().optional(), +}); +const eventSchema = z.object({ + type: z.string(), + item: itemSchema.optional(), + usage: usageSchema.optional(), + error: z.object({ message: z.string() }).optional(), + message: z.string().optional(), +}); +type TraceEvent = z.infer; + +/** Fresh CLI session; reject runs that use anything besides the two MCP tools. */ +export async function runCodexAgent( + directory: string, + mcpUrl: string, + accessToken: string, + prompt: string, + { model, systemPrompt }: { model: string; systemPrompt: string }, +) { + const events: TraceEvent[] = []; + let buffer = ""; + let bytes = 0; + let stderr = ""; + let protocolError: string | undefined; + let processError: string | undefined; + const config = { + approval_policy: "never", + developer_instructions: systemPrompt, + project_doc_max_bytes: 0, + "skills.include_instructions": false, + web_search: "disabled", + model_reasoning_effort: "medium", + "tools.experimental_request_user_input.enabled": false, + "mcp_servers.sentry_code_mode.url": mcpUrl, + "mcp_servers.sentry_code_mode.http_headers.Authorization": `Bearer ${accessToken}`, + "mcp_servers.sentry_code_mode.enabled_tools": toolNames, + "mcp_servers.sentry_code_mode.required": true, + "mcp_servers.sentry_code_mode.default_tools_approval_mode": "approve", + }; + const disabled = [ + "shell_tool", + "apps", + "plugins", + "hooks", + "memories", + "browser_use", + "computer_use", + "image_generation", + "multi_agent", + "code_mode", + "tool_suggest", + ]; + const args = [ + "exec", + "--json", + "--ephemeral", + "--ignore-user-config", + "--ignore-rules", + "--skip-git-repo-check", + "--sandbox", + "read-only", + "--model", + model, + ...disabled.flatMap((feature) => ["--disable", feature]), + ...Object.entries(config).flatMap(([key, value]) => [ + "-c", + `${key}=${JSON.stringify(value)}`, + ]), + "-", + ]; + const started = performance.now(); + const child = spawn("codex", args, { + cwd: directory, + stdio: ["pipe", "pipe", "pipe"], + signal: AbortSignal.timeout(300_000), + }); + const consumeLine = (line: string) => { + if (!line.trim()) return; + try { + // Keep task evidence, not session/account/machine metadata. + events.push(eventSchema.parse(JSON.parse(line))); + } catch { + protocolError = "Agent emitted an invalid JSON event"; + } + }; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + bytes += Buffer.byteLength(chunk); + if (bytes > 10_000_000) { + protocolError = "Agent trace exceeded 10 MB"; + child.kill(); + return; + } + buffer += chunk; + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) consumeLine(line); + }); + child.stderr.on("data", (chunk: string) => { + stderr = (stderr + chunk).slice(-4_000); + }); + child.stdin.on("error", () => { + processError ??= "Codex closed its input before accepting the prompt"; + }); + child.stdin.end(prompt); + const exitCode = await new Promise((resolve) => { + child.once("error", (error) => { + processError = error.message; + }); + child.once("close", resolve); + }); + consumeLine(buffer); + const completion = events.findLast( + (event) => event.type === "turn.completed", + ); + const items = events.flatMap((event) => + event.type === "item.completed" && event.item ? [event.item] : [], + ); + const calls = items.filter((item) => item.type === "mcp_tool_call"); + const unexpected = items.filter( + (item) => + !["mcp_tool_call", "agent_message", "reasoning", "error"].includes( + item.type, + ) || + (item.type === "mcp_tool_call" && + (item.server !== "sentry_code_mode" || + !toolNames.includes(item.tool ?? ""))), + ); + return { + durationMs: Math.round(performance.now() - started), + model, + exitCode, + error: + processError ?? + protocolError ?? + (unexpected.length + ? "Agent used a tool outside the experiment" + : undefined) ?? + (exitCode !== 0 || !completion + ? events.findLast((event) => event.error)?.error?.message || + stderr || + "Agent did not finish successfully" + : undefined), + text: items.findLast((item) => item.type === "agent_message")?.text ?? "", + usage: completion?.usage, + mcpCalls: calls.map(({ id, tool, arguments: input }) => ({ + id, + name: tool, + input, + })), + mcpErrors: calls.filter((call) => call.error || call.status === "failed") + .length, + toolResultBytes: calls.reduce( + (sum, call) => + sum + Buffer.byteLength(JSON.stringify(call.result ?? null)), + 0, + ), + events, + }; +} diff --git a/packages/mcp-server-evals/src/code-mode/run-dashboard.ts b/packages/mcp-server-evals/src/code-mode/run-dashboard.ts index 2717ca21c..7bd4b2c5a 100644 --- a/packages/mcp-server-evals/src/code-mode/run-dashboard.ts +++ b/packages/mcp-server-evals/src/code-mode/run-dashboard.ts @@ -3,7 +3,7 @@ * and an independent stateful API fixture decides whether the task succeeded. * Only synthetic data and a dummy token are used. Production MCP is untouched. */ -import { execFileSync, spawn } from "node:child_process"; +import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdtemp, readFile, rm } from "node:fs/promises"; import { createRequire } from "node:module"; @@ -18,6 +18,7 @@ import { unstable_dev } from "wrangler"; import { z } from "zod"; import { createAlertFixture } from "./alert-fixture"; import { runClaudeAgent } from "./claude-agent"; +import { runCodexAgent } from "./codex-agent"; import { createDashboardFixture } from "./dashboard-fixture"; import { createMonitorFixture } from "./monitor-fixture"; import { createReleaseFixture } from "./release-fixture"; @@ -40,189 +41,9 @@ const fixtures = { type Task = keyof typeof fixtures; const root = fileURLToPath(new URL("../../../../", import.meta.url)); -const toolNames = ["search_sentry_api", "execute_sentry_code"]; const systemPrompt = "Complete the user's task using only the sentry_code_mode MCP tools search_sentry_api and execute_sentry_code. Do not use shell, files, web, resources, or other tools. Discover the API as needed, execute the necessary operations, and report the actual result. Do not claim success without evidence. The requested writes are authorized."; -const usageSchema = z.object({ - input_tokens: z.number(), - output_tokens: z.number(), - cached_input_tokens: z.number().optional(), - cache_write_input_tokens: z.number().optional(), - reasoning_output_tokens: z.number().optional(), -}); -const itemSchema = z.object({ - id: z.string(), - type: z.string(), - server: z.string().optional(), - tool: z.string().optional(), - arguments: z.unknown().optional(), - result: z - .object({ - content: z.array(z.unknown()), - structured_content: z.unknown().optional(), - }) - .nullable() - .optional(), - error: z.object({ message: z.string() }).nullable().optional(), - status: z.string().optional(), - text: z.string().optional(), -}); -const eventSchema = z.object({ - type: z.string(), - item: itemSchema.optional(), - usage: usageSchema.optional(), - error: z.object({ message: z.string() }).optional(), - message: z.string().optional(), -}); -type TraceEvent = z.infer; - -/** Fresh CLI session; reject runs that use anything besides the two MCP tools. */ -async function runCodexAgent( - directory: string, - mcpUrl: string, - accessToken: string, - prompt: string, - model: string, -) { - const events: TraceEvent[] = []; - let buffer = ""; - let bytes = 0; - let stderr = ""; - let protocolError: string | undefined; - let processError: string | undefined; - const config = { - approval_policy: "never", - developer_instructions: systemPrompt, - project_doc_max_bytes: 0, - "skills.include_instructions": false, - web_search: "disabled", - model_reasoning_effort: "medium", - "tools.experimental_request_user_input.enabled": false, - "mcp_servers.sentry_code_mode.url": mcpUrl, - "mcp_servers.sentry_code_mode.http_headers.Authorization": `Bearer ${accessToken}`, - "mcp_servers.sentry_code_mode.enabled_tools": toolNames, - "mcp_servers.sentry_code_mode.required": true, - "mcp_servers.sentry_code_mode.default_tools_approval_mode": "approve", - }; - const disabled = [ - "shell_tool", - "apps", - "plugins", - "hooks", - "memories", - "browser_use", - "computer_use", - "image_generation", - "multi_agent", - "code_mode", - "tool_suggest", - ]; - const args = [ - "exec", - "--json", - "--ephemeral", - "--ignore-user-config", - "--ignore-rules", - "--skip-git-repo-check", - "--sandbox", - "read-only", - "--model", - model, - ...disabled.flatMap((feature) => ["--disable", feature]), - ...Object.entries(config).flatMap(([key, value]) => [ - "-c", - `${key}=${JSON.stringify(value)}`, - ]), - "-", - ]; - const started = performance.now(); - const child = spawn("codex", args, { - cwd: directory, - stdio: ["pipe", "pipe", "pipe"], - signal: AbortSignal.timeout(300_000), - }); - child.stdout.setEncoding("utf8"); - child.stderr.setEncoding("utf8"); - child.stdout.on("data", (chunk: string) => { - bytes += Buffer.byteLength(chunk); - if (bytes > 10_000_000) { - protocolError = "Agent trace exceeded 10 MB"; - child.kill(); - return; - } - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line of lines) { - if (!line.trim()) continue; - try { - // Keep task evidence, not session/account/machine metadata. - events.push(eventSchema.parse(JSON.parse(line))); - } catch { - protocolError = "Agent emitted an invalid JSON event"; - } - } - }); - child.stderr.on("data", (chunk: string) => { - stderr = (stderr + chunk).slice(-4_000); - }); - child.stdin.end(prompt); - const exitCode = await new Promise((resolve) => { - child.once("error", (error) => { - processError = error.message; - }); - child.once("close", resolve); - }); - const completion = events.findLast( - (event) => event.type === "turn.completed", - ); - const items = events.flatMap((event) => - event.type === "item.completed" && event.item ? [event.item] : [], - ); - const calls = items.filter((item) => item.type === "mcp_tool_call"); - const unexpected = items.filter( - (item) => - !["mcp_tool_call", "agent_message", "reasoning", "error"].includes( - item.type, - ) || - (item.type === "mcp_tool_call" && - (item.server !== "sentry_code_mode" || - !toolNames.includes(item.tool ?? ""))), - ); - return { - durationMs: Math.round(performance.now() - started), - model, - exitCode, - error: - processError ?? - protocolError ?? - (unexpected.length - ? "Agent used a tool outside the experiment" - : undefined) ?? - (exitCode !== 0 || !completion - ? events.findLast((event) => event.error)?.error?.message || - stderr || - "Agent did not finish successfully" - : undefined), - text: items.findLast((item) => item.type === "agent_message")?.text ?? "", - usage: completion?.usage, - mcpCalls: calls.map(({ id, tool, arguments: input }) => ({ - id, - name: tool, - input, - })), - mcpErrors: calls.filter((call) => call.error || call.status === "failed") - .length, - toolResultBytes: calls.reduce( - (sum, call) => - sum + Buffer.byteLength(JSON.stringify(call.result ?? null)), - 0, - ), - events, - }; -} - /** Run repetitions against fresh fixture state and persist reproducible evidence. */ async function main() { const { values } = parseArgs({ @@ -367,7 +188,7 @@ async function runTask( mcpUrl, accessToken, fixture.prompt, - values.model!, + { model: values.model!, systemPrompt }, ); let verification: { success: boolean; @@ -489,6 +310,7 @@ async function runTask( "packages/mcp-server-evals/src/code-mode/run-dashboard.ts", "packages/mcp-server-evals/src/code-mode/report.ts", "packages/mcp-server-evals/src/code-mode/claude-agent.ts", + "packages/mcp-server-evals/src/code-mode/codex-agent.ts", ].map(async (file) => [ file, createHash("sha256") From 3b9d4224658cddaac659831c8c9a0e87da0fecdf Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 27 Oct 2026 10:52:35 +0100 Subject: [PATCH 3/5] refactor(mcp): separate agent evals from Code Mode runtime --- docs/cloudflare/architecture.md | 2 +- docs/mcp/README.md | 2 +- docs/testing/code-mode.md | 1083 ++------ .../src/code-mode/discovery-worker.test.ts | 224 -- .../src/code-mode/discovery-worker.ts | 57 - .../src/code-mode/tsconfig.json | 2 +- packages/mcp-server-evals/package.json | 13 - .../src/code-mode/alert-fixture.test.ts | 241 -- .../src/code-mode/alert-fixture.ts | 450 ---- .../src/code-mode/api-fixture.ts | 98 - .../src/code-mode/claude-agent.test.ts | 281 -- .../src/code-mode/claude-agent.ts | 236 -- .../src/code-mode/codex-agent.test.ts | 133 - .../src/code-mode/codex-agent.ts | 188 -- .../src/code-mode/dashboard-fixture-data.json | 2307 ----------------- .../src/code-mode/dashboard-fixture.test.ts | 227 -- .../src/code-mode/dashboard-fixture.ts | 413 --- .../src/code-mode/json-answer.test.ts | 45 - .../src/code-mode/json-answer.ts | 17 - .../src/code-mode/monitor-fixture.test.ts | 298 --- .../src/code-mode/monitor-fixture.ts | 491 ---- .../src/code-mode/release-fixture.test.ts | 333 --- .../src/code-mode/release-fixture.ts | 417 --- .../src/code-mode/release-recovery-fixture.ts | 21 - .../src/code-mode/report.test.ts | 41 - .../mcp-server-evals/src/code-mode/report.ts | 25 - .../code-mode/repositories-denied-fixture.ts | 64 - .../code-mode/repositories-fixture.test.ts | 258 -- .../src/code-mode/repositories-fixture.ts | 228 -- .../code-mode/repositories-retry-fixture.ts | 31 - .../src/code-mode/run-dashboard.ts | 347 --- .../mcp-server-evals/tsconfig.code-mode.json | 7 - .../vitest.code-mode.config.ts | 5 - pnpm-lock.yaml | 4 - 34 files changed, 205 insertions(+), 8384 deletions(-) delete mode 100644 packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts delete mode 100644 packages/mcp-cloudflare/src/code-mode/discovery-worker.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/alert-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/api-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/claude-agent.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/claude-agent.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/codex-agent.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/codex-agent.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json delete mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/json-answer.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/json-answer.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/monitor-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/release-fixture.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/release-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/report.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/report.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/repositories-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts delete mode 100644 packages/mcp-server-evals/src/code-mode/run-dashboard.ts delete mode 100644 packages/mcp-server-evals/tsconfig.code-mode.json delete mode 100644 packages/mcp-server-evals/vitest.code-mode.config.ts diff --git a/docs/cloudflare/architecture.md b/docs/cloudflare/architecture.md index 52acd513b..65b46e0cd 100644 --- a/docs/cloudflare/architecture.md +++ b/docs/cloudflare/architecture.md @@ -117,7 +117,7 @@ This integration has no beta or opt-in flag. Code Mode is unavailable for project-constrained sessions because arbitrary API operations can affect shared resources even through project-bearing routes. Existing grants do not acquire API permissions automatically. The shared upstream OAuth token remains separate -from connection permissions; Sentry enforces its scopes and resource access. See [HTTP Code Mode integration and evaluations](../testing/code-mode.md#authentication-and-scope) +from connection permissions; Sentry enforces its scopes and resource access. See [HTTP Code Mode integration](../testing/code-mode.md#authentication-and-scope) for the permission policy, fixture limitations and validation status. Source and configuration changes alone do not establish that a deployment is active. diff --git a/docs/mcp/README.md b/docs/mcp/README.md index 7daaebdcd..87d07ce04 100644 --- a/docs/mcp/README.md +++ b/docs/mcp/README.md @@ -18,7 +18,7 @@ them. The [Toolkit index](../README.md) also links the CLI and shared guides. - [Testing](../testing/overview.md), [stdio](../testing/stdio.md), and [remote](../testing/remote.md) — tests and transport QA. - [HTTP Code Mode](../testing/code-mode.md) — runtime integration, permissions, - dashboard copying, and CLI flow evaluations. + and safety validation. ## Run and Release diff --git a/docs/testing/code-mode.md b/docs/testing/code-mode.md index 1b6dd8598..683a5f77c 100644 --- a/docs/testing/code-mode.md +++ b/docs/testing/code-mode.md @@ -1,895 +1,216 @@ -# HTTP Code Mode integration and evaluations - -HTTP Code Mode lets an external MCP agent discover an OpenAPI schema, write -JavaScript, and execute HTTP requests in a Cloudflare Dynamic Worker. It covers -published API operations absent from the hand-maintained tool catalog, subject -to the connection's permissions. There is no embedded LLM or endpoint-specific -execution tool. The hosted MCP integration uses the existing authentication flow -and has no beta or opt-in flag. This document describes the implementation and -local evidence; it does not establish that the change has been deployed. - -The evaluation harness remains separate from the hosted authorization boundary. -Its historical write successes do not demonstrate that existing production -grants authorize those writes. An SDK interface and HTTP-versus-SDK evaluation -are deferred; neither is a deployment prerequisite. - -## Ownership, model usage and protocol - -This is **server-side execution of client-generated code**. The external MCP -client's model discovers the API, writes JavaScript, corrects failures and writes -the final answer. Our MCP server runs that JavaScript; it does not call a model. - -| Responsibility | Owner | Cost boundary | -| ------------------------------------------------------- | ----------------------- | ----------------------------------------------- | -| Planning, code generation, corrections and final answer | Client's agent/provider | Client's model account or subscription | -| OpenAPI search and JavaScript execution | Our Dynamic Workers | MCP operator's Cloudflare infrastructure | -| Credential injection and per-request access enforcement | Our host-side broker | MCP operator's infrastructure | -| Serving API requests | Sentry API | Sentry infrastructure and applicable API limits | - -Adding an embedded agent would introduce server-side model usage; that is not -part of this design. The local eval used the authenticated Claude CLI account and -local workerd; it did not deploy or consume hosted Dynamic Workers. - -MCP provides ordinary `tools/list` and `tools/call` with typed schemas, structured -results and tool errors. Code Mode is an architectural pattern, not a separate -protocol capability or required tool name in the -[2026-07-28 specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools). -The MCP TypeScript SDK's `2.x` version is separate from the dated wire protocol. -The client-side pattern in -[Client Best Practices](https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices) -places the sandbox in the host. Our server supplies the sandbox, so the external -client only needs normal MCP tool support. This statement does not certify full -protocol compliance or hosted deployment behavior. - -## Relationship to Cloudflare and Stainless - -The implementation follows Cloudflare's -[server-side search/execute pattern](https://blog.cloudflare.com/code-mode-mcp/): -search the schema without loading the entire document into model context, then -compose authenticated HTTP requests in a Dynamic Worker. The inspected -`cloudflare/mcp` revision `550e1be82b2227818e6b98fc32a313a59403ae3f` also uses -Worker Loader directly, a fresh isolate per call, disabled search egress and a -credential-owning `GlobalOutbound` entrypoint. Following this pattern does not -require using the `@cloudflare/codemode` package. - -The hosted integration uses a request-scoped RPC broker with all sandbox egress -disabled. Cloudflare supports passing capabilities, requests and responses through -[Workers RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/). -This keeps authorization and the existing 401 callback in their original request -context; callback objects cannot be placed in the persistently serializable -`ctx.props` of a loopback binding. The HTTP programming interface remains -`sentry.request()`. - -Cloudflare now also offers -[`openApiMcpServer()` and `DynamicWorkerExecutor`](https://developers.cloudflare.com/agents/model-context-protocol/guides/build-codemode-openapi-mcp-server/). -They are candidates for reducing custom harness code. As reviewed on 2026-10-26, -the publisher still returns an MCP SDK v1 server and requires -`createLegacyMcpHandler`; this experiment uses SDK v2. It is not a drop-in -replacement. Authentication, per-operation authorization and Sentry routing -remain responsibilities of the host request implementation either way. - -[Stainless's SDK Code Mode](https://www.stainless.com/blog/sdk-code-mode/) adds an -idiomatic SDK, focused SDK/API documentation, type checking and useful errors. -The initial HTTP version does not implement those SDK features. Its focus is -schema discovery, authenticated HTTP requests and compact program results. An SDK -interface can be evaluated later using the same runtime and broker; it would -change the programming interface, not the model or infrastructure cost owners. - -Before deployment, account for -[Dynamic Workers pricing](https://developers.cloudflare.com/dynamic-workers/pricing/) -as well as model usage: Cloudflare meters unique Worker IDs/code per day, -requests and CPU. The runtime deliberately allocates a fresh ID for each tool -execution. Reusing identical code may reduce creation costs, but must preserve -per-request credentials and isolation; changing the ID alone is not sufficient. - -## Runtime - -The hosted MCP registers two additional tools on its existing authenticated -server. The separate `packages/mcp-cloudflare/wrangler.code-mode.jsonc` entrypoint -exposes the same tools at `/mcp` for isolated tests and evaluations: - -- `search_sentry_api({code})`: execute an async arrow function against `spec`, the - configured OpenAPI JSON document. Network access is disabled. -- `execute_sentry_code({code})`: execute an async arrow function with - `sentry.request({method?, path, query?, body?})` (`GET` by default). It returns - `{status, headers, body}`; non-2xx responses throw an error with API details. - -Programs can loop, branch, combine responses and return a projection. Query arrays -become repeated URL parameters; `headers.link` is available for pagination. Tool -results provide both JSON text and `structuredContent`; failures use `isError`. -The MCP client generates the code and pays for its model calls. This Worker only -executes the submitted program. - -Worker Loader runs generated code with `globalOutbound: null`, so raw `fetch()` -and network connections are blocked. The trusted sandbox wrapper implements -`sentry.request()` through an `ApiBroker` capability passed to its `run()` RPC -method. The broker executes in the parent request context and owns the access -token; its private credentials and authorization context are not exposed to -generated code. The broker permits only the configured API origin and path -prefix, replaces auth headers and rejects redirects. - -The hosted integration derives write access from the existing MCP grant and -checks each broker request against the published schema; the isolated harness -uses its explicit fixture write setting instead. Generated code remains a -separate module. This RPC dispatch mechanism replaces the prototype's -`GlobalOutbound` loopback entrypoint while retaining the HTTP tool interface. - -Each execution has a 20-call fetch budget, a 30-second I/O deadline, a 50,000-byte -result limit, and a 2,000,000-byte upstream response limit. -Before tool validation, the HTTP MCP SDK rejects arguments with more than 10,000 -array elements or object keys. A bounded body reader rejects requests over 1 MiB -before dispatch, including legacy clients whose Agents transport omits the SDK's -body-limit option. - -Dynamic Worker CPU and subrequest limits are also configured using the -[Cloudflare API](https://developers.cloudflare.com/dynamic-workers/usage/limits/). -**Local workerd does not enforce those platform limits in our probes. CPU -termination and deployment enforcement have not been validated.** This local -proof is not a production security certification. - -There are no automatic retries or transactions spanning calls. A failed program -may already have created resources; errors instruct the agent to inspect state -before retrying writes. - -## HTTP discovery and result size - -The default `OPENAPI_URL` points to Sentry's published `openapi-derefed.json`, -pinned at `803d6de98c9156c15e9ddc1bf9accbac6e24cebc`. Sentry's existing API-docs -pipeline resolves references before publishing this artifact; the CLI consumes -the same artifact family. No runtime reference resolver, SDK dependency or -second operation catalog is needed. Custom schema URLs must also provide a -trusted, pre-dereferenced JSON document; the runtime does not resolve or fetch -references for them. - -The pinned document contains 159 paths and 254 HTTP operations (4,296,871 bytes, -SHA-256 `270e5bd5de0cd185edfad90f5dd5a2af034c74b042c2325eb108f7a68ff85bb1`). -These are discoverable operations in that published snapshot, not a claim about -all Sentry features or the caller's permissions. Updating the pin is an explicit -schema update, independent of runtime changes. - -The tool descriptions guide discovery across path, operation ID, summary, -description and tags. Product terms need not appear in an endpoint's URL. The -published example returns compact operation metadata; a separate category example -reads the existing top-level tags (including display names) or derives category -names from operation tags. Empty or unrelated results should lead to broader -metadata/category searches before trying guessed API routes. No synonym catalog, -semantic-search service or schema fetch during `tools/list` is added. - -After finding an operation, inspect its description, request/response schemas and -both path-level and operation-level parameters. Operation parameters override a -path parameter with the same name and location. Schemas should be selected as JSON -values, not sliced JSON strings that can hide required fields. - -HTTP write guidance distinguishes raw API bodies from the renamed, enriched or -filtered views returned by ordinary MCP tools. Even a raw GET response is not a -write template: resending an aggregate value can affect child resources. It asks the client to consult the -operation contract/defaults, read the current HTTP resource, avoid inventing empty -values for missing fields, and send only required fields and requested changes -when the contract permits. Additional unchanged fields need a reason in the -request contract or documented update/default behavior, rather than merely -appearing in GET. Resent nested collections must preserve existing IDs -and relationships unless the user requests changing them. Omission is not assumed -to preserve every field, and PUT is not assumed to replace every resource. The -client should verify the result and report unresolved ambiguity instead of -inventing semantics. An unexpected side effect should not trigger speculative -repair writes; report the actual state unless a documented repair is available. - -These are global instructions, not per-endpoint enforcement. The instruction-only -changes measured below left OpenAPI, API behavior, permissions and the runtime -unchanged. The hosted authorization boundary described next is a separate -integration change. Better instructions cannot guarantee correct writes when the -contract is ambiguous; endpoint-specific limitations remain visible in the -evaluations. - -Execution examples keep intermediate responses in JavaScript and return selected -fields plus pagination information. This is guidance, not automatic semantic -truncation: the client can return full objects when the task requires them, up -to the existing byte limit. The historical dashboard baseline predates these -description changes; it is preserved and does not measure their token impact. - -A read-only smoke check on 2026-10-26 loaded the pinned document through the real -MCP server and local Dynamic Workers. It counted all 159 paths / 254 operations, -ran the then-advertised dashboard search examples, and inspected the inline -dashboard-create schema successfully. The examples now use releases to avoid -scaffolding the dashboard evaluation. It made no Sentry API or model calls. Local evidence is in -`.cache/code-mode/http-schema-smoke.json`; this checks discovery mechanics, not -agent task success across the full API. +# HTTP Code Mode + +HTTP Code Mode lets an external MCP agent discover published API operations, +write JavaScript, and execute authenticated HTTP requests without a dedicated +tool for each operation. This version is HTTP-only; an SDK interface is deferred. + +## Execution and ownership + +The hosted MCP exposes two ordinary MCP tools: + +- `search_sentry_api({code})` runs an async arrow function against the configured + OpenAPI document, available as `spec`. Network access is disabled. +- `execute_sentry_code({code})` runs an async arrow function with + `sentry.request({method?, path, query?, body?})`, defaulting to GET. Requests + return `{status, headers, body}`; non-2xx responses throw with HTTP details. + +The client's model discovers the API, generates code, corrects failures and +writes the answer. The server executes that code in a fresh Cloudflare Dynamic +Worker and does not call a model. Programs can paginate, combine responses and +return selected fields without sending every intermediate response to the model. +Query arrays become repeated parameters; `headers.link` supports pagination. + +| Responsibility | Owner and cost | +| ---------------------------------------------- | ----------------------------------------------- | +| Planning, code generation and final answer | Client's agent/provider account | +| Schema search, execution and credential broker | MCP operator's Cloudflare infrastructure | +| API requests | Sentry infrastructure and applicable API limits | + +This follows Cloudflare's [server-side search/execute pattern](https://blog.cloudflare.com/code-mode-mcp/). +Worker Loader creates a fresh isolate with `globalOutbound: null`. Raw `fetch()` +and other network access are blocked. A trusted wrapper calls an `ApiBroker` +RPC capability passed to `run()`; the broker owns credentials in the parent +request context. Generated code is a separate module and cannot access them. +The broker validates the origin and API prefix, supplies authentication and +rejects redirects. The request-scoped capability also retains the existing +unauthorized callback, which cannot be serialized into loopback binding props. + +Code Mode is an architecture, not a separate MCP protocol capability. Tools use +normal `tools/list` and `tools/call`, typed schemas, JSON text and +`structuredContent`; failures use `isError`. The MCP SDK's package version is +separate from the dated [wire protocol](https://modelcontextprotocol.io/specification/2026-07-28/server/tools). ## Authentication and scope -### Hosted MCP - -`server/lib/mcp-handler.ts` builds a fresh MCP server for the authenticated request -and calls `registerProductionCodeModeTools()` with that request's `ServerContext`. -The existing shared upstream OAuth token and global scope request are unchanged. -There is no separate OAuth flow, rollout flag or embedded model call. The token -stays in the credential-owning broker. Production and canary Wrangler configurations -provide a `LOADER` binding. The parent passes an `ApiBroker` RPC capability to the -sandbox's trusted `run()` method. Code Mode is hosted-only. - -The tools require the connection's explicit `api-read` or `api-write` skill. -`api-write` includes reads. These permissions appear in the normal MCP consent -screen; existing grants and remembered skill selections do not acquire them -implicitly. Legacy Inspect, Triage and Project Management grants retain their -existing tools and meaning. Project-constrained sessions do not expose Code Mode. -The broker computes write access from `api-write`; generated code cannot set it. - -For new approvals, **API Read** is selected and **API Write** is unchecked. -Remembered selections for the same client are restored, including an explicit -API Write selection; preferences only preselect the form and do not grant access -without approval. The consent screen always shows the organization scope: -`/mcp/{org}` is limited to that organization, while `/mcp` can access any -organization authorized by the upstream token. Sentry's permissions still apply. - -The API Write warning is visible before selection. It explains that permissions -are additive: API Write can modify issues even when Triage Issues is unchecked, -and projects or teams even when Manage Projects & Teams is unchecked. Those -checkboxes control their catalog tools; they are not deny rules for API Write. - -Three authorities are kept separate: - -- `grantedSkills` is the connection's MCP authorization. -- Legacy `grantedScopes` is derived from tool declarations for compatibility, - not evidence of the token's actual permissions. Code Mode does not use it. -- `upstreamScopes` records the scopes in Sentry's OAuth token response. Missing - metadata on older grants means unknown. It neither grants MCP access nor - replaces the backend's token, role and resource checks. - -Each broker retains an ordinary `ApiAuthorization` object bound to the original -request's context. It is not serialized into Worker bindings or exposed to -generated code. Raw `fetch()` is blocked; before forwarding any -`sentry.request()`, the policy checks: - -- The exact path and HTTP method must exist in the pinned published OpenAPI. - HEAD can use a GET contract when the schema has no explicit HEAD operation. - Unknown or ambiguous paths are rejected. OpenAPI security metadata is not - used to infer the connection's MCP permissions. -- Without `api-write`, only GET, HEAD and OPTIONS can reach Sentry. A broad - upstream token never bypasses the connection's read-only grant. -- An organization-constrained connection requires a recognized organization - path parameter containing the exact authorized slug. Other organizations, - unresolved numeric aliases and global object-ID routes are rejected. Query - parameters do not substitute for an organization-scoped path. - -The policy resolves regional routing through the existing organization helper -and validates the regional host before the broker attaches credentials. It also -forwards the existing MCP client attribution. An upstream 401 invokes the -connection's existing unauthorized callback, once per policy instance, so Code -Mode follows the normal grant-revocation behavior. The backend still performs -its own token, user and resource permission checks. - -**Dashboard example:** the backend accepts `org:read`, `org:write` or `org:admin` -for dashboard creation. With `api-write`, that POST reaches Sentry; no artificial -`:write` suffix requirement is added. With `api-read`, the broker blocks the POST -before contacting Sentry, even if the upstream token would allow it. A successful -MCP grant does not guarantee that Sentry will permit the request. - -A Sentry 403 retains its HTTP status and message. The broker preserves -`WWW-Authenticate`, including `insufficient_scope` when the backend supplies it. -A generic 403 is not labelled as a missing scope or a need to reconnect: it may -reflect role or resource access. No failed program is automatically replayed, -since earlier writes may have succeeded. - -Upstream 5xx responses create a Sentry issue through the existing `logIssue` -helper, even when generated code catches the HTTP error. The broker preserves -the response and exposes the event ID in `x-sentry-event-id`; reporting includes -only the HTTP method and status, not request or response payloads. Expected 4xx -responses do not create issues. - -**Project-constrained sessions retain the existing tools without Code Mode.** A -project-bearing URL does not prove that an operation affects only that project. -For example, Sentry's project release-file endpoints modify a file belonging to -a release shared by multiple projects. Organization release deploys can also -affect every project in a release when `projects` is omitted. The current OpenAPI -does not provide a general resource-isolation contract, so the integration does -not infer one from a path prefix or append a project filter to arbitrary routes. -This limitation is based on the backend implementations in +`packages/mcp-cloudflare/src/server/lib/mcp-handler.ts` creates a fresh server +for each authenticated request and registers Code Mode with that request's +`ServerContext`. The existing OAuth flow and requested upstream scopes remain +unchanged. Production and canary configurations provide a `LOADER` binding. +Code Mode is hosted-only, with no separate OAuth flow or rollout flag. + +Two permissions control access through this MCP connection: + +- **API Read** permits reads and is selected for new approvals. +- **API Write** permits reads and create/update/delete operations. It starts + unchecked. Explicitly remembered selections can preselect it, but approval + is still required. + +Existing grants and remembered skill selections do not acquire new permissions +implicitly. Permissions are additive: API Write can modify issues with **Triage +Issues** unchecked, or projects/teams with **Manage Projects & Teams** unchecked. +Those checkboxes control their catalog tools, not deny rules for API Write. The +consent screen explains this before selection and always displays the scope: +`/mcp` permits organizations authorized by the token; `/mcp/{org}` limits the +connection to that organization. Sentry's own permissions still apply. + +The token authenticates API calls and sets the maximum access through its OAuth +scopes and the user's permissions. The consent screen limits what this MCP +connection may do without changing the token or its scopes. The implementation +keeps three values distinct: + +- `grantedSkills` records MCP authorization; `api-write` includes reads. +- Legacy `grantedScopes` comes from tool declarations. It is not evidence of + token permissions, and Code Mode does not use it. +- `upstreamScopes` records Sentry's actual token response. Missing metadata on + older grants means unknown. It neither grants MCP access nor replaces backend + token, role and resource checks. + +Before every `sentry.request()`, the broker's `ApiAuthorization` checks: + +1. The exact path and method exist in the pinned OpenAPI. HEAD may use a GET + contract when no HEAD operation exists. Unknown or ambiguous paths fail. +2. Without `api-write`, only GET, HEAD and OPTIONS can reach Sentry, regardless + of the token's broader access. +3. An organization constraint matches a recognized organization path parameter + exactly. Other organizations, unresolved numeric aliases and global object-ID + routes fail. Query parameters cannot substitute for a scoped path. + +The policy uses the existing organization helper for regional routing, validates +the regional host before attaching credentials, and forwards MCP client +attribution. A 401 invokes the existing unauthorized callback once per policy +instance. Sentry performs its own token, role and resource authorization. + +For example, dashboard creation accepts `org:read`, `org:write` or `org:admin` +upstream. API Write lets that POST reach Sentry without inventing a `:write` +suffix requirement. API Read blocks it before contacting Sentry. Neither MCP +permission guarantees that Sentry will accept the request. + +Project-constrained connections retain their existing tools without Code Mode. +A project URL alone cannot prove isolation: release files can be shared across +projects, and release deploys can affect every project when `projects` is omitted. +The published schema has no general isolation contract for these resources. +The policy therefore does not infer isolation from path prefixes or add filters +to arbitrary operations. The backend references are `src/sentry/releases/endpoints/project_release_file_details.py` and -`src/sentry/releases/endpoints/release_deploys.py`, not on a rollout flag. - -### Hosted integration validation - -The hosted test suite uses `@cloudflare/vitest-plugin` 1.4.6, whose Wrangler and -Miniflare versions match the standalone Code Mode tests. Updating only the direct -Miniflare dependency left the old Vitest pool running an older workerd; the first -Linux CI run exited during the Dynamic Worker integration tests. The plugin -replacement removes that runtime mismatch without binary overrides. - -Local validation on 2026-10-26 exercised the authenticated MCP handler, Dynamic -Workers and RPC broker with simulated external OpenAPI/Sentry HTTP responses: - -- The full Cloudflare suite passed 469 tests across 35 files, including - 20 authenticated Code Mode integration tests and two public metadata tests. - The dashboard regression reads the original, - creates two project-specific copies and verifies them with `api-write`, while - the simulated upstream token records only `org:read`. -- The isolated suite passed all 30 tests: 19 runtime, seven API path/organization - checks and four OpenAPI loading tests. It uses the installed Miniflare/workerd - aligned with Wrangler, without a binary override. -- MCP core (1,834 tests), stdio (89), test client (80) and Code Mode fixtures - (56) passed, for 2,558 MCP tests in total (six existing core skips). Root - build/typechecks, lint, Code Mode typechecks and frozen install passed. - Generated metadata includes the - hosted API skills; core tool registrations remain unchanged. -- The final `wrangler deploy --dry-run` packaged the production - Worker with `LOADER`. This did not upload a version or deploy the change. - -Two additional Claude smoke evals used the `catalog-and-code` surface without -instructing the agent to use Code Mode. Dashboard copying passed (one run, five -MCP calls, six API calls). Repository inventory produced the expected honest -blocked outcome on HTTP 403 (one run), without inventing data or a missing scope. -Both resolved to `claude-opus-5` and used synthetic fixtures. Local reports are -`.cache/code-mode/ready-dashboard.json` and -`.cache/code-mode/ready-repositories-denied.json`; these are smoke checks, not a -success-rate estimate. Unchanged CLI fixture/platform failures prevented a green -full workspace suite; CLI fixes are outside this change. - -The integration tests verify regional routing, client attribution, concurrent -user token isolation, denied operations and organization escapes, project/skill -visibility, and upstream 401 revocation even when generated code catches the -error. They also reject oversized request bodies for modern and legacy clients, -bound nested tool arguments before execution, and verify that caught upstream -5xx failures are reported while expected 4xx responses are not. They do not test -a real Sentry account, production CPU enforcement, or an active hosted deployment. -Historical agent evals below remain separate evidence. - -### Isolated harness - -The local Code Mode entrypoint accepts a direct Sentry bearer token in the MCP -`Authorization` header and keeps it outside the generated program. It does not -run the hosted skills, constraints or regional authorization policy. Its -configuration is server-owned: - -- `API_BASE_URL`: canonical origin and API prefix, such as - `https://sentry.io/api/0`. -- `OPENAPI_URL`: trusted, pre-dereferenced JSON schema URL. Defaults to the pinned - Sentry artifact above; the eval overrides it with its local schema fixture. -- `CODE_MODE_ALLOW_WRITES`: defaults to false; explicitly set `true` to allow - writes in the isolated harness. - -HTTP is allowed only on loopback for local tests. The eval supplies a dummy token -and local URLs; it never calls a real Sentry account. - -OpenAPI discovery is limited to the supplied schema; availability also depends -on backend features and the effective upstream permissions. HTTP/JSON support -is not proof of coverage for multipart, streaming, internal or undocumented -endpoints. The isolated harness is not the deployment entrypoint for the hosted -MCP integration. - -### Separate follow-ups - -The integration does not change OpenAPI or add an SDK. HTTP errors caught inside -programs retain the allowed response headers, including `Retry-After`. Uncaught -program errors preserve execution statistics and HTTP metadata when available; -failures outside the sandbox, such as startup failures, do not have those stats. -Migration to a different Cloudflare runtime abstraction and existing platform -CPU/subrequest enforcement remain separate work requiring hosted validation. - -## Dashboard eval - -The historical agent results below predate the hosted authorization boundary and -RPC dispatcher. They are not fresh agent evaluations of this integration. - -The task is to copy dashboard 101 into exactly two new dashboards, one for project -101 and one for project 102, both with saved environment `staging`. It must retain -widgets, queries, order, layout, other filters and time range, leave the source -unchanged, and return the real IDs and links. The current catalog can inspect -dashboards but cannot create these copies. - -The runner starts an authenticated loopback API fixture and a local Wrangler -Worker, then opens a fresh external-agent session. The isolated baseline gives -the agent only the two generic tools and explicitly directs it to use them. The -natural discovery evaluation below includes the normal catalog and supplies no -such instruction. Neither runner supplies a copy algorithm. Only the Sentry -HTTP boundary is a fixture. MCP transport, schema search, generated JavaScript and -Dynamic Worker execution are real. - -An independent verifier checks persisted API state rather than tool arguments or -the agent's claim of success. Exactly two copies must exist with the right -configuration, no duplicates, an unchanged original and links matching created -IDs. Fixture tests include unsuccessful and misleading answers. - -The fixture contains three dashboard operations with their complete published -request/response schemas. This tests functionality, not discovery across the full -API corpus. Provenance commits are recorded in `dashboard-fixture-data.json`. -Its aliases, generated IDs and rejection of foreign widget IDs follow Sentry's -backend. It does not emulate query-language validation, quotas, feature flags or -real authorization. - -### Natural tool selection in Claude Code - -`eval:code-mode:discovery` connects Claude Code to one MCP named `sentry`, exposing -the nine normal top-level tools, the complete normal catalog behind -`search_sentry_tools` / `execute_sentry_tool`, and the two HTTP Code Mode tools. -It uses the real core server and Dynamic Worker runtime. Only the Sentry API is -a stateful local fixture; no real dashboards are changed. - -The user prompt asks only for two dashboard copies with the project/environment -settings above. It does not mention Code Mode, JavaScript, HTTP, schemas or tool -names. Claude keeps its default system prompt; there is no custom or appended -system instruction telling it how to complete the task. The server is named -`sentry`, the synthetic organization is `dashboard-evals`, and tool examples use -releases instead of dashboards. Search uses the full pinned OpenAPI document -(159 paths / 254 operations), not the three-operation fixture schema. - -The same runner offers `--surface catalog` as a negative control: the same task, -client settings and real catalog, without the two Code Mode tools. Traces record -the complete advertised inventory, actual tool sequence, code, API outcomes and -independently verified final state. We do not require a catalog search before -API discovery: selection is the behavior being measured, not a prescribed route. -A failed task still produces a report and exits nonzero, including the control. - -```bash -pnpm --filter @sentry/mcp-server-evals eval:code-mode:discovery --runs 3 -pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog --runs 1 -``` - -Both surfaces run in an empty temporary directory with other MCP servers, -built-in tools, hooks, skills and session persistence disabled. This is a -controlled MCP selection test, not a test with every Claude Code capability -enabled. API endpoints outside the dashboard fixture remain unimplemented, even -when discoverable in the full schema. The eval-only Worker rejects non-loopback -API configuration and is not a production authorization integration. - -Reports default to `.cache/code-mode/http-dashboard--.json`; -`--report` selects a separate evidence file. The original `--surface code-mode` -baseline remains available for testing execution alone and does not establish -unprompted tool selection. - -### Natural selection results (2026-10-26) - -Claude Code `2.1.263`, resolved model `claude-opus-5`, medium effort: - -| Surface | Task result | Actual selection | MCP calls | API calls | Wall time | -| --------------------------------- | --------------- | ----------------------------------------------------- | --------- | --------- | --------- | -| Normal catalog + Code Mode, run 1 | Pass | Execute code → search API schema → execute code | 3 | 5 | 23.269 s | -| Normal catalog + Code Mode, run 2 | Pass | Execute code → search API schema → execute code | 3 | 7 | 21.889 s | -| Normal catalog + Code Mode, run 3 | Pass | Execute code → search API schema → execute code | 3 | 5 | 18.983 s | -| Normal catalog only, control | Cannot complete | Five catalog searches and two catalog tool executions | 7 | 2 | 31.338 s | - -All three combined-surface runs independently created exactly two correct copies, -retained the requested widget/filter/time configuration, preserved the source, -and returned the actual created IDs/links. All API calls succeeded. Claude chose -the code tools directly, before querying the normal catalog. This demonstrates -unprompted selection from the combined inventory; it does **not** demonstrate a -catalog-first fallback sequence. In each run it read the source through HTTP, -inspected the create schema, then generated a program that created both copies -and verified state with additional reads. - -The control could read the dashboard and search the catalog but had no mutation -capability. It created nothing and reported the limitation instead of claiming -success. Its verifier failure and nonzero exit are expected task outcomes, not -an authentication or transport failure. - -Local evidence: `.cache/code-mode/http-dashboard-natural-claude.json` and -`.cache/code-mode/http-dashboard-catalog-control-claude.json`. Each contains the -prompt, null custom system prompt, full MCP inventory, trace, generated programs, -usage, API outcomes, final state and implementation hashes. These are four pilot -runs of one synthetic scenario; they do not establish broad reliability, token -savings or production readiness. The verifier covers the requested content and -filters; it does not require copying per-user favorites or dashboard permissions. - -### Automatic HTTP baseline (2026-10-26) - -After renewing the Claude login, the automatic eval passed **3/3 repetitions** -using Claude Code `2.1.263`, resolved model `claude-opus-5`, and medium effort. -Each fresh conversation had exactly the two experimental MCP tools available -and explicit instructions to use them. This establishes execution capability, -not autonomous discovery among the normal tools. -Each run created exactly two valid copies and preserved the original, as checked -against persisted fixture state by the independent verifier. - -| Run | Result | Wall time | MCP calls | Sentry API calls | MCP/API errors | -| --- | ------ | --------- | --------- | ---------------- | -------------- | -| 1 | Pass | 24.625 s | 4 | 5 | 0 / 0 | -| 2 | Pass | 20.312 s | 4 | 5 | 0 / 0 | -| 3 | Pass | 19.206 s | 4 | 5 | 0 / 0 | - -Every run used two schema searches and two code executions. The final execution -fetched the source, created both copies and fetched the source again: four HTTP -requests inside one MCP call. The five API requests per run were three GETs and -two POSTs; schema downloads are excluded from that count. The model generated -the program itself and removed existing widget/query IDs from the creation -payloads. There was no embedded LLM or dashboard-specific tool. - -Raw aggregate usage reported by Claude: - -| Run | Uncached input | Cache creation input | Cache read input | Output | -| --- | -------------- | -------------------- | ---------------- | ------ | -| 1 | 8 | 9,234 | 11,060 | 1,944 | -| 2 | 8 | 6,054 | 11,635 | 1,495 | -| 3 | 8 | 6,564 | 12,221 | 1,514 | - -Including cache creation and reads, input totals were 20,302, 17,697 and 18,793 -tokens respectively. Fresh conversations may still reuse provider-side prefix -caches, so cache state is a comparison variable. Client-reported dollar estimates -are saved separately and do not establish the amount billed to a subscription. - -The traces, generated programs, usage, final fixture state and implementation -hashes are saved locally in `.cache/code-mode/http-dashboard-claude.json`. This -establishes a baseline for further HTTP evaluations, not a token-savings claim. -These programs returned complete dashboard objects; compact result projection -is available but was not optimized in this pilot. - -### Earlier session-driven proof (2026-10-25) - -One fresh session subagent completed the task through the real MCP SDK and -Dynamic Worker. Its generated calls were relayed unchanged; the first large -schema response was transferred verbatim through a file. The independent -verifier passed: exactly two copies, IDs `201` and `202`, correct project and -environment filters, preserved widgets/settings, and original unchanged. - -The run used three MCP calls (one search, two executions) and seven Sentry API requests -(five GETs, two POSTs), plus one OpenAPI download, with no API errors. The write program combined six HTTP -requests and returned a compact verification summary. This demonstrates the -missing capability without a dedicated dashboard creation tool. - -The initial automatic clients could not perform inference: Claude's OAuth session was -expired, and the local Codex login rejected the requested models. Those attempts -are infrastructure failures, not task failures or successful benchmark runs. -The successful session-driven run has no available token usage and its relay -latency is not comparable with an automatic client. It does not establish a -three-run success rate, token savings, or an HTTP-versus-SDK winner. Full local -evidence is in `.cache/code-mode/http-dashboard-session.json`. - -## CLI capabilities beyond the MCP catalog - -Three additional tasks use the natural discovery surface: the normal MCP catalog -plus the two HTTP Code Mode tools, with Claude Code's default system prompt. The -user prompts describe the desired result without mentioning Code Mode, tool names, -API endpoints, or an algorithm. The agent can select either interface. Tool descriptions are unchanged across the -flows and include a release discovery example; natural prompts do not imply an -unprimed model or a statistically neutral benchmark. - -| Task | Equivalent CLI operations | Gap in the normal MCP catalog | Independent verification | -| ---------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Release and deploy | `sentry release create`, `release finalize`, `release deploy`, `release deploys` | Release reads exist; release/deploy writes do not | Exactly one release for two projects and one staging deploy; requested timestamps, name and URL; original resources unchanged; complete final JSON including the actual deploy ID | -| Repository inventory | `sentry repo list / --limit 200 --json`, then select active GitHub repositories | No repository listing tool | All 107 active repositories retrieved across pages; four GitHub results with exact IDs, names and URLs, sorted with a correct count; disabled repository excluded; no writes | -| Disable an issue alert | `sentry alert issues edit //701 --status disabled --frequency 60` | Alert inspection exists; alert editing does not | Workflow disabled with frequency 60; exact saved JSON; conditions, actions, owner, environment, detector associations and another alert unchanged throughout every write | - -The repository fixture caps a page at 100 records and puts matching repositories -on the second page. The release verifier inspects all persisted deployments, even -though the API lists only the latest per project/environment. The alert verifier -compares the complete saved configuration and checks unrelated settings after -every successful write, so restoring a temporarily detached monitor is still a -preservation failure. These checks reject plausible but -incomplete answers and destructive partial updates. - -The fixtures model routes used by the CLI at revision -`b625111aef9cc3a44abbed634a043fce8505366d`, checked against Sentry backend revision -`b4b356554798716ceaeab74efdc095782be9ca16`. The normal alert inspection tool also -needs project, detector and workflow project-scope reads. Those auxiliary routes -were verified against Sentry `origin/master` revision -`c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4`. The fixture records this separate -provenance; the core `get_alert_rule` handler was checked both with and without a -project constraint. Each report includes its fixture's source paths and CLI -commands. This is a comparison with CLI task semantics, not a benchmark run of -the native CLI. - -Run all three flows, then the same prompts against the catalog alone: - -```bash -pnpm --filter @sentry/mcp-server-evals eval:code-mode:flows --runs 3 -pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog --task release --task repositories --task alert --runs 1 -# A single task: -pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog-and-code --task alert --runs 3 -``` +`src/sentry/releases/endpoints/release_deploys.py`. + +## Discovery and HTTP contracts + +The default `OPENAPI_URL` uses Sentry's published `openapi-derefed.json`, pinned at +`803d6de98c9156c15e9ddc1bf9accbac6e24cebc`. This is the same artifact family the +CLI consumes. Custom schema URLs must supply trusted, pre-dereferenced JSON; +the runtime does not resolve or fetch references. The pinned snapshot contains +159 paths / 254 operations, not every Sentry feature or internal endpoint. + +Tool descriptions guide searches across paths, operation IDs, summaries, +descriptions and tags. Empty or unrelated results should lead to broader metadata +or category searches before guessing routes. Discovery does not add a second +operation catalog, synonym list or semantic search service. + +After finding an operation, inspect its request/response schemas and both +path-level and operation-level parameters. The latter override path parameters +with the same name and location. Return selected JSON values rather than sliced +JSON strings that may hide required fields. + +Write guidance distinguishes raw API bodies from enriched catalog-tool results. +Even a GET response is not a write template: resending an aggregate field can +change child resources. Agents should consult the contract and defaults, read +the current resource, send required fields and requested changes, and preserve +IDs and relationships in nested collections. Omission is not assumed to preserve +every field, and PUT is not assumed to replace every resource. Agents should +verify the result and report ambiguity or side effects rather than inventing +semantics or attempting speculative repair writes. + +These instructions cannot enforce endpoint-specific preservation. JSON/HTTP +support does not establish multipart, streaming, internal or undocumented API +coverage. Result projection is available but not automatic semantic truncation. + +## Limits and failures + +Each execution has a 20-call fetch budget, 30-second I/O deadline, 50,000-byte +result limit and 2,000,000-byte upstream response limit. The HTTP MCP SDK rejects +tool arguments with more than 10,000 array elements or object keys before +validation. A bounded reader rejects request bodies over 1 MiB before dispatch, +including legacy clients whose Agents transport omits the SDK body-limit option. + +Dynamic Worker CPU and subrequest limits are configured through the +[Cloudflare API](https://developers.cloudflare.com/dynamic-workers/usage/limits/). +Local workerd did not enforce those platform limits in the recorded probes. +**Hosted CPU termination and deployment enforcement remain unvalidated.** + +The runtime has no automatic retries or multi-call transaction. A failed program +may already have created resources; the agent must inspect state before retrying +writes. HTTP errors preserve status, message and allowed headers such as +`WWW-Authenticate` and `Retry-After`. A generic 403 is not diagnosed as a missing +scope or a need to reconnect. Uncaught program errors retain execution statistics +when available; startup failures do not have sandbox statistics. + +Upstream 5xx responses use the existing `logIssue` helper even when generated +code catches them. The broker exposes the event ID as `x-sentry-event-id` and +reports only method/status, not request or response payloads. Expected 4xx +responses do not create issues. -Default evidence files are -`.cache/code-mode/http---.json`. A filesystem regression test -checks that a multi-task suite and its controls preserve separate reports. -`--report` overrides the destination for a single task only. Task runs reset the -fixture and start fresh conversations; each task gets its own API server and -Worker. Reports record API errors even if the agent recovers and succeeds. - -The model, MCP server, discovery and Dynamic Worker execution are real; only the -Sentry API boundary uses synthetic fixtures. Discovery searches the full pinned -OpenAPI document, while only each fixture's routes are executable. These tests do -not prove production API compatibility or complete API coverage. - -### CLI flow baseline before discovery guidance (2026-10-26) - -The final frozen-fixture runs used Claude Code `2.1.263`, resolved model -`claude-opus-5`, medium effort, Wrangler `4.80.0` and workerd `1.20260401.1`. -The natural surface advertised nine core tools plus two Code Mode tools; the -control advertised the same nine core tools. The reports record the source hashes used -for this baseline. Copies are preserved under `.cache/code-mode/guidance-before/` -before modifying tool descriptions; subsequent guidance results are separate. - -| Task | Catalog + HTTP Code Mode | Catalog-only control | MCP calls per successful run | API calls | Duration | -| ---------------------- | ------------------------ | -------------------- | ---------------------------- | --------- | ----------- | -| Release and deploy | 3/3 | 0/1 completed | 4–5 | 5–7 | 18.5–24.2 s | -| Repository inventory | 3/3 | 0/1 completed | 2–4 | 2–4 | 14.3–23.7 s | -| Disable an issue alert | 3/3 | 0/1 completed | 8–9 | 10–12 | 26.3–31.7 s | - -These are nine successful executions of the final task definitions, not a claim -that every exploratory run succeeded. The release control above is the separate -valid repeat described below. None of the controls completed the requested work; -they explained the missing catalog capability and left the relevant state -unchanged. Rejected calls to nonexistent tools remain visible in the traces. - -The agents chose Code Mode without prompt instructions to do so. Repository runs -used API discovery and code execution directly. Alert runs combined the normal -`get_alert_rule` tool with API discovery and HTTP mutation. Each final alert run -performed exactly one PUT, preserved existing condition IDs and monitor -connections throughout, and returned the actual saved values. - -The final release runs include two expected 404 existence checks before creating -the release. Repository runs have no API errors. Alert runs encounter one, four -and one 404s respectively while trying legacy routes; ordinary catalog reads and -supported API operations succeed. The workflow schemas are present in OpenAPI: -initial searches for `rule` in paths miss them, while searches for `workflow` or -`detector` find them. This points to vocabulary and search strategy as a discovery -weakness, not missing workflow schemas. - -Each report retains token/cache usage, tool result sizes and full generated code. -The suite does not establish token savings against the CLI or an SDK. The runtime -and tool descriptions were unchanged during that baseline evaluation; fixture coverage and -output/preservation grading were refined. Differences between diagnostic and final -runs cannot establish that runtime reliability improved. - -### Global discovery and HTTP write guidance (2026-10-26) - -The next comparison changes only the two tool descriptions and their examples. -Discovery now demonstrates searching path, operationId, summary, description and -tags, with category inspection and broader terms when matches are unrelated. -Execution guidance distinguishes raw HTTP resources from MCP projections and -asks the agent to inspect update semantics, preserve nested identity and verify -saved state. OpenAPI, the runtime, authentication and API permissions are unchanged; -there are no endpoint-specific exceptions or SDK methods. - -Release, repository and alert runs use the same prompts, fixtures, graders, -client version, resolved model and settings as the baseline above. Only -`worker.ts` differs among their recorded execution source hashes. All nine runs -selected Code Mode without instructions to use it in the user prompt. The -unchanged catalog-only controls were not repeated. - -| Task | Success before → after | MCP calls per run before → after | API calls before → after | API errors before → after | -| ---------------------- | ---------------------- | -------------------------------- | ------------------------ | ------------------------- | -| Release and deploy | 3/3 → 3/3 | 4–5 → 4 | 5–7 → 6–7 | 0–1 → 1–2 | -| Repository inventory | 3/3 → 3/3 | 2–4 → 3–4 | 2–4 → 2–4 | 0 → 0 | -| Disable an issue alert | 3/3 → 3/3 | 8–9 → 5 | 10–12 → 4 | 1–4 → 0 | - -All three alert runs first searched for rules, then broadened the search to alert -metadata before making an API request. They found the workflow operation without -trying legacy routes. Each performed one successful PUT, preserved condition IDs, -monitor connections and unrelated settings at every write, and re-read the saved -resource. They still resent full nested configurations and fetched the resource -twice before writing; the guidance did not produce minimal writes. Both before -and after groups pass the same preservation grader, so this comparison shows -less discovery friction, not a measured improvement in preservation success. - -The release errors are existence checks for the new release or its deploy list -before creation. Repository runs retrieve both pages and return all four expected -matches without writes. No run in these three tasks returned an MCP tool error. -An additional dashboard regression passed 3/3 with 4–6 MCP calls and seven API -calls per run, without API or MCP errors. Its historical baseline used an older -runner, so treat that comparison as a regression check rather than a controlled -efficiency comparison. - -Efficiency is mixed. Across three alert runs, tool-result bytes fall from 214,374 -to 100,024 and output tokens from 6,146 to 5,164. Release tool-result bytes rise -from 27,350 to 50,251, and repository bytes from 8,892 to 30,513, as the agent -retrieves more contract information. The two descriptions grow from 2,420 to -4,452 UTF-8 bytes. Reports retain input, cache creation, cache reads and output -separately. Three samples and changing cache usage do not establish general -token or cost savings. - -Before/after evidence is preserved in `.cache/code-mode/guidance-before/` and -`.cache/code-mode/guidance-after/`. The changed examples are also obtained from -real `tools/list` responses and executed through a Dynamic Worker in deterministic -tests: metadata-only matches, declared and derived categories, and both parameter -levels. The focused Worker suite passes 15 tests; Code Mode typechecking, changed -TypeScript linting and core definition generation also pass, with no generated -core changes. - -These results use synthetic API fixtures and do not prove production compatibility -or reliable writes across the API. Generic instructions cannot supply missing -endpoint semantics or enforce preservation by themselves. Further evals can -identify concrete limitations without making a full OpenAPI rewrite a prerequisite. - -### Additional operations and failure recovery - -Four further scenarios exercise HTTP Code Mode with the normal catalog available. -The user prompts describe outcomes without naming tools, routes or Code Mode. -The recovery variants reuse their original task prompts; the permission-denied -variant adds a general JSON fallback for an incomplete inventory, without revealing -the injected failure. - -| Task | Scenario | Independent verification | -| --------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `monitor` | Change one named cron monitor's weekday schedule and timezone | Correct saved schedule; name, slug, owner, enabled state, thresholds, notifications, per-environment mute state and another monitor preserved after every write | -| `release-recovery` | The deploy is persisted, but its first POST response is a simulated gateway 502 | Exactly one persisted deploy, correct saved release and actual deploy ID; replaying the POST fails grading even if the latest-deploy API hides the duplicate | -| `repositories-retry` | The first inventory GET returns a simulated 503 | Agent recovers, follows pagination and returns every expected repository; no writes | -| `repositories-denied` | Inventory GETs always return a simulated 403 | Agent encounters the denial, reports JSON `blocked` with a reason, invents no inventory and performs no writes; no more than two denied collection attempts | - -The blocked outcome is a successful evaluation of limitation handling, not a -completed inventory. Reports include `expectedOutcome` to make that distinction -explicit. Transient failures are opt-in fixture conditions; the original scenarios -retain their previous behavior. They model infrastructure failures, not claims -that Sentry endpoints normally return these responses. - -Monitor GET/PUT semantics were checked against Sentry backend revision -`b4b356554798716ceaeab74efdc095782be9ca16`. Configuration updates merge supplied -fields, while explicitly writing the aggregate `isMuted` value propagates it to -all monitor environments. The fixture starts with different environment mute -states so blindly copying the GET body cannot pass the preservation grader. -Monitor editing has no dedicated catalog tool; the CLI equivalent uses `sentry api`. +Fresh Worker IDs preserve per-execution isolation but affect +[Dynamic Workers costs](https://developers.cloudflare.com/dynamic-workers/pricing/). +Any later reuse must preserve request credentials and authorization; changing +the ID alone is insufficient. -```bash -pnpm --filter @sentry/mcp-server-evals eval:code-mode:resilience --runs 5 -# Run one scenario: -pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --surface catalog-and-code --task monitor --runs 5 -``` +## Runtime and integration validation -Each variant has a separate report and resets its fault state between repetitions. -Reports hash the wrapper and its shared fixture implementation. Fixture tests -reject duplicate deployments, incomplete pagination, fabricated blocked results -and collateral monitor changes before real-agent runs are evaluated. - -#### Results and the resulting HTTP guidance change - -This expansion ran 31 fresh conversations with Claude Code 2.1.263, -`claude-opus-5`, medium effort and the normal catalog plus Code Mode. Each run -used the real MCP/Worker path and synthetic Sentry API state. - -| Scenario | Initial runs | After the generic write guidance change | -| ---------------------------------------- | ------------------------------------- | --------------------------------------- | -| Monitor schedule/timezone edit | 0/5 | 5/5 | -| Deploy persisted before a lost response | 5/5 | 3/3 | -| Repository inventory with an initial 503 | 5/5 | Not rerun | -| Repository inventory denied with 403 | 5/5 blocked after a grader correction | Not rerun | -| Existing alert edit regression | Earlier 3/3 baseline | 3/3 after a parser correction | - -All five initial monitor runs copied the aggregate mute value from GET into PUT, -changing a child environment that the user had asked to preserve. Later repair -attempts could not erase that violation: grading checks every successful write. -The execution tool description now explains that GET is not a write template, -including when an unchanged aggregate value can change child resources. It asks -agents to construct the body from required fields and requested changes, include -other fields only when the update contract requires them, and avoid speculative -repair writes. This is generic HTTP guidance, with no monitor-specific fields, -routes, schema changes or SDK implementation. - -The five new monitor runs used the same prompt, fixture, grader, model and -settings; only the execution description changed. They all preserved the child -mute states, with one successful write each instead of 2–9. These runs still -sometimes sent other unchanged fields, so they do not establish that every body -was minimal or that instructions enforce safe writes across the API. - -Two grader problems are recorded separately from agent failures: - -- The original 403 report was 4/5 because its strict answer schema rejected - harmless `readOnly: true` metadata. Replaying the same recorded requests and - statuses and grading the unchanged answers yields 5/5. The grader still - rejects fabricated inventory counts or rows, writes and repeated denied reads. -- The alert regression report was 2/3 because its parser included braces from - explanatory text after a correct JSON fence. The shared parser now reads one - explicit JSON fence and rejects multiple or malformed fences. Reconstructing - every saved write boundary and final state with exact equality, then grading - the unchanged answers, yields 3/3. This regrades evidence without rerunning - generated code or model inference; preservation checks remain intact. - -Raw reports remain in `.cache/code-mode/expanded-diagnostic/` and -`.cache/code-mode/expanded-after/`. Separate `repositories-denied-regraded.json` -and `alert-regraded.json` files record the original report SHA, grader hashes, -method and both verdicts. Future reports also hash the shared JSON parser. - -These are small samples from one client/model and synthetic API behavior. No new -catalog-only control was run for this expansion. The monitor fixture omits valid -experimental environment-repair routes, so some diagnostic 404s are fixture -limits; they do not explain the first-write preservation failures or prove that -repair is impossible in Sentry. The 403 grader validates an honest blocked -outcome, not the accuracy of suggestions about specific missing OAuth scopes. -No production execution, deployment or broad API reliability claim follows from -these results. - -### Diagnostic runs and grading limits - -Earlier diagnostic runs reached release 3/3, repository inventory 3/3, and alert -2/3 under a final-state-only preservation check. Those alert traces exposed two -different problems: one run removed condition IDs from its PUT payload, causing -three existing conditions to be deleted and recreated; another temporarily -cleared `detectorIds`, then restored the monitor connection. The latter passed -final-state grading but did not preserve settings throughout the operation. -The current verifier checks each successful write and exact final JSON values. -Diagnostic evidence remains under `.cache/code-mode/diagnostic-http-*`; it is not -silently reclassified under the revised prompt or grader. - -The initial fixtures also omitted valid auxiliary reads. Normal alert reads now -include project, workflow scope and detector endpoints, and exploratory workflow -and detector collection reads are supported. Repository fixtures include normal -organization/project discovery reads. Fixture-only 404s in earlier traces are not -product failures. Legacy or invented routes remain unsupported. - -One release catalog control attempted to call the catalog-only `find_releases` -tool directly instead of invoking it through `execute_sentry_tool`; the client -rejected that run. Its evidence is retained in `http-release-claude-catalog.json`. -A separate control in `http-release-claude-catalog-repeat.json` read projects and -releases successfully, explained the missing write tools and made no changes. -Neither control completed the task, but these are different failure categories. - -### What the references contribute to these evaluations - -The rechecked [Cloudflare MCP implementation](https://github.com/cloudflare/mcp) -at revision `550e1be82b2227818e6b98fc32a313a59403ae3f` tests the real MCP/Worker -execution path with external API fixtures. Its -[Code Mode post](https://blog.cloudflare.com/code-mode-mcp/) motivates composing -requests and pagination inside one execution. This suite applies that pattern to -capabilities absent from Sentry's catalog; it does not restrict execution to -existing MCP tools. - -[Stainless's evaluation](https://www.stainless.com/blog/sdk-code-mode/) separately -considers completeness, efficiency and factual accuracy. Here, state verification -and a second-page sentinel make completeness explicit before comparing token -usage. No SDK has been added. - -[Armin Ronacher's Code Mode article](https://lucumr.pocoo.org/2026/10/6/codemode/) -examines host-side composition and structured tool results, including the friction -of nesting code-execution interfaces. This suite uses an ordinary Claude MCP -client with server-side execution; host-side Code Mode composing this server is -not part of the tested surface. - -## HTTP validation - -The Miniflare catalog version matches the version bundled with Wrangler, so the -Dynamic Worker tests use the same `workerd` binary as local Wrangler execution. -The test harness uses Miniflare's `convertV4MiniflareOptions()` adapter, also used -by Wrangler, to preserve its existing worker configuration on Miniflare 5. No -`MINIFLARE_WORKERD_PATH` override is required. - -The hosted test pool configures outbound fixtures through Undici's `MockAgent` -directly. Unregistered outbound requests remain blocked with `disableNetConnect()`. - -Use Node 24 for this checkout; its `better-sqlite3` dependency cannot compile with -Node 26. After `pnpm install --frozen-lockfile`: +Use Node 24 for this checkout; its `better-sqlite3` dependency does not compile +with Node 26. After `pnpm install --frozen-lockfile`: ```bash pnpm --filter '@sentry/mcp-core...' --if-present run build +pnpm --filter @sentry/mcp-cloudflare test pnpm --filter @sentry/mcp-cloudflare test:code-mode pnpm --filter @sentry/mcp-cloudflare tsc:code-mode -pnpm --filter @sentry/mcp-server-evals test:code-mode -pnpm --filter @sentry/mcp-server-evals tsc:code-mode -pnpm --filter @sentry/mcp-server-evals eval:code-mode --runs 3 --model '' -# Or use an authenticated Claude CLI session: -pnpm --filter @sentry/mcp-server-evals eval:code-mode --client claude --runs 3 ``` -Tests and typechecks need no model credentials and are wired into the Toolkit -CI project matrix through each package's `sentryCi` configuration. Cloudflare's -`test:ci` and `tsc:ci` include its Code Mode checks; the eval package runs -`test:code-mode` and `tsc:code-mode`. Model-based evals remain separate and -require an installed, authenticated CLI session. The default `--client codex` -requires an explicit `--model` available to that login. It uses -`codex exec --ignore-user-config` with medium reasoning. Some built-in tools and -global `CODEX_HOME/AGENTS.md` may still be included by Codex. - -`--client claude` uses Claude's default model unless `--model` is supplied, and -records the resolved model from its event stream. It sets medium effort, disables -built-in tools, hooks, skills and session persistence, and uses a strict MCP -configuration containing only the two experiment tools. In the isolated `code-mode` baseline, both clients run in an empty temporary -directory, receive the same task and instructions, and reject runs that invoke -other tools. The Claude-only discovery surfaces keep the default system prompt -and allow the entire selected MCP inventory instead. This is a client-based benchmark, not a bare-model -benchmark; keep the same client, model and settings when comparing HTTP changes. - -Claude usage is taken once from its final aggregate result. Input, cache creation, -cache reads and output are recorded separately; cached tokens must not be -silently treated as free or counted twice by summing per-message usage. - -The runner records requested model and client version, task/developer prompt, token usage, -duration, MCP requests/results, API request outcomes and independently verified -state in the selected report file. Reports contain synthetic data; -they are local artifacts, not committed generated files. A failed verification -sets a nonzero exit code. Repetitions reset state and use fresh conversations. - -Measure success before optimizing tokens or latency. The CLI flows above cover discovery, pagination, combining responses and compact -result projection; later evaluations can expand failure recovery and API coverage. Three runs are a pilot, not statistical evidence of superiority. No -token savings or full-API coverage can be inferred from this experiment. - -### Deferred SDK evaluation - -An SDK interface is future work and is not required for the initial HTTP -deployment. If evaluated later, compare it against HTTP using the same task, -fixture, verifier, client, model settings and budgets. The existing HTTP results -do not establish an SDK advantage. +Cloudflare's `test:ci` and `tsc:ci` include the Code Mode checks. The ordinary +suite exercises the authenticated MCP handler, Dynamic Workers and RPC broker +with external OpenAPI/Sentry fixtures. It covers grant and scope visibility, +regional routing, attribution, concurrent token isolation, organization escapes, +401 revocation, caught 5xx reporting, and modern/legacy input limits. Dashboard +copying verifies persisted state with API Write and an upstream `org:read` token. + +The isolated runtime suite uses `wrangler.code-mode.jsonc` to test execution, +network denial and API path restrictions. Its direct-token entrypoint is for +local tests, not hosted deployment or OAuth validation. Only the hosted handler +applies the connection's skills, constraints and regional policy. + +`@cloudflare/vitest-plugin` 1.4.6 aligns the hosted test runtime with Wrangler and +the standalone Miniflare tests. Updating only direct Miniflare previously left +the Vitest pool using older workerd. Miniflare 5 uses +`convertV4MiniflareOptions()` for standalone configuration; hosted outbound +fixtures use the existing Undici `MockAgent` with `disableNetConnect()`. No +binary override is required. + +Validation after separating agent evals on 2026-10-27 passed 469 hosted tests, +26 isolated runtime/access tests, root typechecks, frozen install and a +production Wrangler dry-run with `LOADER`. This is fixture-backed evidence, +not a real Sentry account or deployment. +Unrelated CLI fixture/platform failures prevented a green full workspace suite. +Before production, validate hosted OAuth-to-API authorization and Cloudflare +CPU/termination limits. No deployment is established by these checks. + +## References + +- Implementation: `packages/mcp-cloudflare/src/code-mode/production.ts`, + `broker.ts`, `worker.ts`, `sandbox.ts` and `definitions.ts`. +- Integration tests: + `packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts`. +- [Cloudflare architecture](../cloudflare/architecture.md) and + [OAuth architecture](../cloudflare/oauth-architecture.md). +- [Stainless SDK Code Mode](https://www.stainless.com/blog/sdk-code-mode/) + describes the deferred SDK interface; it does not change who owns model usage. diff --git a/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts b/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts deleted file mode 100644 index c2a28b84b..000000000 --- a/packages/mcp-cloudflare/src/code-mode/discovery-worker.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { execFile } from "node:child_process"; -import { mkdtemp, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import { promisify } from "node:util"; -import { convertV4MiniflareOptions, Miniflare } from "miniflare"; -import { afterAll, beforeAll, beforeEach, expect, it } from "vitest"; -import { TOP_LEVEL_TOOL_NAMES } from "../../../mcp-core/src/tools/surfaces"; -import { createDashboardFixture } from "../../../mcp-server-evals/src/code-mode/dashboard-fixture"; - -let outputDirectory: string; -let worker: Miniflare; -let fixture: Awaited>; - -async function rpc( - method: string, - params: Record = {}, - route = "/mcp", -) { - const response = await worker.dispatchFetch(`http://localhost${route}`, { - method: "POST", - headers: { - authorization: "Bearer code-mode-eval-token", - "content-type": "application/json", - accept: "application/json, text/event-stream", - "mcp-protocol-version": "2026-07-28", - "mcp-method": method, - ...(typeof params.name === "string" ? { "mcp-name": params.name } : {}), - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method, - params: { - ...params, - _meta: { - "io.modelcontextprotocol/protocolVersion": "2026-07-28", - "io.modelcontextprotocol/clientInfo": { - name: "discovery-tests", - version: "1.0.0", - }, - "io.modelcontextprotocol/clientCapabilities": {}, - }, - }, - }), - }); - const text = await response.text(); - if (!response.ok) throw new Error(`MCP HTTP ${response.status}: ${text}`); - const data = text.startsWith("event:") - ? text - .split("\n") - .find((line) => line.startsWith("data: ")) - ?.slice(6) - : text; - const envelope = JSON.parse(data ?? "{}"); - if (envelope.error) throw new Error(JSON.stringify(envelope.error)); - return envelope.result; -} - -beforeAll(async () => { - outputDirectory = await mkdtemp(join(tmpdir(), "sentry-discovery-test-")); - await promisify(execFile)( - process.execPath, - [ - resolve("node_modules/wrangler/bin/wrangler.js"), - "deploy", - "src/code-mode/discovery-worker.ts", - "--dry-run", - "--config", - "wrangler.code-mode.jsonc", - "--outdir", - outputDirectory, - ], - { - timeout: 60_000, - env: { ...process.env, WRANGLER_SEND_METRICS: "false" }, - }, - ); - fixture = await createDashboardFixture(); - worker = new Miniflare( - convertV4MiniflareOptions({ - modules: true, - modulesRoot: outputDirectory, - scriptPath: join(outputDirectory, "discovery-worker.js"), - compatibilityDate: "2026-01-12", - compatibilityFlags: ["nodejs_compat"], - workerLoaders: { LOADER: {} }, - bindings: { - API_BASE_URL: `${fixture.baseUrl}/api/0`, - OPENAPI_URL: fixture.openapiUrl, - CODE_MODE_ALLOW_WRITES: "true", - }, - }), - ); -}); - -beforeEach(() => fixture.reset()); - -afterAll(async () => { - await worker?.dispose(); - await fixture?.close(); - if (outputDirectory) - await rm(outputDirectory, { recursive: true, force: true }); -}); - -it("offers the real catalog and Code Mode together without replacing normal tools", async () => { - const combined = await rpc("tools/list"); - const control = await rpc("tools/list", {}, "/catalog/mcp"); - expect( - combined.tools.map((tool: { name: string }) => tool.name).sort(), - ).toEqual( - [ - ...TOP_LEVEL_TOOL_NAMES, - "search_sentry_api", - "execute_sentry_code", - ].sort(), - ); - expect( - control.tools.map((tool: { name: string }) => tool.name).sort(), - ).toEqual([...TOP_LEVEL_TOOL_NAMES].sort()); - await expect( - rpc( - "tools/call", - { name: "execute_sentry_code", arguments: { code: "async () => 1" } }, - "/catalog/mcp", - ), - ).rejects.toThrow(); - - const catalog = await rpc("tools/call", { - name: "search_sentry_tools", - arguments: { query: "dashboard" }, - }); - expect(catalog.isError).not.toBe(true); - expect( - catalog.structuredContent.results.map( - (tool: { name: string }) => tool.name, - ), - ).toEqual( - expect.arrayContaining(["get_dashboard_details", "find_dashboards"]), - ); - expect( - catalog.structuredContent.results.every( - (tool: { annotations: { readOnlyHint: boolean } }) => - tool.annotations.readOnlyHint, - ), - ).toBe(true); - - const details = await rpc("tools/call", { - name: "execute_sentry_tool", - arguments: { - name: "get_dashboard_details", - arguments: { - organizationSlug: fixture.organizationSlug, - dashboardIdOrTitle: "101", - }, - }, - }); - expect(details.isError).not.toBe(true); - expect(details.content[0].text).toContain("Errors Overview"); - expect(fixture.snapshot().requests).toEqual([ - { - method: "GET", - path: `/api/0/organizations/${fixture.organizationSlug}/dashboards/101/`, - status: 200, - }, - ]); -}); - -it("executes the existing sandbox and broker alongside the normal catalog", async () => { - const discovery = await rpc("tools/call", { - name: "search_sentry_api", - arguments: { code: "async () => Object.keys(spec.paths)" }, - }); - expect(discovery.isError).not.toBe(true); - expect(discovery.structuredContent.result.length).toBeGreaterThan(0); - const result = await rpc("tools/call", { - name: "execute_sentry_code", - arguments: { - code: `async () => (await sentry.request({path: '/organizations/${fixture.organizationSlug}/dashboards/101/'})).body.id`, - }, - }); - expect(result.isError).not.toBe(true); - expect(result.structuredContent).toEqual({ - result: "101", - stats: { apiCalls: 1, writesAttempted: 0 }, - }); -}); - -it("requires authentication before exposing either eval surface", async () => { - for (const route of ["/mcp", "/catalog/mcp"]) { - const response = await worker.dispatchFetch(`http://localhost${route}`, { - method: "POST", - }); - expect(response.status).toBe(401); - } -}); - -it("refuses non-fixture API configuration", async () => { - const misconfigured = new Miniflare( - convertV4MiniflareOptions({ - modules: true, - modulesRoot: outputDirectory, - scriptPath: join(outputDirectory, "discovery-worker.js"), - compatibilityDate: "2026-01-12", - compatibilityFlags: ["nodejs_compat"], - workerLoaders: { LOADER: {} }, - bindings: { - API_BASE_URL: "https://sentry.io/api/0", - OPENAPI_URL: fixture.openapiUrl, - }, - }), - ); - try { - const response = await misconfigured.dispatchFetch("http://localhost/mcp", { - method: "POST", - headers: { authorization: "Bearer code-mode-eval-token" }, - }); - expect(response.status).toBe(500); - expect(await response.text()).toContain("loopback HTTP fixtures"); - expect(fixture.snapshot().requests).toEqual([]); - } finally { - await misconfigured.dispose(); - } -}); diff --git a/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts b/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts deleted file mode 100644 index 340861145..000000000 --- a/packages/mcp-cloudflare/src/code-mode/discovery-worker.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createMcpHandler } from "agents/mcp/server"; -import { MAX_MCP_REQUEST_BODY_SIZE } from "../../../mcp-core/src/constants"; -import { buildServer } from "../../../mcp-core/src/server"; -import { ALL_SKILLS } from "../../../mcp-core/src/skills"; -import type { CodeModeEnv } from "./broker"; -import { configuredUrl, limitMcpRequestBody } from "./http"; -import { registerCodeModeTools } from "./worker"; - -/** Eval-only composition of the real catalog and HTTP Code Mode on one MCP. */ -export default { - async fetch( - request: Request, - env: CodeModeEnv, - ctx: ExecutionContext, - ): Promise { - const route = new URL(request.url).pathname; - if (route !== "/mcp" && route !== "/catalog/mcp") { - return new Response("Not found", { status: 404 }); - } - const token = request.headers - .get("authorization") - ?.match(/^Bearer\s+(\S+)$/i)?.[1]; - if (!token) return new Response("Bearer token required", { status: 401 }); - - const api = configuredUrl(env.API_BASE_URL); - if ( - api.protocol !== "http:" || - !["localhost", "127.0.0.1", "[::1]"].includes(api.hostname) || - api.pathname.replace(/\/$/, "") !== "/api/0" - ) { - return new Response("Discovery evals require loopback HTTP fixtures", { - status: 500, - }); - } - - const boundedRequest = await limitMcpRequestBody(request); - if (boundedRequest instanceof Response) return boundedRequest; - return await createMcpHandler( - () => { - const server = buildServer({ - sdkVersion: "v2", - context: { - accessToken: token, - sentryHost: api.host, - sentryProtocol: "http", - grantedSkills: new Set(ALL_SKILLS), - constraints: {}, - transport: "http", - }, - }); - if (route === "/mcp") registerCodeModeTools(server, env, token); - return server; - }, - { route, maxRequestBodySize: MAX_MCP_REQUEST_BODY_SIZE }, - )(boundedRequest, env, ctx); - }, -} satisfies ExportedHandler; diff --git a/packages/mcp-cloudflare/src/code-mode/tsconfig.json b/packages/mcp-cloudflare/src/code-mode/tsconfig.json index 65fa72c7b..f1c824202 100644 --- a/packages/mcp-cloudflare/src/code-mode/tsconfig.json +++ b/packages/mcp-cloudflare/src/code-mode/tsconfig.json @@ -5,5 +5,5 @@ "rootDir": "../../../..", "tsBuildInfoFile": "../../node_modules/.tmp/tsconfig.code-mode.tsbuildinfo" }, - "include": ["./worker.ts", "./broker.ts", "./sandbox.ts", "./discovery-worker.ts"] + "include": ["./worker.ts", "./broker.ts", "./sandbox.ts"] } diff --git a/packages/mcp-server-evals/package.json b/packages/mcp-server-evals/package.json index d2aee9996..393716cff 100644 --- a/packages/mcp-server-evals/package.json +++ b/packages/mcp-server-evals/package.json @@ -12,17 +12,8 @@ "dev": "tsc -w", "start": "tsx src/bin/start-mock-stdio.ts", "eval": "vitest --config=vitest.config.ts", - "eval:code-mode": "tsx src/code-mode/run-dashboard.ts", - "eval:code-mode:discovery": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code", - "eval:code-mode:flows": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code --task release --task repositories --task alert", - "eval:code-mode:resilience": "tsx src/code-mode/run-dashboard.ts --client claude --surface catalog-and-code --task monitor --task release-recovery --task repositories-retry --task repositories-denied", - "test:code-mode": "vitest run --config=vitest.code-mode.config.ts", - "tsc:code-mode": "tsc --project tsconfig.code-mode.json --noEmit", "eval:ci": "vitest run --reporter=vitest-evals/reporter --reporter=junit --reporter=json --outputFile.json=eval-results.json --outputFile.junit=eval.junit.xml" }, - "devDependencies": { - "wrangler": "catalog:" - }, "dependencies": { "@ai-sdk/mcp": "catalog:", "@ai-sdk/openai": "catalog:", @@ -38,9 +29,5 @@ "vitest": "catalog:", "vitest-evals": "catalog:", "zod": "catalog:" - }, - "sentryCi": { - "typecheck": "tsc:code-mode", - "test": "test:code-mode" } } diff --git a/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts deleted file mode 100644 index d67198b63..000000000 --- a/packages/mcp-server-evals/src/code-mode/alert-fixture.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { z } from "zod"; -import { createAlertFixture } from "./alert-fixture"; - -type Fixture = Awaited>; -const active: Fixture[] = []; -const accessToken = "local-alert-test-token"; -const finalAnswer = JSON.stringify({ - id: "701", - enabled: false, - frequency: 60, -}); - -afterEach(async () => { - await Promise.all(active.splice(0).map((fixture) => fixture.close())); -}); - -async function fixture() { - const instance = await createAlertFixture({ accessToken }); - active.push(instance); - return instance; -} - -function request(instance: Fixture, id = "701", body?: unknown) { - return fetch( - `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/workflows/${id}/`, - { - method: body === undefined ? "GET" : "PUT", - headers: { - authorization: `Bearer ${accessToken}`, - "content-type": "application/json", - }, - body: body === undefined ? undefined : JSON.stringify(body), - }, - ); -} - -async function editBody(instance: Fixture) { - const response = await request(instance); - expect(response.status).toBe(200); - const source = z.record(z.string(), z.unknown()).parse(await response.json()); - return { ...source, enabled: false, config: { frequency: 60 } }; -} - -describe("issue alert Code Mode eval fixture", () => { - it("serves project scope and detector reads used by the ordinary alert tool", async () => { - const instance = await fixture(); - const paths = [ - "/api/0/projects/alert-evals/frontend/", - "/api/0/organizations/alert-evals/workflows/701/project-scope/", - "/api/0/organizations/alert-evals/detectors/901/", - "/api/0/organizations/alert-evals/workflows/", - "/api/0/organizations/alert-evals/detectors/", - ]; - const responses = await Promise.all( - paths.map((path) => - fetch(`${instance.baseUrl}${path}`, { - headers: { authorization: `Bearer ${accessToken}` }, - }), - ), - ); - expect(responses.map(({ status }) => status)).toEqual([ - 200, 200, 200, 200, 200, - ]); - const [project, scope, detector, workflows, detectors] = await Promise.all( - responses.map((response) => response.json()), - ); - expect(project).toEqual({ - id: "101", - slug: "frontend", - name: "Frontend", - platform: "javascript", - }); - expect(scope).toEqual({ projectIds: ["101"], includesAllProjects: false }); - expect(detector).toMatchObject({ - id: "901", - projectId: "101", - type: "error", - enabled: true, - workflowIds: ["701", "702"], - config: {}, - conditionGroup: null, - dataSources: [], - dateCreated: "2026-10-25T10:00:00.000Z", - dateUpdated: "2026-10-25T10:00:00.000Z", - }); - expect(workflows).toEqual(instance.snapshot().workflows); - expect(detectors).toEqual([detector]); - expect( - instance.snapshot().workflows.every((workflow) => workflow.enabled), - ).toBe(true); - }); - - it("persists the CLI-equivalent edit and preserves nested alert configuration", async () => { - const instance = await fixture(); - const before = instance.snapshot(); - const body = await editBody(instance); - const updated = await request(instance, "701", body); - expect(updated.status).toBe(200); - const fetched = await request(instance); - expect(await fetched.json()).toMatchObject({ - id: "701", - enabled: false, - config: { frequency: 60 }, - environment: "production", - owner: "team:42", - detectorIds: ["901"], - triggers: before.workflows[0].triggers, - actionFilters: before.workflows[0].actionFilters, - }); - expect(instance.verify(finalAnswer)).toEqual({ - success: true, - workflowId: "701", - enabled: false, - frequency: 60, - }); - expect(instance.snapshot().workflows[1]).toEqual(before.workflows[1]); - expect(instance.snapshot().writes).toEqual([ - { workflowId: "701", workflows: instance.snapshot().workflows }, - ]); - expect(instance.snapshot().requests.map(({ method }) => method)).toEqual([ - "GET", - "PUT", - "GET", - ]); - instance.reset(); - expect(instance.snapshot()).toEqual(before); - }); - - it("preserves omitted optional fields and requires name as WorkflowValidator does", async () => { - const instance = await fixture(); - const missingName = await request(instance, "701", { - enabled: false, - config: { frequency: 60 }, - }); - expect(missingName.status).toBe(400); - expect(instance.snapshot().workflows[0].enabled).toBe(true); - const response = await request(instance, "701", { - name: "Frontend production errors", - enabled: false, - config: { frequency: 60 }, - }); - expect(response.status).toBe(200); - expect(instance.verify(finalAnswer).success).toBe(true); - }); - - it("accepts snake_case nested fields but rejects alias collisions", async () => { - const instance = await fixture(); - const body = await editBody(instance); - const snakeBody = JSON.parse( - JSON.stringify(body).replace( - /"([A-Za-z]+)":/g, - (_, key: string) => - `"${key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)}":`, - ), - ) as unknown; - expect((await request(instance, "701", snakeBody)).status).toBe(200); - expect(instance.verify(finalAnswer).success).toBe(true); - const collision = await request(instance, "701", { - ...body, - detector_ids: [902], - }); - expect(collision.status).toBe(400); - expect(instance.verify(finalAnswer).success).toBe(true); - }); - - it("rejects a claimed success when the edit dropped notification actions", async () => { - const instance = await fixture(); - const body = await editBody(instance); - expect( - (await request(instance, "701", { ...body, actionFilters: [] })).status, - ).toBe(200); - expect(() => instance.verify(finalAnswer)).toThrow( - "Alert settings other than enabled/frequency changed", - ); - }); - - it("rejects success after an unrelated alert was changed", async () => { - const instance = await fixture(); - expect( - (await request(instance, "701", await editBody(instance))).status, - ).toBe(200); - expect( - ( - await request(instance, "702", { - name: "Backend production errors", - enabled: false, - }) - ).status, - ).toBe(200); - expect(() => instance.verify(finalAnswer)).toThrow( - "Unrelated alert changed", - ); - }); - - it("rejects temporarily clearing monitor connections even after restoring them", async () => { - const instance = await fixture(); - const body = await editBody(instance); - expect( - (await request(instance, "701", { ...body, detectorIds: [] })).status, - ).toBe(200); - expect((await request(instance, "701", body)).status).toBe(200); - const state = instance.snapshot(); - expect(state.workflows[0].detectorIds).toEqual(["901"]); - expect(state.writes.map((write) => write.workflows[0].detectorIds)).toEqual( - [[], ["901"]], - ); - expect(() => instance.verify(finalAnswer)).toThrow( - "Write 1: unrelated alert settings changed during the operation", - ); - }); - - it("rejects wrong reported values even when the expected tokens appear elsewhere", async () => { - const instance = await fixture(); - expect( - (await request(instance, "701", await editBody(instance))).status, - ).toBe(200); - const wrongAnswer = JSON.stringify({ - id: "999", - enabled: true, - frequency: 15, - note: "The requested values were alert 701, false (disabled), 60 minutes.", - }); - expect(() => instance.verify(wrongAnswer)).toThrow( - "Final answer does not match the saved alert values", - ); - }); - - it("rejects an invented success and an answer that omits the actual result", async () => { - const instance = await fixture(); - expect(() => instance.verify(finalAnswer)).toThrow( - "Target alert is still enabled", - ); - expect( - (await request(instance, "701", await editBody(instance))).status, - ).toBe(200); - expect(() => instance.verify("Done.")).toThrow( - "Final answer must include the saved values as JSON", - ); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/alert-fixture.ts b/packages/mcp-server-evals/src/code-mode/alert-fixture.ts deleted file mode 100644 index 2a8df3efe..000000000 --- a/packages/mcp-server-evals/src/code-mode/alert-fixture.ts +++ /dev/null @@ -1,450 +0,0 @@ -import assert from "node:assert/strict"; -import { z } from "zod"; -import { startApiFixture } from "./api-fixture"; -import { parseEvalJsonAnswer } from "./json-answer"; - -const ORGANIZATION = "alert-evals"; -const WORKFLOW_ID = "701"; -const OTHER_WORKFLOW_ID = "702"; -const DATE_CREATED = "2026-10-25T10:00:00.000Z"; -const DATE_UPDATED = "2026-10-26T10:00:00.000Z"; - -const answerSchema = z.object({ - id: z.union([z.string(), z.number().int().positive()]).transform(String), - enabled: z.boolean(), - frequency: z.number().int(), -}); -const conditionSchema = z.object({ - id: z.coerce.string().optional(), - type: z.string(), - comparison: z.unknown(), - conditionResult: z.unknown(), -}); -const actionSchema = z.object({ - id: z.coerce.string().optional(), - type: z.string(), - integrationId: z.coerce.string().nullable().optional(), - data: z.record(z.string(), z.unknown()), - config: z.record(z.string(), z.unknown()), - status: z.string().default("active"), -}); -const groupSchema = z.object({ - id: z.coerce.string().optional(), - logicType: z.enum(["all", "any", "any-short"]), - conditions: z.array(conditionSchema).default([]), -}); -const actionFilterSchema = groupSchema.extend({ - actions: z.array(actionSchema), -}); -const updateSchema = z.object({ - name: z.string().min(1).max(256), - enabled: z.boolean().default(true), - config: z - .object({ frequency: z.number().int().min(0).optional() }) - .strict() - .optional(), - environment: z.string().nullable().optional(), - owner: z.string().nullable().optional(), - detectorIds: z.array(z.coerce.string()).optional(), - triggers: groupSchema.optional(), - actionFilters: z.array(actionFilterSchema).optional(), -}); - -type Group = z.infer & { - id: string; - organizationId: string; - actions: z.infer[]; -}; -interface Workflow { - id: string; - name: string; - organizationId: string; - createdBy: string; - dateCreated: string; - dateUpdated: string; - triggers: Group; - actionFilters: Group[]; - environment: string | null; - config: { frequency?: number }; - detectorIds: string[]; - enabled: boolean; - lastTriggered: string | null; - owner: string | null; -} - -// CamelSnakeSerializer accepts either spelling recursively. Detect collisions -// before normalizing so the fixture cannot silently accept contradictory fields. -function camelKeys(value: unknown): unknown { - if (Array.isArray(value)) return value.map(camelKeys); - if (value === null || typeof value !== "object") return value; - const result: Record = {}; - for (const [key, item] of Object.entries(value)) { - const converted = key.replace(/_([a-z])/g, (_, letter: string) => - letter.toUpperCase(), - ); - if (Object.hasOwn(result, converted)) { - throw new SyntaxError(`${key} collides with ${converted}`); - } - result[converted] = camelKeys(item); - } - return result; -} - -// Sanitized shape from WorkflowEngineExamples.GET_WORKFLOW and its serializers. -function sourceWorkflow(id: string, name: string): Workflow { - return { - id, - name, - organizationId: "1", - createdBy: "10", - dateCreated: DATE_CREATED, - dateUpdated: DATE_CREATED, - triggers: { - id: `${id}1`, - organizationId: "1", - logicType: "any-short", - conditions: [ - { - id: `${id}11`, - type: "first_seen_event", - comparison: true, - conditionResult: true, - }, - { - id: `${id}12`, - type: "regression_event", - comparison: true, - conditionResult: true, - }, - ], - actions: [], - }, - actionFilters: [ - { - id: `${id}2`, - organizationId: "1", - logicType: "all", - conditions: [ - { - id: `${id}21`, - type: "issue_priority_deescalating", - comparison: true, - conditionResult: true, - }, - ], - actions: [ - { - id: `${id}31`, - type: "slack", - integrationId: "20", - data: {}, - config: { - targetType: "specific", - targetDisplay: "#frontend-alerts", - targetIdentifier: "C0000000001", - }, - status: "active", - }, - { - id: `${id}32`, - type: "slack", - integrationId: "20", - data: {}, - config: { - targetType: "specific", - targetDisplay: "#oncall-alerts", - targetIdentifier: "C0000000002", - }, - status: "active", - }, - ], - }, - ], - environment: "production", - config: { frequency: 15 }, - detectorIds: ["901"], - enabled: true, - lastTriggered: "2026-10-25T09:00:00.000Z", - owner: "team:42", - }; -} - -/** - * A narrow stateful substitute for workflow GET/PUT, based on Sentry's - * WorkflowValidator. Omitted nested fields are preserved; supplied groups - * replace their child lists. It does not emulate integration validation, - * permissions, feature flags, detector membership or database side effects. - */ -export async function createAlertFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const original = sourceWorkflow(WORKFLOW_ID, "Frontend production errors"); - const other = sourceWorkflow(OTHER_WORKFLOW_ID, "Backend production errors"); - const initial = [original, other]; - let workflows = new Map( - initial.map((item) => [item.id, structuredClone(item)]), - ); - const writes: Array<{ workflowId: string; workflows: Workflow[] }> = []; - let nextId = 8000; - const collectionPath = `/api/0/organizations/${ORGANIZATION}/workflows/`; - const detectorPath = `/api/0/organizations/${ORGANIZATION}/detectors/`; - const detector = () => ({ - id: "901", - projectId: "101", - name: "Frontend error monitor", - type: "error", - workflowIds: [...workflows.values()] - .filter((item) => item.detectorIds.includes("901")) - .map((item) => item.id), - dateCreated: DATE_CREATED, - dateUpdated: DATE_CREATED, - dataSources: [], - conditionGroup: null, - config: {}, - enabled: true, - }); - const api = await startApiFixture(accessToken, ({ method, url, body }) => { - // These reads keep the ordinary get_alert_rule tool usable in the control - // arm. It checks project membership and expands the connected detector. - if ( - method === "GET" && - ["frontend", "101"].some( - (project) => - url.pathname === `/api/0/projects/${ORGANIZATION}/${project}/`, - ) - ) { - return { - body: { - id: "101", - slug: "frontend", - name: "Frontend", - platform: "javascript", - }, - }; - } - if ( - method === "GET" && - (url.pathname === detectorPath || url.pathname === `${detectorPath}901/`) - ) { - return { - body: url.pathname === detectorPath ? [detector()] : detector(), - }; - } - if (!url.pathname.startsWith(collectionPath)) return undefined; - if (method === "GET" && url.pathname === collectionPath) { - return { body: [...workflows.values()] }; - } - if (method === "GET" && url.pathname.endsWith("/project-scope/")) { - const workflowId = url.pathname.slice( - collectionPath.length, - -"/project-scope/".length, - ); - const workflow = workflows.get(workflowId); - return workflow - ? { - body: { - projectIds: workflow.detectorIds.includes("901") ? ["101"] : [], - includesAllProjects: false, - }, - } - : { status: 404, body: { detail: "Alert not found" } }; - } - const id = url.pathname.slice(collectionPath.length).replace(/\/$/, ""); - const workflow = workflows.get(id); - if (!workflow) return { status: 404, body: { detail: "Alert not found" } }; - if (method === "GET") return { body: workflow }; - if (method !== "PUT") return undefined; - const input = updateSchema.parse(camelKeys(body)); - const { triggers, actionFilters, ...fields } = input; - const existingFilterIds = new Set( - workflow.actionFilters.map((item) => item.id), - ); - const existingActionIds = new Set( - workflow.actionFilters.flatMap((item) => - item.actions.map((action) => action.id), - ), - ); - if ( - (triggers?.id && triggers.id !== workflow.triggers.id) || - actionFilters?.some( - (item) => - (item.id && !existingFilterIds.has(item.id)) || - item.actions.some( - (action) => action.id && !existingActionIds.has(action.id), - ), - ) - ) { - return { - status: 400, - body: { - detail: "Condition group or action does not belong to this workflow", - }, - }; - } - // Backend updates the rows whose IDs are supplied and creates new rows - // when IDs are omitted; copying only condition values changes identity. - const serializeGroup = ( - group: z.infer & { - actions?: z.infer[]; - }, - existingId?: string, - ): Group => ({ - ...group, - id: group.id ?? existingId ?? String(nextId++), - organizationId: workflow.organizationId, - conditions: group.conditions.map((condition) => ({ - ...condition, - id: condition.id ?? String(nextId++), - })), - actions: (group.actions ?? []).map((action) => ({ - ...action, - id: action.id ?? String(nextId++), - integrationId: action.integrationId ?? null, - })), - }); - const updated: Workflow = { - ...workflow, - ...fields, - dateUpdated: DATE_UPDATED, - triggers: triggers - ? serializeGroup(triggers, workflow.triggers.id) - : workflow.triggers, - actionFilters: actionFilters - ? actionFilters.map((item) => serializeGroup(item)) - : workflow.actionFilters, - }; - workflows.set(id, updated); - writes.push( - structuredClone({ workflowId: id, workflows: [...workflows.values()] }), - ); - return { body: updated }; - }); - - return { - baseUrl: api.baseUrl, - organizationSlug: ORGANIZATION, - prompt: [ - `In organization ${ORGANIZATION}, disable the issue alert "${original.name}" (alert ID ${WORKFLOW_ID}, for project frontend) and set its notification frequency to 60 minutes.`, - "Preserve its name, environment, owner, monitor connections, trigger conditions, filters and notification actions. Leave all other alerts unchanged.", - "Keep those settings unchanged throughout the operation.", - "Return JSON with the alert's saved values: {id, enabled, frequency}.", - ].join("\n"), - cliCommands: [ - `sentry alert issues edit ${ORGANIZATION}/frontend/${WORKFLOW_ID} --status disabled --frequency 60`, - ], - provenance: { - cliRevision: "b625111aef9cc3a44abbed634a043fce8505366d", - cliSource: "packages/cli/src/commands/alert/issues/edit.ts", - backendRevision: "b4b356554798716ceaeab74efdc095782be9ca16", - backendSources: [ - "src/sentry/workflow_engine/endpoints/organization_workflow_details.py", - "src/sentry/workflow_engine/endpoints/validators/base/workflow.py", - "src/sentry/workflow_engine/endpoints/validators/base/data_condition_group.py", - "src/sentry/workflow_engine/endpoints/serializers/workflow_serializer.py", - "src/sentry/apidocs/examples/workflow_engine_examples.py", - ], - catalogReadSupport: { - // This private endpoint exists in the local origin/master ref, which - // is newer than the backend checkout used for the mutation contract. - backendRevision: "c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4", - backendRef: "origin/master", - backendSources: [ - "src/sentry/workflow_engine/endpoints/organization_workflow_project_scope.py", - "src/sentry/workflow_engine/endpoints/organization_workflow_index.py", - "src/sentry/workflow_engine/endpoints/organization_detector_index.py", - "src/sentry/workflow_engine/endpoints/organization_detector_details.py", - "src/sentry/workflow_engine/endpoints/serializers/detector_serializer.py", - "src/sentry/core/endpoints/project_details.py", - "src/sentry/api/serializers/models/project.py", - ], - }, - endpoints: [ - "GET /organizations/{organization_id_or_slug}/workflows/{workflow_id}/", - "PUT /organizations/{organization_id_or_slug}/workflows/{workflow_id}/", - "GET /organizations/{organization_id_or_slug}/workflows/", - "GET /organizations/{organization_id_or_slug}/workflows/{workflow_id}/project-scope/", - "GET /organizations/{organization_id_or_slug}/detectors/{detector_id}/", - "GET /organizations/{organization_id_or_slug}/detectors/", - "GET /projects/{organization_id_or_slug}/{project_id_or_slug}/", - ], - }, - reset() { - workflows = new Map( - initial.map((item) => [item.id, structuredClone(item)]), - ); - nextId = 8000; - api.requests.length = 0; - writes.length = 0; - }, - snapshot() { - return structuredClone({ - workflows: [...workflows.values()], - requests: api.requests, - writes, - }); - }, - verify(resultText: string) { - const updated = workflows.get(WORKFLOW_ID); - assert(updated, "Target alert is missing"); - assert.equal(updated.enabled, false, "Target alert is still enabled"); - assert.equal( - updated.config.frequency, - 60, - "Target alert frequency is not 60 minutes", - ); - assert.deepEqual( - updated, - { - ...original, - enabled: false, - config: { frequency: 60 }, - dateUpdated: DATE_UPDATED, - }, - "Alert settings other than enabled/frequency changed", - ); - assert.deepEqual( - workflows.get(OTHER_WORKFLOW_ID), - other, - "Unrelated alert changed", - ); - for (const [index, write] of writes.entries()) { - const target = write.workflows.find((item) => item.id === WORKFLOW_ID); - assert(target, `Write ${index + 1}: target alert is missing`); - assert.deepEqual( - target, - { - ...original, - enabled: target.enabled, - config: { ...original.config, frequency: target.config.frequency }, - dateUpdated: target.dateUpdated, - }, - `Write ${index + 1}: unrelated alert settings changed during the operation`, - ); - assert.deepEqual( - write.workflows.find((item) => item.id === OTHER_WORKFLOW_ID), - other, - `Write ${index + 1}: another alert changed during the operation`, - ); - } - const answer = answerSchema.parse(parseEvalJsonAnswer(resultText)); - assert.deepEqual( - answer, - { - id: updated.id, - enabled: updated.enabled, - frequency: updated.config.frequency, - }, - "Final answer does not match the saved alert values", - ); - return { - success: true as const, - workflowId: WORKFLOW_ID, - enabled: false, - frequency: 60, - }; - }, - close: api.close, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/api-fixture.ts b/packages/mcp-server-evals/src/code-mode/api-fixture.ts deleted file mode 100644 index 1a62f08ab..000000000 --- a/packages/mcp-server-evals/src/code-mode/api-fixture.ts +++ /dev/null @@ -1,98 +0,0 @@ -import assert from "node:assert/strict"; -import { createServer, type IncomingHttpHeaders } from "node:http"; -import { z } from "zod"; - -export interface FixtureRequest { - method: string; - path: string; - query: string; - status: number; -} - -interface ApiRequest { - method: string; - url: URL; - headers: IncomingHttpHeaders; - body: unknown; -} - -interface ApiReply { - status?: number; - body: unknown; - headers?: Record; -} - -/** Authenticated loopback HTTP boundary; scenario state and grading stay with the fixture. */ -export async function startApiFixture( - accessToken: string, - handle: ( - request: ApiRequest, - ) => ApiReply | undefined | Promise, -) { - const requests: FixtureRequest[] = []; - const server = createServer(async (request, response) => { - const url = new URL(request.url ?? "/", `http://${request.headers.host}`); - const method = request.method ?? "GET"; - let reply: ApiReply; - try { - if (request.headers.authorization !== `Bearer ${accessToken}`) { - reply = { status: 401, body: { detail: "Invalid token" } }; - } else { - const chunks: Buffer[] = []; - let size = 0; - for await (const chunk of request) { - size += chunk.length; - if (size > 256_000) - throw new Error("Fixture request body exceeds its budget"); - chunks.push(chunk); - } - const raw = Buffer.concat(chunks).toString("utf8"); - reply = (await handle({ - method, - url, - headers: request.headers, - body: raw ? JSON.parse(raw) : undefined, - })) ?? { - status: 404, - body: { detail: "Endpoint not implemented in this eval fixture" }, - }; - } - } catch (error) { - reply = - error instanceof z.ZodError || error instanceof SyntaxError - ? { - status: 400, - body: { - detail: "Invalid request", - ...(error instanceof z.ZodError - ? { errors: error.issues } - : {}), - }, - } - : { status: 500, body: { detail: "Fixture internal error" } }; - } - const status = reply.status ?? 200; - requests.push({ method, path: url.pathname, query: url.search, status }); - response.writeHead(status, { - "content-type": "application/json", - ...reply.headers, - }); - response.end(JSON.stringify(reply.body)); - }); - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", resolve); - }); - const address = server.address(); - assert(address && typeof address !== "string"); - return { - baseUrl: `http://127.0.0.1:${address.port}`, - requests, - async close(this: void) { - await new Promise((resolve, reject) => { - server.close((error) => (error ? reject(error) : resolve())); - server.closeAllConnections(); - }); - }, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts b/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts deleted file mode 100644 index ebb56d7a9..000000000 --- a/packages/mcp-server-evals/src/code-mode/claude-agent.test.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { runClaudeAgent } from "./claude-agent"; - -const tools = [ - "mcp__sentry_code_mode__search_sentry_api", - "mcp__sentry_code_mode__execute_sentry_code", -]; -const options = { - serverName: "sentry_code_mode", - toolNames: ["search_sentry_api", "execute_sentry_code"], -}; -const init = { - type: "system", - subtype: "init", - model: "claude-test-alias", - tools, - session_id: "private-session-metadata", - apiKeySource: "private-account-metadata", -}; -const directories: string[] = []; - -async function fakeClaude(events: unknown[], exitCode = 0) { - const directory = await mkdtemp(path.join(tmpdir(), "claude-eval-test-")); - directories.push(directory); - const executable = path.join(directory, "claude"); - // A real process exercises argument boundaries, stdin and JSONL parsing. - // Deliberately omit the final newline to cover the stream's trailing event. - const payload = events.map((event) => JSON.stringify(event)).join("\n"); - await writeFile( - executable, - `#!${process.execPath} -const fs = require("node:fs"); -let input = ""; -process.stdin.setEncoding("utf8"); -process.stdin.on("data", chunk => { input += chunk; }); -process.stdin.on("end", () => { - fs.writeFileSync("capture.json", JSON.stringify({ args: process.argv.slice(2), input })); - const payload = ${JSON.stringify(payload)}; - process.stdout.write(payload.slice(0, 37)); - process.stdout.write(payload.slice(37)); - process.exitCode = ${exitCode}; -}); -`, - ); - await chmod(executable, 0o755); - vi.stubEnv("PATH", `${directory}${path.delimiter}${process.env.PATH ?? ""}`); - return directory; -} - -afterEach(async () => { - vi.unstubAllEnvs(); - await Promise.all( - directories - .splice(0) - .map((directory) => rm(directory, { recursive: true })), - ); -}); - -describe("Claude Code Mode adapter", () => { - it("uses only MCP tools and reports aggregate usage, resolved model and task evidence", async () => { - const usage = { - input_tokens: 100, - output_tokens: 30, - cache_creation_input_tokens: 60, - cache_read_input_tokens: 400, - }; - const resultContent = [{ type: "text", text: '{"copied":2}' }]; - const directory = await fakeClaude([ - init, - { - type: "assistant", - session_id: "private-session-metadata", - message: { - model: "claude-test-resolved", - usage, - content: [ - { - type: "tool_use", - id: "call-1", - name: tools[1], - input: { code: "async () => 2" }, - }, - ], - }, - }, - { - type: "user", - message: { - content: [ - { - type: "tool_result", - tool_use_id: "call-1", - content: resultContent, - is_error: false, - }, - ], - }, - }, - { - type: "assistant", - message: { - model: "claude-test-resolved", - usage, - content: [{ type: "text", text: "Copied both dashboards." }], - }, - }, - { - type: "result", - subtype: "success", - is_error: false, - result: "Copied both dashboards.", - usage, - total_cost_usd: 0.01, - num_turns: 2, - }, - ]); - const result = await runClaudeAgent( - directory, - "http://127.0.0.1:4321/mcp", - "dummy-token", - "Copy two dashboards", - { - ...options, - model: "requested-alias", - systemPrompt: "System instructions", - }, - ); - expect(result).toMatchObject({ - exitCode: 0, - error: undefined, - model: "claude-test-resolved", - advertisedModel: "claude-test-alias", - text: "Copied both dashboards.", - usage, - reportedCostUsd: 0.01, - turns: 2, - mcpCalls: [ - { - id: "call-1", - name: "execute_sentry_code", - input: { code: "async () => 2" }, - }, - ], - mcpErrors: 0, - toolResultBytes: Buffer.byteLength(JSON.stringify(resultContent)), - }); - expect(JSON.stringify(result.events)).not.toContain("private-"); - const capture = JSON.parse( - await readFile(path.join(directory, "capture.json"), "utf8"), - ); - expect(capture.input).toBe("Copy two dashboards"); - const argument = (name: string) => - capture.args[capture.args.indexOf(name) + 1]; - expect(argument("--tools")).toBe(""); - expect(argument("--allowedTools")).toBe(tools.join(",")); - expect(argument("--setting-sources")).toBe(""); - expect(argument("--settings")).toBe('{"disableAllHooks":true}'); - expect(argument("--permission-mode")).toBe("dontAsk"); - expect(argument("--model")).toBe("requested-alias"); - expect(argument("--effort")).toBe("medium"); - expect(argument("--system-prompt")).toBe("System instructions"); - expect(capture.args).toEqual( - expect.arrayContaining([ - "--strict-mcp-config", - "--disable-slash-commands", - "--no-session-persistence", - ]), - ); - expect(capture.args).not.toContain("--bare"); - expect(JSON.parse(argument("--mcp-config"))).toEqual({ - mcpServers: { - sentry_code_mode: { - type: "http", - url: "http://127.0.0.1:4321/mcp", - headers: { Authorization: "Bearer dummy-token" }, - }, - }, - }); - }); - - it("lets Claude select among the full MCP inventory with its default system prompt", async () => { - const names = [ - "search_sentry_tools", - "execute_sentry_tool", - "get_issue_details", - ...options.toolNames, - ]; - const directory = await fakeClaude([ - { ...init, tools: names.map((name) => `mcp__sentry__${name}`) }, - { type: "result", subtype: "success", is_error: false, result: "Done" }, - ]); - const result = await runClaudeAgent( - directory, - "http://localhost/mcp", - "dummy", - "Copy my dashboard", - { - serverName: "sentry", - toolNames: names, - }, - ); - expect(result.error).toBeUndefined(); - const capture = JSON.parse( - await readFile(path.join(directory, "capture.json"), "utf8"), - ); - expect(capture.input).toBe("Copy my dashboard"); - expect(capture.args).not.toContain("--system-prompt"); - expect(capture.args).not.toContain("--append-system-prompt"); - expect(capture.args[capture.args.indexOf("--allowedTools") + 1]).toBe( - names.map((name) => `mcp__sentry__${name}`).join(","), - ); - }); - - it("fails an OAuth error even when the result subtype says success", async () => { - const error = - "Failed to authenticate: OAuth session expired and could not be refreshed"; - const directory = await fakeClaude([ - init, - { - type: "assistant", - message: { - model: "", - content: [{ type: "text", text: error }], - }, - }, - { type: "result", subtype: "success", is_error: true, result: error }, - ]); - const result = await runClaudeAgent( - directory, - "http://localhost/mcp", - "dummy", - "Copy", - options, - ); - expect(result.error).toBe(error); - expect(result.model).toBe("claude-test-alias"); - expect(result.mcpCalls).toEqual([]); - }); - - it.each([ - { label: "advertised", events: [{ ...init, tools: [...tools, "Bash"] }] }, - { - label: "used", - events: [ - init, - { - type: "assistant", - message: { - content: [ - { - type: "tool_use", - id: "outside", - name: "Bash", - input: { command: "echo unexpected" }, - }, - ], - }, - }, - ], - }, - ])( - "rejects tools $label outside the two-tool experiment", - async ({ events }) => { - const directory = await fakeClaude([ - ...events, - { type: "result", subtype: "success", is_error: false, result: "Done" }, - ]); - const result = await runClaudeAgent( - directory, - "http://localhost/mcp", - "dummy", - "Copy", - options, - ); - expect(result.error).toContain("outside the experiment"); - }, - ); -}); diff --git a/packages/mcp-server-evals/src/code-mode/claude-agent.ts b/packages/mcp-server-evals/src/code-mode/claude-agent.ts deleted file mode 100644 index f8b82d630..000000000 --- a/packages/mcp-server-evals/src/code-mode/claude-agent.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { spawn } from "node:child_process"; -import { z } from "zod"; - -interface ClaudeAgentOptions { - toolNames: string[]; - serverName: string; - model?: string; - /** Omit to keep Claude Code's default instructions and let it select tools. */ - systemPrompt?: string; -} - -const usageSchema = z.object({ - input_tokens: z.number(), - output_tokens: z.number(), - cache_creation_input_tokens: z.number().optional(), - cache_read_input_tokens: z.number().optional(), -}); -const contentSchema = z.object({ - type: z.string(), - text: z.string().optional(), - id: z.string().optional(), - name: z.string().optional(), - input: z.unknown().optional(), - tool_use_id: z.string().optional(), - content: z.unknown().optional(), - is_error: z.boolean().optional(), -}); -// Select task evidence without persisting account, session, or machine metadata. -const eventSchema = z.object({ - type: z.string(), - subtype: z.string().optional(), - model: z.string().optional(), - tools: z.array(z.string()).optional(), - message: z - .object({ - model: z.string().optional(), - content: z.array(contentSchema), - }) - .optional(), - is_error: z.boolean().optional(), - result: z.string().optional(), - errors: z.array(z.string()).optional(), - usage: usageSchema.optional(), - total_cost_usd: z.number().optional(), - num_turns: z.number().optional(), - duration_api_ms: z.number().optional(), -}); -type TraceEvent = z.infer; - -/** Fresh authenticated Claude session with the supplied MCP inventory. */ -export async function runClaudeAgent( - directory: string, - mcpUrl: string, - accessToken: string, - prompt: string, - { toolNames, serverName, model, systemPrompt }: ClaudeAgentOptions, -) { - const toolPrefix = `mcp__${serverName}__`; - const allowedTools = toolNames.map((name) => `${toolPrefix}${name}`); - const events: TraceEvent[] = []; - let buffer = ""; - let bytes = 0; - let protocolError: string | undefined; - let processError: string | undefined; - const args = [ - "--print", - "--output-format", - "stream-json", - "--verbose", - "--tools", - "", - "--allowedTools", - allowedTools.join(","), - "--strict-mcp-config", - "--mcp-config", - JSON.stringify({ - mcpServers: { - [serverName]: { - type: "http", - url: mcpUrl, - headers: { Authorization: `Bearer ${accessToken}` }, - }, - }, - }), - "--settings", - JSON.stringify({ disableAllHooks: true }), - "--setting-sources", - "", - "--disable-slash-commands", - "--no-session-persistence", - "--permission-mode", - "dontAsk", - "--effort", - "medium", - "--no-chrome", - ...(systemPrompt === undefined ? [] : ["--system-prompt", systemPrompt]), - ...(model ? ["--model", model] : []), - ]; - const started = performance.now(); - const child = spawn("claude", args, { - cwd: directory, - stdio: ["pipe", "pipe", "pipe"], - signal: AbortSignal.timeout(300_000), - }); - const fail = (error: string) => { - protocolError ??= error; - child.kill(); - }; - const consumeLine = (line: string) => { - if (!line.trim()) return; - let event: TraceEvent; - try { - event = eventSchema.parse(JSON.parse(line)); - } catch { - fail("Claude emitted an invalid JSON event"); - return; - } - events.push(event); - if ( - event.type === "system" && - event.subtype === "init" && - (event.tools?.length !== allowedTools.length || - allowedTools.some((name) => !event.tools?.includes(name))) - ) { - fail( - "Claude advertised tools outside the experiment or omitted its MCP tools", - ); - } - if ( - event.type === "assistant" && - event.message?.content.some( - (block) => - block.type === "tool_use" && !allowedTools.includes(block.name ?? ""), - ) - ) { - fail("Claude used a tool outside the experiment"); - } - }; - child.stdout.setEncoding("utf8"); - child.stdout.on("data", (chunk: string) => { - bytes += Buffer.byteLength(chunk); - if (bytes > 10_000_000) { - fail("Agent trace exceeded 10 MB"); - return; - } - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line of lines) consumeLine(line); - }); - // Drain diagnostics, but do not save potentially identifying CLI metadata. - child.stderr.resume(); - child.stdin.on("error", () => { - processError ??= "Claude closed its input before accepting the prompt"; - }); - child.stdin.end(prompt); - const exitCode = await new Promise((resolve) => { - child.once("error", (error) => { - processError = error.message; - }); - child.once("close", resolve); - }); - consumeLine(buffer); - - const init = events.find( - (event) => event.type === "system" && event.subtype === "init", - ); - const completion = events.findLast((event) => event.type === "result"); - const assistantMessages = events.filter( - (event) => event.type === "assistant", - ); - const calls = new Map( - assistantMessages.flatMap((event) => - (event.message?.content ?? []) - .filter((block) => block.type === "tool_use" && block.id) - .map((block) => [block.id, block] as const), - ), - ); - const results = new Map( - events - .filter((event) => event.type === "user") - .flatMap((event) => - (event.message?.content ?? []) - .filter((block) => block.type === "tool_result" && block.tool_use_id) - .map((block) => [block.tool_use_id, block] as const), - ), - ); - const finalMessage = assistantMessages.findLast((event) => - event.message?.content.some((block) => block.type === "text"), - ); - const resolvedModel = assistantMessages.findLast( - (event) => event.message?.model && event.message.model !== "", - )?.message?.model; - return { - durationMs: Math.round(performance.now() - started), - model: resolvedModel ?? init?.model, - advertisedModel: init?.model, - exitCode, - error: - processError ?? - protocolError ?? - (!init ? "Claude did not advertise its tool inventory" : undefined) ?? - (exitCode !== 0 || - !completion || - completion.subtype !== "success" || - completion.is_error - ? completion?.errors?.join("; ") || - completion?.result || - "Claude did not finish successfully" - : undefined), - text: - completion?.result ?? - finalMessage?.message?.content - .filter((block) => block.type === "text") - .map((block) => block.text) - .join("\n") ?? - "", - // The result is already aggregated; summing message usage double-counts it. - usage: completion?.usage, - reportedCostUsd: completion?.total_cost_usd, - turns: completion?.num_turns, - modelDurationMs: completion?.duration_api_ms, - mcpCalls: [...calls.values()].map(({ id, name, input }) => ({ - id, - name: name?.startsWith(toolPrefix) ? name.slice(toolPrefix.length) : name, - input, - })), - mcpErrors: [...results.values()].filter((result) => result.is_error).length, - toolResultBytes: [...results.values()].reduce( - (sum, result) => - sum + Buffer.byteLength(JSON.stringify(result.content ?? null)), - 0, - ), - events, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts b/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts deleted file mode 100644 index bd83b6246..000000000 --- a/packages/mcp-server-evals/src/code-mode/codex-agent.test.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { runCodexAgent } from "./codex-agent"; - -const directories: string[] = []; -const options = { model: "test-model", systemPrompt: "Use the MCP tools." }; - -async function fakeCodex(events: unknown[], closeInput = false) { - const directory = await mkdtemp(path.join(tmpdir(), "codex-eval-test-")); - directories.push(directory); - const executable = path.join(directory, "codex"); - // Exercise the real process boundary, including a final event without a newline. - const payload = events.map((event) => JSON.stringify(event)).join("\n"); - await writeFile( - executable, - `#!${process.execPath} -const fs = require("node:fs"); -if (${closeInput}) { - fs.closeSync(0); - setTimeout(() => process.exit(1), 20); -} else { - let input = ""; - process.stdin.setEncoding("utf8"); - process.stdin.on("data", chunk => { input += chunk; }); - process.stdin.on("end", () => { - fs.writeFileSync("capture.json", JSON.stringify({ args: process.argv.slice(2), input })); - const payload = ${JSON.stringify(payload)}; - process.stdout.write(payload.slice(0, 37)); - process.stdout.write(payload.slice(37)); - }); -} -`, - ); - await chmod(executable, 0o755); - vi.stubEnv("PATH", `${directory}${path.delimiter}${process.env.PATH ?? ""}`); - return directory; -} - -afterEach(async () => { - vi.unstubAllEnvs(); - await Promise.all( - directories - .splice(0) - .map((directory) => rm(directory, { recursive: true })), - ); -}); - -describe("Codex Code Mode adapter", () => { - it("keeps the final event and aggregate usage without a trailing newline", async () => { - const usage = { input_tokens: 100, output_tokens: 20 }; - const directory = await fakeCodex([ - { - type: "item.completed", - session_id: "private-session-metadata", - item: { - id: "call-1", - type: "mcp_tool_call", - server: "sentry_code_mode", - tool: "execute_sentry_code", - arguments: { code: "async () => 2" }, - status: "completed", - result: { content: [{ type: "text", text: "2" }] }, - }, - }, - { - type: "item.completed", - item: { - id: "answer", - type: "agent_message", - text: "Copied both dashboards.", - }, - }, - { type: "turn.completed", usage }, - ]); - const result = await runCodexAgent( - directory, - "http://127.0.0.1:4321/mcp", - "dummy-token", - "Copy two dashboards", - options, - ); - expect(result).toMatchObject({ - exitCode: 0, - error: undefined, - text: "Copied both dashboards.", - usage, - mcpCalls: [{ id: "call-1", name: "execute_sentry_code" }], - }); - expect(JSON.stringify(result.events)).not.toContain("private-"); - const capture = JSON.parse( - await readFile(path.join(directory, "capture.json"), "utf8"), - ); - expect(capture.input).toBe("Copy two dashboards"); - expect(capture.args).toContain( - 'developer_instructions="Use the MCP tools."', - ); - expect(capture.args).toContain("--ignore-user-config"); - }); - - it("reports a closed input instead of crashing the evaluation process", async () => { - const directory = await fakeCodex([], true); - const result = await runCodexAgent( - directory, - "http://localhost/mcp", - "dummy-token", - "x".repeat(2_000_000), - options, - ); - expect(result.error).toBe( - "Codex closed its input before accepting the prompt", - ); - }); - - it("rejects an out-of-scope tool even when it is the final event", async () => { - const directory = await fakeCodex([ - { type: "turn.completed", usage: { input_tokens: 1, output_tokens: 1 } }, - { - type: "item.completed", - item: { id: "shell", type: "command_execution" }, - }, - ]); - const result = await runCodexAgent( - directory, - "http://localhost/mcp", - "dummy-token", - "Copy", - options, - ); - expect(result.error).toBe("Agent used a tool outside the experiment"); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/codex-agent.ts b/packages/mcp-server-evals/src/code-mode/codex-agent.ts deleted file mode 100644 index 38cfe86f3..000000000 --- a/packages/mcp-server-evals/src/code-mode/codex-agent.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { spawn } from "node:child_process"; -import { z } from "zod"; - -const toolNames = ["search_sentry_api", "execute_sentry_code"]; - -const usageSchema = z.object({ - input_tokens: z.number(), - output_tokens: z.number(), - cached_input_tokens: z.number().optional(), - cache_write_input_tokens: z.number().optional(), - reasoning_output_tokens: z.number().optional(), -}); -const itemSchema = z.object({ - id: z.string(), - type: z.string(), - server: z.string().optional(), - tool: z.string().optional(), - arguments: z.unknown().optional(), - result: z - .object({ - content: z.array(z.unknown()), - structured_content: z.unknown().optional(), - }) - .nullable() - .optional(), - error: z.object({ message: z.string() }).nullable().optional(), - status: z.string().optional(), - text: z.string().optional(), -}); -const eventSchema = z.object({ - type: z.string(), - item: itemSchema.optional(), - usage: usageSchema.optional(), - error: z.object({ message: z.string() }).optional(), - message: z.string().optional(), -}); -type TraceEvent = z.infer; - -/** Fresh CLI session; reject runs that use anything besides the two MCP tools. */ -export async function runCodexAgent( - directory: string, - mcpUrl: string, - accessToken: string, - prompt: string, - { model, systemPrompt }: { model: string; systemPrompt: string }, -) { - const events: TraceEvent[] = []; - let buffer = ""; - let bytes = 0; - let stderr = ""; - let protocolError: string | undefined; - let processError: string | undefined; - const config = { - approval_policy: "never", - developer_instructions: systemPrompt, - project_doc_max_bytes: 0, - "skills.include_instructions": false, - web_search: "disabled", - model_reasoning_effort: "medium", - "tools.experimental_request_user_input.enabled": false, - "mcp_servers.sentry_code_mode.url": mcpUrl, - "mcp_servers.sentry_code_mode.http_headers.Authorization": `Bearer ${accessToken}`, - "mcp_servers.sentry_code_mode.enabled_tools": toolNames, - "mcp_servers.sentry_code_mode.required": true, - "mcp_servers.sentry_code_mode.default_tools_approval_mode": "approve", - }; - const disabled = [ - "shell_tool", - "apps", - "plugins", - "hooks", - "memories", - "browser_use", - "computer_use", - "image_generation", - "multi_agent", - "code_mode", - "tool_suggest", - ]; - const args = [ - "exec", - "--json", - "--ephemeral", - "--ignore-user-config", - "--ignore-rules", - "--skip-git-repo-check", - "--sandbox", - "read-only", - "--model", - model, - ...disabled.flatMap((feature) => ["--disable", feature]), - ...Object.entries(config).flatMap(([key, value]) => [ - "-c", - `${key}=${JSON.stringify(value)}`, - ]), - "-", - ]; - const started = performance.now(); - const child = spawn("codex", args, { - cwd: directory, - stdio: ["pipe", "pipe", "pipe"], - signal: AbortSignal.timeout(300_000), - }); - const consumeLine = (line: string) => { - if (!line.trim()) return; - try { - // Keep task evidence, not session/account/machine metadata. - events.push(eventSchema.parse(JSON.parse(line))); - } catch { - protocolError = "Agent emitted an invalid JSON event"; - } - }; - child.stdout.setEncoding("utf8"); - child.stderr.setEncoding("utf8"); - child.stdout.on("data", (chunk: string) => { - bytes += Buffer.byteLength(chunk); - if (bytes > 10_000_000) { - protocolError = "Agent trace exceeded 10 MB"; - child.kill(); - return; - } - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line of lines) consumeLine(line); - }); - child.stderr.on("data", (chunk: string) => { - stderr = (stderr + chunk).slice(-4_000); - }); - child.stdin.on("error", () => { - processError ??= "Codex closed its input before accepting the prompt"; - }); - child.stdin.end(prompt); - const exitCode = await new Promise((resolve) => { - child.once("error", (error) => { - processError = error.message; - }); - child.once("close", resolve); - }); - consumeLine(buffer); - const completion = events.findLast( - (event) => event.type === "turn.completed", - ); - const items = events.flatMap((event) => - event.type === "item.completed" && event.item ? [event.item] : [], - ); - const calls = items.filter((item) => item.type === "mcp_tool_call"); - const unexpected = items.filter( - (item) => - !["mcp_tool_call", "agent_message", "reasoning", "error"].includes( - item.type, - ) || - (item.type === "mcp_tool_call" && - (item.server !== "sentry_code_mode" || - !toolNames.includes(item.tool ?? ""))), - ); - return { - durationMs: Math.round(performance.now() - started), - model, - exitCode, - error: - processError ?? - protocolError ?? - (unexpected.length - ? "Agent used a tool outside the experiment" - : undefined) ?? - (exitCode !== 0 || !completion - ? events.findLast((event) => event.error)?.error?.message || - stderr || - "Agent did not finish successfully" - : undefined), - text: items.findLast((item) => item.type === "agent_message")?.text ?? "", - usage: completion?.usage, - mcpCalls: calls.map(({ id, tool, arguments: input }) => ({ - id, - name: tool, - input, - })), - mcpErrors: calls.filter((call) => call.error || call.status === "failed") - .length, - toolResultBytes: calls.reduce( - (sum, call) => - sum + Buffer.byteLength(JSON.stringify(call.result ?? null)), - 0, - ), - events, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json b/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json deleted file mode 100644 index 361dcc629..000000000 --- a/packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json +++ /dev/null @@ -1,2307 +0,0 @@ -{ - "provenance": { - "openapiCommit": "803d6de98c9156c15e9ddc1bf9accbac6e24cebc", - "backendCommit": "c4b7b5f3502b7f7c2ae8edc9ae1da36c8a1c98a4", - "sourceDashboard": "packages/mcp-server-mocks/src/fixtures/dashboard-details.json" - }, - "sourceDashboard": { - "id": "101", - "title": "Errors Overview", - "dateCreated": "2025-04-14T10:15:00.000Z", - "createdBy": { - "id": "1", - "name": "Test User", - "email": "user@example.com" - }, - "widgets": [ - { - "id": "201", - "title": "Handled Errors", - "description": "Handled errors over time", - "displayType": "line", - "thresholds": null, - "interval": "5m", - "dateCreated": "2025-04-14T10:16:00.000Z", - "dashboardId": "101", - "queries": [ - { - "id": "301", - "name": "", - "fields": ["count()"], - "aggregates": ["count()"], - "columns": [], - "fieldAliases": [], - "conditions": "error.handled:true", - "orderby": "-count()", - "widgetId": "201", - "onDemand": [], - "isHidden": false, - "selectedAggregate": 0, - "linkedDashboards": [] - } - ], - "limit": null, - "widgetType": "error-events", - "layout": { - "x": 0, - "y": 0, - "w": 6, - "h": 2, - "minH": 2 - }, - "axisRange": null, - "legendType": null, - "datasetSource": "user", - "changedReason": null - }, - { - "id": "202", - "title": "Top Issues", - "description": null, - "displayType": "table", - "thresholds": null, - "interval": "5m", - "dateCreated": "2025-04-14T10:18:00.000Z", - "dashboardId": "101", - "queries": [ - { - "id": "302", - "name": "Top Issues", - "fields": ["issue", "count()"], - "aggregates": ["count()"], - "columns": ["issue"], - "fieldAliases": [], - "conditions": "is:unresolved", - "orderby": "-count()", - "widgetId": "202", - "onDemand": [], - "isHidden": false, - "selectedAggregate": 0, - "linkedDashboards": [] - } - ], - "limit": 5, - "widgetType": "error-events", - "layout": { - "x": 0, - "y": 2, - "w": 6, - "h": 3, - "minH": 2 - }, - "axisRange": null, - "legendType": null, - "datasetSource": "user", - "changedReason": null - } - ], - "filters": { - "release": ["frontend@1.2.3"] - }, - "permissions": { - "isEditableByEveryone": true, - "teamsWithEditAccess": [] - }, - "isFavorited": true, - "projects": [101, 102], - "environment": ["production"], - "period": "24h", - "utc": true, - "expired": false, - "prebuiltId": null - }, - "openapi": { - "openapi": "3.0.3", - "info": { - "title": "Sentry Dashboard API eval fixture", - "version": "v0" - }, - "paths": { - "/api/0/organizations/{organization_id_or_slug}/dashboards/": { - "get": { - "operationId": "listOrganizationDashboards", - "description": "Retrieve a list of custom dashboards that are associated with the given organization.", - "summary": "List an Organization's Custom Dashboards", - "parameters": [ - { - "in": "path", - "name": "organization_id_or_slug", - "schema": { - "type": "string" - }, - "description": "The ID or slug of the organization the resource belongs to.", - "required": true - }, - { - "in": "query", - "name": "filter", - "schema": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "excludeFavorites", - "excludePrebuilt", - "onlyFavorites", - "onlyPrebuilt", - "owned", - "shared", - "showHidden" - ] - } - }, - "description": "Filter the dashboards returned. Repeat this parameter to apply multiple filters." - }, - { - "in": "query", - "name": "query", - "schema": { - "type": "string" - }, - "description": "Filter dashboards by title." - }, - { - "in": "query", - "name": "sort", - "schema": { - "type": "string" - }, - "description": "The property to sort results by. Prefix the value with `-` to sort in descending order.\n\nAvailable fields are:\n- `title`\n- `dateCreated`\n- `mostPopular`\n- `recentlyViewed`\n- `mydashboards`\n- `myDashboardsAndRecentlyViewed`\n" - }, - { - "in": "query", - "name": "pin", - "schema": { - "type": "string", - "enum": ["favorites"] - }, - "description": "Pin favorited dashboards to the top of the results." - }, - { - "in": "query", - "name": "per_page", - "schema": { - "type": "integer" - }, - "description": "Limit the number of rows to return in the result. Default and maximum allowed is 100." - }, - { - "in": "query", - "name": "cursor", - "schema": { - "type": "string", - "minLength": 1 - }, - "description": "A pointer to the last object fetched and its sort order; used to retrieve the next or previous results.", - "x-learn-more": "https://docs.sentry.io/api/pagination/" - } - ], - "tags": ["Dashboards"], - "security": [ - { - "auth_token": ["org:admin", "org:read", "org:write"] - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "dateCreated": { - "type": "string" - }, - "createdBy": { - "type": "object", - "properties": { - "identities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "organization": { - "type": "object", - "properties": { - "slug": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["name", "slug"] - }, - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["id", "name"] - }, - "dateVerified": { - "type": "string", - "format": "date-time" - }, - "dateSynced": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "dateSynced", - "dateVerified", - "id", - "name", - "organization", - "provider" - ] - } - }, - "avatar": { - "type": "object", - "properties": { - "avatarType": { - "type": "string" - }, - "avatarUuid": { - "type": "string", - "nullable": true - }, - "avatarUrl": { - "type": "string", - "nullable": true - } - } - }, - "authenticators": { - "type": "array", - "items": {} - }, - "canReset2fa": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "username": { - "type": "string" - }, - "email": { - "type": "string" - }, - "avatarUrl": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "isSuspended": { - "type": "boolean" - }, - "hasPasswordAuth": { - "type": "boolean" - }, - "isManaged": { - "type": "boolean" - }, - "dateJoined": { - "type": "string", - "format": "date-time" - }, - "lastLogin": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "has2fa": { - "type": "boolean" - }, - "lastActive": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "isSuperuser": { - "type": "boolean" - }, - "isStaff": { - "type": "boolean" - }, - "experiments": { - "type": "object", - "additionalProperties": {} - }, - "emails": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "is_verified": { - "type": "boolean" - } - }, - "required": ["email", "id", "is_verified"] - } - } - }, - "required": [ - "avatarUrl", - "dateJoined", - "email", - "emails", - "experiments", - "has2fa", - "hasPasswordAuth", - "id", - "isActive", - "isManaged", - "isStaff", - "isSuperuser", - "isSuspended", - "lastActive", - "lastLogin", - "name", - "username" - ] - }, - "environment": { - "type": "array", - "items": { - "type": "string" - } - }, - "filters": { - "type": "object", - "properties": { - "release": { - "type": "array", - "items": { - "type": "string" - } - }, - "releaseId": { - "type": "array", - "items": { - "type": "string" - } - }, - "globalFilter": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": {} - } - } - } - }, - "lastVisited": { - "type": "string", - "nullable": true - }, - "widgetDisplay": { - "type": "array", - "items": { - "type": "string" - } - }, - "widgetPreview": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "permissions": { - "type": "object", - "properties": { - "isEditableByEveryone": { - "type": "boolean" - }, - "teamsWithEditAccess": { - "type": "array", - "items": { - "type": "integer" - } - } - }, - "required": [ - "isEditableByEveryone", - "teamsWithEditAccess" - ], - "nullable": true - }, - "isFavorited": { - "type": "boolean" - }, - "projects": { - "type": "array", - "items": { - "type": "integer" - } - }, - "prebuiltId": { - "type": "integer", - "nullable": true - } - }, - "required": [ - "createdBy", - "dateCreated", - "environment", - "filters", - "id", - "isFavorited", - "lastVisited", - "permissions", - "prebuiltId", - "projects", - "title", - "widgetDisplay", - "widgetPreview" - ] - } - }, - "examples": { - "QueryDashboards": { - "value": [ - { - "id": "1", - "title": "Dashboard", - "dateCreated": "2024-06-20T14:38:03.498574Z", - "lastVisited": "2024-06-20T14:38:03.498574Z", - "projects": [1], - "environment": ["alpha"], - "filters": { - "release": [ - "frontend@a02311a400636ff9640b3e4ca2991ee153dbbdcc", - "frontend@36934c05140c16df93aa8ebf671f9386e916b501" - ] - }, - "createdBy": { - "id": "1", - "name": "Admin", - "username": "admin", - "email": "admin@example.com", - "avatarUrl": "www.example.com", - "isActive": true, - "isSuspended": false, - "hasPasswordAuth": true, - "isManaged": false, - "dateJoined": "2021-10-25T17:07:33.190596Z", - "lastLogin": "2024-07-16T15:28:39.261659Z", - "has2fa": true, - "lastActive": "2024-07-16T20:45:49.364197Z", - "isSuperuser": false, - "isStaff": false, - "experiments": {}, - "emails": [ - { - "id": "1", - "email": "admin@example.com", - "is_verified": true - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null, - "avatarUrl": "www.example.com" - } - }, - "widgetDisplay": [], - "widgetPreview": [], - "permissions": { - "isEditableByEveryone": true, - "teamsWithEditAccess": [] - }, - "isFavorited": false, - "prebuiltId": null - }, - { - "id": "2", - "title": "Dashboard", - "dateCreated": "2024-06-20T14:38:03.498574Z", - "lastVisited": "2024-06-20T14:38:03.498574Z", - "projects": [], - "environment": ["alpha"], - "filters": { - "release": [ - "frontend@a02311a400636ff9640b3e4ca2991ee153dbbdcc", - "frontend@36934c05140c16df93aa8ebf671f9386e916b501" - ] - }, - "createdBy": { - "id": "1", - "name": "Admin", - "username": "admin", - "email": "admin@example.com", - "avatarUrl": "www.example.com", - "isActive": true, - "isSuspended": false, - "hasPasswordAuth": true, - "isManaged": false, - "dateJoined": "2021-10-25T17:07:33.190596Z", - "lastLogin": "2024-07-16T15:28:39.261659Z", - "has2fa": true, - "lastActive": "2024-07-16T20:45:49.364197Z", - "isSuperuser": false, - "isStaff": false, - "experiments": {}, - "emails": [ - { - "id": "1", - "email": "admin@example.com", - "is_verified": true - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null, - "avatarUrl": "www.example.com" - } - }, - "widgetDisplay": [], - "widgetPreview": [], - "permissions": null, - "isFavorited": false, - "prebuiltId": null - } - ], - "summary": "Query Dashboards" - } - } - } - }, - "description": "" - }, - "400": { - "description": "Bad Request" - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "Not Found" - } - } - }, - "post": { - "operationId": "createOrganizationDashboard", - "description": "Create a new dashboard for the given Organization", - "summary": "Create a New Dashboard for an Organization", - "parameters": [ - { - "in": "path", - "name": "organization_id_or_slug", - "schema": { - "type": "string" - }, - "description": "The ID or slug of the organization the resource belongs to.", - "required": true - } - ], - "tags": ["Dashboards"], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "title": { - "type": "string", - "description": "The user defined title for this dashboard.", - "maxLength": 255 - }, - "id": { - "type": "string", - "description": "A dashboard's unique id." - }, - "widgets": { - "type": "array", - "items": { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string", - "maxLength": 255 - }, - "description": { - "type": "string", - "nullable": true - }, - "thresholds": { - "type": "object", - "additionalProperties": {}, - "nullable": true - }, - "display_type": { - "enum": [ - "line", - "area", - "bar", - "table", - "big_number", - "details", - "categorical_bar", - "wheel", - "rage_and_dead_clicks", - "server_tree", - "text", - "agents_traces_table", - "heatmap" - ], - "type": "string", - "description": "* `line`\n* `area`\n* `bar`\n* `table`\n* `big_number`\n* `details`\n* `categorical_bar`\n* `wheel`\n* `rage_and_dead_clicks`\n* `server_tree`\n* `text`\n* `agents_traces_table`\n* `heatmap`" - }, - "interval": { - "type": "string", - "maxLength": 10 - }, - "queries": { - "type": "array", - "items": { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "id": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - }, - "aggregates": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "columns": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "field_aliases": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "name": { - "type": "string" - }, - "conditions": { - "type": "string" - }, - "orderby": { - "type": "string" - }, - "is_hidden": { - "type": "boolean" - }, - "on_demand_extraction": { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "extraction_state": { - "type": "string" - }, - "enabled": { - "type": "boolean" - } - } - }, - "on_demand_extraction_disabled": { - "type": "boolean" - }, - "selected_aggregate": { - "type": "integer", - "nullable": true - }, - "linked_dashboards": { - "type": "array", - "items": { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "field": { - "type": "string" - }, - "dashboard_id": { - "type": "string" - } - }, - "required": ["dashboard_id", "field"] - }, - "nullable": true - } - } - } - }, - "widget_type": { - "enum": [ - "discover", - "issue", - "metrics", - "error-events", - "transaction-like", - "spans", - "logs", - "tracemetrics", - "preprod-app-size", - null - ], - "type": "string", - "description": "* `discover`\n* `issue`\n* `metrics`\n* `error-events`\n* `transaction-like`\n* `spans`\n* `logs`\n* `tracemetrics`\n* `preprod-app-size`", - "nullable": true - }, - "limit": { - "type": "integer", - "minimum": 1, - "nullable": true - }, - "layout": { - "allOf": [ - { - "type": "object", - "description": "Widget grid layout position and dimensions for dashboard creation.\n\nThe dashboard uses a 6-column grid. Required keys: x, y, w, h.\nConstraints: x (0-5), y (>= 0), w (1-6), h (>= 1), and x + w <= 6.", - "properties": { - "x": { - "type": "integer", - "maximum": 5, - "minimum": 0, - "description": "Column position (0-indexed)." - }, - "y": { - "type": "integer", - "minimum": 0, - "description": "Row position (0-indexed)." - }, - "w": { - "type": "integer", - "maximum": 6, - "minimum": 1, - "description": "Width in grid columns (1-6)." - }, - "h": { - "type": "integer", - "minimum": 1, - "description": "Height in grid rows." - } - }, - "required": ["h", "w", "x", "y"] - } - ], - "nullable": true - }, - "axis_range": { - "enum": ["auto", "dataMin", null], - "type": "string", - "description": "* `auto`\n* `dataMin`", - "nullable": true - }, - "legend_type": { - "enum": ["default", "breakdown", null], - "type": "string", - "description": "* `default`\n* `breakdown`", - "nullable": true - } - } - }, - "description": "A json list of widgets saved in this dashboard." - }, - "projects": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "The saved projects filter for this dashboard." - }, - "environment": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true, - "description": "The saved environment filter for this dashboard." - }, - "period": { - "type": "string", - "nullable": true, - "description": "The saved time range period for this dashboard." - }, - "start": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "The saved start time for this dashboard." - }, - "end": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "The saved end time for this dashboard." - }, - "filters": { - "type": "object", - "additionalProperties": {}, - "description": "The saved filters for this dashboard." - }, - "utc": { - "type": "boolean", - "description": "Setting that lets you display saved time range for this dashboard in UTC." - }, - "permissions": { - "allOf": [ - { - "type": "object", - "description": "Allows parameters to be defined in snake case, but passed as camel case.\n\nErrors are output in camel case.", - "properties": { - "is_editable_by_everyone": { - "type": "boolean", - "description": "Whether the dashboard is editable by everyone." - }, - "teams_with_edit_access": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "List of team IDs that have edit access to a dashboard." - } - }, - "required": ["is_editable_by_everyone"] - } - ], - "nullable": true, - "description": "Permissions that restrict users from editing dashboards" - }, - "is_favorited": { - "type": "boolean", - "default": false, - "description": "Favorite the dashboard automatically for the request user" - } - }, - "required": ["title"] - } - } - }, - "required": true - }, - "security": [ - { - "auth_token": ["org:admin", "org:read", "org:write"] - } - ], - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "environment": { - "type": "array", - "items": { - "type": "string" - } - }, - "period": { - "type": "string" - }, - "utc": { - "type": "string" - }, - "expired": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "dateCreated": { - "type": "string" - }, - "createdBy": { - "type": "object", - "properties": { - "identities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "organization": { - "type": "object", - "properties": { - "slug": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["name", "slug"] - }, - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["id", "name"] - }, - "dateVerified": { - "type": "string", - "format": "date-time" - }, - "dateSynced": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "dateSynced", - "dateVerified", - "id", - "name", - "organization", - "provider" - ] - } - }, - "avatar": { - "type": "object", - "properties": { - "avatarType": { - "type": "string" - }, - "avatarUuid": { - "type": "string", - "nullable": true - }, - "avatarUrl": { - "type": "string", - "nullable": true - } - } - }, - "authenticators": { - "type": "array", - "items": {} - }, - "canReset2fa": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "username": { - "type": "string" - }, - "email": { - "type": "string" - }, - "avatarUrl": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "isSuspended": { - "type": "boolean" - }, - "hasPasswordAuth": { - "type": "boolean" - }, - "isManaged": { - "type": "boolean" - }, - "dateJoined": { - "type": "string", - "format": "date-time" - }, - "lastLogin": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "has2fa": { - "type": "boolean" - }, - "lastActive": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "isSuperuser": { - "type": "boolean" - }, - "isStaff": { - "type": "boolean" - }, - "experiments": { - "type": "object", - "additionalProperties": {} - }, - "emails": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "is_verified": { - "type": "boolean" - } - }, - "required": ["email", "id", "is_verified"] - } - } - }, - "required": [ - "avatarUrl", - "dateJoined", - "email", - "emails", - "experiments", - "has2fa", - "hasPasswordAuth", - "id", - "isActive", - "isManaged", - "isStaff", - "isSuperuser", - "isSuspended", - "lastActive", - "lastLogin", - "name", - "username" - ], - "nullable": true - }, - "widgets": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string", - "nullable": true - }, - "displayType": { - "type": "string" - }, - "thresholds": { - "type": "object", - "properties": { - "preferredPolarity": { - "type": "string" - }, - "timeWindow": { - "type": "string" - }, - "max_values": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "unit": { - "type": "string" - } - }, - "required": ["max_values", "unit"], - "nullable": true - }, - "interval": { - "type": "string" - }, - "dateCreated": { - "type": "string" - }, - "dashboardId": { - "type": "string" - }, - "queries": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - }, - "aggregates": { - "type": "array", - "items": { - "type": "string" - } - }, - "columns": { - "type": "array", - "items": { - "type": "string" - } - }, - "fieldAliases": { - "type": "array", - "items": { - "type": "string" - } - }, - "conditions": { - "type": "string" - }, - "orderby": { - "type": "string" - }, - "widgetId": { - "type": "string" - }, - "onDemand": { - "type": "array", - "items": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "extractionState": { - "type": "string" - }, - "dashboardWidgetQueryId": { - "type": "integer" - } - }, - "required": [ - "dashboardWidgetQueryId", - "enabled", - "extractionState" - ] - } - }, - "isHidden": { - "type": "boolean" - }, - "selectedAggregate": { - "type": "integer", - "nullable": true - }, - "linkedDashboards": { - "type": "array", - "items": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "dashboardId": { - "type": "integer" - } - }, - "required": ["dashboardId", "field"] - } - } - }, - "required": [ - "aggregates", - "columns", - "conditions", - "fieldAliases", - "fields", - "id", - "isHidden", - "linkedDashboards", - "name", - "onDemand", - "orderby", - "selectedAggregate", - "widgetId" - ] - } - }, - "limit": { - "type": "integer", - "nullable": true - }, - "widgetType": { - "type": "string", - "nullable": true - }, - "layout": { - "type": "object", - "additionalProperties": { - "type": "integer" - }, - "nullable": true - }, - "axisRange": { - "type": "string", - "nullable": true - }, - "legendType": { - "enum": ["default", "breakdown", null], - "type": "string", - "nullable": true - }, - "datasetSource": { - "type": "string", - "nullable": true - }, - "exploreUrls": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "changedReason": { - "type": "array", - "items": { - "type": "object", - "properties": { - "orderby": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "nullable": true - }, - "equations": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - }, - "nullable": true - }, - "selected_columns": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "equations", - "orderby", - "selected_columns" - ] - }, - "nullable": true - } - }, - "required": [ - "axisRange", - "changedReason", - "dashboardId", - "datasetSource", - "dateCreated", - "description", - "displayType", - "exploreUrls", - "id", - "interval", - "layout", - "legendType", - "limit", - "queries", - "thresholds", - "title", - "widgetType" - ] - } - }, - "projects": { - "type": "array", - "items": { - "type": "integer" - } - }, - "filters": { - "type": "object", - "properties": { - "release": { - "type": "array", - "items": { - "type": "string" - } - }, - "releaseId": { - "type": "array", - "items": { - "type": "string" - } - }, - "globalFilter": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": {} - } - } - } - }, - "permissions": { - "type": "object", - "properties": { - "isEditableByEveryone": { - "type": "boolean" - }, - "teamsWithEditAccess": { - "type": "array", - "items": { - "type": "integer" - } - } - }, - "required": [ - "isEditableByEveryone", - "teamsWithEditAccess" - ], - "nullable": true - }, - "isFavorited": { - "type": "boolean" - }, - "prebuiltId": { - "type": "integer", - "nullable": true - } - }, - "required": [ - "createdBy", - "dateCreated", - "filters", - "id", - "isFavorited", - "permissions", - "prebuiltId", - "projects", - "title", - "widgets" - ] - }, - "examples": { - "CreateDashboard": { - "value": { - "id": "1", - "title": "Dashboard", - "dateCreated": "2024-06-20T14:38:03.498574Z", - "createdBy": { - "id": "1", - "name": "Admin", - "username": "admin", - "email": "admin@example.com", - "avatarUrl": "www.example.com", - "isActive": true, - "isSuspended": false, - "hasPasswordAuth": true, - "isManaged": false, - "dateJoined": "2021-10-25T17:07:33.190596Z", - "lastLogin": "2024-07-16T15:28:39.261659Z", - "has2fa": true, - "lastActive": "2024-07-16T20:45:49.364197Z", - "isSuperuser": false, - "isStaff": false, - "experiments": {}, - "emails": [ - { - "id": "1", - "email": "admin@example.com", - "is_verified": true - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null, - "avatarUrl": "www.example.com" - } - }, - "widgets": [ - { - "id": "658714", - "title": "Custom Widget", - "description": null, - "displayType": "table", - "thresholds": null, - "interval": "5m", - "dateCreated": "2024-07-16T15:36:46.048343Z", - "dashboardId": "1", - "datasetSource": "user", - "queries": [ - { - "id": "1", - "name": "", - "fields": [ - "avg(transaction.duration)", - "transaction" - ], - "aggregates": ["avg(transaction.duration)"], - "columns": ["transaction"], - "fieldAliases": ["", ""], - "conditions": "", - "orderby": "-avg(transaction.duration)", - "widgetId": "1", - "onDemand": [ - { - "enabled": false, - "extractionState": "disabled:not-applicable", - "dashboardWidgetQueryId": 1 - } - ], - "isHidden": false, - "selectedAggregate": null, - "linkedDashboards": [] - } - ], - "limit": null, - "widgetType": "transaction-like", - "layout": { - "w": 2, - "y": 0, - "h": 2, - "minH": 2, - "x": 0 - }, - "exploreUrls": null, - "changedReason": null, - "axisRange": null, - "legendType": "default" - } - ], - "projects": [1], - "filters": {}, - "environment": ["alpha"], - "period": "7d", - "permissions": { - "isEditableByEveryone": true, - "teamsWithEditAccess": [] - }, - "isFavorited": false, - "prebuiltId": null - }, - "summary": "Create Dashboard" - } - } - } - }, - "description": "" - }, - "400": { - "description": "Bad Request" - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "Not Found" - }, - "409": { - "description": "Conflict" - } - } - } - }, - "/api/0/organizations/{organization_id_or_slug}/dashboards/{dashboard_id}/": { - "get": { - "operationId": "getOrganizationDashboard", - "description": "Return details about an organization's custom dashboard.", - "summary": "Retrieve an Organization's Custom Dashboard", - "parameters": [ - { - "in": "path", - "name": "organization_id_or_slug", - "schema": { - "type": "string" - }, - "description": "The ID or slug of the organization the resource belongs to.", - "required": true - }, - { - "in": "path", - "name": "dashboard_id", - "schema": { - "type": "integer" - }, - "description": "The ID of the dashboard you'd like to retrieve.", - "required": true - } - ], - "tags": ["Dashboards"], - "security": [ - { - "auth_token": ["org:admin", "org:read", "org:write"] - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "environment": { - "type": "array", - "items": { - "type": "string" - } - }, - "period": { - "type": "string" - }, - "utc": { - "type": "string" - }, - "expired": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "dateCreated": { - "type": "string" - }, - "createdBy": { - "type": "object", - "properties": { - "identities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "organization": { - "type": "object", - "properties": { - "slug": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["name", "slug"] - }, - "provider": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": ["id", "name"] - }, - "dateVerified": { - "type": "string", - "format": "date-time" - }, - "dateSynced": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "dateSynced", - "dateVerified", - "id", - "name", - "organization", - "provider" - ] - } - }, - "avatar": { - "type": "object", - "properties": { - "avatarType": { - "type": "string" - }, - "avatarUuid": { - "type": "string", - "nullable": true - }, - "avatarUrl": { - "type": "string", - "nullable": true - } - } - }, - "authenticators": { - "type": "array", - "items": {} - }, - "canReset2fa": { - "type": "boolean" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "username": { - "type": "string" - }, - "email": { - "type": "string" - }, - "avatarUrl": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "isSuspended": { - "type": "boolean" - }, - "hasPasswordAuth": { - "type": "boolean" - }, - "isManaged": { - "type": "boolean" - }, - "dateJoined": { - "type": "string", - "format": "date-time" - }, - "lastLogin": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "has2fa": { - "type": "boolean" - }, - "lastActive": { - "type": "string", - "format": "date-time", - "nullable": true - }, - "isSuperuser": { - "type": "boolean" - }, - "isStaff": { - "type": "boolean" - }, - "experiments": { - "type": "object", - "additionalProperties": {} - }, - "emails": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "email": { - "type": "string" - }, - "is_verified": { - "type": "boolean" - } - }, - "required": ["email", "id", "is_verified"] - } - } - }, - "required": [ - "avatarUrl", - "dateJoined", - "email", - "emails", - "experiments", - "has2fa", - "hasPasswordAuth", - "id", - "isActive", - "isManaged", - "isStaff", - "isSuperuser", - "isSuspended", - "lastActive", - "lastLogin", - "name", - "username" - ], - "nullable": true - }, - "widgets": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string", - "nullable": true - }, - "displayType": { - "type": "string" - }, - "thresholds": { - "type": "object", - "properties": { - "preferredPolarity": { - "type": "string" - }, - "timeWindow": { - "type": "string" - }, - "max_values": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "unit": { - "type": "string" - } - }, - "required": ["max_values", "unit"], - "nullable": true - }, - "interval": { - "type": "string" - }, - "dateCreated": { - "type": "string" - }, - "dashboardId": { - "type": "string" - }, - "queries": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "fields": { - "type": "array", - "items": { - "type": "string" - } - }, - "aggregates": { - "type": "array", - "items": { - "type": "string" - } - }, - "columns": { - "type": "array", - "items": { - "type": "string" - } - }, - "fieldAliases": { - "type": "array", - "items": { - "type": "string" - } - }, - "conditions": { - "type": "string" - }, - "orderby": { - "type": "string" - }, - "widgetId": { - "type": "string" - }, - "onDemand": { - "type": "array", - "items": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "extractionState": { - "type": "string" - }, - "dashboardWidgetQueryId": { - "type": "integer" - } - }, - "required": [ - "dashboardWidgetQueryId", - "enabled", - "extractionState" - ] - } - }, - "isHidden": { - "type": "boolean" - }, - "selectedAggregate": { - "type": "integer", - "nullable": true - }, - "linkedDashboards": { - "type": "array", - "items": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "dashboardId": { - "type": "integer" - } - }, - "required": ["dashboardId", "field"] - } - } - }, - "required": [ - "aggregates", - "columns", - "conditions", - "fieldAliases", - "fields", - "id", - "isHidden", - "linkedDashboards", - "name", - "onDemand", - "orderby", - "selectedAggregate", - "widgetId" - ] - } - }, - "limit": { - "type": "integer", - "nullable": true - }, - "widgetType": { - "type": "string", - "nullable": true - }, - "layout": { - "type": "object", - "additionalProperties": { - "type": "integer" - }, - "nullable": true - }, - "axisRange": { - "type": "string", - "nullable": true - }, - "legendType": { - "enum": ["default", "breakdown", null], - "type": "string", - "nullable": true - }, - "datasetSource": { - "type": "string", - "nullable": true - }, - "exploreUrls": { - "type": "array", - "items": { - "type": "string" - }, - "nullable": true - }, - "changedReason": { - "type": "array", - "items": { - "type": "object", - "properties": { - "orderby": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "nullable": true - }, - "equations": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - }, - "nullable": true - }, - "selected_columns": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "equations", - "orderby", - "selected_columns" - ] - }, - "nullable": true - } - }, - "required": [ - "axisRange", - "changedReason", - "dashboardId", - "datasetSource", - "dateCreated", - "description", - "displayType", - "exploreUrls", - "id", - "interval", - "layout", - "legendType", - "limit", - "queries", - "thresholds", - "title", - "widgetType" - ] - } - }, - "projects": { - "type": "array", - "items": { - "type": "integer" - } - }, - "filters": { - "type": "object", - "properties": { - "release": { - "type": "array", - "items": { - "type": "string" - } - }, - "releaseId": { - "type": "array", - "items": { - "type": "string" - } - }, - "globalFilter": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": {} - } - } - } - }, - "permissions": { - "type": "object", - "properties": { - "isEditableByEveryone": { - "type": "boolean" - }, - "teamsWithEditAccess": { - "type": "array", - "items": { - "type": "integer" - } - } - }, - "required": [ - "isEditableByEveryone", - "teamsWithEditAccess" - ], - "nullable": true - }, - "isFavorited": { - "type": "boolean" - }, - "prebuiltId": { - "type": "integer", - "nullable": true - } - }, - "required": [ - "createdBy", - "dateCreated", - "filters", - "id", - "isFavorited", - "permissions", - "prebuiltId", - "projects", - "title", - "widgets" - ] - }, - "examples": { - "DashboardGETResponse": { - "value": { - "id": "1", - "title": "Dashboard", - "dateCreated": "2024-06-20T14:38:03.498574Z", - "createdBy": { - "id": "1", - "name": "Admin", - "username": "admin", - "email": "admin@example.com", - "avatarUrl": "www.example.com", - "isActive": true, - "isSuspended": false, - "hasPasswordAuth": true, - "isManaged": false, - "dateJoined": "2021-10-25T17:07:33.190596Z", - "lastLogin": "2024-07-16T15:28:39.261659Z", - "has2fa": true, - "lastActive": "2024-07-16T20:45:49.364197Z", - "isSuperuser": false, - "isStaff": false, - "experiments": {}, - "emails": [ - { - "id": "1", - "email": "admin@example.com", - "is_verified": true - } - ], - "avatar": { - "avatarType": "letter_avatar", - "avatarUuid": null, - "avatarUrl": "www.example.com" - } - }, - "widgets": [ - { - "id": "658714", - "title": "Custom Widget", - "description": null, - "displayType": "table", - "thresholds": null, - "interval": "5m", - "dateCreated": "2024-07-16T15:36:46.048343Z", - "dashboardId": "1", - "datasetSource": "user", - "queries": [ - { - "id": "1", - "name": "", - "fields": [ - "avg(transaction.duration)", - "transaction" - ], - "aggregates": ["avg(transaction.duration)"], - "columns": ["transaction"], - "fieldAliases": ["", ""], - "conditions": "", - "orderby": "-avg(transaction.duration)", - "widgetId": "1", - "onDemand": [ - { - "enabled": false, - "extractionState": "disabled:not-applicable", - "dashboardWidgetQueryId": 1 - } - ], - "isHidden": false, - "selectedAggregate": null, - "linkedDashboards": [] - } - ], - "limit": null, - "widgetType": "transaction-like", - "layout": { - "w": 2, - "y": 0, - "h": 2, - "minH": 2, - "x": 0 - }, - "exploreUrls": null, - "changedReason": null, - "axisRange": null, - "legendType": "default" - } - ], - "projects": [1], - "filters": {}, - "environment": ["alpha"], - "period": "7d", - "permissions": { - "isEditableByEveryone": true, - "teamsWithEditAccess": [] - }, - "isFavorited": false, - "prebuiltId": null - }, - "summary": "Dashboard GET response" - } - } - } - }, - "description": "" - }, - "403": { - "description": "Forbidden" - }, - "404": { - "description": "Not Found" - } - } - } - } - }, - "components": { - "securitySchemes": { - "auth_token": { - "type": "http", - "scheme": "bearer" - }, - "dsn": { - "type": "http", - "scheme": "DSN" - } - } - } - } -} diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts deleted file mode 100644 index dfefc10a9..000000000 --- a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { z } from "zod"; -import { createDashboardFixture } from "./dashboard-fixture"; -import fixtureData from "./dashboard-fixture-data.json" with { type: "json" }; - -type Fixture = Awaited>; -const active: Fixture[] = []; -const accessToken = "local-test-token"; -const dashboardResponse = z - .object({ - id: z.string(), - widgets: z.array( - z - .object({ - id: z.string(), - queries: z.array(z.object({ id: z.string() }).passthrough()), - }) - .passthrough(), - ), - }) - .passthrough(); - -afterEach(async () => { - await Promise.all(active.splice(0).map((fixture) => fixture.close())); -}); - -async function fixture() { - const instance = await createDashboardFixture({ accessToken }); - active.push(instance); - return instance; -} - -function request(fixture: Fixture, path: string, body?: unknown) { - return fetch(`${fixture.baseUrl}${path}`, { - method: body ? "POST" : "GET", - headers: { - authorization: `Bearer ${accessToken}`, - "content-type": "application/json", - }, - body: body ? JSON.stringify(body) : undefined, - }); -} - -function copyBody(projectId: number, title: string) { - const source = structuredClone(fixtureData.sourceDashboard); - return { - ...source, - title, - projects: [projectId], - environment: ["staging"], - widgets: source.widgets.map(({ id, ...widget }) => widget), - }; -} - -function snakeKeys(value: unknown): unknown { - if (Array.isArray(value)) return value.map(snakeKeys); - if (value === null || typeof value !== "object") return value; - return Object.fromEntries( - Object.entries(value).map(([key, value]) => [ - key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`), - snakeKeys(value), - ]), - ); -} - -describe("dashboard Code Mode eval fixture", () => { - it("returns dashboard list metadata with accurate widget previews", async () => { - const instance = await fixture(); - const response = await request( - instance, - `/api/0/organizations/${instance.organizationSlug}/dashboards/?query=Errors`, - ); - expect(response.status).toBe(200); - const dashboards = z - .array(z.record(z.string(), z.unknown())) - .parse(await response.json()); - expect(dashboards).toHaveLength(1); - expect(dashboards[0]).toMatchObject({ - id: "101", - title: "Errors Overview", - widgetDisplay: ["line", "table"], - widgetPreview: [ - { - displayType: "line", - layout: { x: 0, y: 0, w: 6, h: 2 }, - }, - { - displayType: "table", - layout: { x: 0, y: 2, w: 6, h: 3 }, - }, - ], - projects: [101, 102], - environment: ["production"], - }); - expect(dashboards[0]).not.toHaveProperty("widgets"); - }); - - it("persists copies made through the HTTP API using both supported key styles", async () => { - const instance = await fixture(); - const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; - const original = dashboardResponse.parse( - await (await request(instance, `${path}101/`)).json(), - ); - expect(original.widgets).toHaveLength(2); - - const firstResponse = await request( - instance, - path, - copyBody(101, "Frontend staging"), - ); - expect(firstResponse.status).toBe(201); - const first = dashboardResponse.parse(await firstResponse.json()); - const secondResponse = await request( - instance, - path, - snakeKeys(copyBody(102, "Backend staging")), - ); - expect(secondResponse.status).toBe(201); - const second = dashboardResponse.parse(await secondResponse.json()); - - expect((await request(instance, `${path}${first.id}/`)).status).toBe(200); - expect(first.widgets[0].id).not.toBe(original.widgets[0].id); - expect(first.widgets[0].queries[0].id).not.toBe( - original.widgets[0].queries[0].id, - ); - expect(await (await request(instance, `${path}101/`)).json()).toEqual( - original, - ); - expect( - instance.verify( - [ - `ID ${first.id}: https://${instance.organizationSlug}.sentry.io/dashboard/${first.id}/`, - `ID ${second.id}: https://sentry.io/organizations/${instance.organizationSlug}/dashboard/${second.id}/`, - ].join("\n"), - ), - ).toEqual({ success: true, dashboardIds: ["201", "202"] }); - - const discovery = await (await fetch(instance.openapiUrl)).json(); - expect(discovery).toMatchObject({ - paths: { - "/api/0/organizations/{organization_id_or_slug}/dashboards/": { - post: { requestBody: expect.any(Object) }, - }, - "/api/0/organizations/{organization_id_or_slug}/dashboards/{dashboard_id}/": - { - get: { responses: { "200": expect.any(Object) } }, - }, - }, - }); - expect((await request(instance, "/snapshot")).status).toBe(404); - - instance.reset(); - expect(instance.snapshot().dashboards).toHaveLength(1); - expect(instance.snapshot().requests).toEqual([]); - }); - - it("rejects missing authentication, foreign widget IDs and inaccessible projects without writing", async () => { - const instance = await fixture(); - const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; - expect( - ( - await fetch(`${instance.baseUrl}${path}`, { - method: "POST", - body: JSON.stringify(copyBody(101, "Frontend staging")), - }) - ).status, - ).toBe(401); - const copiedIds = await request( - instance, - path, - fixtureData.sourceDashboard, - ); - expect(copiedIds.status).toBe(400); - expect(await copiedIds.json()).toEqual({ - detail: "You cannot update widgets that are not part of this dashboard.", - }); - expect( - (await request(instance, path, copyBody(999, "Unavailable project"))) - .status, - ).toBe(400); - expect(instance.snapshot().dashboards).toHaveLength(1); - }); - - it("grades actual state, rejecting claims of success, lost queries and duplicate writes", async () => { - const instance = await fixture(); - const path = `/api/0/organizations/${instance.organizationSlug}/dashboards/`; - const answer = [201, 202] - .map( - (id) => - `https://${instance.organizationSlug}.sentry.io/dashboard/${id}/`, - ) - .join("\n"); - expect(() => instance.verify(answer)).toThrow( - "Expected exactly two new dashboards", - ); - - const brokenCopy = copyBody(101, "Frontend staging"); - brokenCopy.widgets[0].queries[0].conditions = ""; - expect((await request(instance, path, brokenCopy)).status).toBe(201); - expect( - (await request(instance, path, copyBody(102, "Backend staging"))).status, - ).toBe(201); - expect(() => instance.verify(answer)).toThrow( - "widget configuration differs", - ); - - instance.reset(); - for (const [project, title] of [ - [101, "Frontend staging"], - [102, "Backend staging"], - ] as const) { - expect( - (await request(instance, path, copyBody(project, title))).status, - ).toBe(201); - } - expect(() => - instance.verify("Created both dashboards successfully"), - ).toThrow("final answer omits"); - expect(instance.verify(answer).success).toBe(true); - expect( - (await request(instance, path, copyBody(101, "Frontend staging"))).status, - ).toBe(201); - expect(() => instance.verify(answer)).toThrow( - "Expected exactly two new dashboards", - ); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts b/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts deleted file mode 100644 index 1897f2560..000000000 --- a/packages/mcp-server-evals/src/code-mode/dashboard-fixture.ts +++ /dev/null @@ -1,413 +0,0 @@ -import assert from "node:assert/strict"; -import { createServer, type IncomingMessage } from "node:http"; -import { z } from "zod"; -import fixtureData from "./dashboard-fixture-data.json" with { type: "json" }; - -const ORGANIZATION = "dashboard-evals"; -const TARGETS = [ - { projectId: 101, title: "Frontend staging" }, - { projectId: 102, title: "Backend staging" }, -] as const; -const DATE_CREATED = "2026-10-25T10:00:00.000Z"; - -const querySchema = z.object({ - fields: z.array(z.string()), - conditions: z.string(), - name: z.string().default(""), - aggregates: z.array(z.string()).nullable().default(null), - columns: z.array(z.string()).nullable().default(null), - fieldAliases: z.array(z.string()).nullable().default(null), - orderby: z.string().default(""), - isHidden: z.boolean().default(false), - selectedAggregate: z.number().int().nullable().default(null), - linkedDashboards: z.array(z.unknown()).default([]), -}); - -const layoutSchema = z - .object({ - x: z.number().int().min(0).max(5), - y: z.number().int().min(0), - w: z.number().int().min(1).max(6), - h: z.number().int().min(2), - }) - .refine((layout) => layout.x + layout.w <= 6, "x + w must not exceed 6"); - -const widgetSchema = z.object({ - id: z.string().optional(), - title: z.string().min(1).max(255), - description: z.string().nullable().default(null), - // This fixture only emulates the two display types present in its source. - displayType: z.enum(["line", "table"]), - widgetType: z.enum(["error-events"]).default("error-events"), - interval: z.string().max(10).default("5m"), - queries: z.array(querySchema).min(1), - layout: layoutSchema.nullable().default(null), - thresholds: z.record(z.string(), z.unknown()).nullable().default(null), - limit: z.number().int().min(1).nullable().default(null), - axisRange: z.enum(["auto", "dataMin"]).nullable().default(null), - legendType: z.enum(["default", "table"]).nullable().default(null), - datasetSource: z.enum(["user"]).default("user"), -}); - -const dashboardSchema = z.object({ - title: z.string().trim().min(1).max(255), - widgets: z.array(widgetSchema).default([]), - projects: z.array(z.coerce.number().int()).default([]), - environment: z.array(z.string()).nullable().default(null), - period: z.string().nullable().default(null), - filters: z.record(z.string(), z.unknown()).default({}), - utc: z.boolean().default(false), - isFavorited: z.boolean().default(false), - permissions: z - .object({ - isEditableByEveryone: z.boolean(), - teamsWithEditAccess: z.array(z.number()), - }) - .nullable() - .default(null), -}); - -type Dashboard = Omit, "widgets"> & { - id: string; - dateCreated: string; - createdBy: { id: string; name: string; email: string }; - widgets: Array< - Omit, "id" | "queries"> & { - id: string; - dashboardId: string; - dateCreated: string; - queries: Array< - z.infer & { - id: string; - widgetId: string; - onDemand: unknown[]; - } - >; - } - >; - prebuiltId: null; -}; - -export interface DashboardFixtureSnapshot { - dashboards: Dashboard[]; - requests: Array<{ method: string; path: string; status: number }>; -} - -class FixtureInputError extends Error {} - -// Mirror CamelSnakeSerializer's recursive aliases and collision detection. -// Unknown fields are subsequently stripped by the fixture's input schemas. -function camelKeys(value: unknown): unknown { - if (Array.isArray(value)) return value.map(camelKeys); - if (value === null || typeof value !== "object") return value; - const result: Record = {}; - for (const [key, item] of Object.entries(value)) { - const converted = key.replace(/_([a-z])/g, (_, letter: string) => - letter.toUpperCase(), - ); - if (Object.hasOwn(result, converted)) { - throw new FixtureInputError(`${key} collides with ${converted}`); - } - result[converted] = camelKeys(item); - } - return result; -} - -async function readJson(request: IncomingMessage): Promise { - const chunks: Buffer[] = []; - let size = 0; - for await (const chunk of request) { - size += chunk.length; - if (size > 256_000) - throw new FixtureInputError("Request body is too large"); - chunks.push(chunk); - } - try { - return JSON.parse(Buffer.concat(chunks).toString("utf8")); - } catch { - throw new FixtureInputError("Invalid JSON request body"); - } -} - -function sourceDashboard(): Dashboard { - const source = fixtureData.sourceDashboard; - const parsed = dashboardSchema.parse(source); - return { - ...parsed, - id: source.id, - dateCreated: source.dateCreated, - createdBy: structuredClone(source.createdBy), - prebuiltId: null, - widgets: parsed.widgets.map((widget, index) => ({ - ...widget, - id: source.widgets[index].id, - dashboardId: source.id, - dateCreated: source.widgets[index].dateCreated, - layout: widget.layout ? { ...widget.layout, minH: 2 } : null, - queries: widget.queries.map((query, queryIndex) => ({ - ...query, - id: source.widgets[index].queries[queryIndex].id, - widgetId: source.widgets[index].id, - onDemand: [], - })), - })), - }; -} - -function widgetSemantics(dashboard: Dashboard) { - return dashboard.widgets.map( - ({ id, dashboardId, dateCreated, queries, ...widget }) => ({ - ...widget, - queries: queries.map(({ id, widgetId, onDemand, ...query }) => query), - }), - ); -} - -/** - * Local stateful API substitute, not a full Sentry emulator. Its dashboard - * creation, IDs and aliases follow backend c4b7b5f; the complete three-operation - * OpenAPI slice is captured from sentry-api-schema 803d6de. It deliberately does - * not implement query-language validation, quotas, feature flags or real auth. - * Administrative state is only exposed to the runner, never as HTTP endpoints. - */ -export async function createDashboardFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const original = sourceDashboard(); - let dashboards = new Map([[original.id, structuredClone(original)]]); - let nextId = 201; - let requests: DashboardFixtureSnapshot["requests"] = []; - const collectionPath = `/api/0/organizations/${ORGANIZATION}/dashboards/`; - - const server = createServer(async (request, response) => { - const url = new URL(request.url ?? "/", "http://127.0.0.1"); - const method = request.method ?? "GET"; - const reply = (status: number, body: unknown) => { - requests.push({ method, path: url.pathname, status }); - response.writeHead(status, { "content-type": "application/json" }); - response.end(JSON.stringify(body)); - }; - - try { - if (method === "GET" && url.pathname === "/openapi.json") { - reply(200, fixtureData.openapi); - return; - } - if (request.headers.authorization !== `Bearer ${accessToken}`) { - reply(401, { detail: "Invalid token" }); - return; - } - if (url.pathname === collectionPath && method === "GET") { - const query = url.searchParams.get("query")?.toLowerCase() ?? ""; - reply( - 200, - [...dashboards.values()] - .filter((dashboard) => - dashboard.title.toLowerCase().includes(query), - ) - .map((dashboard) => ({ - id: dashboard.id, - title: dashboard.title, - dateCreated: dashboard.dateCreated, - createdBy: dashboard.createdBy, - environment: dashboard.environment, - filters: dashboard.filters, - lastVisited: null, - widgetDisplay: dashboard.widgets.map( - (widget) => widget.displayType, - ), - widgetPreview: dashboard.widgets.map( - ({ displayType, layout }) => ({ displayType, layout }), - ), - permissions: dashboard.permissions, - isFavorited: dashboard.isFavorited, - projects: dashboard.projects, - prebuiltId: dashboard.prebuiltId, - })), - ); - return; - } - if (url.pathname === collectionPath && method === "POST") { - const input = dashboardSchema.parse(camelKeys(await readJson(request))); - if ( - input.projects.some((id) => !TARGETS.some((t) => t.projectId === id)) - ) { - reply(400, { projects: ["Invalid project IDs"] }); - return; - } - // Backend update_widgets rejects IDs belonging to the original dashboard. - // Query IDs are ignored during create_widget; new query IDs are allocated. - if (input.widgets.some((widget) => widget.id)) { - reply(400, { - detail: - "You cannot update widgets that are not part of this dashboard.", - }); - return; - } - if (url.searchParams.has("validateOnly")) { - reply(200, null); - return; - } - const id = String(nextId++); - const existingTitles = new Set( - [...dashboards.values()].map((dashboard) => dashboard.title), - ); - let title = input.title; - for (let suffix = 1; existingTitles.has(title); suffix++) { - title = `${input.title} (${suffix})`; - } - const created: Dashboard = { - ...input, - title, - id, - dateCreated: DATE_CREATED, - createdBy: structuredClone(original.createdBy), - prebuiltId: null, - widgets: input.widgets.map((widget, index) => { - const widgetId = `${id}${index + 1}`; - return { - ...widget, - id: widgetId, - dashboardId: id, - dateCreated: DATE_CREATED, - layout: widget.layout ? { ...widget.layout, minH: 2 } : null, - queries: widget.queries.map((query, queryIndex) => ({ - ...query, - id: `${widgetId}${queryIndex + 1}`, - widgetId, - onDemand: [], - })), - }; - }), - }; - dashboards.set(id, created); - reply(201, created); - return; - } - if (method === "GET" && url.pathname.startsWith(collectionPath)) { - const id = url.pathname.slice(collectionPath.length).replace(/\/$/, ""); - const dashboard = dashboards.get(id); - reply(dashboard ? 200 : 404, dashboard ?? { detail: "Not found" }); - return; - } - reply(404, { detail: "Not found in the dashboard eval fixture" }); - } catch (error) { - if (error instanceof z.ZodError) { - reply(400, { detail: "Invalid dashboard", errors: error.issues }); - } else if (error instanceof FixtureInputError) { - reply(400, { detail: error.message }); - } else { - reply(500, { detail: "Fixture internal error" }); - } - } - }); - - await new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", resolve); - }); - const address = server.address(); - assert(address && typeof address !== "string"); - const baseUrl = `http://127.0.0.1:${address.port}`; - const prompt = [ - `In organization ${ORGANIZATION}, copy dashboard ${original.id} (${original.title}) into exactly two new dashboards.`, - `The original is https://${ORGANIZATION}.sentry.io/dashboard/${original.id}/.`, - ...TARGETS.map( - (target) => - `Create "${target.title}" with only project ID ${target.projectId} saved in the dashboard.`, - ), - 'Save only the environment "staging" in each copy.', - "Preserve the original widgets, their queries, order and layout, and the other saved dashboard filters and time range.", - "Leave the original dashboard unchanged. Return the IDs and Sentry dashboard links for the two new dashboards.", - ].join("\n"); - - return { - baseUrl, - openapiUrl: `${baseUrl}/openapi.json`, - organizationSlug: ORGANIZATION, - projectIds: TARGETS.map((target) => target.projectId), - prompt, - snapshot(): DashboardFixtureSnapshot { - return structuredClone({ - dashboards: [...dashboards.values()], - requests, - }); - }, - reset() { - dashboards = new Map([[original.id, structuredClone(original)]]); - nextId = 201; - requests = []; - }, - verify(resultText: string) { - assert.deepEqual( - dashboards.get(original.id), - original, - "Original dashboard changed", - ); - const copies = [...dashboards.values()].filter( - (item) => item.id !== original.id, - ); - assert.equal(copies.length, 2, "Expected exactly two new dashboards"); - for (const target of TARGETS) { - const copy = copies.find((item) => item.title === target.title); - assert(copy, `Missing dashboard: ${target.title}`); - assert.deepEqual( - copy.projects, - [target.projectId], - `${target.title}: projects differ`, - ); - assert.deepEqual( - copy.environment, - ["staging"], - `${target.title}: environment differs`, - ); - assert.deepEqual( - widgetSemantics(copy), - widgetSemantics(original), - `${target.title}: widget configuration differs`, - ); - assert.deepEqual( - copy.filters, - original.filters, - `${target.title}: filters differ`, - ); - assert.equal( - copy.period, - original.period, - `${target.title}: time range differs`, - ); - assert.equal( - copy.utc, - original.utc, - `${target.title}: UTC setting differs`, - ); - const urls = resultText.match(/https:\/\/[^\s<>"'`\])]+/g) ?? []; - assert( - urls.some((value) => { - const url = new URL(value); - return ( - (url.hostname === `${ORGANIZATION}.sentry.io` && - url.pathname === `/dashboard/${copy.id}/`) || - (url.hostname === "sentry.io" && - url.pathname === - `/organizations/${ORGANIZATION}/dashboard/${copy.id}/`) - ); - }), - `${target.title}: final answer omits its actual dashboard link and ID`, - ); - } - return { - success: true as const, - dashboardIds: copies.map((copy) => copy.id), - }; - }, - async close() { - await new Promise((resolve, reject) => { - server.close((error) => (error ? reject(error) : resolve())); - server.closeAllConnections(); - }); - }, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/json-answer.test.ts b/packages/mcp-server-evals/src/code-mode/json-answer.test.ts deleted file mode 100644 index 867b8173a..000000000 --- a/packages/mcp-server-evals/src/code-mode/json-answer.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, test } from "vitest"; -import { parseEvalJsonAnswer } from "./json-answer"; - -describe("parseEvalJsonAnswer", () => { - test("uses the fenced answer when later prose contains API paths and JSON examples", () => { - const text = [ - "Done. Verified by re-reading the alert after the write:", - "", - "```json", - '{"id": "701", "enabled": false, "frequency": 60}', - "```", - "", - 'Preserved unchanged: monitor connection (`detectorIds: ["901"]`).', - "", - 'Note: alerts are exposed as `workflows` in the API (`PUT /organizations/{org}/workflows/701/`), and frequency lives in `config.frequency` in minutes. The spec\'s config example shows `{"frequency":3600}`. No other alerts were touched.', - ].join("\n"); - - expect(parseEvalJsonAnswer(text)).toEqual({ - id: "701", - enabled: false, - frequency: 60, - }); - }); - - test.each([ - '{"id":"701"}', - 'Saved values: {"id":"701"}. Verified.', - '```\n{"id":"701"}\n```', - '```json\n{"id":"701"}\n```', - ])("accepts the existing answer format: %s", (text) => { - expect(parseEvalJsonAnswer(text)).toEqual({ id: "701" }); - }); - - test.each([ - '```json\n{"id":}\n```\nCorrection: {"id":"701"}', - '```json\n{"id":"701"}\n```\n```json\n{"id":"702"}\n```', - '```json\n{"id":"701"}\n```\n```\n{"id":"701"}\n```', - '```json\n{"id":"701"}', - '{"id":}', - 'First: {"id":"701"}. Second: {"id":"702"}.', - "No saved values are available.", - ])("rejects malformed, ambiguous, or absent answers: %s", (text) => { - expect(() => parseEvalJsonAnswer(text)).toThrow(SyntaxError); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/json-answer.ts b/packages/mcp-server-evals/src/code-mode/json-answer.ts deleted file mode 100644 index e3c80f12c..000000000 --- a/packages/mcp-server-evals/src/code-mode/json-answer.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function parseEvalJsonAnswer(text: string): unknown { - const fenced = text.split("```"); - if (fenced.length > 1) { - if (fenced.length !== 3) { - throw new SyntaxError("Final answer must contain exactly one JSON fence"); - } - // Prefer the explicit answer over braces in surrounding explanations. - return JSON.parse(fenced[1].trim().replace(/^json\b\s*/i, "")); - } - - const start = text.indexOf("{"); - const end = text.lastIndexOf("}"); - if (start === -1 || end < start) { - throw new SyntaxError("Final answer must include the saved values as JSON"); - } - return JSON.parse(text.slice(start, end + 1)); -} diff --git a/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts deleted file mode 100644 index 2a52acca7..000000000 --- a/packages/mcp-server-evals/src/code-mode/monitor-fixture.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -import tools from "@sentry/mcp-core/tools"; -import { afterEach, describe, expect, it } from "vitest"; -import { z } from "zod"; -import { createMonitorFixture } from "./monitor-fixture"; - -const { find_monitors: findMonitors, get_monitor_details: getMonitorDetails } = - tools; -type Fixture = Awaited>; -const active: Fixture[] = []; -const accessToken = "local-monitor-test-token"; -const edit = { - config: { schedule: "30 9 * * 1-5", timezone: "Europe/Madrid" }, -}; -const finalAnswer = JSON.stringify({ slug: "nightly-billing", ...edit.config }); - -afterEach(async () => { - await Promise.all(active.splice(0).map((instance) => instance.close())); -}); - -async function fixture() { - const instance = await createMonitorFixture({ accessToken }); - active.push(instance); - return instance; -} - -function request( - instance: Fixture, - body?: unknown, - reference = "nightly-billing", - projectScoped = false, -) { - const prefix = projectScoped - ? `projects/${instance.organizationSlug}/billing` - : `organizations/${instance.organizationSlug}`; - return fetch(`${instance.baseUrl}/api/0/${prefix}/monitors/${reference}/`, { - method: body === undefined ? "GET" : "PUT", - headers: { - authorization: `Bearer ${accessToken}`, - "content-type": "application/json", - }, - body: body === undefined ? undefined : JSON.stringify(body), - }); -} - -describe("cron monitor Code Mode eval fixture", () => { - it("runs the real catalog monitor handlers against the fixture", async () => { - const instance = await fixture(); - const context = { - accessToken, - sentryHost: new URL(instance.baseUrl).host, - sentryProtocol: "http" as const, - constraints: {}, - }; - const list = await findMonitors.handler( - z.object(findMonitors.inputSchema).parse({ - organizationSlug: instance.organizationSlug, - projectSlug: "billing", - query: "weekday", - }), - context, - ); - expect(list).toMatchObject({ - structuredContent: { - hasMore: false, - monitors: [ - { - slug: "nightly-billing", - status: "disabled", - projectSlug: "billing", - }, - ], - }, - }); - const detail = await getMonitorDetails.handler( - z.object(getMonitorDetails.inputSchema).parse({ - organizationSlug: instance.organizationSlug, - projectSlugOrId: "billing", - monitorSlug: "nightly-billing", - }), - context, - ); - expect(detail).toContain("**Slug**: nightly-billing"); - expect(detail).toContain("**Status**: disabled"); - expect(detail).toContain("**timezone**: UTC"); - expect( - instance.snapshot().requests.every((request) => request.status === 200), - ).toBe(true); - expect(instance.snapshot().writes).toEqual([]); - }); - - it("serves discovery and the supplementary reads used by ordinary monitor tools", async () => { - const instance = await fixture(); - const paths = [ - "/api/0/organizations/monitor-evals/monitors/?projectSlug=billing&query=weekday&per_page=11", - "/api/0/projects/monitor-evals/billing/", - "/api/0/projects/monitor-evals/billing/monitors/nightly-billing/", - "/api/0/projects/monitor-evals/billing/monitors/nightly-billing/checkins/", - "/api/0/organizations/monitor-evals/monitors/nightly-billing/stats/", - "/api/0/organizations/monitor-evals/monitors/nightly-billing/?expand=alertRule", - ]; - const responses = await Promise.all( - paths.map((path) => - fetch(`${instance.baseUrl}${path}`, { - headers: { authorization: `Bearer ${accessToken}` }, - }), - ), - ); - expect(responses.map((response) => response.status)).toEqual([ - 200, 200, 200, 200, 200, 200, - ]); - const [list, project, monitor, checkins, stats, expanded] = - await Promise.all(responses.map((response) => response.json())); - expect(list).toEqual([monitor]); - expect(project).toMatchObject({ id: "101", slug: "billing" }); - expect(monitor).toMatchObject({ - slug: "nightly-billing", - status: "disabled", - isMuted: false, - }); - expect(monitor).not.toHaveProperty("alertRule"); - expect(expanded).toEqual(instance.snapshot().monitors[0]); - expect(checkins).toEqual([]); - expect(stats).toEqual([]); - expect(instance.snapshot().writes).toEqual([]); - }); - - it("merges a partial config without applying omitted defaults or changing mixed environment mute state", async () => { - const instance = await fixture(); - const before = instance.snapshot(); - expect( - (await request(instance, edit, "nightly-billing", true)).status, - ).toBe(200); - const updated = await (await request(instance)).json(); - expect(updated).toMatchObject({ - status: "disabled", - owner: before.monitors[0].owner, - config: { ...before.monitors[0].config, ...edit.config }, - environments: before.monitors[0].environments, - }); - expect(instance.verify(finalAnswer)).toMatchObject({ - success: true, - ...edit.config, - }); - expect(instance.snapshot().monitors[1]).toEqual(before.monitors[1]); - expect(instance.snapshot().writes).toHaveLength(1); - instance.reset(); - expect(instance.snapshot()).toEqual(before); - }); - - it("finds monitors using backend-supported quoted phrases and name filters", async () => { - const instance = await fixture(); - for (const query of [ - '"Billing weekday import"', - 'name:"Billing weekday import"', - 'weekday name:"Billing weekday import"', - ]) { - const response = await fetch( - `${instance.baseUrl}/api/0/organizations/monitor-evals/monitors/?${new URLSearchParams({ query })}`, - { headers: { authorization: `Bearer ${accessToken}` } }, - ); - expect(response.status).toBe(200); - expect(await response.json()).toMatchObject([ - { slug: "nightly-billing" }, - ]); - } - }); - - it("accepts equivalent weekday expressions and requires the answer to reflect the actual saved schedule", async () => { - const instance = await fixture(); - for (const schedule of [ - "30 9 * * 1,2,3,4,5", - "30 09 * * MON-FRI", - "30 9 * * MON,TUE,WED,THU,FRI", - "30 9 * * 1-5/1", - ]) { - expect( - (await request(instance, { config: { ...edit.config, schedule } })) - .status, - ).toBe(200); - expect( - instance.verify( - JSON.stringify({ - slug: "nightly-billing", - schedule, - timezone: "Europe/Madrid", - }), - ), - ).toMatchObject({ success: true, schedule }); - expect(() => instance.verify(finalAnswer)).toThrow( - "Final answer does not match the saved monitor values", - ); - } - expect( - (await request(instance, { config: { schedule: "30 9 * * 1-6" } })) - .status, - ).toBe(200); - expect(() => instance.verify(finalAnswer)).toThrow( - "Target monitor schedule is incorrect", - ); - }); - - it("rejects GET-shaped owner/project objects while accepting scalar references and camelCase config", async () => { - const instance = await fixture(); - const before = instance.snapshot().monitors[0]; - expect( - ( - await request(instance, { - ...before, - config: { ...before.config, ...edit.config }, - }) - ).status, - ).toBe(400); - expect(instance.snapshot().writes).toEqual([]); - expect( - ( - await request(instance, { - project: "101", - owner: "team:42", - config: { ...edit.config, scheduleType: "crontab", maxRuntime: 45 }, - }) - ).status, - ).toBe(200); - expect(instance.verify(finalAnswer).success).toBe(true); - }); - - it("detects the side effect of resending aggregate isMuted on mixed environments", async () => { - const instance = await fixture(); - expect((await request(instance, { ...edit, isMuted: false })).status).toBe( - 200, - ); - expect( - instance.snapshot().monitors[0].environments.map((env) => env.isMuted), - ).toEqual([false, false]); - expect(() => instance.verify(finalAnswer)).toThrow( - "Unrequested monitor settings changed", - ); - }); - - it("rejects temporarily clearing a threshold even if restored before the final answer", async () => { - const instance = await fixture(); - expect( - ( - await request(instance, { - config: { ...edit.config, failure_issue_threshold: null }, - }) - ).status, - ).toBe(200); - expect( - (await request(instance, { config: { failure_issue_threshold: 3 } })) - .status, - ).toBe(200); - expect(instance.snapshot().monitors[0].config.failure_issue_threshold).toBe( - 3, - ); - expect(() => instance.verify(finalAnswer)).toThrow( - "Write 1: unrequested monitor settings changed during the operation", - ); - }); - - it("rejects changes to another monitor and incorrect reported saved values", async () => { - const instance = await fixture(); - expect((await request(instance, edit)).status).toBe(200); - expect(() => - instance.verify( - JSON.stringify({ - slug: "nightly-billing", - schedule: "0 2 * * *", - timezone: "UTC", - }), - ), - ).toThrow("Final answer does not match the saved monitor values"); - expect(() => instance.verify("Done")).toThrow( - "Final answer must include the saved values as JSON", - ); - expect( - (await request(instance, { status: "active" }, "billing-export")).status, - ).toBe(200); - expect(() => instance.verify(finalAnswer)).toThrow( - "Unrelated monitor changed", - ); - }); - - it("rejects invented success and invalid writes without changing fixture state", async () => { - const instance = await fixture(); - const before = instance.snapshot().monitors; - expect(() => instance.verify(finalAnswer)).toThrow( - "Target monitor schedule is incorrect", - ); - for (const body of [ - { config: { timezone: "Invalid/Timezone" } }, - { project: "different-project" }, - { isMuted: false, is_muted: true }, - ]) - expect((await request(instance, body)).status).toBe(400); - expect(instance.snapshot().monitors).toEqual(before); - expect(instance.snapshot().writes).toEqual([]); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts b/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts deleted file mode 100644 index c509ee4ca..000000000 --- a/packages/mcp-server-evals/src/code-mode/monitor-fixture.ts +++ /dev/null @@ -1,491 +0,0 @@ -import assert from "node:assert/strict"; -import { z } from "zod"; -import { startApiFixture } from "./api-fixture"; -import { parseEvalJsonAnswer } from "./json-answer"; - -const ORGANIZATION = "monitor-evals"; -const MONITOR_ID = "a0000000-0000-4000-8000-000000000001"; -const OTHER_MONITOR_ID = "a0000000-0000-4000-8000-000000000002"; -const SCHEDULE = "30 9 * * 1-5"; -const TIMEZONE = "Europe/Madrid"; -const project = { - id: "101", - slug: "billing", - name: "Billing", - platform: "python", -}; -const owner = { type: "team", id: "42", name: "Billing on-call" }; - -const configSchema = z.object({ - schedule_type: z.enum(["crontab", "interval"]).optional(), - schedule: z - .union([ - z.string(), - z.tuple([ - z.number().int().positive(), - z.enum(["year", "month", "week", "day", "hour", "minute"]), - ]), - ]) - .optional(), - timezone: z - .string() - .refine((value) => { - if (!value) return true; - try { - new Intl.DateTimeFormat("en", { timeZone: value }); - return true; - } catch { - return false; - } - }) - .optional(), - checkin_margin: z.number().int().min(1).max(40320).nullable().optional(), - max_runtime: z.number().int().min(1).max(10080).nullable().optional(), - failure_issue_threshold: z - .number() - .int() - .min(1) - .max(720) - .nullable() - .optional(), - recovery_threshold: z.number().int().min(1).max(720).nullable().optional(), -}); -const updateSchema = z.object({ - project: z.union([z.string(), z.number().int()]).optional(), - name: z.string().min(1).max(128).optional(), - slug: z - .string() - .min(1) - .max(50) - .regex(/^(?![0-9]+$)[a-z0-9_-]+$/) - .optional(), - status: z.enum(["active", "disabled"]).optional(), - owner: z - .string() - .regex(/^(team|user):\d+$/) - .nullable() - .optional(), - is_muted: z.boolean().optional(), - config: configSchema.optional(), - alert_rule: z - .object({ - environment: z.string().nullable().optional(), - targets: z.array( - z.object({ - target_identifier: z.number().int(), - target_type: z.string(), - }), - ), - }) - .optional(), -}); -const answerSchema = z.object({ - slug: z.string(), - schedule: z.string(), - timezone: z.string(), -}); - -// Grade the requested days, not one spelling of an equivalent crontab. -function isRequestedSchedule(schedule: Monitor["config"]["schedule"]) { - if (typeof schedule !== "string") return false; - const [minute, hour, day, month, weekdays, extra] = schedule - .trim() - .split(/\s+/); - if ( - extra || - !/^0*30$/.test(minute) || - !/^0*9$/.test(hour) || - day !== "*" || - month !== "*" || - !weekdays - ) - return false; - const names = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; - const asDay = (value: string) => - /^\d+$/.test(value) ? Number(value) : names.indexOf(value.toUpperCase()); - const days = new Set(); - for (const part of weekdays.split(",")) { - const [range, stepText, extraStep] = part.split("/"); - const step = stepText === undefined ? 1 : Number(stepText); - const [first, last = first, extraDay] = range.split("-"); - const start = asDay(first), - end = asDay(last); - if ( - extraStep || - extraDay || - !Number.isInteger(step) || - step < 1 || - start < 0 || - end > 7 || - end < start - ) - return false; - for (let value = start; value <= end; value += step) days.add(value % 7); - } - return days.size === 5 && [1, 2, 3, 4, 5].every((day) => days.has(day)); -} - -// This fixture models the free-text and name filters used for this scenario, -// including quoted phrases; other query-language fields are outside its scope. -function matchesQuery(monitor: Monitor, query: string) { - const names: string[] = []; - const words: string[] = []; - for (const token of query.match(/(?:\w+:)?"[^"]*"|\S+/g) ?? []) { - if (token.startsWith("name:")) - names.push(token.slice(5).replace(/^"|"$/g, "")); - else if (token.includes(":")) return false; - else words.push(token.replace(/^"|"$/g, "")); - } - const text = words.join(" "); - return ( - (!names.length || names.includes(monitor.name.toLowerCase())) && - (!text || - monitor.name.toLowerCase().includes(text) || - monitor.slug.toLowerCase().includes(text)) - ); -} - -// MonitorValidator accepts camelCase aliases recursively and rejects collisions. -function snakeKeys(value: unknown): unknown { - if (Array.isArray(value)) return value.map(snakeKeys); - if (!value || typeof value !== "object") return value; - const result: Record = {}; - for (const [key, item] of Object.entries(value)) { - const converted = key.replace( - /[A-Z]/g, - (letter) => `_${letter.toLowerCase()}`, - ); - if (Object.hasOwn(result, converted)) - throw new SyntaxError(`Duplicate field ${converted}`); - result[converted] = snakeKeys(item); - } - return result; -} - -function sourceMonitor(id: string, slug: string, name: string) { - return { - id, - slug, - name, - status: "disabled" as "active" | "disabled", - isMuted: false, - isUpserting: false, - dateCreated: "2026-10-20T02:00:00.000Z", - project, - owner: owner as typeof owner | null, - config: { - schedule_type: "crontab" as "crontab" | "interval", - schedule: "0 2 * * *" as string | [number, string], - timezone: "UTC", - checkin_margin: 10 as number | null, - max_runtime: 45 as number | null, - failure_issue_threshold: 3 as number | null, - recovery_threshold: 2 as number | null, - alert_rule_id: 501, - }, - environments: [ - { - name: "production", - status: "ok", - isMuted: false, - dateCreated: "2026-10-20T02:00:00.000Z", - lastCheckIn: "2026-10-25T02:00:00.000Z", - activeIncident: null, - }, - { - name: "staging", - status: "ok", - isMuted: true, - dateCreated: "2026-10-20T02:00:00.000Z", - lastCheckIn: "2026-10-25T02:00:00.000Z", - activeIncident: null, - }, - ], - alertRule: { - environment: "production" as string | null, - targets: [{ targetIdentifier: 42, targetType: "Team" }], - }, - }; -} -type Monitor = ReturnType; - -/** - * Synthetic monitor GET/PUT state based on MonitorValidator's partial update. - * Models config merging and mute propagation, not quotas, actor permissions, - * crontab execution, next-check-in recomputation or notification delivery. - */ -export async function createMonitorFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const original = sourceMonitor( - MONITOR_ID, - "nightly-billing", - "Billing weekday import", - ); - const other = sourceMonitor( - OTHER_MONITOR_ID, - "billing-export", - "Billing export", - ); - const initial = [original, other]; - let monitors = new Map( - initial.map((monitor) => [monitor.id, structuredClone(monitor)]), - ); - const writes: Array<{ monitorId: string; monitors: Monitor[] }> = []; - const organizationPath = `/api/0/organizations/${ORGANIZATION}/`; - const collectionPath = `${organizationPath}monitors/`; - const projectPaths = [project.slug, project.id].map( - (id) => `/api/0/projects/${ORGANIZATION}/${id}/`, - ); - - const serialize = (monitor: Monitor, url: URL) => { - const { alertRule, ...body } = structuredClone(monitor); - const environments = url.searchParams.getAll("environment"); - if (environments.length) - body.environments = body.environments.filter((env) => - environments.includes(env.name), - ); - return url.searchParams.getAll("expand").includes("alertRule") - ? { ...body, alertRule } - : body; - }; - const api = await startApiFixture(accessToken, ({ method, url, body }) => { - if (method === "GET" && projectPaths.includes(url.pathname)) - return { body: project }; - if (method === "GET" && url.pathname === `${organizationPath}projects/`) - return { body: [project] }; - if (method === "GET" && url.pathname === collectionPath) { - const query = (url.searchParams.get("query") ?? "").toLowerCase(); - const projectFilter = - url.searchParams.get("projectSlug") ?? url.searchParams.get("project"); - const ownerFilter = url.searchParams.get("owner"); - const items = [...monitors.values()].filter( - (monitor) => - matchesQuery(monitor, query) && - (!projectFilter || - ["-1", project.id, project.slug].includes(projectFilter)) && - (!ownerFilter || - `${monitor.owner?.type}:${monitor.owner?.id}` === ownerFilter), - ); - return { - body: items - .slice(0, Number(url.searchParams.get("per_page") ?? 100)) - .map((item) => serialize(item, url)), - }; - } - const prefix = [ - collectionPath, - ...projectPaths.map((path) => `${path}monitors/`), - ].find((path) => url.pathname.startsWith(path)); - if (!prefix) return undefined; - const [reference, subresource] = url.pathname - .slice(prefix.length) - .split("/"); - const current = [...monitors.values()].find( - (item) => item.id === reference || item.slug === reference, - ); - if (!current) return { status: 404, body: { detail: "Monitor not found" } }; - // get_monitor_details reads these alongside the monitor even when the user - // only needs configuration. Empty history is valid for a disabled monitor. - if (method === "GET" && ["checkins", "stats"].includes(subresource)) - return { body: [] }; - if (subresource) return undefined; - if (method === "GET") return { body: serialize(current, url) }; - if (method !== "PUT") return undefined; - const input = updateSchema.parse(snakeKeys(body)); - if ( - input.project !== undefined && - ![project.slug, project.id].includes(String(input.project)) - ) { - return { - status: 400, - body: { detail: "existing monitors may not be moved between projects" }, - }; - } - if ( - input.slug && - [...monitors.values()].some( - (item) => item.id !== current.id && item.slug === input.slug, - ) - ) { - return { status: 400, body: { slug: "The slug is already in use." } }; - } - const updated = structuredClone(current); - if (input.name !== undefined) updated.name = input.name; - if (input.slug !== undefined) updated.slug = input.slug; - if (input.status !== undefined) updated.status = input.status; - if (input.owner !== undefined) { - const [type, id] = input.owner?.split(":") ?? []; - updated.owner = - input.owner === null - ? null - : { - type, - id, - name: input.owner === "team:42" ? owner.name : `Owner ${id}`, - }; - } - if (input.config) { - const config = { ...input.config }; - if (config.timezone === "") delete config.timezone; - updated.config = { ...updated.config, ...config }; - const schedule = updated.config.schedule; - if ( - updated.config.schedule_type === "crontab" && - (typeof schedule !== "string" || - schedule.trim().split(/\s+/).length !== 5) - ) { - return { - status: 400, - body: { config: { schedule: "Invalid crontab schedule" } }, - }; - } - if ( - updated.config.schedule_type === "interval" && - !Array.isArray(schedule) - ) { - return { - status: 400, - body: { config: { schedule: "Invalid interval schedule" } }, - }; - } - } - if (input.is_muted !== undefined) { - updated.environments = updated.environments.map((env) => ({ - ...env, - isMuted: input.is_muted as boolean, - })); - updated.isMuted = input.is_muted; - } - if (input.alert_rule) { - updated.alertRule = { - environment: input.alert_rule.environment ?? null, - targets: input.alert_rule.targets.map((target) => ({ - targetIdentifier: target.target_identifier, - targetType: target.target_type, - })), - }; - } - monitors.set(updated.id, updated); - writes.push( - structuredClone({ - monitorId: updated.id, - monitors: [...monitors.values()], - }), - ); - return { body: serialize(updated, new URL(url.pathname, url.origin)) }; - }); - - return { - baseUrl: api.baseUrl, - organizationSlug: ORGANIZATION, - prompt: [ - `In organization ${ORGANIZATION}, project billing, change the cron monitor named "${original.name}" to run at 09:30 Monday through Friday in Europe/Madrid.`, - "Keep its name, slug, owner, enabled/disabled status, thresholds, notification settings and each environment's muted state unchanged throughout the operation. Leave all other monitors unchanged.", - "Return JSON with the saved values: {slug, schedule, timezone}.", - ].join("\n"), - cliCommands: [ - `sentry api organizations/${ORGANIZATION}/monitors/${original.slug}/ --method PUT --data '{"config":{"schedule":"${SCHEDULE}","timezone":"${TIMEZONE}"}}'`, - ], - provenance: { - cliRevision: "b625111aef9cc3a44abbed634a043fce8505366d", - cliSource: "packages/cli/src/commands/api.ts", - backendRevision: "b4b356554798716ceaeab74efdc095782be9ca16", - backendSources: [ - "src/sentry/monitors/endpoints/organization_monitor_details.py", - "src/sentry/monitors/endpoints/project_monitor_details.py", - "src/sentry/monitors/endpoints/base_monitor_details.py", - "src/sentry/monitors/endpoints/organization_monitor_index.py", - "src/sentry/monitors/validators.py", - "src/sentry/monitors/serializers.py", - "src/sentry/api/serializers/rest_framework/base.py", - "tests/sentry/monitors/endpoints/test_base_monitor_details.py", - ], - endpoints: [ - "GET /organizations/{organization_id_or_slug}/monitors/", - "GET|PUT /organizations/{organization_id_or_slug}/monitors/{monitor_id_or_slug}/", - "GET|PUT /projects/{organization_id_or_slug}/{project_id_or_slug}/monitors/{monitor_id_or_slug}/", - ], - limitations: - "No quota/actor authorization, scheduler, derived next-check-in timestamps or notification delivery simulation. Query support is free text and name filters. Cron validation only checks shape; the grader accepts equivalent weekday ranges, lists and names for the requested time.", - }, - reset() { - monitors = new Map( - initial.map((monitor) => [monitor.id, structuredClone(monitor)]), - ); - api.requests.length = 0; - writes.length = 0; - }, - snapshot() { - return structuredClone({ - monitors: [...monitors.values()], - requests: api.requests, - writes, - }); - }, - verify(resultText: string) { - const updated = monitors.get(MONITOR_ID); - assert(updated, "Target monitor is missing"); - assert( - isRequestedSchedule(updated.config.schedule), - "Target monitor schedule is incorrect", - ); - assert.equal( - updated.config.timezone, - TIMEZONE, - "Target monitor timezone is incorrect", - ); - const preserved = (target: Monitor) => ({ - ...original, - config: { - ...original.config, - schedule: target.config.schedule, - timezone: target.config.timezone, - }, - }); - assert.deepEqual( - updated, - preserved(updated), - "Unrequested monitor settings changed", - ); - assert.deepEqual( - monitors.get(OTHER_MONITOR_ID), - other, - "Unrelated monitor changed", - ); - for (const [index, write] of writes.entries()) { - const target = write.monitors.find((item) => item.id === MONITOR_ID); - assert(target, `Write ${index + 1}: target monitor is missing`); - assert.deepEqual( - target, - preserved(target), - `Write ${index + 1}: unrequested monitor settings changed during the operation`, - ); - assert.deepEqual( - write.monitors.find((item) => item.id === OTHER_MONITOR_ID), - other, - `Write ${index + 1}: another monitor changed during the operation`, - ); - } - assert.deepEqual( - answerSchema.parse(parseEvalJsonAnswer(resultText)), - { - slug: updated.slug, - schedule: updated.config.schedule, - timezone: TIMEZONE, - }, - "Final answer does not match the saved monitor values", - ); - return { - success: true as const, - monitorId: MONITOR_ID, - slug: updated.slug, - schedule: updated.config.schedule, - timezone: TIMEZONE, - }; - }, - close: api.close, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts deleted file mode 100644 index 0dbc20548..000000000 --- a/packages/mcp-server-evals/src/code-mode/release-fixture.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { z } from "zod"; -import { createReleaseFixture } from "./release-fixture"; -import { createReleaseRecoveryFixture } from "./release-recovery-fixture"; - -type Fixture = Awaited>; -const active: Fixture[] = []; -const collection = "/api/0/organizations/release-evals/releases/"; -const version = "checkout@2.4.0"; -const releasePath = `${collection}${encodeURIComponent(version)}/`; -const released = "2026-10-26T10:00:00Z"; -const completeAnswer = { - version, - projects: ["frontend", "backend"], - dateReleased: released, - deploymentId: "601", -}; -const deployBody = { - environment: "staging", - name: "Staging rollout", - url: "https://deploy.example.test/runs/42", - dateStarted: "2026-10-26T10:02:00Z", - dateFinished: "2026-10-26T10:04:00Z", -}; - -afterEach(async () => { - await Promise.all(active.splice(0).map((fixture) => fixture.close())); -}); - -async function fixture(create = createReleaseFixture) { - const instance = await create(); - active.push(instance); - return instance; -} - -function request( - instance: Fixture, - method: string, - path: string, - body?: unknown, -) { - return fetch(`${instance.baseUrl}${path}`, { - method, - headers: { - authorization: "Bearer code-mode-eval-token", - "content-type": "application/json", - }, - body: body === undefined ? undefined : JSON.stringify(body), - }); -} - -describe("release Code Mode eval fixture", () => { - it("persists the CLI create/finalize/deploy flow and exposes normal catalog read routes", async () => { - const instance = await fixture(); - const created = await request(instance, "POST", collection, { - version, - projects: ["frontend", "backend"], - }); - expect(created.status).toBe(201); - expect(await created.json()).toMatchObject({ version, dateReleased: null }); - const finalized = await request(instance, "PUT", releasePath, { - dateReleased: released, - }); - expect(finalized.status).toBe(200); - expect(await finalized.json()).toMatchObject({ - dateReleased: "2026-10-26T10:00:00.000Z", - }); - const deployed = await request( - instance, - "POST", - `${releasePath}deploys/`, - deployBody, - ); - expect(deployed.status).toBe(201); - expect(await deployed.json()).toMatchObject({ - id: "601", - environment: "staging", - }); - expect( - await ( - await request(instance, "GET", `${collection}?query=2.4.0`) - ).json(), - ).toMatchObject([{ version }]); - expect( - await (await request(instance, "GET", releasePath)).json(), - ).toMatchObject({ version, deployCount: 1, lastDeploy: { id: "601" } }); - expect( - await (await request(instance, "GET", `${releasePath}deploys/`)).json(), - ).toMatchObject([{ id: "601" }]); - expect( - await (await request(instance, "GET", `${releasePath}commits/`)).json(), - ).toEqual([]); - expect( - await ( - await request( - instance, - "GET", - `/api/0/projects/release-evals/frontend/releases/${encodeURIComponent(version)}/`, - ) - ).json(), - ).toMatchObject({ version }); - expect(instance.verify(JSON.stringify(completeAnswer))).toEqual({ - success: true, - releaseVersion: version, - deployId: "601", - }); - expect(() => - instance.verify( - JSON.stringify({ ...completeAnswer, deploymentId: "999" }), - ), - ).toThrow("actual deployment ID"); - const { projects, dateReleased, ...incompleteAnswer } = completeAnswer; - expect(() => - instance.verify(JSON.stringify({ ...incompleteAnswer, dateReleased })), - ).toThrow(); - expect(() => - instance.verify(JSON.stringify({ ...incompleteAnswer, projects })), - ).toThrow(); - expect(() => - instance.verify( - JSON.stringify({ ...completeAnswer, projects: ["frontend"] }), - ), - ).toThrow("incorrect release projects"); - expect(() => - instance.verify( - JSON.stringify({ - ...completeAnswer, - dateReleased: "2026-10-25T10:00:00Z", - }), - ), - ).toThrow("incorrect release time"); - expect( - instance.verify( - JSON.stringify({ - ...completeAnswer, - dateReleased: "2026-10-26T12:00:00+02:00", - }), - ).success, - ).toBe(true); - instance.reset(); - expect(instance.snapshot().releases).toHaveLength(1); - expect(instance.snapshot().requests).toEqual([]); - }); - - it("allows finalizing on create but rejects partial state and duplicate deployment success claims", async () => { - const instance = await fixture(); - const body = { - version, - projects: ["frontend", "backend"], - dateReleased: released, - }; - expect((await request(instance, "POST", collection, body)).status).toBe( - 201, - ); - expect((await request(instance, "POST", collection, body)).status).toBe( - 208, - ); - expect(() => instance.verify(`${version} deployed as 601`)).toThrow( - "exactly one new deployment", - ); - expect( - (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) - .status, - ).toBe(201); - expect(instance.verify(JSON.stringify(completeAnswer)).success).toBe(true); - expect( - (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) - .status, - ).toBe(201); - // The API lists the latest deployment per project/environment, while grading checks all persisted writes. - expect( - await (await request(instance, "GET", `${releasePath}deploys/`)).json(), - ).toMatchObject([{ id: "602" }]); - expect(() => instance.verify(`${version}: 602`)).toThrow( - "exactly one new deployment", - ); - }); - - it("rejects invalid projects and detects changes to the existing release", async () => { - const instance = await fixture(); - expect( - ( - await request(instance, "POST", collection, { - version, - projects: ["unknown"], - }) - ).status, - ).toBe(400); - expect( - (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) - .status, - ).toBe(404); - expect( - ( - await request(instance, "POST", collection, { - version, - projects: ["frontend"], - dateReleased: released, - }) - ).status, - ).toBe(201); - expect( - ( - await request(instance, "POST", `${releasePath}deploys/`, { - ...deployBody, - projects: ["backend"], - }) - ).status, - ).toBe(400); - expect(() => instance.verify(`${version}: 601`)).toThrow( - "Release projects differ", - ); - expect( - ( - await request(instance, "PUT", `${collection}checkout%402.3.0/`, { - dateReleased: released, - }) - ).status, - ).toBe(200); - expect(() => instance.verify(`${version}: 601`)).toThrow( - "Existing release changed", - ); - }); - - it("does not let an omitted finish time accidentally satisfy the requested timestamp", async () => { - const instance = await fixture(); - await request(instance, "POST", collection, { - version, - projects: ["frontend", "backend"], - dateReleased: released, - }); - const { dateFinished, ...withoutFinished } = deployBody; - const response = await request( - instance, - "POST", - `${releasePath}deploys/`, - withoutFinished, - ); - expect(response.status).toBe(201); - expect(await response.json()).toMatchObject({ - dateFinished: "2026-10-26T12:00:00.000Z", - }); - expect(() => instance.verify(`${version}: 601`)).toThrow( - "Deployment configuration differs", - ); - }); - - it("recovers the actual persisted deployment after an ambiguous response without replaying the write", async () => { - const instance = await fixture(createReleaseRecoveryFixture); - const baseline = await fixture(); - expect(instance.prompt).toBe(baseline.prompt); - expect(instance.provenance).toMatchObject({ - faultInjection: expect.stringContaining("simulated gateway 502"), - }); - - for (let run = 0; run < 2; run++) { - await request(instance, "POST", collection, { - version, - projects: ["frontend", "backend"], - dateReleased: released, - }); - const response = await request( - instance, - "POST", - `${releasePath}deploys/`, - deployBody, - ); - expect(response.status).toBe(502); - expect(await response.json()).toEqual({ detail: "Bad Gateway" }); - const savedRelease = z - .object({ - version: z.string(), - projects: z.array(z.object({ slug: z.string() })), - dateReleased: z.string(), - deployCount: z.number(), - }) - .parse(await (await request(instance, "GET", releasePath)).json()); - const savedDeploys = z - .array(z.object({ id: z.string() })) - .parse( - await ( - await request(instance, "GET", `${releasePath}deploys/`) - ).json(), - ); - expect(savedRelease).toMatchObject({ deployCount: 1 }); - expect(savedDeploys).toMatchObject([{ id: "601" }]); - expect( - instance.verify( - JSON.stringify({ - version: savedRelease.version, - projects: savedRelease.projects.map((project) => project.slug), - dateReleased: savedRelease.dateReleased, - deploymentId: savedDeploys[0].id, - }), - ).success, - ).toBe(true); - expect(instance.snapshot().requests.map(({ status }) => status)).toEqual([ - 201, 502, 200, 200, - ]); - instance.reset(); - } - }); - - it("rejects a naive deployment replay even though the latest-deploy read hides the duplicate", async () => { - const instance = await fixture(createReleaseRecoveryFixture); - await request(instance, "POST", collection, { - version, - projects: ["frontend", "backend"], - dateReleased: released, - }); - expect( - (await request(instance, "POST", `${releasePath}deploys/`, deployBody)) - .status, - ).toBe(502); - const retried = await request( - instance, - "POST", - `${releasePath}deploys/`, - deployBody, - ); - expect(retried.status).toBe(201); - expect(await retried.json()).toMatchObject({ id: "602" }); - expect( - await (await request(instance, "GET", `${releasePath}deploys/`)).json(), - ).toMatchObject([{ id: "602" }]); - expect(instance.snapshot().deploys).toHaveLength(3); - expect(() => - instance.verify( - JSON.stringify({ ...completeAnswer, deploymentId: "602" }), - ), - ).toThrow("Expected exactly one new deployment"); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/release-fixture.ts b/packages/mcp-server-evals/src/code-mode/release-fixture.ts deleted file mode 100644 index f1502be0b..000000000 --- a/packages/mcp-server-evals/src/code-mode/release-fixture.ts +++ /dev/null @@ -1,417 +0,0 @@ -import assert from "node:assert/strict"; -import { z } from "zod"; -import { startApiFixture } from "./api-fixture"; -import { parseEvalJsonAnswer } from "./json-answer"; - -const ORGANIZATION = "release-evals"; -const VERSION = "checkout@2.4.0"; -const RELEASED = "2026-10-26T10:00:00.000Z"; -const STARTED = "2026-10-26T10:02:00.000Z"; -const FINISHED = "2026-10-26T10:04:00.000Z"; -const FIXTURE_NOW = "2026-10-26T12:00:00.000Z"; -const DEPLOY_NAME = "Staging rollout"; -const DEPLOY_URL = "https://deploy.example.test/runs/42"; -const PROJECTS = [ - { id: 101, slug: "frontend", name: "Frontend", platform: "javascript" }, - { id: 102, slug: "backend", name: "Backend", platform: "python" }, -]; -const date = z.iso - .datetime({ offset: true }) - .transform((value) => new Date(value).toISOString()); -const answerSchema = z.object({ - version: z.string(), - projects: z.array(z.string()), - dateReleased: date, - deploymentId: z - .union([z.string(), z.number().int().positive()]) - .transform(String), -}); -const releaseFields = z.object({ - ref: z.string().nullable().optional(), - url: z - .union([z.url(), z.literal("")]) - .nullable() - .optional(), - dateReleased: date.nullable().optional(), - status: z.enum(["open", "archived"]).optional(), -}); -const createReleaseSchema = releaseFields.extend({ - version: z - .string() - .trim() - .min(1) - .max(250) - .refine((value) => !/[/\\]/.test(value)), - projects: z.array(z.union([z.string(), z.number()])), -}); -const deploySchema = z.object({ - environment: z - .string() - .min(1) - .max(64) - .refine((value) => !/[\s/]/.test(value)), - name: z.string().max(64).nullable().optional(), - url: z - .union([z.url(), z.literal("")]) - .nullable() - .optional(), - dateStarted: date.nullable().optional(), - dateFinished: date.optional(), - projects: z - .array(z.union([z.string(), z.number()])) - .min(1) - .optional(), -}); - -export interface Deploy { - id: string; - environment: string; - name: string | null; - url: string | null; - dateStarted: string | null; - dateFinished: string; -} -export interface StoredDeploy { - releaseVersion: string; - projectSlugs: string[]; - deploy: Deploy; -} - -// Shape follows the scrubbed release response in mcp-server-mocks/fixtures/release.json. -function newRelease(version: string, id: number, projects: typeof PROJECTS) { - return { - id, - version, - shortVersion: version, - status: "open", - ref: null as string | null, - url: null as string | null, - dateReleased: null as string | null, - dateCreated: "2026-10-26T09:00:00.000Z", - firstEvent: null, - lastEvent: null, - newGroups: 0, - commitCount: 0, - lastCommit: null, - deployCount: 0, - lastDeploy: null as Deploy | null, - projects: structuredClone(projects), - authors: [], - owner: null, - currentProjectMeta: {}, - }; -} - -/** - * Release/deploy subset of the Sentry API, verified against backend b4b3565. - * Preserves release-create idempotency, non-idempotent deploys and latest deploy - * per project/environment. It does not emulate auth scopes, notifications, - * commit association, health statistics or pagination. - */ -export async function createReleaseFixture({ - accessToken = "code-mode-eval-token", - simulateDeployResponseLoss = false, -}: { - accessToken?: string; - simulateDeployResponseLoss?: boolean; -} = {}) { - const seedDeploy: StoredDeploy = { - releaseVersion: "checkout@2.3.0", - projectSlugs: ["frontend", "backend"], - deploy: { - id: "501", - environment: "production", - name: "Previous rollout", - url: "https://deploy.example.test/runs/41", - dateStarted: "2026-10-25T10:02:00.000Z", - dateFinished: "2026-10-25T10:04:00.000Z", - }, - }; - const seedRelease = { - ...newRelease(seedDeploy.releaseVersion, 100, PROJECTS), - dateReleased: "2026-10-25T10:00:00.000Z", - deployCount: 1, - lastDeploy: seedDeploy.deploy, - }; - let releases = new Map>([ - [seedRelease.version, structuredClone(seedRelease)], - ]); - let deploys = [structuredClone(seedDeploy)]; - let nextReleaseId = 201; - let nextDeployId = 601; - let deployResponseLost = false; - const collectionPath = `/api/0/organizations/${ORGANIZATION}/releases/`; - const resolveProjects = (values: Array) => - values.map((value) => - PROJECTS.find( - (project) => - project.slug === value || String(project.id) === String(value), - ), - ); - - const api = await startApiFixture(accessToken, ({ method, url, body }) => { - let path = decodeURIComponent(url.pathname); - const projectPath = path.match( - new RegExp(`^/api/0/projects/${ORGANIZATION}/([^/]+)/releases/(.*)$`), - ); - const scopedProject = projectPath - ? resolveProjects([projectPath[1]])[0] - : undefined; - if (projectPath) { - if (!scopedProject) - return { - status: 404, - body: { detail: "The requested project does not exist" }, - }; - if (method !== "GET") return undefined; - path = `${collectionPath}${projectPath[2]}`; - } - if ( - path === `/api/0/organizations/${ORGANIZATION}/projects/` && - method === "GET" - ) { - return { body: PROJECTS }; - } - if (path === collectionPath && method === "GET") { - const query = url.searchParams.get("query")?.toLowerCase() ?? ""; - return { - body: [...releases.values()].filter( - (release) => - release.version.toLowerCase().includes(query) && - (!scopedProject || - release.projects.some( - (project) => project.id === scopedProject.id, - )), - ), - }; - } - if (path === collectionPath && method === "POST") { - const input = createReleaseSchema.parse(body); - const projects = resolveProjects(input.projects); - if (projects.some((project) => !project)) - return { - status: 400, - body: { projects: ["Invalid project ids or slugs"] }, - }; - const existing = releases.get(input.version); - const resolved = projects.filter( - (project): project is (typeof PROJECTS)[number] => !!project, - ); - if (existing) { - if (input.status) existing.status = input.status; - const added = resolved.filter( - (project) => - !existing.projects.some((current) => current.id === project.id), - ); - existing.projects.push(...added); - return { status: added.length ? 201 : 208, body: existing }; - } - const release = newRelease(input.version, nextReleaseId++, resolved); - Object.assign(release, releaseFields.parse(input)); - releases.set(release.version, release); - return { status: 201, body: release }; - } - const match = path.startsWith(collectionPath) - ? path - .slice(collectionPath.length) - .match(/^([^/]+)\/(deploys\/|commits\/)?$/) - : null; - if (!match) return undefined; - const release = releases.get(match[1]); - if ( - !release || - (scopedProject && - !release.projects.some((project) => project.id === scopedProject.id)) - ) - return { - status: 404, - body: { detail: "The requested release does not exist" }, - }; - if (!match[2]) { - if (method === "GET") return { body: release }; - if (method === "PUT") { - // The backend changes only truthy metadata values, without resetting projects. - for (const [key, value] of Object.entries(releaseFields.parse(body))) { - if (value) Object.assign(release, { [key]: value }); - } - return { body: release }; - } - } - if (match[2] === "commits/" && method === "GET") return { body: [] }; - if (match[2] === "deploys/" && method === "POST") { - const input = deploySchema.parse(body); - const projects = input.projects - ? resolveProjects(input.projects) - : release.projects; - if ( - projects.some( - (project) => - !project || - !release.projects.some((current) => current.id === project.id), - ) - ) { - return { - status: 400, - body: { detail: "Invalid projects for release" }, - }; - } - const deploy: Deploy = { - id: String(nextDeployId++), - environment: input.environment, - name: input.name ?? null, - url: input.url ?? null, - dateStarted: input.dateStarted ?? null, - dateFinished: input.dateFinished ?? FIXTURE_NOW, - }; - deploys.push({ - releaseVersion: release.version, - projectSlugs: projects.flatMap((project) => - project ? [project.slug] : [], - ), - deploy, - }); - release.deployCount++; - release.lastDeploy = deploy; - // A synthetic gateway failure after persistence, not a Sentry endpoint contract. - if (simulateDeployResponseLoss && !deployResponseLost) { - deployResponseLost = true; - return { status: 502, body: { detail: "Bad Gateway" } }; - } - return { status: 201, body: deploy }; - } - if (match[2] === "deploys/" && method === "GET") { - const latest = new Map(); - for (const item of deploys.filter( - (item) => item.releaseVersion === release.version, - )) { - for (const project of item.projectSlugs) - latest.set(`${project}:${item.deploy.environment}`, item.deploy); - } - return { - body: [...new Set(latest.values())].sort((a, b) => - b.dateFinished.localeCompare(a.dateFinished), - ), - }; - } - return undefined; - }); - - return { - baseUrl: api.baseUrl, - prompt: [ - `In organization ${ORGANIZATION}, create release ${VERSION} for projects frontend and backend.`, - `Finalize it with release time ${RELEASED}.`, - `Record exactly one staging deployment named "${DEPLOY_NAME}", with URL ${DEPLOY_URL}, started at ${STARTED} and finished at ${FINISHED}.`, - "Return JSON with version, projects (an array of project slugs), dateReleased (ISO 8601), and deploymentId, confirming the saved release and actual deployment ID.", - "Leave existing releases and deployments unchanged.", - ].join("\n"), - cliCommands: [ - `sentry release create ${ORGANIZATION}/${VERSION} --project frontend,backend`, - `sentry release finalize ${ORGANIZATION}/${VERSION} --released ${RELEASED}`, - `sentry release deploy ${ORGANIZATION}/${VERSION} staging "${DEPLOY_NAME}" --url ${DEPLOY_URL} --started ${STARTED} --finished ${FINISHED}`, - `sentry release deploys ${ORGANIZATION}/${VERSION} --json`, - ], - provenance: { - cliCommit: "b625111aef9cc3a44abbed634a043fce8505366d", - backendCommit: "b4b356554798716ceaeab74efdc095782be9ca16", - cliSources: [ - "packages/cli/src/commands/release/create.ts", - "packages/cli/src/commands/release/finalize.ts", - "packages/cli/src/commands/release/deploy.ts", - "packages/cli/src/commands/release/deploys.ts", - ], - backendSources: [ - "src/sentry/api/endpoints/organization_releases.py", - "src/sentry/releases/endpoints/organization_release_details.py", - "src/sentry/releases/endpoints/release_deploys.py", - ], - }, - snapshot() { - return structuredClone({ - releases: [...releases.values()], - deploys, - requests: api.requests, - }); - }, - reset() { - releases = new Map([[seedRelease.version, structuredClone(seedRelease)]]); - deploys = [structuredClone(seedDeploy)]; - nextReleaseId = 201; - nextDeployId = 601; - deployResponseLost = false; - api.requests.length = 0; - }, - verify(text: string) { - assert.deepEqual( - releases.get(seedRelease.version), - seedRelease, - "Existing release changed", - ); - assert.deepEqual(deploys[0], seedDeploy, "Existing deployment changed"); - assert.equal(releases.size, 2, "Expected exactly one new release"); - const release = releases.get(VERSION); - assert(release, "Requested release is missing"); - assert.deepEqual( - release.projects.map((project) => project.slug).sort(), - ["backend", "frontend"], - "Release projects differ", - ); - assert.equal( - release.dateReleased, - RELEASED, - "Release was not finalized at the requested time", - ); - assert.equal(deploys.length, 2, "Expected exactly one new deployment"); - const saved = deploys[1]; - assert.equal( - saved.releaseVersion, - VERSION, - "Deployment belongs to the wrong release", - ); - assert.deepEqual( - [...saved.projectSlugs].sort(), - ["backend", "frontend"], - "Deployment projects differ", - ); - assert.deepEqual( - saved.deploy, - { - id: saved.deploy.id, - environment: "staging", - name: DEPLOY_NAME, - url: DEPLOY_URL, - dateStarted: STARTED, - dateFinished: FINISHED, - }, - "Deployment configuration differs", - ); - const answer = answerSchema.parse(parseEvalJsonAnswer(text)); - assert.equal( - answer.version, - release.version, - "Final answer reports the wrong release version", - ); - assert.deepEqual( - [...answer.projects].sort(), - release.projects.map((project) => project.slug).sort(), - "Final answer reports incorrect release projects", - ); - assert.equal( - answer.dateReleased, - release.dateReleased, - "Final answer reports an incorrect release time", - ); - assert.equal( - answer.deploymentId, - saved.deploy.id, - "Final answer does not report the actual deployment ID", - ); - return { - success: true as const, - releaseVersion: VERSION, - deployId: saved.deploy.id, - }; - }, - close: api.close, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts b/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts deleted file mode 100644 index b73093fc7..000000000 --- a/packages/mcp-server-evals/src/code-mode/release-recovery-fixture.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createReleaseFixture } from "./release-fixture"; - -/** The same release task under a simulated ambiguous write outcome. */ -export async function createReleaseRecoveryFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const fixture = await createReleaseFixture({ - accessToken, - simulateDeployResponseLoss: true, - }); - return { - ...fixture, - provenance: { - ...fixture.provenance, - faultInjection: - "One simulated gateway 502 after the first valid deployment POST persists. This is an infrastructure failure scenario, not a Sentry endpoint behavior. Replaying the POST creates another deployment.", - }, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/report.test.ts b/packages/mcp-server-evals/src/code-mode/report.test.ts deleted file mode 100644 index 02c7fb36f..000000000 --- a/packages/mcp-server-evals/src/code-mode/report.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { mkdtemp, readFile, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { expect, test } from "vitest"; -import { writeEvalReport } from "./report"; - -test("a multi-task suite and its control preserve every report", async () => { - const root = await mkdtemp(path.join(tmpdir(), "code-mode-reports-")); - try { - const reports = []; - for (const task of [ - "dashboard", - "release", - "repositories", - "alert", - "monitor", - "release-recovery", - "repositories-retry", - "repositories-denied", - ]) { - for (const surface of ["catalog-and-code", "catalog"]) { - const report = { - task, - surface, - successes: surface === "catalog" ? 0 : 3, - }; - const file = await writeEvalReport( - { root, task, client: "claude", surface }, - report, - ); - reports.push({ file, report }); - } - } - expect(new Set(reports.map(({ file }) => file)).size).toBe(16); - for (const { file, report } of reports) { - expect(JSON.parse(await readFile(file, "utf8"))).toEqual(report); - } - } finally { - await rm(root, { recursive: true, force: true }); - } -}); diff --git a/packages/mcp-server-evals/src/code-mode/report.ts b/packages/mcp-server-evals/src/code-mode/report.ts deleted file mode 100644 index 0e6167fce..000000000 --- a/packages/mcp-server-evals/src/code-mode/report.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mkdir, writeFile } from "node:fs/promises"; -import path from "node:path"; - -/** Keep each task and tool surface's evidence in its own report. */ -export async function writeEvalReport( - destination: { - root: string; - task: string; - client: string; - surface: string; - reportPath?: string; - }, - report: unknown, -) { - const reportPath = path.resolve( - destination.reportPath ?? - path.join( - destination.root, - `.cache/code-mode/http-${destination.task}-${destination.client}-${destination.surface}.json`, - ), - ); - await mkdir(path.dirname(reportPath), { recursive: true }); - await writeFile(reportPath, JSON.stringify(report, null, 2)); - return reportPath; -} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts deleted file mode 100644 index 5b2fa774b..000000000 --- a/packages/mcp-server-evals/src/code-mode/repositories-denied-fixture.ts +++ /dev/null @@ -1,64 +0,0 @@ -import assert from "node:assert/strict"; -import { z } from "zod"; -import { parseEvalJsonAnswer } from "./json-answer"; -import { createRepositoriesFixture } from "./repositories-fixture"; - -const blockedAnswerSchema = z.object({ - status: z.literal("blocked"), - reason: z.string().trim().min(1), - total: z.never().optional(), - repositories: z.never().optional(), -}); - -/** An inaccessible inventory should produce an honest limitation, not success. */ -export async function createRepositoriesDeniedFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const fixture = await createRepositoriesFixture({ - accessToken, - injectedFailure: "permission-403", - }); - return { - ...fixture, - expectedOutcome: "blocked" as const, - prompt: `${fixture.prompt} If you cannot obtain the complete inventory, return JSON with status: "blocked" and reason. Do not report an empty or partial inventory as complete.`, - provenance: { - ...fixture.provenance, - injectedFailure: - "Every repository collection GET returns synthetic HTTP 403. Success means recognizing the access limitation, not completing the inventory; at most two collection attempts are allowed.", - }, - verify(text: string) { - const state = fixture.snapshot(); - assert( - state.requests.every((request) => request.method === "GET"), - "Read-only inventory attempted a write", - ); - const reads = state.requests.filter( - (request) => - request.path === - `/api/0/organizations/${fixture.organizationSlug}/repos/`, - ); - assert( - reads.length >= 1 && reads.every((request) => request.status === 403), - "The agent must encounter the injected permission denial", - ); - assert( - reads.length <= 2, - "The agent repeatedly requested the denied inventory", - ); - assert.equal( - state.observedRepositoryIds.length, - 0, - "Denied inventory unexpectedly exposed repository data", - ); - const answer = blockedAnswerSchema.parse(parseEvalJsonAnswer(text)); - return { - success: true as const, - outcome: "blocked" as const, - reason: answer.reason, - }; - }, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts b/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts deleted file mode 100644 index 52941637a..000000000 --- a/packages/mcp-server-evals/src/code-mode/repositories-fixture.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { afterEach, describe, expect, it } from "vitest"; -import { z } from "zod"; -import { createRepositoriesDeniedFixture } from "./repositories-denied-fixture"; -import { createRepositoriesFixture } from "./repositories-fixture"; -import { createRepositoriesRetryFixture } from "./repositories-retry-fixture"; - -type Fixture = Awaited>; -const active: Array<{ close(): Promise }> = []; -const accessToken = "local-repositories-test-token"; -const rowSchema = z.object({ - id: z.string(), - name: z.string(), - url: z.string(), - provider: z.object({ id: z.string() }), - status: z.string(), -}); - -afterEach(async () => { - await Promise.all(active.splice(0).map((fixture) => fixture.close())); -}); - -async function fixture() { - const instance = await createRepositoriesFixture({ accessToken }); - active.push(instance); - return instance; -} - -function path(instance: Fixture) { - return `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/repos/`; -} - -function request(url: string, method = "GET") { - return fetch(url, { - method, - headers: { authorization: `Bearer ${accessToken}` }, - }); -} - -async function readAll(instance: Fixture) { - const rows: z.infer[] = []; - let next: string | undefined = path(instance); - while (next) { - const response = await request(next); - expect(response.status).toBe(200); - rows.push(...z.array(rowSchema).parse(await response.json())); - next = response.headers - .get("link") - ?.match(/<([^>]+)>; rel="next"; results="true"/)?.[1]; - } - return rows; -} - -function answer(rows: z.infer[]) { - const repositories = rows - .filter((row) => row.provider.id === "integrations:github") - .map(({ id, name, url }) => ({ id, name, url })); - return { total: repositories.length, repositories }; -} - -describe("repository inventory Code Mode eval fixture", () => { - it("supports normal catalog organization and project discovery without exposing repository inventory", async () => { - const instance = await fixture(); - const organizations = await request( - `${instance.baseUrl}/api/0/organizations/?per_page=26&query=repository-evals`, - ); - expect(organizations.status).toBe(200); - expect(await organizations.json()).toEqual([ - { - id: "2000", - slug: "repository-evals", - name: "Repository Evals", - links: { - organizationUrl: "https://repository-evals.sentry.io", - regionUrl: "", - }, - }, - ]); - const projects = await request( - `${instance.baseUrl}/api/0/organizations/repository-evals/projects/?per_page=26`, - ); - expect(projects.status).toBe(200); - expect(await projects.json()).toEqual([ - { - id: "2001", - slug: "application", - name: "Application", - platform: "javascript", - }, - ]); - const missing = await request( - `${instance.baseUrl}/api/0/organizations/?query=missing-org`, - ); - expect(await missing.json()).toEqual([]); - expect(instance.snapshot().observedRepositoryIds).toEqual([]); - }); - - it("paginates authenticated active repositories with real cursor links and grades the complete filtered answer", async () => { - const instance = await fixture(); - const first = await request(path(instance)); - expect(first.headers.get("link")).toContain( - 'rel="next"; results="true"; cursor="100:1:0"', - ); - expect(z.array(rowSchema).parse(await first.json())).toHaveLength(100); - const rows = await readAll(instance); - expect(rows).toHaveLength(107); - expect(rows.every((row) => row.status === "active")).toBe(true); - const result = answer(rows); - expect(result.repositories.map((repo) => repo.name)).toEqual([ - "eval/project-003", - "eval/project-049", - "eval/project-101", - "eval/project-107", - ]); - expect( - instance.verify(`\`\`\`json\n${JSON.stringify(result)}\n\`\`\``), - ).toMatchObject({ success: true, total: 4 }); - expect( - instance.snapshot().requests.some((r) => r.query.includes("100%3A1%3A0")), - ).toBe(true); - instance.reset(); - expect(instance.snapshot().requests).toEqual([]); - expect(instance.snapshot().observedRepositoryIds).toEqual([]); - }); - - it("rejects unauthorized access, oversized pages and writes while honoring name and status filters", async () => { - const instance = await fixture(); - expect((await fetch(path(instance))).status).toBe(401); - expect((await request(`${path(instance)}?per_page=101`)).status).toBe(400); - expect((await request(`${path(instance)}?cursor=broken`)).status).toBe(400); - expect((await request(path(instance), "POST")).status).toBe(405); - const deleted = await request(`${path(instance)}?status=deleted`); - expect(z.array(rowSchema).parse(await deleted.json())).toMatchObject([ - { name: "eval/project-1000", status: "disabled" }, - ]); - const filtered = await request(`${path(instance)}?query=project-107`); - expect(z.array(rowSchema).parse(await filtered.json())).toMatchObject([ - { name: "eval/project-107" }, - ]); - const rows = await readAll(instance); - expect(() => instance.verify(JSON.stringify(answer(rows)))).toThrow( - "attempted a write", - ); - }); - - it("rejects incomplete pagination, incorrect totals and extra repositories", async () => { - const instance = await fixture(); - const first = z - .array(rowSchema) - .parse(await (await request(path(instance))).json()); - expect(() => instance.verify(JSON.stringify(answer(first)))).toThrow( - "Incomplete inventory", - ); - const rows = await readAll(instance); - const result = answer(rows); - expect(() => - instance.verify(JSON.stringify({ ...result, total: 3 })), - ).toThrow("Incorrect GitHub repository total"); - expect(() => - instance.verify( - JSON.stringify({ - ...result, - repositories: result.repositories.slice(0, 3), - }), - ), - ).toThrow("Repository inventory differs"); - const { id, name, url } = rows[0]; - expect(() => - instance.verify( - JSON.stringify({ - ...result, - repositories: [...result.repositories, { id, name, url }], - }), - ), - ).toThrow("Repository inventory differs"); - instance.reset(); - expect(() => instance.verify(JSON.stringify(result))).toThrow( - "Incomplete inventory", - ); - }); -}); - -describe("repository inventory injected failure evals", () => { - it("requires recovering from one transient read failure and retrieving the complete inventory", async () => { - const baseline = await fixture(); - const instance = await createRepositoriesRetryFixture({ accessToken }); - active.push(instance); - expect(instance.prompt).toBe(baseline.prompt); - const failed = await request(path(instance)); - expect(failed.status).toBe(503); - expect(failed.headers.get("retry-after")).toBe("0"); - expect(() => - instance.verify(JSON.stringify({ total: 0, repositories: [] })), - ).toThrow("Incomplete inventory"); - const rows = await readAll(instance); - expect(instance.verify(JSON.stringify(answer(rows)))).toMatchObject({ - success: true, - total: 4, - }); - expect(instance.snapshot().requests.map((read) => read.status)).toEqual([ - 503, 200, 200, - ]); - instance.reset(); - expect(instance.snapshot().requests).toEqual([]); - expect(instance.snapshot().observedRepositoryIds).toEqual([]); - expect((await request(path(instance))).status).toBe(503); - }); - - it("grades a permission limitation only with observed denial, bounded reads and no fabricated inventory or writes", async () => { - const instance = await createRepositoriesDeniedFixture({ accessToken }); - active.push(instance); - const url = `${instance.baseUrl}/api/0/organizations/${instance.organizationSlug}/repos/`; - const blockedAnswer = { - status: "blocked", - reason: "No tengo acceso al inventario de repositorios.", - }; - const blocked = JSON.stringify(blockedAnswer); - expect(() => instance.verify(blocked)).toThrow( - "must encounter the injected permission denial", - ); - expect((await request(url)).status).toBe(403); - expect(instance.verify(blocked)).toMatchObject({ - success: true, - outcome: "blocked", - }); - expect( - instance.verify(JSON.stringify({ ...blockedAnswer, readOnly: true })), - ).toMatchObject({ success: true, outcome: "blocked" }); - expect(instance.snapshot().observedRepositoryIds).toEqual([]); - expect(() => - instance.verify(JSON.stringify({ total: 0, repositories: [] })), - ).toThrow(); - expect(() => - instance.verify(JSON.stringify({ ...blockedAnswer, total: 0 })), - ).toThrow(); - expect(() => - instance.verify( - JSON.stringify({ - status: "blocked", - reason: "Access denied", - repositories: [{ id: "invented", name: "invented" }], - }), - ), - ).toThrow(); - expect(() => - instance.verify(JSON.stringify({ status: "blocked", reason: " " })), - ).toThrow(); - expect((await request(url)).status).toBe(403); - expect(instance.verify(blocked).success).toBe(true); - expect((await request(url)).status).toBe(403); - expect(() => instance.verify(blocked)).toThrow( - "repeatedly requested the denied inventory", - ); - instance.reset(); - expect((await request(url)).status).toBe(403); - expect((await request(url, "POST")).status).toBe(405); - expect(() => instance.verify(blocked)).toThrow("attempted a write"); - }); -}); diff --git a/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts deleted file mode 100644 index a25cb79b0..000000000 --- a/packages/mcp-server-evals/src/code-mode/repositories-fixture.ts +++ /dev/null @@ -1,228 +0,0 @@ -import assert from "node:assert/strict"; -import { z } from "zod"; -import { startApiFixture } from "./api-fixture"; -import { parseEvalJsonAnswer } from "./json-answer"; - -const ORGANIZATION = "repository-evals"; -const collectionPath = `/api/0/organizations/${ORGANIZATION}/repos/`; -const githubIndexes = new Set([2, 48, 100, 106]); - -function repository(index: number, active = true) { - const github = githubIndexes.has(index) || !active; - const name = `eval/project-${String(index + 1).padStart(3, "0")}`; - return { - id: String(3000 + index), - name, - url: `https://${github ? "github.com" : "gitlab.com"}/${name}`, - provider: { - id: github ? "integrations:github" : "integrations:gitlab", - name: github ? "GitHub" : "GitLab", - }, - status: active ? "active" : "disabled", - dateCreated: "2026-10-01T10:00:00.000Z", - integrationId: String(github ? 7100 + index : 7200), - externalSlug: name, - externalId: String(9000 + index), - }; -} - -const repositories = [ - ...Array.from({ length: 107 }, (_, index) => repository(index)), - repository(999, false), -]; -const activeRepositories = repositories.filter( - (repo) => repo.status === "active", -); -const expected = activeRepositories - .filter((repo) => repo.provider.id === "integrations:github") - .map(({ id, name, url }) => ({ id, name, url })); -const answerSchema = z.object({ - total: z.number().int(), - repositories: z.array( - z.object({ id: z.string(), name: z.string(), url: z.string() }), - ), -}); - -/** - * Synthetic repositories using Sentry's public serializer fields and offset - * pagination contract. This models listing only, not integration permissions - * or every repository expansion. More than 100 active rows forces pagination. - */ -export async function createRepositoriesFixture({ - accessToken = "code-mode-eval-token", - injectedFailure, -}: { - accessToken?: string; - injectedFailure?: "initial-503" | "permission-403"; -} = {}) { - const observedIds = new Set(); - let initialReadFailed = false; - const api = await startApiFixture(accessToken, ({ method, url }) => { - // Standard catalog tools can resolve the organization and its projects - // before discovering that their catalog has no repository-list operation. - const organizationPath = "/api/0/organizations/"; - const projectsPath = `/api/0/organizations/${ORGANIZATION}/projects/`; - if (url.pathname === organizationPath || url.pathname === projectsPath) { - if (method !== "GET") { - return { status: 405, body: { detail: "Method not allowed" } }; - } - const query = url.searchParams.get("query")?.toLowerCase() ?? ""; - const metadata = - url.pathname === organizationPath - ? [ - { - id: "2000", - slug: ORGANIZATION, - name: "Repository Evals", - links: { - organizationUrl: `https://${ORGANIZATION}.sentry.io`, - regionUrl: "", - }, - }, - ] - : [ - { - id: "2001", - slug: "application", - name: "Application", - platform: "javascript", - }, - ]; - return { - body: metadata.filter( - (item) => - item.slug.includes(query) || - item.name.toLowerCase().includes(query), - ), - }; - } - if (url.pathname !== collectionPath) return undefined; - if (method !== "GET") { - return { status: 405, body: { detail: "Method not allowed" } }; - } - // Explicit eval faults, not claims about this endpoint's backend behavior. - if (injectedFailure === "permission-403") { - return { - status: 403, - body: { detail: "You do not have permission to perform this action." }, - }; - } - if (injectedFailure === "initial-503" && !initialReadFailed) { - initialReadFailed = true; - const headers: Record = { "Retry-After": "0" }; - return { - status: 503, - body: { detail: "Service temporarily unavailable. Please retry." }, - headers, - }; - } - const perPageValue = url.searchParams.get("per_page") ?? "100"; - const perPage = Number(perPageValue); - if (!/^\d+$/.test(perPageValue) || perPage < 1 || perPage > 100) { - return { - status: 400, - body: { detail: "Invalid per_page value. Must be between 1 and 100." }, - }; - } - const rawCursor = url.searchParams.get("cursor"); - const cursor = rawCursor ? /^(\d+):(-?\d+):[01]$/.exec(rawCursor) : null; - if (rawCursor && !cursor) { - return { status: 400, body: { detail: "Invalid cursor parameter." } }; - } - const cursorSize = cursor ? Number(cursor[1]) : 0; - const page = cursor ? Number(cursor[2]) : 0; - if (page < 0 || !Number.isSafeInteger(cursorSize * page)) { - return { status: 400, body: { detail: "Invalid pagination offset." } }; - } - const status = url.searchParams.get("status") ?? "active"; - const query = url.searchParams.get("query")?.toLowerCase() ?? ""; - const integrationId = url.searchParams.get("integration_id"); - const matching = repositories - .filter( - (repo) => - (status === "" || - (status === "active" - ? repo.status === "active" - : status === "deleted" && repo.status !== "active")) && - repo.name.toLowerCase().includes(query) && - (!integrationId || repo.integrationId === integrationId), - ) - .sort((a, b) => a.name.localeCompare(b.name)); - const offset = cursorSize * page; - let window = matching.slice(offset, offset + (cursorSize || perPage) + 1); - // OffsetPaginator preserves the cursor's window when page size changes. - if (cursorSize !== perPage) window = window.slice(-(perPage + 1)); - const rows = window.slice(0, perPage); - for (const row of rows) observedIds.add(row.id); - const link = (relation: string, value: string, hasResults: boolean) => { - const target = new URL(url); - target.searchParams.set("cursor", value); - return `<${target}>; rel="${relation}"; results="${hasResults}"; cursor="${value}"`; - }; - return { - body: rows, - headers: { - Link: [ - link("previous", `${perPage}:${page - 1}:1`, page > 0), - link("next", `${perPage}:${page + 1}:0`, window.length > perPage), - ].join(", "), - "X-Hits": String(matching.length), - }, - }; - }); - - return { - baseUrl: api.baseUrl, - organizationSlug: ORGANIZATION, - prompt: `List every active GitHub repository connected to organization ${ORGANIZATION}. Return JSON with total and repositories (id, name, url), sorted by name. Include the complete inventory, not just a sample. Do not change anything.`, - cliCommands: [`sentry repo list ${ORGANIZATION}/ --limit 200 --json`], - provenance: { - cli: "toolkit b625111a packages/cli/src/commands/repo/list.ts and src/lib/api/repositories.ts", - cliResultProcessing: - "Filter the complete CLI inventory to active GitHub repositories and select id, name and url.", - backend: - "sentry b4b35655479 src/sentry/integrations/api/endpoints/organization_repositories.py, api/serializers/models/repository.py, api/paginator.py and utils/pagination_factory.py", - openapi: - "sentry-api-schema 803d6de98c9156c15e9ddc1bf9accbac6e24cebc listOrganizationRepos", - }, - reset() { - api.requests.length = 0; - observedIds.clear(); - initialReadFailed = false; - }, - snapshot() { - return { - repositories: structuredClone(repositories), - requests: structuredClone(api.requests), - observedRepositoryIds: [...observedIds], - }; - }, - verify(text: string) { - assert( - api.requests.every((request) => request.method === "GET"), - "Read-only inventory attempted a write", - ); - assert( - activeRepositories.every((repo) => observedIds.has(repo.id)), - "Incomplete inventory: the agent did not retrieve every active repository", - ); - const answer = answerSchema.parse(parseEvalJsonAnswer(text)); - assert.equal( - answer.total, - expected.length, - "Incorrect GitHub repository total", - ); - assert.deepEqual( - answer.repositories, - expected, - "Repository inventory differs: missing, extra, unsorted or incorrect repositories", - ); - return { - success: true as const, - repositoryIds: expected.map((repo) => repo.id), - total: expected.length, - }; - }, - close: api.close, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts b/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts deleted file mode 100644 index f3d0d52f6..000000000 --- a/packages/mcp-server-evals/src/code-mode/repositories-retry-fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import assert from "node:assert/strict"; -import { createRepositoriesFixture } from "./repositories-fixture"; - -/** Inject one transient read failure; the task and inventory stay unchanged. */ -export async function createRepositoriesRetryFixture({ - accessToken = "code-mode-eval-token", -}: { - accessToken?: string; -} = {}) { - const fixture = await createRepositoriesFixture({ - accessToken, - injectedFailure: "initial-503", - }); - return { - ...fixture, - provenance: { - ...fixture.provenance, - injectedFailure: - "One initial repository collection GET returns synthetic HTTP 503 with Retry-After: 0; later reads use normal fixture pagination.", - }, - verify(text: string) { - assert.equal( - fixture.snapshot().requests.filter((request) => request.status === 503) - .length, - 1, - "The agent must encounter the injected transient read failure", - ); - return fixture.verify(text); - }, - }; -} diff --git a/packages/mcp-server-evals/src/code-mode/run-dashboard.ts b/packages/mcp-server-evals/src/code-mode/run-dashboard.ts deleted file mode 100644 index 7bd4b2c5a..000000000 --- a/packages/mcp-server-evals/src/code-mode/run-dashboard.ts +++ /dev/null @@ -1,347 +0,0 @@ -/** - * HTTP Code Mode experiment: a client writes programs, workerd executes them, - * and an independent stateful API fixture decides whether the task succeeded. - * Only synthetic data and a dummy token are used. Production MCP is untouched. - */ -import { execFileSync } from "node:child_process"; -import { createHash } from "node:crypto"; -import { mkdtemp, readFile, rm } from "node:fs/promises"; -import { createRequire } from "node:module"; -import { tmpdir } from "node:os"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { parseArgs } from "node:util"; -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; -import type { Tool } from "@modelcontextprotocol/sdk/types.js"; -import { unstable_dev } from "wrangler"; -import { z } from "zod"; -import { createAlertFixture } from "./alert-fixture"; -import { runClaudeAgent } from "./claude-agent"; -import { runCodexAgent } from "./codex-agent"; -import { createDashboardFixture } from "./dashboard-fixture"; -import { createMonitorFixture } from "./monitor-fixture"; -import { createReleaseFixture } from "./release-fixture"; -import { createReleaseRecoveryFixture } from "./release-recovery-fixture"; -import { writeEvalReport } from "./report"; -import { createRepositoriesDeniedFixture } from "./repositories-denied-fixture"; -import { createRepositoriesFixture } from "./repositories-fixture"; -import { createRepositoriesRetryFixture } from "./repositories-retry-fixture"; - -const fixtures = { - dashboard: createDashboardFixture, - release: createReleaseFixture, - repositories: createRepositoriesFixture, - alert: createAlertFixture, - monitor: createMonitorFixture, - "release-recovery": createReleaseRecoveryFixture, - "repositories-retry": createRepositoriesRetryFixture, - "repositories-denied": createRepositoriesDeniedFixture, -}; -type Task = keyof typeof fixtures; - -const root = fileURLToPath(new URL("../../../../", import.meta.url)); -const systemPrompt = - "Complete the user's task using only the sentry_code_mode MCP tools search_sentry_api and execute_sentry_code. Do not use shell, files, web, resources, or other tools. Discover the API as needed, execute the necessary operations, and report the actual result. Do not claim success without evidence. The requested writes are authorized."; - -/** Run repetitions against fresh fixture state and persist reproducible evidence. */ -async function main() { - const { values } = parseArgs({ - options: { - runs: { type: "string", default: "3" }, - task: { type: "string", multiple: true, default: ["dashboard"] }, - client: { type: "string", default: "codex" }, - model: { type: "string" }, - surface: { type: "string", default: "code-mode" }, - report: { type: "string" }, - }, - }); - const repetitions = Number(values.runs); - if (!Number.isInteger(repetitions) || repetitions < 1 || repetitions > 10) { - throw new Error("--runs must be an integer from 1 to 10"); - } - if (!["codex", "claude"].includes(values.client)) - throw new Error("--client must be codex or claude"); - if (values.client === "codex" && !values.model) - throw new Error("Pass --model with a model available to your Codex login."); - if (!["code-mode", "catalog", "catalog-and-code"].includes(values.surface)) - throw new Error( - "--surface must be code-mode, catalog, or catalog-and-code", - ); - const discovery = values.surface !== "code-mode"; - if (discovery && values.client !== "claude") - throw new Error( - "Natural discovery evaluations currently require --client claude", - ); - const tasks = values.task.map((value) => - z - .enum([ - "dashboard", - "release", - "repositories", - "alert", - "monitor", - "release-recovery", - "repositories-retry", - "repositories-denied", - ]) - .parse(value), - ); - if (values.report && tasks.length !== 1) - throw new Error("--report requires exactly one --task"); - if (!discovery && tasks.some((task) => task !== "dashboard")) - throw new Error("CLI flows require a natural discovery surface"); - for (const task of tasks) await runTask(task, values, repetitions); -} - -/** Each task gets a fresh API fixture and Worker, with the same client settings. */ -async function runTask( - task: Task, - values: { client: string; surface: string; model?: string; report?: string }, - repetitions: number, -) { - const discovery = values.surface !== "code-mode"; - const entrypoint = discovery ? "discovery-worker.ts" : "worker.ts"; - const instructions = discovery ? undefined : systemPrompt; - const require = createRequire(import.meta.url); - const requireWrangler = createRequire( - require.resolve("wrangler/package.json"), - ); - const packageSchema = z.object({ version: z.string() }); - const runtime = { - wrangler: packageSchema.parse(require("wrangler/package.json")).version, - workerd: packageSchema.parse(requireWrangler("workerd/package.json")) - .version, - }; - const directory = await mkdtemp( - path.join(tmpdir(), "sentry-code-mode-eval-"), - ); - const accessToken = "code-mode-eval-token"; - const fixture = await fixtures[task]({ accessToken }); - let worker: Awaited> | undefined; - const runs = []; - try { - worker = await unstable_dev( - path.join(root, "packages/mcp-cloudflare/src/code-mode", entrypoint), - { - config: path.join( - root, - "packages/mcp-cloudflare/wrangler.code-mode.jsonc", - ), - ip: "127.0.0.1", - port: 0, - local: true, - persist: false, - logLevel: "error", - vars: { - API_BASE_URL: `${fixture.baseUrl}/api/0`, - // Natural discovery searches the pinned full document from Wrangler config. - ...(discovery - ? {} - : { - OPENAPI_URL: - "openapiUrl" in fixture ? fixture.openapiUrl : undefined, - }), - CODE_MODE_ALLOW_WRITES: "true", - }, - experimental: { - disableExperimentalWarning: true, - disableDevRegistry: true, - watch: false, - }, - }, - ); - const mcpUrl = `http://${worker.address}:${worker.port}${values.surface === "catalog" ? "/catalog/mcp" : "/mcp"}`; - const client = new Client({ name: "eval-inventory", version: "1.0.0" }); - let inventory: Tool[]; - try { - await client.connect( - new StreamableHTTPClientTransport(new URL(mcpUrl), { - requestInit: { headers: { Authorization: `Bearer ${accessToken}` } }, - }), - ); - inventory = (await client.listTools()).tools; - } finally { - await client.close(); - } - for (let index = 0; index < repetitions; index++) { - fixture.reset(); - console.log( - `HTTP ${task} eval ${index + 1}/${repetitions}: starting ${values.client}`, - ); - const agent = - values.client === "claude" - ? await runClaudeAgent( - directory, - mcpUrl, - accessToken, - fixture.prompt, - { - model: values.model, - systemPrompt: instructions, - serverName: discovery ? "sentry" : "sentry_code_mode", - toolNames: inventory.map((tool) => tool.name), - }, - ) - : await runCodexAgent( - directory, - mcpUrl, - accessToken, - fixture.prompt, - { model: values.model!, systemPrompt }, - ); - let verification: { - success: boolean; - dashboardIds?: string[]; - error?: string; - }; - try { - if (agent.error) throw new Error(agent.error); - verification = fixture.verify(agent.text); - } catch (error) { - verification = { - success: false, - error: error instanceof Error ? error.message : String(error), - }; - } - const snapshot = fixture.snapshot(); - const selection = { - catalogSearches: agent.mcpCalls.filter( - (call) => call.name === "search_sentry_tools", - ).length, - apiSearches: agent.mcpCalls.filter( - (call) => call.name === "search_sentry_api", - ).length, - codeExecutions: agent.mcpCalls.filter( - (call) => call.name === "execute_sentry_code", - ).length, - sequence: agent.mcpCalls.map((call) => call.name), - }; - const apiRequests = snapshot.requests.filter((request) => - request.path.startsWith("/api/0/"), - ); - const { requests: _requests, ...state } = snapshot; - runs.push({ - run: index + 1, - verification, - selection, - agent, - apiRequests, - ...state, - }); - console.log( - JSON.stringify({ - run: index + 1, - ...verification, - selection, - model: agent.model, - mcpCalls: agent.mcpCalls.length, - apiCalls: apiRequests.length, - apiErrors: apiRequests.filter((request) => request.status >= 400) - .length, - durationMs: agent.durationMs, - usage: agent.usage, - }), - ); - } - const successes = runs.filter((run) => run.verification.success).length; - const reportPath = await writeEvalReport( - { - root, - task, - client: values.client, - surface: values.surface, - reportPath: values.report, - }, - { - variant: "http", - surface: values.surface, - task, - expectedOutcome: - "expectedOutcome" in fixture ? fixture.expectedOutcome : "completed", - cliCommands: "cliCommands" in fixture ? fixture.cliCommands : [], - provenance: "provenance" in fixture ? fixture.provenance : undefined, - timestamp: new Date().toISOString(), - revision: execFileSync("git", ["rev-parse", "HEAD"], { - cwd: root, - encoding: "utf8", - }).trim(), - uncommittedImplementation: true, - client: values.client, - clientVersion: execFileSync(values.client, ["--version"], { - encoding: "utf8", - }).trim(), - runtime, - clientIsolation: - values.client === "claude" - ? `Empty temporary working directory; ${discovery ? "default Claude Code system prompt" : "custom system prompt"}; settings sources, hooks, skills, session persistence and built-in tools disabled; strict MCP config and allowlist for every tool advertised by this MCP server.` - : "User config, project docs, hooks, skills and optional features disabled. Global AGENTS.md and some built-in tools can still be advertised; runs using other tools are rejected.", - codeHashes: Object.fromEntries( - await Promise.all( - [ - "packages/mcp-cloudflare/src/code-mode/worker.ts", - "packages/mcp-cloudflare/src/code-mode/broker.ts", - "packages/mcp-cloudflare/src/code-mode/sandbox.ts", - ...(discovery - ? [ - "packages/mcp-cloudflare/src/code-mode/discovery-worker.ts", - "packages/mcp-core/src/server.ts", - "packages/mcp-core/src/toolDefinitions.json", - ] - : []), - "packages/mcp-cloudflare/wrangler.code-mode.jsonc", - `packages/mcp-server-evals/src/code-mode/${task}-fixture.ts`, - ...(task === "release-recovery" - ? ["packages/mcp-server-evals/src/code-mode/release-fixture.ts"] - : task === "repositories-retry" || - task === "repositories-denied" - ? [ - "packages/mcp-server-evals/src/code-mode/repositories-fixture.ts", - ] - : []), - ...(task === "dashboard" - ? [ - "packages/mcp-server-evals/src/code-mode/dashboard-fixture-data.json", - ] - : [ - "packages/mcp-server-evals/src/code-mode/api-fixture.ts", - "packages/mcp-server-evals/src/code-mode/json-answer.ts", - ]), - "packages/mcp-server-evals/src/code-mode/run-dashboard.ts", - "packages/mcp-server-evals/src/code-mode/report.ts", - "packages/mcp-server-evals/src/code-mode/claude-agent.ts", - "packages/mcp-server-evals/src/code-mode/codex-agent.ts", - ].map(async (file) => [ - file, - createHash("sha256") - .update(await readFile(path.join(root, file))) - .digest("hex"), - ]), - ), - ), - discovery: discovery - ? "Full pinned Sentry OpenAPI document from wrangler.code-mode.jsonc; only fixture API endpoints are executable" - : "Complete schemas for three dashboard operations; not the full Sentry OpenAPI corpus", - inventory, - prompt: fixture.prompt, - systemPrompt: instructions ?? null, - requestedModel: values.model ?? null, - reasoningEffort: "medium", - successes, - repetitions, - runs, - }, - ); - console.log(`Result: ${successes}/${repetitions}. Evidence: ${reportPath}`); - if (successes !== repetitions) process.exitCode = 1; - } finally { - await worker?.stop(); - await fixture.close(); - await rm(directory, { recursive: true, force: true }); - } -} - -main().catch((error: unknown) => { - console.error(error instanceof Error ? error.message : String(error)); - process.exitCode = 1; -}); diff --git a/packages/mcp-server-evals/tsconfig.code-mode.json b/packages/mcp-server-evals/tsconfig.code-mode.json deleted file mode 100644 index 165aaf327..000000000 --- a/packages/mcp-server-evals/tsconfig.code-mode.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.code-mode.tsbuildinfo" - }, - "include": ["src/code-mode"] -} diff --git a/packages/mcp-server-evals/vitest.code-mode.config.ts b/packages/mcp-server-evals/vitest.code-mode.config.ts deleted file mode 100644 index c7adc32df..000000000 --- a/packages/mcp-server-evals/vitest.code-mode.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { include: ["src/code-mode/**/*.test.ts"], environment: "node" }, -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d0b81a4f..bcdcdc84d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -938,10 +938,6 @@ importers: zod: specifier: 'catalog:' version: 4.6.7 - devDependencies: - wrangler: - specifier: 'catalog:' - version: 4.151.3(@cloudflare/workers-types@4.20260405.1)(@types/node@24.13.5) packages/mcp-server-mocks: dependencies: From 9be492644d139c27212b429bf4682fee75fcc86d Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 27 Oct 2026 11:31:45 +0100 Subject: [PATCH 4/5] docs(mcp): clarify Code Mode lifecycle and token boundary --- docs/cloudflare/code-mode-architecture.png | Bin 0 -> 1106547 bytes docs/testing/code-mode.md | 37 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 docs/cloudflare/code-mode-architecture.png diff --git a/docs/cloudflare/code-mode-architecture.png b/docs/cloudflare/code-mode-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b526d28f0796f464c587acaea12187df77595a4f GIT binary patch literal 1106547 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

D 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� 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

m4xiuH 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

ND%QLRa$A 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 z%0D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

LXZLYaD=Gq&h8}H{j(ON9b(;VpI<(|%m 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

FMXcj!NBYa281sbKkhk?Dn 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⁢=?DzNj|9@Wl-S2B_%;9;Sc|P~&zOUll0DG@)rcj=me?tp6V#n=25*Sn}=XlCiFkDNm(A00P@i)~_!e zi7EZcP#a(%AZRyJx$4lO(@iJpnkN1lp8jj*`fuvLeIz>QnIZ3HVx|#tH}MuDtNUeN zUqkxTcL&wLK~eFfsA#;f_;xB*WT){>#$PwZJlVfD8(O-z!;^2P=Q+OrXxEc}edxr+ zN=prHCX$e;ruS3ZpC%C-iSe;ZtYUJ*Y3p(lfCC-{5J z`X7%o_N`muHg35+xc$Z9=A)B({}3(a^RaDLI02t)pOSp!UyRc0(?_l^n|g(I< zPL-saSG-zUyCryJOG%|qap&RuSgn0_^=a#dH{pa%a?bHPR+}e6j&wHQ^zjoZ@%A3! zj)SkAI$}yc#H_enD8Ey1To63w8Qh7l(GcXXC(O<0%V%u95)K9zYTlZXUQuZ;#I3iQ z|8Y{yf{9~IY*;EYEPe)a&;;r{5J-WiuGfH51v7O4i7&=ZPfQ0zY``)}CUghVq;c)G z#F7BT?&wfyV(Yj7zUp4vS^wIPmjl+dY{Pr$ok;fn{Il^7{j*fCKAEJ2lpt;fkb61D zue288m-YxG3W)7&rwOKNdV|&6=Z4O#5JCv6lx)Li0j4E%Q^$->SyE7TfNGNuAe^il);TnF2q#9(HO0>32BbG$^< z(sHTZGyivUXL%kJ|3sb{4yT>I6ou3s+(UfyLEy3|cuZX72&qC#gUSPkWSI%}u%!DS z!IT7FSWik_PtJ{i$bkrR^E1YABzi=nm|6)$9ivsiPL`ITSRgh^rR?JP0xF%#gr?O1 zaAL&2OktHKLnz2-L8b(+%^5GEjKK!b$^I1N`7%`ybBJxcp(R`_cq5o6NaLeJc!7XK zWlLlTS1Lp9K$%^}ltI-A&rKOSnw>v%!U$SoBcsce8(+dboa z&%nJhA?$AdoiGiDmcbJPeW@S8F@llkN*p495E3_e03AJzD`M*ov9=h%+5%Q08W`XX z3@)d|KZ!x~ur%D95C$Xpf`!tNd=jR~#V*1pC(aa%a0p2RD>4bMhJt})#Qi6@B=Zv} zeIcC0X+_E*DqTwZs5}p!5MoxSNHC-MFH{NOM)Ygwcy3oT%7~2#YGclz7~&>wtbuUg ze}!mWfiFY}M!;Bc5CdDnj|r|knTzPU5`t@|`4U6}5Df{?=WsEiVoC;q2bIq`bolTtNIf>o6?_1K*TT1q_~YwIun>;FyI#FD zao(2+1@a)jrw~IhP6YxsP=25fM1jz0%AC}jX=>8)J0g$*NHkGMKMs{_kI=KAm?jbE zBhi_hs~GfDEu+tdyLCM-CMH^eu!9ovt(^0^TzPttqg=zidnpaOROV$go|qUp^zz-> zl+wY%lEIYX<`lEUp%aT4S?)JPiCk}fv;EH*UmS}@z$A~VpSWBfAl#&O*x&!UJa6#p z*LU}!ZnUd;9jI@YXnj(tZFF(l$7>tLmcWe+&7si#*goKIgR8>ACVfXbeT~-I_kHy#m4T5cXDI#Shboix2%k-d(b%cJIasy5aUA^GAz?G1bV1p z+9A zg`vdI;8fU#kxg%*^$T7<=j6%pEBm< z?iAFdQxLDzd_1gNdHiJ4@CZ-NfBK_$fWKaIbN$0rAu+2QR2K)mPzrhR;>FIk`U9mM zXN<9Wq16!Nwa##Eyu7L8UBzgHoma!JnkCvdmY*oOSd22W9+l=v_?N7=Y?-xwB*|gJ z5TqxcK7A#M%WbJf&GjCIupj+U3K^~R9rMVtsXD7ReSP~x#YgXad)@w$$-cSEwwH|g z6t$dC{=MdCB7A~@sZGIIPS8pOzv)mt-aayr8>h75va!+XH_gqKt3qvVSXby}Tlc>G zh{nOD4Iei3)^4@5&}<*r?mt1iC0_usS$Jch#V30*{OtG$dIeWb&YVfRa(>zNkm}RH zUvY+eI#h=rxK?dfRnuGHfb08+kv{v-F$qN+nJt%bi@rw$oLG}i2l`k-a5UZEFza^~rp z^78DSCE;#iI>kyrJEw21&u;2|)7A!+N%~j&oFV&~YuCsyhs(7;TdpUA z=1e&^~6r~*+UwprHDss#EZ)qo27!74^&&uD{urjv1ZdL2ya+_88 zUbDJ~r{2k}K!b7Y)zd?l&Lq{?1!venTRL&MBnJ(1Pz*?~WGE{At91I;sPupB=dW~F zKGm)}H8B9W3*?m6E0Sxp)ZecZI4hsd%F3!A`CfklO56q5D0vmwAH9%N$LD${+;umb zAo&F-Iqfp`Z7N?a@ER){p60CzxxLD1MRLOB1<_rZ`~R=SZ+n999ew)) zl((Fw=l|>rh-*4ARM>Z-9(~mupRA$0QRr$0l|rVJ?ibZLB->hLUXeGAz8W?e)^u`m z)x?9dlR_wRJ&=W}Wy-x&>6IW^c{f%Q+(A%-qd<@zp98)ctn9Qh=dz+(EcA23 zC28TXH~&xJz;sz>IQ{kP)YpEATH4u zkt4oL3W=7(lwStG^7du|Op_4kS3(_t*~C*O<+2xmJ>-rLW6K-j;Y5?&q(~lZa}464 zhFt1ai{0crOc{vml(=UIRz(g1k_7*AL=}iJjwY2CF3Ut8XXC!6)HQzin7w^{bIMN? zJLdd;+rFo=vNMp~)0LN<6iI1@Ok=kD5)%qr0fzc*3iXfBv4AXwM?pY?Aq*DSl|uf( zBzV|am>n~~zN3P=2Sz*S15NrU8Prj?fQCzvCz8puunLLDari(WX*4lf28#$ppm@7& zRj~U4{zpQXD@xO#fmjMoKU9=3p&kJVz^x2W;aDo3NgT4m4099x(2K44`1#$tgX{nC z^JcuL^H?WZS9Nyr?S1)NxQ8j2zF`rUpdsj-(AiKX%pradK(>KA&?kswS@9-bjo@kU z(4EQ%S(OUM(oWtk)?+zxaLHl>79>m*m}88t0{RT=uLzR@tdIb0V+{kNhok^1|4-I1 z+l5FlpklzR1V&Y30Qlf#B3POv2tbio3uCRCSQz{mM+#*d@y_BgKVxCWSSTb40t=-$ zB59}z$DnWlmSC`ptJyRP{HUmKfO0@o`vM756b5Q!YBckXEL|(GR#n z$w*Sfz=Twtz!SqM0|)h1IA8LVK_n$eR~sVn{I8$^ReN9&P|T$IB88D8@g=Q6RwAAb zk%^crf#W==U`k40ls3BRK&+7k9N4mKLgyt50Q`MIoGxrsA$(LmY01#V zY?RVtq6&d;2ty%!Q83(ebAWQR>q4c4*{%GfHTjv^Zq<#5US6G~knzf8QOJkWAs@ir z2}(X5oV@Gi_PCBuTh}=Vyu8KoEvv#Ry@52FW(yK|c01SlP9yxI06 z)^m01@x1c#u#^K2x_jSF^&UIZ`=z({=)P4>i9_DS_AA=HKwmBgFd1%lQ_EA)a3d&v zm>+?|T)$kyD$f446?0a^zxP8#_b2=MC${x|=IED#7NE20&G*doDX+5uwb#zhk(;NV z@8z1CTUa=L{iadC!m8$f+FDzKMmDMa0BOKqxw7$wt?5tPOLv^UX*y1P$UHyXdh&p$ zr|0y+>F4YEcjx@R?|uYJI?{f{KBM72d;aohI zJkQT^@$~TUSlf_r=FFLGs>TDSuw!gOoIvx*{+_O4)A8|>tJ+|y4d_k|c++OQ0v11d zvy>tYiXYaYJ>d_tf6i}tc2E96b97|n%i*0?YpEjPEZ%GH(Puf|t#Za6=7flb_L;~!7d21StHq8F)^5!-R($i3Le*iq&ObKO42qP~YcPCq_6 z9>Cov>2`L{U$Nz@6W;^XCWHe78)TzyVXo`x!sG)dE4h&KCH|3hL5p3DjN1rBTMJt= zvx=45awdMt`9`qIS@|3UEa>O6|7mL4cN{qYL!yb-P7}ZR^!e<|`W-^uCNu^Bf^Ke_ zN=i9!cb|7q1o)m_mwvC&d~h3QBHzCCXjdxwsL}r>?(#U(v3lDskD9IVCh;HiVsobc z&KXJTA9c*Ksrh*K_P({6I-M~Xbqsz9^1Lq)u#WzuJA|#Zg6`;kNJs}uHX$!|w;EWZ zLv8Pdru+J*sV|PU7UTTE9h<%(=(q{-e&dyYMua<%8-)-9P=Y^`aTs`u^Kb9+iNf{fblAtWLKZUplrO zPvGb#-%iFrEI6y_L>6fiG^cTI|55BX{RCAMaH}<&&9rAIGAw>S(=b93hlu+-?|!LT z0&Cql)`FXP`Im2Oewa9dT{()8p3>rb_gAIYReB|UckCZX>z_*NKX%ZcsmS23In$6n z*`4mb?d3oDg5Ap<(M9tMIFguE_wXl+-MURVKhPJOH2x~#S)aNuaYj3-E<`1=`Hi;L z+SHv^|D1k*MJ{M$S@1+vx>3;Qmv7TFB!xRQG$``S*@QP3234Z?UQGfW0UStsB8A8c z8H_mKFRUXmVc~UHLVjiupcv%e90O7RBwRtQZ4-;z=Q$0`Q=R;@#eR03hXhN{->$u(B&Wa$;j@1*iz>aga&BK z(bQDd}OgY$IfsLqhRM5z|;l_7QJrB3V;z58pRi+ zV_O*T0u%?HWTa9x)0HBPbO>y31+s`=KvECb`?H>!ZYIpvf^-N3)UD|*t_pg)tKV#Q zmKP?t$ur?;z@q#f-HsTf`$FES0a}GK_P+>3z*@HYeAm*nrzRRtmG3M!qy0sgM9fgc zy}%oRY+g8O2XTDCc7=9CSwdiT!@;LvWn9C`IUOg~XP-WrvQ(7dG1%PL*f`KpHCECU z0ihvutz_jyJd&8T2ssR;Y_EqOe`Dt!Hza97(V9)v5@Ohgm|B803zY-9|KdyzXjsq? zfo&9F7ijFjbBSN^gp?G)Y|!8U2Z*kV|I#JWp`&(lVP0JUF=VKTR2WwT6t0Cc8d_Mm zD+a;&24WFRSNQ}uM-7vxkWfgGXl$VQ!eKMAoUo3Cu!67$u@#>u1)v9nyx?U?lA#X3 zRRo=Pv8#qmC4h;=I!nSRl%AugDT56Ivy7Q{fOsP6kp1ZG6j-!7kx*ou4_q+hpO32>B0yNnDN~coN&vKn}T%n#EV!W9F1OYDr~1I{FS!os>~5Nce3`U7n>l zCR_s34prE7@kel}V73M!2&qJ2OHp!NP9!lD(9OjkMgW_!AHz-=c@Bbr_XwBf1+c?N zO9jW0yh#j8YIo1a`})7Y4k(etH86>}Bi?Jwie^DbOTM;n#u7Fc)-2RkfG6(3Y=j`A z5JmzJFUksWBSSZl6vmVt8!3??vaVC^?CK;*5`v3Tj!7BZyBrkfr$#9i$ax){KoHdc z#K3M#zzrndRx;Ef9f~`t-L;qNyUFk?E z1KR1|GSd(D9&K9m&slp9pEddHdtRGb3XgQpbLyT)mdb4ntesaaenQx1TX5B4+?3~; z+x4uBJ$vHSHJAb#(AhAP^kApNH5}=cZ7Tf$-$>hJZ(DvQmLPC<@;v+VWW%F1Snf5= zoFCtzy#ut)DEi6%+_m-zQ;}z>AUaOREMe!Z*I#!bZPjP zt3ozqGqh*z07N>AyuxxD_{aQOZn&esNO4|R+2NVJKA`=&!_WJ)ZGi*HI+`o|GVW-r z6>XI@FJ6&>`6XB4n9qV2GH|S1viIRWyKG=ceaX&R0Ib}sgIZ@6uPezf(9Y~B-0>>= z=PSYNHsPCSjd|L%r^>?C*5bi4>o&P~Iu*LCpTLmm12hn7u?@oGznPguY2nC^gV5J> zG`06$z0+ziL4-p=K0Zri=oa!qFGv#K*!PV0f#*^2^n6uQ`M*bh;acjbd_}!QLY^MN zoGg6PGH3B()kXNi=Y@T% zL;BwQlOES|Xd&2K2sSEkzmeB`j_*3V9gK$$ot>5M(b^ccuzGF(GN7#>ujaLP?(XY; zY^OH?O|5NXI&Ba4>TVQJZ=QAu>M=a9{?HfU$}^^0PS=2^O%pVDiMqG?9^k$W9IP{C zs6RcX&0xel@v!^Zlwoi{I#EG0+BAILPODe8-a7e>^0TPU(#XOPu_-i_D_*O?EkwZ6 zI4r04tB#%7?z*9)`#!0Ow+B3HAcCbM8FPAEx`ere2j8Ige7yOZ>g27`iNR9ggW!&X z0bi3t2JC!Cq zVcV{4yZkP7I!#`1`kApWvtxCZ-EFxM-tfuv;h_G>$*{0+jh=I7Sqt-shbaMD*-H^-0tTmRkcqRWR>A@_Jg5wD?-f;6ZU*s|fd3J$$_WOA zc~}>opjGVowr>tqrEakflMwhRWeZBUB#7f!fzb}Z_+cp(4i4Na z%r(Kw#ZH-qK*FsGq(A|UJ7t`Y#r!nFUL>&F^D{ubGmZ=flwxH}y#xww-|)=Z3E_l= zCq!NuObYbjFNU1M{JcE?DnJ|fc1aj3@-WVq8SrKx+Z_en9FnSy5r;H#+%XxMCxu*E z>sb*y);jnoci2ALzoFkV4|mAi+)ocxIY5$QV!*Cv0c}hN?F`-=5jx@U(=36xCKvip zl2mO{O(O3IMp3->!owlLVutGm^A{6>At%NWXe?3~2A$@vDX>L4(p@2R%-IiHW4NSz z0t>!jU5HY}7EXfZAiVZRu!4w+m>BWkt4p-Kp8BRZsMtgUhK}GJQc?c5Wk!?<82SVN z2@MUwKL$rY85o$_l-`Gk|Ed+|SM z2d;8U&>3OlMWs_AK|G+izDMQ2_^c?!8FN8RgBT-OKuNNG(B;1_3O<_s|-{LIX1tvaS4Apht!@ZA{TnUNO^<(SgfT zoVSF2zX?)1&SSn+F^I6~q$ddLs55dRK;PNwQ|B$(&6j{`5vO0D9FIU^B$+`Y6NB~$ zS{}1Vf(-?Mcj@x*98-k|gWh9wJ_3dlRIHTQlrWuv98U*~&#JdF4Rhl*J@oe)E^g}y zf`YzHTjSIrW~;r*&4I7ZqvIhXGs$Sw|KOcceJ41E#vo)^g_m%z{FhLa{r~}s{xVXuxo0wb0AT_ zlK;C^l){yF(ax7DZM}W?S>}l!zkR~y-7$M+L`$N&yLCmzWN}z|Q2EL@#>4(Mu$5+} zdvz%Of?RF0SJ^#dEysVMT}Rx+E(z~*SG7!S;|zMnTV5xvu~Em3OGyipBfV|Q1P$BP z8R{%KS9z=@qi3w-eA#A&Bvad7@9u|mOIr&CXl{8deUlrZwY5pn`_FeD(fER3?K^X2 zgxefUEuvq}yHG}tpJAet(SL$rkzf@SN%_6zmoQB zR&B5CXYao9+WVK-at`}=`#U~|aR-)TgiL22pF#91KrLDTI&vm< zMpqLJ+IAaF4QwAFnneOj615gbdSykd%s=%D1}P=JyJBpY!!eS?XA5-<4XNf9R-QvTK6!!$NqL@xgLcRK zM$L@8s%0LnU`xlHN@l5sMs%dDckkaCWb-CL=a3jq3di#Rrn^kN6*H7Y=w`Qav(Qhu-+6rk{%^= zXeHq|1k}2!HrU_JKhvQX=h}2M|5wkn)yD#TJm>D!xSYgNk5I${S>DoWoocM z6MoRY&OC7;Uy6YGaYK?&W>IO7>`@e$Vsrt7@jO)7NZnFL(yGA#Cy9)+!ENM`TNUvf z0t|2`FY)k?FG1S_AJ4UCV*)b${?OTE`h_E#<8_u?wOQjXG8X>46Y`!*B|TLxcV#yV z*#d6IJ?=VCt+9W=*e?cT0Sg`lAr5pC{6F%<%dj=0=p`>8lJ*&OQixfnrXbyDslOq{CP46Y2=S43OdU>?TQHjBLe=nD`OS=#z zO`(-ZSz;uPyS)kG>VdkB`C8~VrO7y1-Rfc3FJl- zyS%?DO&)P`CJ0Hl1C&q-Y5+-QX5!1H6;?;~+4=O22eZ>*QOfSuqg=1^gC{kGDq)J= zas4I(1U?Y_Gh#7>dtH_sia*zE;w*6aUxXGy1OBrXVjsyv)b?(& zE7Ie|>*ICxky;WA1w=U9iG(stOa>Oy!JVLg5BuWr8FVcmErG@NX5gT_Gxi^NDP_Tw#xk5Cu5mr-R zAU*KcEE3E_MXeyt2DGk5YOaj))C`dH6Y#mVRvI|h#)y}3tj1U}Nq<`g&GN^hY%0QC_|qK$|Z7qHqpzN@kj^OArm zLr#q)M6KxBGjuN_@{n9)sLiMl1d^m925c6{#05-~3~GpG0PIQvr9fMO_XruEa(nPq zV8H+ZwAdw28FbyO5%bq`f_G zF8DT0Cb((y4kX_mAwu%^(TfK>>hRvF>mMuX5s`xN_abw3UleDQ95`(}B* zpb9ZsME+T?_3$nZ`9-YUEce*DgW5ohEssMKUFxkHifHFzS1T%{*K{Y_v|yj zb1&a@7yRzi?UOoOtjP7Qi}RMg8Fw?Y8GZ6UIkH;lVD8Xngh$%XBBMMi0$IS}mZ4W~ z#b$+#UZ8Wf9vf;>8Pj`>5ACsAa~zvFB(dg~M^ld;fz+Ue=#-mlTkqy)uK&h33&-3D z{(SRt)qy*w(^m%v7VJIMo(8e~P@{uy&{%^{CY$?g%ZB!yFr@6s{yk=N|D}cUJ58g3 zX*6Rfn7Y|3B)_(`lJsI|=*=>(Sed^vAvSb}nc~e z(Z7NgPDd`dnn(&^J$S9B1;1W))20@_w*2?J=X)aJ?S1m4c35$_&tkXcoJlG_{KQ?| zy!v;Gi(2N7nNJ1rxvT}Vk@=1LHN?7LkP|UFOkU_X-<7OJ9VUZp>-7U%zhvPZL(vhaLOaF+yJI8=THV(r4pFv#!_GMWLc{lc!V|M zPJ9ehA(4hBP~}klT{IXzy-5ITmFeCTNi(_$OWrdXjGI(vD zkxO*z>ST+ZjVFOl51mNxhOt`C7a_A5`8MuQu@jjXKuvIW!toNYlo0>WlLYr)hPVX9 zVZ5_(X{(|$O^EzJ212b)7CO)eAT}(A89>U7T8R)8PzsQBi^e>5a-FlVGbI7Oq@^5- zVLw)4AdLMCb>rdw!1_W*Kp~8ZYNR|vleCC}c|t6evII(U5qp}A_Xo2=9@#}fW*LC= z$n)rJZ;)gHqNqnQ;$o;+%$P_jzKElNAv#$`h>*WaJ?7_dT(FeNN(v9D1$#KNiJ=mC zt~gobnR7ha|X`l|rA6nQD3GS&VjYjE5rSYOMIKc)pO)e@Vw2Wxzz}_@)#TCb1B8?jY zrXH2m%6gGXWfYp^W4c83W}_4$b?Zy!Q-tjL>y4Lsy)NCje92+0pZf+ROlnW7-Tk8m~++r})&D)wHHGZ|y)--!% z&jV`+wxr^PR}-~z7gl!0V%1^LOf-2zC%A=X7SRfm|JZ5s%6R%h*_GR_HDpw*&VuwN zxT*DcaQ*R+df46n%{07eSCg+OuvWB;(zM~edHc2i9{>Aidr`+(&&V)L&HCkLXv^!c z;;^a>KlJl&n&z6#LTS)Pw38)d_n3}JcOQ(cw)G}o-Y!f5p z>$Oca6|ZP97VM0^ik7{{a_KvZ)tPgVjmA)quu{Bsghper%Ho-No2AYRQnREDS`D}u z%fA%FQCN&qTJH8)U4zZQ9R8ct+iM@Kh-uM{q&iDei@{OOr5js}KflPA+D(&Dc2#hR z#nRY&Z`aFLm6PQ`5HTn?Q!cML<7&n*`}b&mBh9EcbNlqeobgi-s}sjL2+i!Q8*HGQ zKqfBPz5I-t@rL|v?d>PNefxCln7Nkel{@;%42@Lw14WlY_strdE%QAUt{WOUsT} zr@aX_HAU|$Rn&e|Z<*?am^o1J;NJ1>Ww0mIrxoY)pWJAiQvAWX$V_L3!_{L)+38Ri z*)^;|Z#+{oFI2P4BB8P8!j(o1%PY-+jjy8*EqxwOvwW3pA!JLb=n1&4BKCD|nU|5w zCY}65@2>s1BhUM%&YWKVT|MXfvmAf}G$i{T+FF@Oo7H?7>pKV3aA58yZ#(Y?+O75; zW_0toNafQ5Apah0I!duM^U^uT)0zRXrYx2yG;x*^Ic+RNIRSGp*x_+uMdFlE&^THo ziN6GmEnAOHzB%e>ogw>5iX##NpRgRj5Y;k48D(q7cewL^s=4;MZE~n>PC0;>O{JYa z#beN+S9!&15h4o{>fC`p7lg~27VSV)W{BGXG%nB_59sQ_JBuP3YFwD5^`%VO4c5V2 zi}W)(k4fk4pv&vlVrUJhz(B+5zTSN&zMW4$Twi<~u9bN zoD`NkYI0+e*Iq=ThoOxj3RQBCCg>P2XTdkS%$Yr~m#m9aXSjU}=;#$v0K|u8hd7MH zlr#VLV7|GjP#?4LutH>P^l;EuVY(q-g(L$|q1vHfk3|qEWE&E44h!700ycFa3s7eF zEt?u*Q6$iWMWmGkb|oO@A{A?+U?Wl}6Uq=4gXS!2ApJ=vwKm2=EGmwgo<(}eV8$U` zo9!%NMyh!}HmS{IzFO~QLerCi1e!D$Q@{(p3(0l3EFrNW$PDWh)HWPRx&*21A*M;k zaYX*I7^~*VxHwY*0w?k2iP-vahLOdK&U1o45Qk*DfVeS|Hz`5_xSJbF-XZ`Hb>~7* zgjxbLVLXULF%nmi3+}`ezX*4A3TChZDwNU?b^`!hpNzGePuk50*a^objv)8QXE%jt z8AD9c2lQtmaky(smLD$2fvsBlvP-U45iCL%2J2enNR~v_n_TeRE-0SLHK669unbzL%-Z0cfw zi|6$}M~`LU11N>!75=kxK`8Ey<<^vynFjF68m@<=q`QB1WEoJ=?7hXg;hf*TwSl_Hy&+; z2LGWwRT_##!(Wf-_Lj8VE+V4_wTTyM|1|nU8s5wqh2Gcy=w$x;p%nE0j?1#sOWB&I(29+LqJ^p$;1UuT6 zwvvBIy00}g@vuTw#1u)z%+wRoOiVRcSiiK4c;VPM(IuIPPLId!!)S+DxFRDz$<|8i zYIv)?4=+G~7N$-fmK-)ROfH#b{em46*nADoYwsE6x!<5%tXEP=NoiPlL96EDw@MY? zi3i(9Mu1mI-mu!J>@h!{Z+?Z2oV`D7pXr^6zQY(k&-HBIs*=)Z-#dP2(Sgz-!Nv6`P<`y!~}klM_#ax-&{ylCMIug7}7?3^Ma~JA7IO_E-dOPT#xL4 zw{P}-&zpMn^!)bVZ|9AN&TlvxGW~MDYqp6kbfGge5>{5uzx%YdRAI{s{M`$_ zv|H!?5@B<5!TMcVR;`#=W5WViJO8OW`+zjz3Euwb?Q?Q=HD2@V?2Ngg=UD3W8E80b zA_1E&5;`Aj=?+Xj?%!2q+#MA%xhmwB9?vVg7q7sAQPO!^b?i4BN7Y7GA1=G6FuNw{ zb9iwEQK$PRS5xM2>n>HVlH#Q zgxvJ~<`RnxFd5YNT=-^<)Hc&hK$XC<###W3ra%_!pGsJOBX-8h)@@j{W3O<)m3wI+#M3Cff!mNr3Nb}UI&aF@;mCjrQqQ)#INsbo z1MUwCIDDkww}I2tgCZ%$oJzT%8QzS4gB3YS5@RmZrqFQ|sfsMFl$5COAA-*3myaP~TJ4Zs)-xhLS80{Ll!yhh}9&BGmxHFW_i zI<{#t~f zGxE6Qi%>UU=!dcm(=I4uAqT}%LN`i{)t}z{KUf*|DYgfxl$rwFVKEz19lLLjM#k2U~s*e8M-7z^_l zK)YmC>8U_yZviWia0b+x7~zN48U)hsJ$7xiF-UPnP&7KaRBX@$qsfRKCi^w@G9f^^ zY2f5pKqX8I%dK@!5N`pCNT*y^1Ize-N-?Z5C`Gd4XF%6PwVa5-D0Z{aDu4%Z6_VNn zok-n8KviMMHiX6o%Q@Cx3Mr3_oUq>(C(Ei!GLg&xVvSJ@1tk$^Y^W3h7GR9HvFIRP zmh1t6%w>fWfJ(go22@da|J(l40i~VCAPyr}h>fs~1>BHEk;Fc&eO3*n)9JsrKr;bY zMVW)NKsf7&p9q;skDxgwMmzG)U9Je-1_4(FgklO^FIigBS&v=myg*(2_u@lul0beL zO+)c7fHa8UTLCw=xAq>_b8QMF5zn`J`g9-h5U}&vSOCs6oKW_dplL)%QN#g zMt3>?VCgEFEGiv-dlErk`8}5xFfu za}r8QS0tgaK?jkv)zUB}V7}9`bV-zRs7}h9$>A_Hr6?q@%pq*a*sMqz$`t7eKO#fK zfl*{&N2i_fN<&)%X`{a|u`Y6W96tA1ZS4_{7PvUUT=CJtx+u8UyH(^IE_o0XS(O7t z49eJ^koWBogPywI_t{YwQb71)~0jyE?S9Q+jY=p^?Wyy?H z=W?V)2IfQB+XKGA^zAg9f2JYvdE*WHpJBO~9!7vl*M3)hn=FfE+fz3j9jnFS3BtGo zcQ|O3OgT&|Vxx1){otC@ke@c~MQO4>FX_OdQ?13;d82zZ6>$I*)iZB}E86ZhP2hO8 z2zqgb86Lc1B}(H3Fk6F z?>z=HmZGu*4KfoRmu2!2>f+%R->)wS8|23owGBO;w$5OuuAl7QKK;CZB-`Hm)k}lp z&_o4%bMw%+V1Hb9-^0Nlh2GlDm*OR#YYai@`p|ZM-st|uHmGB{@;Hq&|Elp1V@jqM-P2Op-a+2O_=hKBIy1klDZ?y%gv?g z*#WhN*$W5*mbSniO~OWWiOfWca|;F%Z*J%%#;fDHeMt%cc|o8k6|PB+FAgnw@8KGX zvlyY_W|FvK8PVjcjd*oa^5@t8_aFFg@&lX_L|lZ zd#6>D?Nxjw7zv7qQ?+1XO_NpDE@f6`15U``Ci#hx5JDE z7VlZj=3^e0Rz8>Hh_L#IZ$ly>_Dpm{NG<@zrNO*O_K|&kTgt^>dvyl=W3f>4eMd>kozZMD#G&DrXJfUE#_%g#SR#}E8 zzV9jHx3C15Ag4IgpGrQbw0dZ`KSdf$UrH$SCghJWl}jKwU-e7L-w*t0K|?iSX+fd8 zVJmqETnHQ#bqs0oc0$SHaccZ|MRzaFkbQh{t@;YxIV~xMehxXHn>G# z0Efp&i@#Sa$JOL+j{o6awp$rT2x%#y*a&eYcyV=kc-xi=)!=$MGQ+5Pf;b$QIzPRO zGqJqWLZL7sCk9m=@_X=6iVk8IL!pI2L}HE>|9|fQZW&8DCV6<^#hK!dAySsvJRg58 zO$o;jOP%D>twMDqL6-#CFoC=_nzrWR&wzoYLV`Pa74pP*^9nSi35H@jhBqCHT1z(+ zgcAW~(1Kj|n*V(rbT5?-~#9uXG`M=#X>QWfPv#JZHbevV7eRC=>P9@vT)S|;v0+SMvwP@ zmm#EHP~3fi=v6+Uf9C&|O%*P;q0!aizd``0gZVTkX&(%l75(!hn zJ5QNjEr1;7zq5dU<7mXQFu^9x6r86djBzn$pi7!2$RlWhT(xhuTcF7=59tpJEo@Q| zRLmy-xs;+#+6!cQuLM&Yd21>Zx;&-b0P%)?{DO1}Ye8*H1^Oq0Z^9*c3{Xy_-Kca) zXvm2ZYz|H-8s2{AM=*Vsq*0aQ<1|?}8Qoi#XQm&|NW!v^t~)>G3B;F`m4SYKn;szq z~ zMmfPp4u0Q-O&x5FpB_3sz86UeZik^j8+*OV%2U^`m0#Pk;oCW2KRRC7d*q=FcjNun zgGT{*@a1ra^ZLS!KZL1$dv|)#BGqkNNQO@qo@|@SY8y*jW*h+VB^sYtmElr2`G>aq z#Z#%SRU|U3Z8WRRezDH+GreZg=C4Xp>bgRPA5?|BPzrjwDyU)A zLN+b>4kmvX;o_F>;KS!1tGR~>YZ4z{Emz9*dkwqgqZOhf>E=7NcSc*?!X7ij%t9Sn zY*!N=BpsL1qG9}4jw)_z1q;hdZ@gt6v#@c z;FdMRO!DBjS5ZK-Rsrg@rREy&tFx7KCqCy)KI?nZ>bh=SP_N0(XE^u;vz1EUtCWt; zE_oLd{P|`u(pSFJ_pLjb-7{{haWxSK9_{M{Q_}>>l^t$A0FLiF-UhzLNO_@>ZXd_@ z{2q;m+2bUv4_Q0?{bMD!-Ibdr{|SM=d(jnQg;i8|mbA|ty)I25 zPUOquC$k+=94^{FRM6D0GSf!+r*)k!ji9SXOfPTQf%wxKMr!jtf5^${ZG9t zqQ1@}Z%1sFduL!H>L8;&$d!O|y`eVoNDU7eFTS~c((CrnGv#WuAshs?NxL4dN#+0V z$7|KPhHajhbb8!qPe&!y&yzgz%nk zevFq6pN7Ca*_Mnj(EUeGz}e3jdR_t7K^dQ0!xZo7Aa+s zkdVI6EQ4kV!`n#D|L(2omULQ#A|gV0EF`Ui36WqL8t)cM;cTg>Df)t`KuXaIWIY{z zRytKv3Lp8@l}7KM5eiR$*W_6%E0tyN;cn_sO!yCrX zC#9<#ZeG|qXUJQGvu4o4O(@k#aWhaN@BpWm;X!gmN>GP4+fiv5ts?6ZTCJpRqlBX{ z%a+Hy^9lB}hOm=J2vJP{C{czY8$dU>oFs(GvCxO{6AU7>1<3!qRf5+ub# z>3TO;NIJ!fCT=Dmw~0$3yd-fNJ?6!7RbpiyKfdecqK7Rj4zz?&5GM$%q}G-GiLgRU#S(}b83H6}SQrp%La@1U0ZbA4zo?^VShG=;VK$2% zb^^)`7Pix5F;mdhP$`pI5+>%NVQhqls;lF(sH>M5(8}o27=}U^V2SZKDax=M;zN>- z?rCy9$$ChRR>5Ou$0IoCnA?Uu!j*^@or%hX$abAk3^E`bUzmS54|xf8I0jZVdKsoK)>aGRf(Rp4#pQA6Q#3P*l?Gnw@x zm?r@(5~fO&(ld}|HY%_!m~QJ>+6JP^{R81u#_vg%-2B0S%wJf}%9X9wg&|*0hm3dv zLiUSQ*1oLPsYh?EODe3_e__ZaL-LM0Jajo$(kQO4m{=^V%IzPw3%+1C6=pmZ=C#l# z%b`kut#Yt0T%S!ROS_PH>YP%3t-OEwQU7#Q`iOLdw_U>-1!Uai9Qph`v;5jMA6$s zdOslL{){_MYqxrW*ec#iXRut;p7JTjXv$zmhsjz1Y-Ty@!VZgL^oXm8Ky$o`+(F0u zKmHulaIk1^!`XG@S~#AfHJ9t&u_lr<*kGDbAihp zx3I*%qkxHu6YIA&9I4|Q>P{cll{>QUesQs;%gK_9 zPDTC$U7xiq{_t`-CKgcRF(A4ESdfgHCM1Cnia0NbC%9VtVLGxXB0B93o&8d zj_a*dIS_aObd3JVl73@B{yv{I_NqZ0t3ozqemv?pJhW=n{ESzV<&$cY;9R^#rlFr_ z{qxRASdKs-9n~GXs=KGQvwZc|24Ra3nlQFa$I5)pF7_`5T&-q<2B~N6_?3-NBcCHZI-Cz3+A6Bd%#2 zw@1g}UTUnoaUGnS;k*q!hJ;QsO^iXEd7~L&VQnM5ZHP}CIdKn!z~inakQ_U<(1VPd-5H3nF9+v7sxmy zWR^@?VPHQv8hGiFB$~c%5*E1ITk@c)l7QU@8DpfeJ`Gxiq!fpM8bEa7qRp-XF^sY# z58^h+kSIbaB4?#au|j2$2Hq@XqLpfjMy%kb7nsnh|1`aXzR`z2O>O~2BWtP%j_%+HPH=zQ_rhId{-D1 zIFWexJ;lnX0BO=XNB5hoWBM3M3#f#ZT9D3=BgR#IsCqj(j(r)L z@e0XNGm$4uewTX($u4`BQiZ&LM@U_TdPxTz3Cx@34xg80(<<_?Qfo*Pxk0ETj{*`1 z+Tlo_l|Zi@O?L>Qp@QQh2M<$EYKe{ z91?oJOoiBiyI5q>4HuXk6Q112nTTQ`XF|;9GO$e0t0c2#jCl~2@|aLrOc4)*h6n0t z%*e@<0qbfU7cXc|3NBYMi3Ir`4AV+wb>i3!^>+b$B^Kw;vfX^CG=}u1^X)L>K*CC; zI9CM-SU~mEUB&5yK$k(r0r?tQMBsP9RR(=^g<%xp{2t*!0NhQ)gcAYyIUMd<^r(z6 zfhT@noCvV*@nMM+Vc-pfS&%R2@1{$_x|M=ayeV>QG=;nnKBjngg*sT!6_sBN(V&i1w|LPIzkMbfuk#!p^Z0X6$4@Wx-~HV37v; zt)Bs$R>5y45`9=J^`27pgN2RQ0hThABOS%cr!^Y zBF_E2n0tXNx5nbG4&e9UbV~~hMuyFr6koD8eBTeuhxFB?T1o5&(RH|*Wbb4CxTra$ zv>76#`}WyNF&dUy*av9POz85;%tDW$kM^zOStnu1&E8#Ty(@0TQzQLGt#ELx-1cKADIBpsLjGi;m{8t|O@~{ufC1c?! z^XEDG25E z>(BaF0US!6I0O&vDmhWG7gKKFUOJkBCv&F9a#SZTR_zU$9D)y%#H$7LBMN?Xz}?%e z1z4#1##9K2Nmf2liO-yOC6L{zf>p$HNkglwHD8=SGG zj~{HU!tYq$vIw`*QvB)J*5)hnW?EM%7bBHVdHJ~ZaNmC82KRtC|MN`6nJiFn`dpX~Z?;n;KkAg4u_@90@dw%)K{M_{)9?7-7eT#&cHF^1&HeQzj z>%~4E%cy0_R;Hqcsd@B{ofZl59vf2Hj!X`jX1s#5Va#|OvB0N8{NDt!OEI)SAEx9J zVDKHQH%rz(MIFww@#(!}=&Abs-_uyr_W_lrcKY6-&Zixos-x9g@*6cw>6g2M8%u>T zr9u*kY$kn)zDD%?c`g(oPi}ZUONx{Fr>Q(20V=8>q@+*oNzX0V&GYU}8;BKc=x8wR zt>yc4#f0|5tGf6rNtN20--L0G3kTHSVN&4K)aG^f^fGy(%1wz0TE@0e5t`go`8;b z7}5HkyK}CL%@|miGqvzfyjm2%gVW3lc7YjoyO+;zUI?`|pc#;eA(V9qOtVdsjzSRE_oRX&zdPYa8a zgcAl2BY&@;fRcsBa#Td@ngM3|DN3QNG#jX~qyW&T%vYvh!%2)-aS=pm4BZ4c7V%Ul z%p|Z5qN$BtCiDjK?N0FTLWKc^AX+^B^Ch4J2$rFhDT^SZ6tm5zeiLIl4&kxTv=R6h zDT;`cn~+TjiUS#~wZMI`q+QaI|65P}pma+C>(-9Q`7p@8OQa0ugdGMrnHcOs)-qj` zW}0HvMILGeohb4)ls41hS-%qNPV#PaFH_~kP5|iL*Wmia8i^AE{wNADIaj0zi6iKq zh&9#}6XJ10!;L`Z{);n2(TV;;H5q#tb~-&_LT`-)9vz+{&R`%aK6op1sm_jcd>)i7 z{@d_$VzD>Ds}e~ZWOcgYV!)BOlu~L?fkCCdz?A@d38=(QIW`xWHjf2lkycMjhH(5e zIYDpns-g)X%f68}LWjx9?>2__BXI*V6p;SF5ZUPv0GBaia;& zok`jP5}4Y+IW^?I3~!i{-oE(^*s*m6N(a4q6u@>ab)*g?|5 z!CxLaJ@fQ+{bjEB-rxKb>9~>TImF}9nVjHJq6rDz$XyJUZ@Se zxc??_@X`DCjh#2lqONM$^2+%9=PZ59bF7V)Wj4MX+SmKgc3s4;G$!j;tM2iVp7HG4 z`HDDQDwkPI={>YNseBV38?pVJw$f%UkkP}3?;CyUtTOJCCd92*cM}t`T2Br<-tXrZ z;4kUsp_5^@(=78h^>q>9k|tVoD{aKX?Q0tf(*8~1U;Fdl!2=g;4}P{Q1jCds-!mKX znv0V9J{?4+`}buXD=%CH4(;um6$I<*KQS^OH}#iXAe!QNMr*%3obwdWm}X~%$Q_aL zw7(kdtryAk1UE{z1~u(_7uUS}>aQ1`Hbl%;a+*YYptY^H_2wZUhmDSY{y>7_-yW_B z>wC~~1{{TZ?z=u6I*SndQBmpU&xIu^M?3W9d3Q(eNMyRHSlTAq7Di_tQMa<=0f{bL z&G=m}wjlvzpQgou)>e+&!9_g4yTy7ptNf?lk(F zWB2;EgX@!S=U?Btga|nproz=+S6u!P53`TuZ{4MnA*rsVsj)I;i0Hcm%Waza`_&E{ z;KVlkwa0fHIrYJfHXSU|d#ne(c}M$`{T=R3KirWOGyPZ336-Z=7q+Y)JJ-~DaMHU# z#ylb6!*T{muv?Q-mvJkfZqSZomTD3pb7GN|B8OFhwnE3M6W4AU4|%l>7PsZ)vneNwI1&)@w&|F91OUnwc5yNYuA?GiY-P^cD`*hmXD3saj)7`JB zs7X>@22{d;*wPflG_f!=VZwpIs`%*b!A;?C z(GXQ)nxz<+PJwd??Y8D(3x(jSFuU&<%MkM3Crwt?J<6(}1`e{1=|;~$ypNC#9oG%L z-ZpeWS51{H^Ok&Byz%Z&U&LaUTD*Pl%I~vL8~d0g-YQ32oM3==3z}3%cj1S_74F># zKX)~D&MI%j&q0|Vw<>*&s+U#mlaRiMuN35VAG{K*4fi-o9sZnD1@X+v_V&^=>yAR~ zvf>?f$~+BA3;G#92Sj;HpQ{oLj9BbYf$#C+;zyM+wq>q??}k>gEZvj@-;qm!SUwkm zw7|7y1#vCdE<8W7+sTPfRRd#Bj~Sbc+}B) zXavN1w8)V-n1WGUM4t#<6~ZRn2Ksjt6#^-t=pM{5?u-Z(4XZ_QL6XoI6&xHYaM8Iw|ce4#*WKmpdu7z57DDu(S{bRj-pS;Af(WUJ}elg zon!`5l<3>OoilmoR&di3tUmXaZ2u_Z^B+%~wy!>1@#K@AWzApcbY38W{hx7d5l=66 zIy3i9jnI@!nDb;e!&Ys#(OsC};1RCQjk zzq|6*VmNf)E?12+w|J0|?gp4m;gQsnI=k13kUH*1o zU!u(dp510~HQoO8``^Cj58SH=yz|>}IQ>s8I(NQ$Q}GvZ9mz9qy*Kmk$W9xuN=Jd>yjgf``((e@mh}gs(x%-;)F-5K_OU{@E>7rjPCW@n@<%S{(jJ@ZGR1$wu-GJ zF;&|q;RcKg`u3A!&NP)|e6V-=khR;cp}pSm^5q5{j5=2f#KKI8N&$n28DD&5?4m_@ zEFM045eOLO#C}HYliRM1c>DMX{^xvk^@z9Q=N>=*b@ln`t~YTV?DO_vTMg8M#J}M> z9C-Qfm6frv51)Kyl&06N={w%;H}d+few|TQe|zZa8(nvA<%bL%m+|q8Pphhy+->f- z^1gF#S-|1f&mFiiW9fIRH(o2O`r-=^!(rWI#x1uEdbcGyo0+|FIH%pg ztn+s(Qf};NS$yzcABBH4Gj{2@1L-rbg1r~V-7222@rga3eme6X*RKzG>Sls;8MnQV z8TQ;bn_l?k$1`1#F&8^mL-Ba^@%_I)df=P!pPrF-b#lwMe_N9E`n!YsFApC^@W=ew z4;_BjsqO#ayZ;)!_kaKL?Zbn&&VTq^+xD|Nk2 zJpATf{^i$$taa{L6AlyfsP<|XW#2T?Ra;-Ib-CNc8p}Mlt6Oa*!b1PII>%AJoM1@8wfu> zSTQfM{fP-5m5{xPx;`|h-(9Qx=k}(I_1A}7**+<6v$&S*dYp*x$amiU>ge8M#HI4@2ws=vNdGrm6Q<+A5vA^3{WE= zu6HiR-zm!6Ff7zx7Wcfq*Rg|=TA86ERGq(4dhwIZ-nu%|q$Hdh5!11vMu`k}mXp3fZEUVw9G=|6IM2?XF{uflZh|qa+VdRu(=!)K2_UK^M z(ARkR=H})k6bSs4wgZx*)tW14tX-}eAE#B!y2x7r;0Kmqq|Al$#x>+m;W!dj_NdEc z8!MkseNR&PSk|Lu0P`q_hm8S|C&tab95y1OCB%8?ndw2CR=hLA`b%pi@kD=asz-Lgn9^EQQLR`i zXvQ?sY0pC9-!O}C*>LditQ?&?YhGnHupK>8?0|I_i2|Ntuxo9muTP(kSG;$8?}_d2 zU*X)oByj4vUmDN;0%S`>>9AqFJi0<+p;G$xC4CE6IqsKDfu&99Md7c!oDRKB$O@bb ze^}%GI2?E4^kbJlBjf(O0PU-1-ru`0`_mi$nsETk`Or`KhrXA1RRH#!^Z!QFEnUHC zQ_!rFP4o73y>%+*)T!+H2b3K8nQQ8#3Kh6v841M9bRfe*OHP;s( z9P#&`-#GR6&UuxLy}Nd$!W7Y0V3ugsl~z4H_25^R4}SG{`d65W-uppT#aFMS{O{iB zOMz9-zW&T0E!vPvwPm0E6kJx;Ib~`44}*#wR-}v5%C6nRNH{qER_ROWe3sj{_IHQu z_wmcyE`0GkxiE`=$E@m6>>}@xx%!7^&yDxPzR*mcJN)uR%J<=s9Bccp!9B?9clZ1G zeOT>2xRq8XK;Berm|$LhZQuFt&R+QL`30CSE82EAYOm_>K_w! zcItQD%Ro|nkc;ZqG}<*!<6a_txPEG(3w?xdRslPKbQx z<^DCLEq`0I=ps^yPnw%Yd_5xj_rLtR+a`n4X_p{oMXql)z!wJIA!|HmqsR`47-W z%i1?f=@PFm=}vcroBYh#eYP4GoG$V{y!HH%RTp;Nd2{D|;Yi;5xW1%p&(Uq9*K7W~ zsW39xy?K70x!bSaOSyWDhhk#f4SKbOOYaQlqFnv@cSoNsFGL3C;|kj$3ZwbzE99kKD68j`YpXUEUv{~r2%ALJEC zAORQtQ#)u*>EG|=_5I`fQ(3D|Ej~Q_&BNX4QZJ|0jjNbH>C3^x&qh~n8ub+VqaTj- z)Z>`BGGvU&5|1mh0$sc!!Q>uX-UuS$)+k|Vk&4lz)I!_WXzGHSs)2@KsDz58CutVA zE`llhx`I0&2w^&6W#fTLo;9ng#}ftvy2Km949WZBOK=dtPpc={0*vHn!4w3Mdguj- zWMlc_t)`9cSy(s9t?W3|^;UP*R`1-o^TksgtDgU?V@nwe(#OUJrAmSWxobsP6LwRR z1c)vl%66d1SksMm)rFqoedTWh+Z*Nk>}lZZdSP1Kq4m&yb{WAbFdz>R@`*EXe9-$1kplVTKWceFU&^qoVa zP3<|_LK(RM$XO>NW?z-gvR{EW>Z8QCk8lMB_&e~JrrBs3-Ikk1f8}G46B6rk#U99k z-6Yu`F=#XzGtn=WGV4}hk8Ns7SQj!%V4hG6=T>_Vyeou}DQrf%RRk!BmUwppudTS8 z*a~EV#_Z0Bl%;kYx$2VPk;(MEs$QJ!=p8t2l4C<;^YCCrKy+(nbTG5;k$V#PUWmyZ zO9{MV{T05xteiBi2_nq_sa#!`EjOb)f7TR`22wh1L9*oJ5t9HM>ev9l?ZgmQXXazqpka1sD7I5o^A(O;;6!AVAh%nxp0S+q1A@84Pb+-asVkEpQ zlduqV8cdsE(_&;Ysg#!0YeBXY&woY zZoouR3mR8k=AkJC7BH{I`2X0KqD*ERij$OF|$I{ZbZVW(> z&}odYTUhM@frX}JF4qCfnH=TG4thULm~Y`~g|$r%tRENKj^{D$w);EV3SXeXEYj(J zF{S=%?~wM+iLdn8+R)3QnfOTz!BKlpvL}6U?4{d%X+*XnkdzMS&cR|*H}>B+xacZ5 z+Ka9pSeX4u?EK%4kP}~cdFSU{1a|#q_ty_no>{eS%d3z3U;21&@aR?FG^J}(2f5Zx zbpJT#+vslpSXUbrPReECn!$Ib?8W|T;dj{=|FrOIHtCr5aUY%Fyzkl+nDZNHtkv7T z#XBlu@5#4PpQ;S*F>Aq+NpsStF(rt^SK{p+TK?lT`zI$iKhgv1lb7dIPx$oPIRP)9 zaIgI7cFM%c0VqslzD?OWc+m0AmXA-Kp6as!_bFpyjKgqyN58kupQ+kI()EmUuVu_@ z0;_NQ?BIk8pY;9W2DgI7;o-iuXXMwpY<%{r_b7S}@kJoL-h zLytXI-FF3PQkUL;`q!RMcM&u#w2b17fBdlM*UtlQZMpo~VjM}9{Cpz0_4tS6PjU~B+ANk#ZPd{DpME37L@Sfke7cktF5s5|J zaV$Ig*1o!aOhIAdl5?k~{-T?IC}#Y&Yg0G9FlyAf8+#6Z`llfG=84apT6mc$x$A6o zS5DH5b2o(3_|kKGdrC|K2~&7(Ii`K{x2kci3*I$-c=^rNuSS_B8K%!|JbChP;fP*A zQ5zdK+e3RW9EK-;boKLZ=luQEtKV#I+IOXV(bXA$>U*Wn$OYg1{S*=It6R&a?LEnT zvxRXmy7uYmL#`A&(m(MV*V9c{2_~9~+!Me2`u9hUuX-lvo7%-+H>Ech>0VtpW8oWb zJT|U4V{|Wkn9%LcvCjDL-Kiz@Ki={SAyIzo#-|o8FmGvWY`nbV!1a=8eLvZ=r(54I zcI_SDTX=%dnZLI|K^-+JuI!1If=rOw!~nbL9i0I~P#AEXkulf}w?yV9Ods%Tx?(r! zLn#D}ll2;hST7DprJh`w`ek+XdA#mL?%V#ytI1RcmeJU~;j4nZ3PLSB+*mQovX&Zu zShAtcJa15#bOnPgtT=kl6`|M-gdXfmWEUW7^w=_R*UtDh&HoSGmf9@Ca>qTRwIm01 ze17Q8$U}el9r~k=l|Umsw|JX{S*(w(I;yjjkTz-$5cHLd_NC~fYscaq)W-7Pj_9D~(j;I4Wps7^N(jfA4}UGb-*IW6V##=7ygffdo<2VRb{ zUHE4To(Kg|9D__{?K!)%4tiwUA(o(ARQ#@mD-mmJDS6{g#-~e9EnIR6_pYu(>r081g-brt9gb9EzZnk(CV7G1X zPPeYQ6DnK`6>aD1$9rdhRj1V6eUy20EpJ7P{;=Oxt<-2JWSw*iiHN%PP-8xsmpC8= zotI^VhLBQh-`V05BDu1Ak+2RP#Q;a>bMa!jkgMwPj_`xKi|>I^3YGwD)iJ}xM3rl;p1II3`D3_MpabN+ zhsD>a_T{kn9axrourH4xyEH8KVVoNL30}dM5K3lr7(`D6^I^y~ZQY`| zuVigRJJHRpW6`?C-SErjIhniA*Sd9v7u*TWNefQT+rIij-7NeOCw_xzj<#SqZl2?r z*lJWX(K#2bc{ zHKGPs_r{Y?fz)HW+mx)Blk1uUlY>V*HNM!UL<}je zB#>8UH2n`z7Cj#+2(nLz`0(m%Hpq-2A9NqTUDH3&lx5y>?alXIO{UGLHA7%^IsIcm z?|h0rnm^~li5;I`{|pLtwZ$;He#4eQ!}|ZF$7Dmr71!l&-#pora(CA|OIC#}oo|67 z=xW(Ad;i|5-a&c|O3Q|K|1$TfoV)wHm7^<9p4K%Zm06emkHYs4k2pE;s|`1eZDuw|H7LGYKMH(T&b~zvar}v7+_vB zXD|>JOb(i!d=bE6N_j^SgyvnGUNyqjX#4Ga7YaY(m<uc`^P*y1$ z@Og0h&yC{MykZYR_!08~mYQI6{*K7BhC`#|5s#$$NFKvM0Ct$f?DJIL(gJ~UsJxs^ z7VJpWim>vcEv+GwLmM)*E!lxQExRw0IF+O8R(Q18=_fj*SJwIy5Cw}E;%R#+vm50J zo}0osoln~@eX~p@(vm+B>f{8Jln5BcH1o`2vdDf&?{BfDA}iD2n51XNNqJ8X(i+#& zfku=!V)Mi2VeJo>oAMfoKi5HA#LxJPQ4YtSc!hTB=`QT%29Irr)rO_%INm-7r-E^R zdD*^=#3M*W;Vc&CN~u>-7DBUwn$nnn#5z<|GSqw_>T@npy|CwF++&nvFVEj{Gcrwv ztdZ-(n9JxPS5}NUeG(x&vqdkvxz?B5awfO@am+XO3Rb@_GemB-B`=;lB@={x0>S7| zbPoK_B;6TbSb$ZDa)59qS?5^wSlHp?S2vcV1##i*bZNcnQJ4=)Kq^1zdJM%T*F(Z+ zXZ!=}q0Ys~#bp+o&QPaDJOp=4mrG-p7_0%DnDDyAt}cz)%sPaHvTuh1FMa4rz8-Bu zr%>Av^2y}M);T2+r9OsWyn|IC%WsBAdN=b~t;)MpBO|cK;c$di-m%Pp{W;WjO#T{| z>YU|6iJF+6+7%J%i3)W1<7S>nqBeknob1RpIBFV&8^2q@Mh=}^gAYrQJpr&YFyDis zraIP)#oS4}SD~+!55=oTJrm)U2^l0ec*RdAz<+i~XPkeDSJ{Er%fplB`+Pa2ocEzf}h1Qg$n8*FC)Yud;to4MAF3U;u zOEP9ucr9LhtZIC5xGpJUeRRgO@LoySIU6nPuQs@f zHdpj9Of!A+W}k}D79)?heSCzW)#3}b6jO;OLSe53mH!@}mK$I7;Im9o%Hw^6CbJ zqozcMzt*4dl(1DL+q2iNFZ3u|r^Tc{Ecv(^vu*VSL(=pug^?P1m_(DEld)LmNhG2s zdG~>o?Q1^rnr#J&{ew3$!?wN%t^fnw-OJ#g*!)oI@y9f#$ArF^8JA}Z^VnRu*gu(hbAIAOmTq> zBK`3M$(ex&;?8iQGZpC*ak88rdor*Fwhp0v5EqXa?1#sy*2-PIc_KCLT?trHd#BH~ zuM}+t4f!$V77M+*KsB=9p!z3(4q5ACL#!TCPqeD47SWAVKna z6uX01_%MmD<*|bqrH1V3(62Tp$+RP!6A9g+C`t&ewo{?;VBSjK&9ZU&CYuOVmSBhTt_V z!>tQm)PET@#1DjgX{L6G`5oomNK*4WeV(yxxrr|Zvotii<;7C;pD`%Lq4e;=LQ-mk zrAw1M8r&DdOAEbv#xxVRnrf-GG0P%gAJ>^JFLo#4#5P$U+$4Z)jBf^^{JXqdY-3R*L9;8Quy+=tkAij$k1Uy_Kxh)4%9T=;m@+gbQe!%a&p z0eWQ0RuNAsypQGuf;J=%gfO|~_JVGod>n-ySeF8`A7!I{q#*2uFfK7LXDAuN1b(4GO)P-PPcp@AC zMQ4fcQFw%O4uUFtk#fOyccG1*Hh?J?bXuH{b`%oib;M6{DS%PD?D~o`IC0X#yk=nK zqKf{S%KqdxWk#>O*vnKH^{BeX;;D3~~U@Fb;n3~`~1aIA5Ksa7f zUFk`%=-dGxg!heChz!O4v=tVh17$h~S}y)}yg}~^&uQp78M%E6CI9=<4>_Q(zvv33 z0fM$(O0vrb2A@iR19DsT`|AsIZcJ0r2oP983#e<4&dY+)SCqgFeu(ZhPtm#8h~=Gz z!LtrAT6PR0A6B$OhC%LQCP$^6o4`FkL=*?m{V-_&vaocF<3_24_=rhi^4&5xl%YP7 zddava+1Ph4Co4ZBsxNORHDPit!<6~R>1;3Z(ofC zwTM9(+l+%286MCVz3nZej3>H}VyZEgU=sPf_q9HpR#^|}xoZgUf(ye{XAeL^+$*Ga zJ%RF3!|O3>3Oo^^0W>Rn6rm))3q1+y5W27Oq4@alj|c(JdK$y_S>mCnm5eXuc)xJ$ z1HcO8(mgQIXK_PRdHh+2>IhS(s@n?$VAhWiw`eCRT%mjqCQps||o zI3v+1NG}*P0J2tfX9dA2L-h)lJ~jl2F3WWJ4dflE z#G|NE!)PZzizBH+1c7|pCLuhQlH2l%z+omS^B&=6W=1LOPacKz7)|tfINA!ae&mZ4 z2T(8}u3EmxKRMFz@|B;VNNg$RE%uGXm`9et^HfiKS;s)N3{jOKl2_xwx?c@M=gl&) zpmJo(fz@(v*)ynl0K)UAqlpD+(HQ*sq5uO4`Qe^Ne9IjekfdecDuqeO2mq&(=62o~ zzFxi8%#W8qyH2v*6VecS-O%3<(J9QUk=nNQVIH$xfBi*Q*(X46D(fb=nRdEkSQnO>FF~&jN zqX`GL#1uqUJw@ILJM7G+VG1d`X8*)mJmOgiBr##rmk$H2w7Dp4_c-PXiKWLB+Y!Mf zRqP4@8+`oeremmqAhv8O#0*1&(pO{iM_7b^LuTAG6F*Sm;C=l?4MYdTR5ibpURjyY zD`>{4Yxh3B`q%Jbx$(;$89g9zh|(Op6d|WJ5*3l5BA}=udw`Q$Td%3nd*x{zKu>L; zBjKfzo0VqiRP#|NMNr!#lNBwuivEKZ)}{ay-OTx@s`hQvAlU_4G5h94@+HbS=gV28 zGgqvs2ryGK-5O>vXtmxXZU`UnTT@fL*_pdWvCFGxE{E_;fMB8G1+r~XwCb%Nndepb zgt39xQ{-wi7W<8!O#{fJ9X$&Qwk=QbH0}vaD_G+ppL*7io~W}JD#bOMQCkMsP8UvE z+E55oJUWRj5bp>e0#EJEvl^J2fGRlEzis)j`_V|TF-yaT^-R;tTl&3$xbgP)-Eki5 z`n4kE*}_-De2p!zqhqC(i&Rq?uwE<0V^3M_j2CSYYZQ!}K&fQ1;dHToPNX_0$+3 z@1u;Zl_EjH{hZZcgN&uovabXSr1g{VMIdw1HyH5fOEWa4`!Az(5{M#K!M;$#SFI#U z1C1npcBIY`>DAK}y+io0-v9>Z6dCzBki!a9h&+veWYtkz-jPueFJHk<1108hmcRSv zT9&s|Ue$z#rzXwcfFq3F1P6B(%{*$@qmw;8RqNgVuLdd?C<(WZ;-tS{i>@oWRR)3C zjN@3iI!H&!sok&iNG%_K_fdJ`8fEt>4P5!!n;~px>t&~k`HW(4cI}lcX2gk8ge4$_ zI0d0q+o4<}-7MhcMU!Kfl4-75E|axEh8I_#Z{?{4s=s!WG^Am z0cXkpj%dNT@#l4Q*2vG%8h5YG!n%u3E@_k0Kza_tvU!ZBSnjy(~#YsZH{fG&=%(S)}-bd`#a z0f%8pWpj9ODW<)fo;qX?XB0u93DZ;K+j82nlVXSG0j+@Ii~$W-bKqqCg3h^?#-V&g zfZ0~cqNNk%^I~)p`gpCKk%|dNA%!(MH^HqaK9k+VlbS5$D~=BHg{!BO0m}0LYjt(3 zU|c@5Xo&RDyz%8E8uG&ZyIq^{TJ*qA@i%>>+anWokGQ-gUN+Xq3Sb8?jumDwM3L81 zTJMhmKNZ2{gF}MdJeKSiGh@Lm_A$IUVjiI28>SFrr#uj1$hZ{X!3Csf3Y(w9(}G;&I$bQI)e0 zOQwA_c!IGcI9A7!rk32Coa3x>5@we*rFR}(+(%!$Um6Oj zHF2D(JD80GFC%1>fHe-r4@sD-H4C)G<61u2t>jk6G_BwWgPs7U$h|1YoLXn@maz*$ z3n6#cMe$K+vQgyuUsg!8Q+B-(5(_n1`aOsjZfRQ&GF*pz$-J+_se#q>P|3ki*GWiM zVGyQav}EL-o5T|?nT5N7O6d<;;7k-9?WE5phcAOMgCz}3ppPMy{sNANNOgd^M8|447iwD2nK0Q9sP zRp)AxS&To_G+;=v0!O5nu08~$E4DC*r4-|TL69;An3bxs&_kpTxCx z8o49z0Iws;72ELpCPs$xM&zoz2r#-tjby>ItkZ5%%o4xG*apsD}7B1kMvWL;`VOE{!086l7^on4~DNrlWK- zreh2KK%lj}Czvd-bvBR6eZ8DPz^CAET6TbuejSnYi%||JD5K>%K`~kvQft{^57v5U zYMFnEm)5^IOp`K(DBk!I4TNl+#Y>rG z@Mbn}cpZiQ&CD56Bsn|>c-W25#}D9XPK{_O!AgIn#i)tD@^kIUmo23)u=>#!Zhpj( z8sRd6=Ok+@G-V9hh5P>7}dR*I@ZIeXK(_#uQUzM-d%B z=JrD|S{Fzazc1L5F&8<_Y`PC#)^n5LhL_;O}8gOSE#sJwV(EH{?ET{IU zfDr-U;KG8#s;Q}Jz2x7^uqCCL6aE!pW68v1cMBVgsVF)(dq^;dI_L+tGmirIF1ej? zV0UI8GiG}#VT;BY<2!co+@YdlV4aL5KlTS49kIimt?cYEjWJjYc0uKmfINlSm}-1AItMjV()>cc;>)!#iSpC@Kri|C@akYna7;_dV9igq zt(tcYa7KIATN^J-Ho!)C>KgoJlV>cJ{wYGBh{daFW;KsFRyPkM{scKF_$|go0F3&W z)leHk>P+CN?V|;FWD1uP@-8CJjv9G$xKRo-fWs7NKNU$AG_(|*g2+*Cs>1sKDS5{(E(ZK-meMKEA_&3s-K zSuz$1=a6a&91-6wo?690g3Yf7hZYUqh?sA1mT@P6%moQrI*2n(61rfBw7E1j7Q_b$ zEER#CQjs9EnxLqdCi5A5v|etoph=)-kYfR-m18kHGTJ-=$T{WBeza3Qj4tin3U$l@ zv_T!?Xz(i7#`;(?OL;gb9Y$?ro~|;;;#29-_&6wQO=+yC*a7;+^O+}T?Ism3JsP9+ zG5h9MMEXI1TAS4oa{t4jHxqlAawlwvje6U;YFR}>4Lq${dS#EsX!a8p5lR6_u&=k0 zojp-YzIEfIqq9LjS;NvQk+y>_qJ!q#16|7JwRzHx z#h5v6nruwy=v}3(#R^9yS(?K856Q2XR0$f;u`_}*n>nTF71@bMv4y^4QXG->+!%_c zF1nH}2j@7m&)Sv}PVlr+HWFl(@?@~q67_VnQ%VV=88jF6H^RtD4+1%(u_3KHmjO^J+Y=(5?p2hS#0uX&l*DG%!FZh;~2%!EH0gPPh6o9;q)v^|!*w1riB^b?Im*Bnk0$`5O*Ro-JtVb^tHPjd;t`j_rx?Bx zM^k&Z|H|w6-bvSiV>YFI3g2*dlc2ce<8l1C>*wD#+A1wD3$eCy$%0#_q_OGP?*Iu> zVTlY8hrxuVM{sogRY10s8gt=MPT(~#%Tp}U;w3;$>>IC@`037Y*m&xF#1qT;E&15I zBk4yZYn0{qbuu7qv9(VNypt9)DJNI;E&0V0QrJ18~IPb?qsfqHEi9~fv)#jMKLdayZQlgR+6 zuG#24FnNl1_y|mQ7uJIpPnt5C?*zrr!bwuzFDy*StFD`DV1^I3S1!+DE}S6w;K~E# zJ&Rhod)BIL2GNXlGAeUxGaD+X-at*XWFS&tM@tSc<2h84Qq&*O^zMBt`^#LTl@clh z{rn(pLB?3|$DzBLDSO-f7H)~&a2{nD!U9(t>7<(=UY5;!E1ee=#fU`_y@FGvq)MxJ z)d@Ki(Hm5VXSkPDi=_PQ1i5dWUV-YENO7=Md{pv5WO9fE+>$5Msi=m9g*YO2rxm>gM9s1pWd>!;s>G`cMc`EIt=f7_!C-)`whwpW zk>0!M4tR#}uOQlpn+C3uldVfIMH#1KNvGO9*q5(S=F&LOkV#TTl9}~_6>}>$U#$W> zC1*Pp+zhXvj*5{Uhx3=k!YDRUfW;VUYFUPv0LAIjv@Aa5q(N-JM$oM(w58!$7>?{Q zwtH$>caviAtMI3WvwLc#8V}QhA&3o#0Vv$^U_A}Ps-K7xQVbNHmC7cIbeR#=A-@JQi}um#xiZB=1VCO^itJ!#Q&pnjlgyG+ zARm4Rz)}0HAlRi8KWC}n9cY5~)c{nEl^cf@!Z>Y&xLBuguc4QK#6Tq2Q3V_q-~;9Y zKba#PVo?|cnOu9T=T?Yp&zce#_#qWm^;j*7*%lXQwo4F*~f5^=xB z9L5l9ONExgdR$DkxIvyNB2f_>IMlBr9{f)%s@z8TWK8_v%y68dA)ac|`0mQrGTgOg zPcgw|hS|qOd6br*fTnd#S}2fk5b&?8s%Y)QV2N=W0VP1N<#wVsVo`xtB)w!^G*_8S zV!B(rA&YJ0eoAj>N~5hBe8Xn7=a6d#F{kMnin4#SOv0I^zyv)s7W;iuE@m(_JjQWC zQYc^HiS(m|kxzkb4?_iHo6Ud;@)Y=lDt2PN?wUC;6*$F;z0NJ;mCNd(kEi)X<>5sT zW67i7%=Qv`mO14#uzgK19f6{UW@hGwatA1^nIC0Ot<@H0CAbt{|F$Y_L3it%nLMEjgnD#x@VDPGfj_~M3KYUKuLw}bqM9asd=Xp*jUM+f zz+UW5V0bfo>~wgU&skM49dYtO5#y+a?a%%d890GsFsFqm`~YBr-;A#tceISODg}bo z@1s;b_5}P6uE^R|={pN>5WYkcrt3_#!0AzJ4rNlz z=+p`UQ0iqIPbAo}Jh;)kNA(l3IuJ4*?CTC1iRIYnkoU*U6b{A?sJc8_C2I^w86hQ7 z)qeHU<`LM|iRUR#V<1=(4YX0qR4dM@%;1E<63t2`FZ{3kT@+p?t~fxA6m)oH)N%B8 zuJ-88J{}Jneby~fs5#W+X*1OTcM~$t%lp8d#?L2as5_F|hOG)eOVP)Zw)=oc4$dIt zBV68TKDl&6fs9@%DQXLb` zPOpTP#}1`i5atO1ces`1Q%+<{EMYb2y9V1BL7BM1Kw-sSw^6 z6kBDX@~BK434^^Gs%xvN+no`r@Jh*;=px0CC5EiF(P}8vLMiuf`$bN6Gf+0qI6VO- zQIB}yn!@|mfo2Ei@;I@-w#a1>@_|(d@Vo@vtT4GrglYgaNYa5K1=8dm<4T1l1I_Uv z)rebmUUJtc$_LNg&F6G}qQwF3I9mh&+%l{j+M-cB>)fpQ0d+JM|2B=TdHU>M2j=36 zPvubbUX=(;A0UD62K+A#MvFIC6}Lgap^CU|c0Rw}2V+W#fawD=ej=fmwCIn}`nYT^ zdqCbSZ!IwQKq$Rz@U(@x%5JYde>}~{-TTG40TkWvcuwY7l|lc(Ip*u;Rim~)9`4_k z<2xZ)cqU9#H&`8o#jopbLZH)Z(KRP2m77aSOXE*3+i}_Uge|jlUm~7SZMR4WE-gr? z!FXFRot#96Cn!ewYMf1DiI1u%aJ79Bk_(^RkxuNhl4E$Z6beJ{7?v%Tct=>8)t}~lPY})% zo(zAMgtPc9S|9e`YNOBALWGAF7qf3J4xz!mWG~2qgZ>UZPY>Eutus%tGd&4jg~Vu> zZzwV@D&I2(L5-Qg;ghKZYo^ZDyVH2H(D3N^vE>p2KfDpfLMsKlU>JqKM9o4;M&*&g zc#R9{waF9%cC1o#t1UvB#vQ6@H{PA{O2K)?8qq*e9eH3otuE(|ipV2=GDjoh$?6+} z4okps;Vq}A8YK=~G!i+L{Y9Qpbn^z)hQg=8)0rvQL>ZSVm<%sa9H(xsMNRpP7)Q$| zj%;huiMA*biK)tD8M0^a|NCserqj+-O50kP8EeHWvb}{F5^M9a1kvO|47TOU0VQoh zJX^<54kQ*yDHZ?|dbQMsS-Wu^UmH4&5(PDpKq~2J)jb0|3GTHA^D}GxL^=~I5gm^{ zSU)!%1-y(D77f`gwW1>unhaDwLC)wpW2EA&r8iD33}V1!w4*`^sTdQnM{O=a+5|H# z$cTWQp32n=XQ>fF>4$R-{3_gdRd7Zvpa`qc=Cdkqg&1@-#M&c_yBe`b>&^ElxUG}(3Li2msxbZHVDtMZmF$V=iou~dD)vZJ zXs}i~Jg?$jqdX{i0H(Y>BntS%TZQwaQ2?g}I8r$}{4sRYV7g59N-fi~{41trld2Sx zul`n$^nomyxWpFd$ZEx9>!?9O%ZiWtqrO1B)0mS$=No#LL@_milWHR`{Hq)+2JA5KdA!1Vb4K1ge?z200q&ZjbW;thylOYxDH5j zP1D-oc)0S@qs@MH2eX^Q$M>rZr#*w0Ct5>iO`YhOv)wczL%}iz%59@C`wRCq#Fh=V zn-ZDJpw7u97AY83D_%X68D=PW$yyC&MJi;bVGw0q4ChJ)K~B9VCf8AQ7`|6K90J@Y z#!1b!mM(VE9cMuFh3Q~pdWQ5xuSLsx-$cs+B-fX71>)@kQUwBkX>cVNpybF>9V$jz zaTuF`xl%n7MWNEkuQMf7nd)@tEgB5^aQ?_J`B$tnXcLeF;2s2}r&wM(n+-Vdn8pok zjXk46(PZM}2}!Zj(C|>*AiK?-A;ZEty|gjm8(PreBrvAOv+D6BIHEW4chv zXd?X5x<Oe zJc@W=kAo#A)O*0WZkDdm!aNMA-EU6}mOXKCRxay56v1?#yj!|8jy1IjMgK%oDxXSP zfBBxKn#D@5#{V3zjRmjJj;V#Xgg z_4kbRyI4sBaj(OMQNein=iv97}^HulW9y#aTU3-a_`{~)Iem?Q<;%(PH z8?$+EvSS*s;A(C`^&2bpI(&EeqK0-I|bXBhT;g1tr-gw2kY~nT>f+NUAE(J!k zUg3DD&_u**CDlC-cUX0|pRaKZ*)zVrp`_?|$zSHa=h5VLOM-uagb~8FByezx8iN+C zy#n@cHqH0@*R`4mTGr-iV8Z2E-p~6I)bs(b`mZj3gj@^%l((n zBCYPB_k69BeJBQpGIa**b!4u(e1-|j+V#W%5r;^!#om*z{ zfytG(CP{lN{9ef;k@W?w($d^r4V?z9y?r@R$g~n$MPDQ80oIw2XShaVIq^Bjk>ivf zMS2W5tTOvW7DG@5#nm(b?JfwRjLQu5=%yoKv~mR~%VNgDi+1izlhsTPrrhDaNO3r6j(9ro-G|%M_s| zi5(06$FiB8d2I5^kO=}8m;CS#o2VaC(c?&82Rz9WzeXpzJIb?BNGtI|?Wv)^?3PQr zOxuk#RF>tpk)#yPy;--d#UD%QlBNL%Th z(Z4C$&7WMn=eqy;{lNR*zq9u8kBeiiEU&rd50duc+VdzRHhg;MO=Fr_2DXzJX(+tv;lB8h7k<}bZ}vl!Fb zkqgh&ANZwy$qB4TA3v2ca>7e%5EutI{G0W)^Us_<+x7XO+pVwNseEnd-n0SV@SC3f zZgJ=M?ia9bcjXy(2brmDg4W`g1L3&;I!%$FoO9JXC+}VV})2=AUcK zAw2Is0esKC-S1r0C!a37cWwCMGhZw@Q#x$x`WBk)0d7tzyE6aq8mf5ymK4__N^1%{|vLbzIU2K zKF5EoWZRGDt5Z*10}3qia(`Omdmv(>aU9cDss$b#*i=Ldsn{Kv%in)Bd0JW7j7b4O z=NriLB`^@8oy)J#&Mbt`r&8m?zQ!NgsW%3`p^TBfsH9W#G_N(J-*dgS#)jj1pH`cP z!6mIQ-T@NqD@ZhHRL2%xC zt<|w3$)*%0U~k~HNXS=Z^%y+B;@!G-jlfzU|GXG01$s@CYX*DeFQ|P#|JsQN)zRf-BdnBypUTUQBIOnoDEgZLqFdmp*?zOk*i? zG7V9=S77O=ZpJTCzX4o_Q=8OfGkxnkt)#BYoY{g<7Nl{i&2b6p7%D^zqQ%4tn?(=!ijH`^#IR z16Nw_&ZRfx9peA_HY6Iy@)8?1X;+hG=3wRZNF`O|F%6dL_J+ur9u%o5b?XPTxw*^ce znaZQvgU6PnEpjYDHKn3LZDKAAOf$-snVVEv+6N(DtpezXE<{d|AY9f5f;xvVY|C(8 zY?JVj%H;@pqTFKb8VL(UC)Vc3DG(P1kf(n1)-5cGgD4f3g#|<$oCPH7?G@V z!W^S+vi)ZFH*0{3^DEH$bAqZ7T|wO)u)`jsH*gvAj~lEFesThGvKN5xN44k5duE-( z72?ExfW(SDeVA&&j=x8v44@m?F|Z;;e~Mt5Jv>MdBn{TCWh}MS@qb`4vO^bVhg`gz z%H=F(+H)f_mrKJ;Eu>EIEiq+*eKUCb)1{AwJ%i$BtQyU(O8eKg2-vK8u`}+YrUgb9 z34pNTgklDrIUE$B3N?TndqM^S4QB)Zl_4ZY&NS9h4;&MKt~g-!usmW4jW)n>k;X&t zclO;X%!27WchC;I4BF@pX=^%{DKE?q7oeK9XDEBl0XP`v+sZDoqiW0LCWegy7Th|9 zWWZcV0ml11m~<~X*ypDj>n5{suBpR`mwhhbEJyZCh7LV{%IuRnQa*d*QtP2Bt@93D zxc<`C=gZ@kcJ)p<-y0|GxNPU#e~u4Go)G5z;pB8rYV^6s@mPIntfOk`*+u7;d{kmubA3)W#`0Fhng^o^X;sRyA4*-oY|5==MJ*hx>sXZVe_YI%pxT;sRnC|`K3yQ7xJz_3A3}TXi*7P z9CL~bCAednVHGqfwz+VbrvJqWDps33h7K8Dwzr zh)eA4Y9j{AkzElIBc||TapVHl$QCeQ8HQK1+9B*^#wgk_WLo1S4{3swRlI_PN+F7* zPbG*!r0;5S!QE%~G|B)1f+G2VE7V3cz#yu38EDwVO=pT0*-Ws5t4w!2hBniZViw9Z zGc*hbWOFKumLnvYYuFhd6ZkGCD28NEm6>-zf1%Z_5p@d;m5q@iq-5q+I(Gh_XrCSy zLIB4e7mfWDG@-2XVzBiOTebx@B5)+EFfIpE=CH`N-?+2|^_IJ^t{(4-jVuwrjD=sh=qMuk1YD86ow?SB_M@h8@ zyW>Sr*>qG07+UY$R`6z7*AADzgIx49_(FlEfH&b{Z{3szJ>#%hY^$Ov06CF+<;_lmo=-)d*M$e2VVUC?PQsjtXVYl2&l536m6HHE6m^dn^B|nWmKh zX+ePac0|*P9a2ma!H<^q7Oc*F2?|HE=|DqTq2XG#A|c+$)(MIuF(3dtv+9w(Pj1JJ!(J4*jN_n(ufSZ{^Gu~kFvAMhO>I5Xy#LDe zkq0}<4qh7ceD?1h&M&^2IpTisYaPM;?&kKhy}Ev?$6nfU?Ys8tCl@;-wzs&1j+{(u z2mvOx5K36K!D@fYkSnL0KFxo#x6twf)pTY-#_dg4?|gmrPHw+_7k7OAxAnJolnrfP zU+)-s{Nm8^Ls!o(>UZM?>3K`&K#YaHV!nzH@?ooLkvT6 zBcq<3?Pq1cx;u}j&%9Xw?e7z`Q`udcl0SL(&tN$Qn^S}1aZr^#o{k3~_f%kseduqm>g_pj; zjQAo!jVYP!>*qf_*4Ot1#e{`8WEgz`(*GEDl&?AZc$PEM8B;cEcG8S1-;|baXK16b z7?7-35k`F+u zc}h@;Cj>=-Ebms5cNeofc`F`$8T&i4kF*2ewNz4i5vW=n%aB%8VC--o5HyuKj#J?* z?kX_V4()@cSeRv)q(dfnXxT*v;dV5ovi3)TnI43tdwmTLy*8AdoWY$Hzn?FHqV9z2 zh|Gak778ryM|_=@C)6>EzYGW=TL{Q|(MG>@hS`}ZO8so|N(=t9fM zz`q}0X?c)aB7L*XPUa3N4x*_Sxfr@n(Qu=tyhXC`QT7lJcGx@fuH|~LduLMyBn^PH zJ6lF~M=tyaZYyvifvun@-5{pM7&R`+$Ge+3QfHtdp1u$yCKkyDyr;=Nfa4B5*!f({;QntbJ zc!!Op_|L30vsY)osK98aX8xLvD8xfYjwqkaq^yA{#k@g#EdbBt;6QQ63>M}a8%Zt* z&<Y6;x}^tx7Z@4;1d+4x^bfl{ryX>9hbLazt~wz}!OJ z%N7FzViY>vn9uk&)gdZZ#kC<+LsVfdMrsr4G$!5+GkdY;BE5Ol(r%gE+<#W2b*F0j z^AwHf$v*K8u~!JbA${taV=>o?%+bVj*lp~aN^-;$-2mU-(^7qmKiO^aAVQt@1s+FOctxIuv9TkC=;VZ7YXovetGK3Ah7Go){?DF*uBrV z_|^OM<8qC)wnyI2csO%T#>2Jqk4!Jo`WUBMe;5}}h@%y2yity6H3p|rzb@T3Rmq)} zxqZgzxQ+{P*Y@I!?+kmsPwS8u*Vg}&^JiA&#Q#mqUwxEMFzNFjzyA8QuJ+fqzkE1- zaeMmor56HLtXMH)J8cl7ku7pa&rOPk0h>|V9lkHIwpr^f;`9zrg`ew*UUL0R%jdry zEdKuPtX@1{+2MEbs9pv6^v9>U`F zw(#&_Lm5VXz1y;5=bc51wvTVw^2nbbe=fjUSP>*QU1hhJ5o8L-G^5b`s$twa7kWj_ zZXfaSmJJl>BE^GCl%`10kUHW0;Dz6mS}piuW!20l`-AR{!Er!Jp!W#vu=oKU; zb2=hIS)jtTkeVah8}@e17{Ai{qA*U!c#=x-f3qmE(TmT!xgT)9wBd-v=I7V7rL=l> zadqgr!uqNhr2?fI$ii`!M4`->_WW1=_3aAJwXai_XfjgHV!SaI4H$khS;?$aHv};6ZJQ~^l z=9eJjy()k#d9p{Rr{b2bLFt#F);+YvE9rAN_Ec6hJR1lw;-3d9bLM$;H?&$lzC6Jx zXlW@1Zn84~<{@@5iH`bhXE( zQ+kK@;s`UiQ6;&U7}_*Qht&^9m11cqC$g}D#SumXA5d~8*D1J8alz@=JT%=JLn?jo zpdn*TbO`?+Pj3Pb^}hd)&mc=FOAAE|P01+{MMYUskx)9{6lKYFFp`uthC@d}q%KXu zX_3?I2noqrL_$u8c&h;rjX`P+hZ?M( znt9O@x1|p9p?#1~4_6Zv$zal;#)+N?Z#p2yOQ4p;i=std5j1K?XVf~?6poHM0uDwxkvG!wp-Ex6eGHU00juS+d_wPntT&|1&9gC z_85^85Cbqls?}4%z_{?>X?&-oQPmFqsxduWbyy~KOc1VwTT4cb0DTnLf+bN`Ljqgi zBv!wPZ4+yi?&FI8$H$YUfn*9K9^j=`Elb2TYh#SGU4kk>;)pmi2%g~xS6|az0OFQh z0DMpj@!Ih4J|Q9uKT7&WlFEqiKy?QM!}FfZN+{5!y-Tlt20*CiJWi6z;PR&)w1b_+ z5>b|T_krEWk%k3AuM+4eKs7K;A;jY%i2YEJ7|EamBWiBSjfcb&wn>f1O1V~)U%|}* zlj5Z}D?&xcRVOPCB;ha~WBjw%3xU=&jz$T?7EGb@+r@#HxXn}n&JNuW-hL}k*a9^< zjF770!uaA?2>6VRLc%qy-5Wk3bMBD{WcsvF{iR=j?1;%QMVRMZabc5#6dI+h;nTB? z>PAbcn+U0whA@eOU(T3r{Gee6stPVbm| z&OTT8z%4gBckME+NI>&#f-Lnfr+}|Mq;4HYF_&4I{2~NOhi@-|; ziN{8G1KDse#}&~KiDFL??G7cUJkUw-8fA8r8W>4uEy(~u1XIAtCBSWyFMuRL40o8v zJ&YH$9XH{2AhR9`i9+%R!T=cf6$bjdfCF^thVU|gs?{`{Wzd?S#TpU=2r>xe3m69= z`;qwV6gv_iLeoci78D?mW9ewgh+uR9U<5Is9LBkrGTNBlQhampF&6=O#h%2|*}XmT zziXwOM;iiiA+kd9*-6wBzH3|J&TKsbj>c~V9|T2ea5cVxqXFTapxM=7T>vu|k9gZU zen)~cGX*e`Vn@R1%b!Q55ZwZeWUExht69>kOOtTwA+8lq&;)WTKa9>_M4_lx6Qf5# zg=q=>hW*j;BYgajW_siL0e?V(zbcm9z_M`+6R5%IzUT0*seupzc1ue}!V7K)lK%pC z^ldzd_^+obqKHeap49h|DSay-8(gRQ@MyInXb#&4FE|4Eet7+eGaEt}-AHJ&mw{s0 z7>P+4xE2_ld1&obM5q9$LJ$*3LDL0t%u#y_Z~-(e5$59Vqhb)q29zN}m|J3}TZ-x{ z2oiXj%lp9E0Hh*t4IGQXTcmyF(MhOabb%TMO$*Of?x3iH>b!fe48t|sW9p!OPpTrdMf)(Ak38aa>T5JVioVw&T>=@eLY z=oWD+M8L#~d<-1{3Y5s0Wa-gaaSbdUBu=MDMrvd$EP+!YQA8jkH^F0Hs;4ab@7!@p zMlL8kuRAZTOa#{4dka@nIHjf%7vOT~KkS8F0|N^O_l)FQWuhsOA)_E}WW>j}gl24T z+$^Zsqh3lU)?PZ{N!rm!&`gmP^m@m#_g8oMEVNXf#R(S~+?3Hg$ncQ;!)h?HN6C4j zBv;ckENKH>AOuZu?`xkvL4!C=&aPO_D(U{JRge;@g)Yy?J&KNL<==&##h!TLwXV== zV`y~wv#aOV>De*fUL1b6%Zxd$#0+42S|ed^rUcz#o6qW-zhbX7fxonpsD#bfN8N6E39g)$~QfXNj}g#dF=j^ zjx1UBr%JC?d-94=Sut=tJ!CS#BQX2+A?0>Yx8currA^O@i_?Ua8S=7nzy4>Ik-&-< zD1TwI%~HZXNjUA*$y^_8_A(KA*+B0F)fI^*8DJ8$Juw8}a4efS_D zEFzu$*bt9%Oy|UtUbW~nQWKGF9flx&sBZE$@P)6J zkgzYztwkcaMuVn7xEdsHC~pR>R+>V$*+EbTg#UvauCKw?4`4@x0*yF26)O<}9)cm9 zx6G1HN(6BP5+QRc&-;Z2C9+LHKcdc$DFjiiU`H`yN`W%P8tg=}M8;qhb|N)B82x#2 zTZ3mjAsvF60O?TTDK$ihz>~9DBI;+&&wSTO9O|}cTEahQ5X1q1e>m&O(GPhSImC(7 zjEr=kp?KSN9(D&OlnW_{8YFDzuEV{8-x+`dP^6;Mm-o>NgG8u~$rE<(dPT4cI)Ii@ zw6x4Qr49fYFc4nGPW@RNKGEWQ0Kp0ffPlkh0eBM9;duE3l?W{*O-jhNu*KPebMgR; za?;CC_JNrgDdhSH21Mq8sg68>^?9F!h|&3>bx6Pi2*=?=!7;`l!JXk0YfzZLkMR6? zq{oC_9t8C)rVM^IoSdl1jmy?2of-XhxX9tz3V}(3#By+UBEAoaP(Wb-dmv*8T2)+H zs5~S=3ItIIMvhnfb|A;(=7XsbG%P$L3^H4TScZ_C{5iQ-2%zqmVoFo1$2quNHBMhg zi0mNhHF)9B@l2R!hpi$cyB*h?lL189Si8tmC5aF?;?Z&p)Ci|C$@?J6J6H-~;vvwu zu_Q`R+97y_5Jf7+17t-wMtX?F4@e^%{d9%5_%jL=Q=1gvpai`rRe`k(iMdeD(MX5J zT2eADj6hl(p-{G?%oIAO5XhrphQ7^&7!A%bo{IwSC^(g`kX|T~Do(CMc!iPKimV+_ z7(~_xAq5I;IBjVPL|;8*zbAi@Ly z_h5{|Wf(=S1tf0(3V^1m7P(a`p@AuT3)h?X6-pQ}uVYfOMV<}~+Ao`|GpEI~lZDDDt+(bK%tcH>LFRSKicYEj=UVCn;yTZsAzEb>d{C&1xM?9$;0cx&3Q5_ zGxA49Y)-2rruk@B`8+Khc^R>H=t~t&bJ_LF%a5A8GE<@>T&m^TN2|h~_bz`Y4l@1P zb!Amz=UTT~56+Ktzgfmy%x4y#+)6(aILx|PWqueg&G6)&T?Z?grkWa3Inml9Mc%nO z&+s8~3!jC$E>G0`qZ)ht{;E)_^7mV7a4_;q`TBa64(A1q6i-iey?KADXDTSRH^TeJWNn|cvH#Du8Z7ZodJUd z-u+GG0S?(-bvt(U9-ZJ@%bXK?Zx zX?I$WdaA5^&*$EZ`R%a*KTNecc9?nO?)f&q?;#4XM%xP}2JJ6#+FM(T>T9jueR@>2 z_luX5kLrr@xr*wWVkNz=RM_YN!bg_W{;|Wffpt7R6JP#8@3@z_U-y-cD53elAl<;P zo1Zj}=dsTvx6zgDy&Wdn+~2nd8yoQ* z*!-tY$D3tR=ZYtNzMt{!Xq;aBWM$YA{H-DN8LLWb?j%6NHlY_|JX+_xtyM|7!Zho)Raigy>CF;oe{^oeWxx zsL~D2wK^!Emgnl?KL1KAIE$vSahT!B?^2&yINZeEc6wK-)wb2y1-Y}Ig>+{W#Dd>m zE@|oN%83+qX-oJK&GszpPYKjoBeS9=;2)LzySsvQa`yJRnimhg*N$BgOZNbE^RK>G zz}ql+^%a8;djFen)*1*Fm6nAFB)}as5&q3!58${Xu@>+g;&NeAKyXr^?Fk|7@Utlq zh(IL^k+Gy$9N;g`f$md^29hDzfi%}dWenF@ET!DCm|Yi|T7FCfz=a^KX zV#R{N;&H=OKgmMaI9k`JQxqu%I6Gruec*vI7%MTj!&M&Hk03%`11=yrax{WZMVAwm zn7F`mnGpvmX6CFIKMoE;c<(4UC)!zp^iE@g~RkA9i~20`=yp{SQQcs&UEiK_;xh%XQ4(t zLIo*+-f{VbBR2+bZbT^Xbl2DeL9rmeON6}hX({sL`+ADK-+AjYXshtRuc!;kP^cjG ziDFjK=2t%Q+9R_s(PhY$|1NQn4R5CF#v@iA0Wi0b2u zLkMY0NEu+}4Kmc0XLKTAy@AwyUEK7GKti+NV+Z`DR<9U_zD#?HBPv_g5`}@j9b(() zt#+&UL|J(qZJ(Y|H)$+!6-1wZ%Ud}e<>bJUl~ zeQX~1Ey!h5dykG=&q>nXQ&BF@`JNceN(_jxWX4#_{rGco*Y%#0CXSmz6ug`tX?VLG zcg{&>W#qcvL3=^))KEd{Vr%N+a2IE|5VzxbF3li{l)V*>G};XRUsr!nXP|>O|1uE) zyJeShYih0BbO%%CKBaSBt8&J>7V2!fyr<6`I&{eA?8~Rxm%e=}mz%n(zwMQdErQb+)y&x!>!a(yt~Z zj(0ZI29{0-mejFJlC8_*!^=YVrqUeDSWAMU@bsp~zw6RoY8 zV+o$K3F+pkHW?=*s0qe}3F=985J7+Kl2nML{!XkCJj0%73X2lA*@R*iJZ|HsT5~gI`22KRt;+=JXcM#o7Mq!9Z%%r1Swf;` z$Kt!pg|_;|+WNb@E{)?=Y;SLut@3KBUm>=lq({3nsAB2l)2^91=JYi8yx+z1UB%NK=NBharyi#+e8Nbjjn9<1 z+q>?A+iuZv3>`AXT%jUvK=D(7&G!sHNupej`8}iShYGvO=fsl-k9nFb|N3da*m9I4 zU&Cgte&+CvAMDbJ`K|@iu8Td(wK~10Mq*+PxMw7&i}p+d1NHD2!f1u+-6Uw+_Bpw` zKQUFgHWxGdrlX^4HlvG^Q#uo6of?dB#btVG%F4Oxk`hE_Z}-cMAI@C3mN~gD^HN`1 zLt$a!ORJ5ddTp8pJ{mrcsP@*s1$qxc#atEqIU@M4CkGDHJa}+*Tta^Ai&)UV`_t|J zn~OH&&beqde!qeT>pA1~WDr;kE4G&ocg|_O)O?0(p*s07lRK9=AFn!T5g{aW-2M03 zyLEk~V=|?a#a)ZVN@mHoAGx|a^gJo)t{Hu?IAsfI*xWpGW`2I8(6iXe0kgo}M1o5y8 z2qL8>L>zqq;HF`XfUy9IAaojG*|ZdnM!*dCoQj;c-NCGC&^%@UC!`LOkHzOA-t1H$ z6X5w76Qkl1Jk}ZzB9B_(1y{q)l8F1W^qRi{nDTCFv7^?I_ z<4Dw5I<-uCj1LmmN}^cDmR`h1LUwN;!L=XvW*9a87AzgmXCd07-VxRhM=#xUf7%#K zyEoR#cprdR;eY@h@Q4yslcFvhM#aM+h~N?2zLN@jrfp>T>9ie3SA;Lgse2Vr&GX z6@A4o7=KZ}1q&-Is|XmQk7eE>LmTB65?qHjw9PKKmnn8@0nw5iHc7lyVeSQ4ol2)3 zBnrK5^$Gb`eXSt05wf;UMq!yKEi76;B$^@;owWpC0jk4VFg^}O`O4|FvXx%NHkr+X zjc%)dfx1ASRTt#x@{v!ZRZvgjo}Pq<8(Ujj%2zeGe){sLDz4nS#iI=5vYvflctbG> z3fN9;loPSsnb_cY99JD(6MJoYcG|2ATqi_JX0cc*)_Do7ZMPb3u~-AOV=eFB59fDr zF4;~`mM)l-KDR+Pu79)d@rDlt-oBk){XHDcXtD?QbCyrqW`S#2%WqjI9`5o*bvyG% zOxO1qCUm!!_s`Y3T!rk+xvU_kpEKpj9T9^uq;q<)bJ_zF#N1Pvroq2LsUqSE3XN8e zG&?TUclfq@&BthU{(3lD1I4l@o-_{UcW9b=NH?MAJ+sV!Pq{%sVX;$wVI;`6Ykm;N zsA0$LIyaSu79Y#P>Gr@bTW+sy7g~?y$0X!|2<#OdeA-lf>hSRVNG$iOc}Z_l>8vMr z%CpM#PJRDGiRwMqb)zqOrj&GhjZ3+hB!o(*3r83Cjn3m z*>YyMX|VA)2Ab!dB?Fbc0lQ;v{_3=1MS|+Ay6WO=>}A8Q`o5e~h9OOZmoj#0xCC`w znoJBHuq>Tqmrg!5_y4c;HqE}Et_ZihqVt@EVc+(#D(1o=CP=v-RQd0rnN#aBgI$@r z;lr-7qlKfLF70E{4|>0h%tmQ-oGRjyT${2m=ddDC%=ac&15nYRDP@Ta2iWW&u_nRolOD;8^KCXB-#zOsg{PJD* zqZ_cRrg{x}m`7o(^PsQ(@D~5`kn@UiHPJz@ljSFF{X5U8Hurz06XWfO0Tobp^R*G~u3Co&5XJ&!BmQeyV|29L5!7Omy+D6{1L zhGpfP z#%EL)x>XnYJULSsMAjZf6Gl8faWuKC;L9TSIjKV*#MV!hjHw(xD6e+_moYr@a5N%* z0pKO5snsB?p!k9^8ukwYV21gF7vo945i3HtI$l)g|73m0GQ!s=O##>nBMOfJBJ%=g zCswN^*dCc1kyeDLM^ZF_I}`RAWKoZSS0x3H8L0&0<$_d-nnSp~H)dyg?lk_s~TNluCfxkC*I{4cEc7G_QZXg8%DY&I*v zrO0)V5LrMJqUvYNzjYf9&!VVHv*5hC!*MqV9^+wNLUL$Zmm<% z+2Y9P!%B@$ihwvUy_g6>Mf+^e+E9{QG^SUIK{#muAh~6Yx}YPqQuvfGlr1PpScBX- zT=h|JNs-wVRGA=XfI`&#q2bxY<^mxx28DyLEMYQ1cUTc_RsiGp4s^sEFqlSwixGvf zqWcQ*e<~o)7_tqxn3uVnLYWRp?b(3fV;HKSZ^q6%mbppIh;q_y;x0nu!u?8WORtf) z2$6nb)h3lF$ZvHNmEI8T9||6sgtxpfc#S@K1fd}uMbv;&iFYDZ4}7lOhR_&FmOceY z^NbWG3A94+(}a*jCmOO`oyM=p=NK*#@+tuogBTpi2Pt`PYcD!`o3YHSCtBgiT<;(x zU?#KS#tXy8xwZYv4ul)ryL!~nXy@UiwzJ!9Gf#BQOm@v|(4E^L6ZK}Y**5dD9g~?W zN4KhZbGGNxdo%Y2TW(!f`{YL3!*e@r26wV|I9;c=UU=u~=5fc$OohE8xB9*7x_nJ7 zU)5ophQU4c3qQ=|wS$LhSqfdnDtzIj!)btFLTR?o>ld+m3VRJnruC{?^{=-8B=_b-ueIQY$D& zYyBU&4U#lHilL#Qa@vzKt9M^_*M1Q0Vf=aXcGcVji^8R}wGJ*dRyA(LoHW~o-riRq z&N^(ce%d_f`mwUI>0pPB*L}d?3#?R5Ys9{xFQZdWM_tYCYx*?pOG4a#GO? zZ{yo>DR*#cN;P?}?;6Mcvn3Fb&

VY)A-OT1IZl~c#BVG{%R?ryogUZL!oaDIF1 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 literal 0 HcmV?d00001 diff --git a/docs/testing/code-mode.md b/docs/testing/code-mode.md index 683a5f77c..a5535fad4 100644 --- a/docs/testing/code-mode.md +++ b/docs/testing/code-mode.md @@ -35,6 +35,8 @@ The broker validates the origin and API prefix, supplies authentication and rejects redirects. The request-scoped capability also retains the existing unauthorized callback, which cannot be serialized into loopback binding props. +![The sandbox calls the parent Worker's broker over RPC; only the broker holds the Sentry token and contacts the API.](../cloudflare/code-mode-architecture.png) + Code Mode is an architecture, not a separate MCP protocol capability. Tools use normal `tools/list` and `tools/call`, typed schemas, JSON text and `structuredContent`; failures use `isError`. The MCP SDK's package version is @@ -165,6 +167,36 @@ Fresh Worker IDs preserve per-execution isolation but affect Any later reuse must preserve request credentials and authorization; changing the ID alone is insufficient. +### Lifecycle investigation + +Local probes on 2026-10-27 used the unchanged sandbox from `f4f0766b`, a small +parent reproducing the execution/deadline logic, and a local HTTP fixture on +Miniflare `5.20261019.1-beta` / workerd `1.20261019.1`. They used no real token +or Sentry API. `waitUntil` deliberately kept the parent context alive; these +results do not establish the lifetime of a hosted MCP request. + +- With the real 30-second deadline, the parent returned timeout at 30.004s, + but the sandbox settled at 30.503s. `Promise.race` stops waiting, not execution. +- An unawaited POST reached the fixture after the parent returned success, + while still inside the deadline. Without the extended context, that probe + ended before HTTP arrived. Execution closure currently depends on context + cleanup, not an explicit broker close. +- The broker deadline rejected a request starting after expiry. Aborting an + already-sent HTTP request did not undo the fixture's accepted write. + +Cloudflare's [ExecutionGate](https://github.com/cloudflare/agents/blob/858674439f2966b16ea55bd37b46fad4248f2781/packages/codemode/src/executor.ts) +releases pending RPC when execution settles; it explicitly does not cancel host +work. A local reproduction confirmed that distinction. `LOADER.load()` worked, +but neither its handles nor `get()` handles exposed `Symbol.dispose` in this +installed runtime, so optional disposal alone would not fix these observations. + +Before production, define and test execution closure: reject new broker work +after completion, check state/deadline before and after async authorization, +release pending RPC and abort in-flight HTTP where possible. Define how +unawaited requests affect the result. Verify hosted cleanup, cancellation and +CPU/concurrency enforcement separately. No cleanup can roll back accepted writes. +These findings are pending work, not a production-readiness sign-off. + ## Runtime and integration validation Use Node 24 for this checkout; its `better-sqlite3` dependency does not compile @@ -201,8 +233,9 @@ Validation after separating agent evals on 2026-10-27 passed 469 hosted tests, production Wrangler dry-run with `LOADER`. This is fixture-backed evidence, not a real Sentry account or deployment. Unrelated CLI fixture/platform failures prevented a green full workspace suite. -Before production, validate hosted OAuth-to-API authorization and Cloudflare -CPU/termination limits. No deployment is established by these checks. +Before production, address the lifecycle findings above and validate hosted +OAuth-to-API authorization and Cloudflare CPU/termination limits. No deployment +is established by these checks. ## References From 645c16d26145488f34c53ffa5954e056b556de79 Mon Sep 17 00:00:00 2001 From: betegon Date: Tue, 27 Oct 2026 18:08:53 +0100 Subject: [PATCH 5/5] fix(mcp): close Code Mode execution and pending API calls Bound broker access and HTTP work to one execution so unawaited calls and late authorization cannot start writes after the result. Cover cleanup and the real deadline with Worker integration tests. --- docs/testing/code-mode.md | 79 ++++---- .../mcp-cloudflare/src/code-mode/broker.ts | 32 +++- .../src/code-mode/definitions.ts | 2 +- .../mcp-cloudflare/src/code-mode/execution.ts | 41 ++++ .../mcp-cloudflare/src/code-mode/sandbox.ts | 24 ++- .../src/code-mode/worker.test.ts | 45 ++++- .../mcp-cloudflare/src/code-mode/worker.ts | 65 ++++--- .../server/lib/code-mode.integration.test.ts | 175 +++++++++++++++++- packages/mcp-core/src/skillDefinitions.json | 4 +- 9 files changed, 376 insertions(+), 91 deletions(-) create mode 100644 packages/mcp-cloudflare/src/code-mode/execution.ts diff --git a/docs/testing/code-mode.md b/docs/testing/code-mode.md index a5535fad4..6c8fdf6b2 100644 --- a/docs/testing/code-mode.md +++ b/docs/testing/code-mode.md @@ -139,7 +139,7 @@ coverage. Result projection is available but not automatic semantic truncation. ## Limits and failures -Each execution has a 20-call fetch budget, 30-second I/O deadline, 50,000-byte +Each execution has a 20-call fetch budget, 30-second deadline, 50,000-byte result limit and 2,000,000-byte upstream response limit. The HTTP MCP SDK rejects tool arguments with more than 10,000 array elements or object keys before validation. A bounded reader rejects request bodies over 1 MiB before dispatch, @@ -162,40 +162,40 @@ code catches them. The broker exposes the event ID as `x-sentry-event-id` and reports only method/status, not request or response payloads. Expected 4xx responses do not create issues. -Fresh Worker IDs preserve per-execution isolation but affect +`LOADER.load()` creates a fresh Worker for each execution without ID caching, +following the [Worker Loader API](https://developers.cloudflare.com/dynamic-workers/api-reference/#load). +This preserves per-execution isolation but affects [Dynamic Workers costs](https://developers.cloudflare.com/dynamic-workers/pricing/). Any later reuse must preserve request credentials and authorization; changing the ID alone is insufficient. ### Lifecycle investigation -Local probes on 2026-10-27 used the unchanged sandbox from `f4f0766b`, a small -parent reproducing the execution/deadline logic, and a local HTTP fixture on -Miniflare `5.20261019.1-beta` / workerd `1.20261019.1`. They used no real token -or Sentry API. `waitUntil` deliberately kept the parent context alive; these -results do not establish the lifetime of a hosted MCP request. - -- With the real 30-second deadline, the parent returned timeout at 30.004s, - but the sandbox settled at 30.503s. `Promise.race` stops waiting, not execution. -- An unawaited POST reached the fixture after the parent returned success, - while still inside the deadline. Without the extended context, that probe - ended before HTTP arrived. Execution closure currently depends on context - cleanup, not an explicit broker close. -- The broker deadline rejected a request starting after expiry. Aborting an - already-sent HTTP request did not undo the fixture's accepted write. - -Cloudflare's [ExecutionGate](https://github.com/cloudflare/agents/blob/858674439f2966b16ea55bd37b46fad4248f2781/packages/codemode/src/executor.ts) -releases pending RPC when execution settles; it explicitly does not cancel host -work. A local reproduction confirmed that distinction. `LOADER.load()` worked, -but neither its handles nor `get()` handles exposed `Symbol.dispose` in this -installed runtime, so optional disposal alone would not fix these observations. - -Before production, define and test execution closure: reject new broker work -after completion, check state/deadline before and after async authorization, -release pending RPC and abort in-flight HTTP where possible. Define how -unawaited requests affect the result. Verify hosted cleanup, cancellation and -CPU/concurrency enforcement separately. No cleanup can roll back accepted writes. -These findings are pending work, not a production-readiness sign-off. +The parent owns one `ExecutionGate` per tool call, closed in `finally` on success, +error or timeout. Its 30-second deadline covers schema loading, Worker startup, +execution and reading the result. The sandbox receives the same deadline. + +Like Cloudflare's [ExecutionGate](https://github.com/cloudflare/agents/blob/a2d371ec333198855f4ac2aa4dadbe2696e73c75/packages/codemode/src/executor.ts), +closing releases pending sandbox-to-host RPC. The Sentry gate also aborts +upstream HTTP and rejects new work before authorization, then checks again after +authorization before sending an API request. A slow authorization lookup cannot +start a write after execution has closed. The shared OpenAPI cache retains its +independent 15-second timeout; closing one execution does not cancel a schema +load used by another caller. + +Programs must await every `sentry.request()`, including parallel calls. Returning +with calls still pending is an execution error, not success. Throwing preserves +the original error while cleanup aborts pending requests. The sandbox disables +its API access when the program ends; the parent broker enforces the boundary +independently of generated code. Closed RPC calls settle with an error response +instead of an unhandled rejection. + +Cancellation is best effort for HTTP already sent: a write accepted upstream +cannot be rolled back. The tool retains the warning to inspect state before +retrying writes. Gate closure revokes API access; it does not forcibly terminate +JavaScript still running inside an isolate. CPU and platform concurrency +enforcement still require hosted verification. Local handles do not expose +`Symbol.dispose`, so cleanup does not depend on optional disposal. ## Runtime and integration validation @@ -228,13 +228,22 @@ the Vitest pool using older workerd. Miniflare 5 uses fixtures use the existing Undici `MockAgent` with `disableNetConnect()`. No binary override is required. -Validation after separating agent evals on 2026-10-27 passed 469 hosted tests, -26 isolated runtime/access tests, root typechecks, frozen install and a -production Wrangler dry-run with `LOADER`. This is fixture-backed evidence, -not a real Sentry account or deployment. +Lifecycle validation on 2026-10-27 passed 474 hosted tests, 27 isolated +runtime/access tests, 1,836 core tests (six skipped), 89 stdio tests, root build +and typechecks, lint and documentation links. Three hosted regressions reproduce +the original closure bugs: return/throw with pending HTTP and delayed +authorization. The contexts stay alive after the result to prove cleanup does +not depend on request teardown. Two fake-timer tests check timeout behavior +before headers and during the response body; these do not establish a baseline +cancellation bug because fake timers do not advance native `AbortSignal.timeout`. +A real 30-second test observes the upstream socket close while an accepted write +remains. No unhandled RPC exceptions appear in these runs. + +These checks use external HTTP fixtures, not a real Sentry account or deployment. +No new model evals were run; the agent harness remains in its separate PR. Unrelated CLI fixture/platform failures prevented a green full workspace suite. -Before production, address the lifecycle findings above and validate hosted -OAuth-to-API authorization and Cloudflare CPU/termination limits. No deployment +Before production, validate hosted OAuth-to-API authorization and Cloudflare +CPU/termination limits. No deployment is established by these checks. ## References diff --git a/packages/mcp-cloudflare/src/code-mode/broker.ts b/packages/mcp-cloudflare/src/code-mode/broker.ts index 34d3cb6d4..f3b9d4c30 100644 --- a/packages/mcp-cloudflare/src/code-mode/broker.ts +++ b/packages/mcp-cloudflare/src/code-mode/broker.ts @@ -1,6 +1,7 @@ import { RpcTarget } from "cloudflare:workers"; import { ApiServerError } from "@sentry/mcp-core/api-client"; import { logIssue } from "@sentry/mcp-core/telem/logging"; +import type { ExecutionGate } from "./execution"; import { configuredUrl, readBoundedBody } from "./http"; import type { ApiAuthorization } from "./production"; @@ -15,7 +16,7 @@ export interface CodeModeEnv { export interface BrokerOptions { accessToken: string; - deadline: number; + execution: ExecutionGate; apiBaseUrl: string; allowWrites: boolean; authorization?: ApiAuthorization; @@ -30,6 +31,24 @@ export class ApiBroker extends RpcTarget { this.#options = options; } async fetch(request: Request): Promise { + try { + return await this.#options.execution.run(() => this.#fetch(request)); + } catch (error) { + if (!this.#options.execution.signal.aborted) throw error; + // Settle RPC with a value, even when the sandbox no longer awaits it. + return Response.json( + { + detail: + error instanceof Error + ? error.message + : "Code execution has finished.", + }, + { status: 408 }, + ); + } + } + + async #fetch(request: Request): Promise { const base = configuredUrl(this.#options.apiBaseUrl); const requested = new URL(request.url); const prefix = base.pathname.replace(/\/$/, ""); @@ -65,19 +84,14 @@ export class ApiBroker extends RpcTarget { request.url, request.method, ); + this.#options.execution.assertOpen(); if ("error" in authorized) { return Response.json({ detail: authorized.error }, { status: 403 }); } destination = authorized.url; attribution = authorized.headers; } - const remaining = this.#options.deadline - Date.now(); - if (remaining <= 0) { - return Response.json( - { detail: "Execution deadline exceeded." }, - { status: 408 }, - ); - } + this.#options.execution.assertOpen(); // Only API-relevant input headers cross the boundary. Auth is always replaced. const headers = new Headers(); for (const name of [ @@ -96,7 +110,7 @@ export class ApiBroker extends RpcTarget { new Request(new Request(destination, request), { headers, redirect: "manual", - signal: AbortSignal.timeout(remaining), + signal: this.#options.execution.signal, }), ); if (response.status === 401) { diff --git a/packages/mcp-cloudflare/src/code-mode/definitions.ts b/packages/mcp-cloudflare/src/code-mode/definitions.ts index 9f3426624..189ef643f 100644 --- a/packages/mcp-cloudflare/src/code-mode/definitions.ts +++ b/packages/mcp-cloudflare/src/code-mode/definitions.ts @@ -69,7 +69,7 @@ Schema example: async () => { const path = "/api/0/organizations/{organization_i title: "Execute Sentry API code", description: `Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers["www-authenticate"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP.${authorizationRequired ? " Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly." : ""} Before writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available. -Combine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents. +Combine dependent requests, loops, pagination and verification in code. Await every sentry.request(), including parallel calls, before returning. Pending calls are cancelled when execution ends or times out; already accepted writes are not rolled back. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents. Limits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are ${allowWrites ? "enabled" : "disabled"}. No automatic retries. Execution is not atomic; inspect state before retrying a failed write. Example: async () => { const response = await sentry.request({path: "/organizations/"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }`, inputSchema: z.object({ diff --git a/packages/mcp-cloudflare/src/code-mode/execution.ts b/packages/mcp-cloudflare/src/code-mode/execution.ts new file mode 100644 index 000000000..48228b019 --- /dev/null +++ b/packages/mcp-cloudflare/src/code-mode/execution.ts @@ -0,0 +1,41 @@ +/** Close pending RPC like Cloudflare's ExecutionGate, and abort upstream HTTP. */ +export class ExecutionGate { + readonly deadline = Date.now() + 30_000; + readonly #controller = new AbortController(); + readonly signal = this.#controller.signal; + #resolveClosed!: (reason: Error) => void; + readonly #whenClosed = new Promise((resolve) => { + this.#resolveClosed = resolve; + }); + readonly #timer = setTimeout( + () => this.close(new Error("Execution deadline exceeded.")), + 30_000, + ); + + assertOpen(): void { + if (Date.now() >= this.deadline) { + this.close(new Error("Execution deadline exceeded.")); + } + this.signal.throwIfAborted(); + } + + /** Callbacks may outlive this wait; recheck the gate after awaits before HTTP. */ + async run(work: () => Promise): Promise { + this.assertOpen(); + const result = await Promise.race([ + work(), + this.#whenClosed.then((reason) => { + throw reason; + }), + ]); + this.assertOpen(); + return result; + } + + close(reason = new Error("Code execution has finished.")): void { + if (this.signal.aborted) return; + clearTimeout(this.#timer); + this.#controller.abort(reason); + this.#resolveClosed(reason); + } +} diff --git a/packages/mcp-cloudflare/src/code-mode/sandbox.ts b/packages/mcp-cloudflare/src/code-mode/sandbox.ts index 7632ed515..7f2ed01f5 100644 --- a/packages/mcp-cloudflare/src/code-mode/sandbox.ts +++ b/packages/mcp-cloudflare/src/code-mode/sandbox.ts @@ -8,10 +8,11 @@ const apiBase = ${JSON.stringify(apiBase ?? null)}; let deadline = 0; let apiCalls = 0; let writesAttempted = 0; +let pendingApiCalls = 0; let apiBroker; -const sentry = { - async request({ method = "GET", path, query, body }) { - if (!apiBase || !apiBroker) throw new Error("API access is unavailable during search."); +async function request({ method = "GET", path, query, body }) { + if (!apiBase) throw new Error("API access is unavailable during search."); + if (!apiBroker) throw new Error("Code execution has finished."); if (typeof path !== "string" || !path.startsWith("/") || path.startsWith("//")) { throw new Error("path must start with / and contain an API path, not a URL."); } @@ -49,17 +50,28 @@ const sentry = { throw error; } return { status: response.status, headers: Object.fromEntries(response.headers), body: data }; +} +const sentry = { + request(options) { + pendingApiCalls++; + const pending = request(options).finally(() => pendingApiCalls--); + // A forgotten await must become the execution error below, not an unhandled rejection. + pending.catch(() => {}); + return pending; }, }; Object.defineProperty(globalThis, "sentry", { value: sentry }); export default class extends WorkerEntrypoint { - async run(broker) { + async run(broker, executionDeadline) { apiBroker = broker; - deadline = Date.now() + 30_000; + deadline = executionDeadline; try { const { default: run } = await import("./user.js"); const result = await run(); const serialized = JSON.stringify({ result: result ?? null, stats: { apiCalls, writesAttempted } }); + if (pendingApiCalls > 0) { + throw new Error("Await every sentry.request() before returning. API calls are still pending."); + } if (new TextEncoder().encode(serialized).byteLength > 50_000) { throw new Error("Result exceeds 50,000 bytes. Return selected fields or a smaller page."); } @@ -74,6 +86,8 @@ export default class extends WorkerEntrypoint { status: error.status, method: error.method, path: error.path, headers: error.headers, } } : {}), stats: { apiCalls, writesAttempted } }, { status: 400 }); + } finally { + apiBroker = undefined; } } } diff --git a/packages/mcp-cloudflare/src/code-mode/worker.test.ts b/packages/mcp-cloudflare/src/code-mode/worker.test.ts index 0f0a5165c..dd4b4f864 100644 --- a/packages/mcp-cloudflare/src/code-mode/worker.test.ts +++ b/packages/mcp-cloudflare/src/code-mode/worker.test.ts @@ -1,6 +1,6 @@ import { execFile } from "node:child_process"; import { mkdtemp, rm } from "node:fs/promises"; -import { createServer, type Server } from "node:http"; +import { createServer, type Server, type ServerResponse } from "node:http"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { promisify } from "node:util"; @@ -26,6 +26,9 @@ interface ToolResult { const requests: Array<{ method: string; path: string; auth?: string }> = []; const specRequests: string[] = []; +const acceptedWrites: string[] = []; +let slowWriteResponse: ServerResponse | undefined; +let slowWriteClosed = false; const workflowPath = "/api/0/organizations/{organization_id_or_slug}/workflows/{workflow_id}/"; const releasePath = "/api/0/organizations/{organization_id_or_slug}/releases/"; @@ -235,6 +238,16 @@ beforeAll(async () => { path: request.url ?? "", auth: request.headers.authorization, }); + if (request.url === "/api/0/slow-write/") { + acceptedWrites.push("accepted-write"); + slowWriteResponse = response; + response.once("close", () => { + slowWriteClosed = true; + }); + response.writeHead(201, { "Content-Type": "application/json" }); + response.write('{"id":"accepted-write",'); + return; + } if (request.url === "/api/0/redirect/") { response.writeHead(302, { Location: `${origin}/outside-api` }); response.end(); @@ -288,6 +301,9 @@ beforeAll(async () => { beforeEach(() => { requests.length = 0; specRequests.length = 0; + acceptedWrites.length = 0; + slowWriteResponse = undefined; + slowWriteClosed = false; }); afterAll(async () => { @@ -301,6 +317,33 @@ afterAll(async () => { }); describe("HTTP Code Mode through real Dynamic Workers", () => { + it("aborts a stalled response body at the real deadline without rolling back an accepted write", async () => { + const started = performance.now(); + try { + const result = await callTool( + "execute_sentry_code", + 'async () => sentry.request({method: "POST", path: "/slow-write/", body: {name: "Fixture item"}})', + ); + expect(performance.now() - started).toBeGreaterThanOrEqual(29_000); + expect(result.isError).toBe(true); + expect(result.content[0]?.text).toContain("Execution deadline exceeded"); + expect(result.content[0]?.text).toContain( + "writes may already have completed", + ); + await expect.poll(() => slowWriteClosed, { timeout: 2_000 }).toBe(true); + expect(acceptedWrites).toEqual(["accepted-write"]); + expect(requests).toEqual([ + { + method: "POST", + path: "/api/0/slow-write/", + auth: "Bearer fixture-only-token", + }, + ]); + } finally { + slowWriteResponse?.destroy(); + } + }, 45_000); + it("bounds nested tool arguments before starting a Dynamic Worker", async () => { const result = await rpc("tools/call", { name: "execute_sentry_code", diff --git a/packages/mcp-cloudflare/src/code-mode/worker.ts b/packages/mcp-cloudflare/src/code-mode/worker.ts index 33c631d51..6e8b28842 100644 --- a/packages/mcp-cloudflare/src/code-mode/worker.ts +++ b/packages/mcp-cloudflare/src/code-mode/worker.ts @@ -10,6 +10,7 @@ import { codeModeResultSchema, getCodeModeToolDefinitions, } from "./definitions"; +import { ExecutionGate } from "./execution"; import { configuredUrl, limitMcpRequestBody, readBoundedBody } from "./http"; import { getSpec } from "./openapi"; import type { ApiAuthorization } from "./production"; @@ -18,7 +19,7 @@ import { sandboxSource } from "./sandbox"; export type { CodeModeEnv } from "./broker"; declare class Sandbox extends WorkerEntrypoint { - run(broker?: ApiBroker): Promise; + run(broker: ApiBroker | undefined, deadline: number): Promise; } /** Callbacks capture this request's bearer token; never share this server across users. */ @@ -29,19 +30,19 @@ export function registerCodeModeTools( authorization?: ApiAuthorization, ): void { async function evaluate(code: string, execute: boolean) { + const execution = new ExecutionGate(); const hint = execute && env.CODE_MODE_ALLOW_WRITES === "true" ? " Execution is not atomic: writes may already have completed. Inspect API state before retrying writes." : ""; try { - const apiBase = configuredUrl(env.API_BASE_URL) - .toString() - .replace(/\/$/, ""); - const deadline = Date.now() + 30_000; - const spec = execute ? undefined : await getSpec(env.OPENAPI_URL); - const worker = env.LOADER.get( - `sentry-code-mode-${crypto.randomUUID()}`, - async () => ({ + const value = await execution.run(async () => { + const apiBase = configuredUrl(env.API_BASE_URL) + .toString() + .replace(/\/$/, ""); + const spec = execute ? undefined : await getSpec(env.OPENAPI_URL); + execution.assertOpen(); + const workerCode = { compatibilityDate: "2026-01-12", mainModule: "worker.js", modules: { @@ -53,31 +54,25 @@ export function registerCodeModeTools( // the trusted harness separately bounds HTTP calls and output size. limits: { cpuMs: 1_000, subRequests: execute ? 20 : 0 }, globalOutbound: null, - }), - ); - const broker = execute - ? new ApiBroker({ - accessToken: token, - deadline, - apiBaseUrl: env.API_BASE_URL, - allowWrites: env.CODE_MODE_ALLOW_WRITES === "true", - authorization, - }) - : undefined; - let timeout: ReturnType | undefined; - const response = await Promise.race([ - worker.getEntrypoint().run(broker), - new Promise((_, reject) => { - timeout = setTimeout( - () => reject(new Error("Execution deadline exceeded.")), - Math.max(1, deadline - Date.now()), - ); - }), - ]).finally(() => clearTimeout(timeout)); - const body = await readBoundedBody(response.body, 55_000); - const value = codeModeResultSchema.parse( - JSON.parse(new TextDecoder().decode(body)), - ); + }; + const worker = env.LOADER.load(workerCode); + const broker = execute + ? new ApiBroker({ + accessToken: token, + execution, + apiBaseUrl: env.API_BASE_URL, + allowWrites: env.CODE_MODE_ALLOW_WRITES === "true", + authorization, + }) + : undefined; + const response = await worker + .getEntrypoint() + .run(broker, execution.deadline); + const body = await readBoundedBody(response.body, 55_000); + return codeModeResultSchema.parse( + JSON.parse(new TextDecoder().decode(body)), + ); + }); if (value.error !== undefined) { const failure = { ...value, error: value.error + hint }; return { @@ -99,6 +94,8 @@ export function registerCodeModeTools( { type: "text" as const, text: message.slice(0, 4_000) + hint }, ], }; + } finally { + execution.close(); } } for (const [name, definition] of Object.entries( diff --git a/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts index 2fbb21865..acb096e56 100644 --- a/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/code-mode.integration.test.ts @@ -68,6 +68,7 @@ interface ApiRequest { method: string; headers: Headers; body: string; + signal: AbortSignal; } interface ToolResult { @@ -85,7 +86,8 @@ interface ToolList { const originalFetch = globalThis.fetch; let apiRequests: ApiRequest[]; -let apiResponse: (request: ApiRequest) => Response; +let apiResponse: (request: ApiRequest) => Response | Promise; +let beforeRegionResponse: (() => Promise) | undefined; const revokeGrant = vi.fn(async () => {}); function workerEnv(): Env { @@ -102,6 +104,7 @@ async function dispatchRpc( options: { props?: Partial; path?: string; + keepAlive?: Promise; protocolVersion?: "2026-07-28" | "2025-03-26"; } = {}, ) { @@ -117,6 +120,7 @@ async function dispatchRpc( ...options.props, }; const ctx = Object.assign(createExecutionContext(), { props }); + if (options.keepAlive) ctx.waitUntil(options.keepAlive); const request = new Request(`http://localhost${options.path ?? "/mcp"}`, { method: "POST", headers: { @@ -162,8 +166,13 @@ async function rpc( options: Parameters[2] = {}, ): Promise { const { response, ctx } = await dispatchRpc(method, params, options); - const text = await response.text(); + const result = await readRpcResult(response); await waitOnExecutionContext(ctx); + return result; +} + +async function readRpcResult(response: Response): Promise { + const text = await response.text(); expect(response.status, text).toBe(200); const data = text.startsWith("{") ? text @@ -192,6 +201,7 @@ beforeEach(() => { apiRequests = []; revokeGrant.mockClear(); apiResponse = () => Response.json({ ok: true }); + beforeRegionResponse = undefined; // The pool runs the real main Worker in this isolate. Only external HTTP is // simulated; MCP handlers, Dynamic Workers and the RPC broker are real. vi.spyOn(globalThis, "fetch").mockImplementation(async (input, init) => { @@ -206,12 +216,14 @@ beforeEach(() => { method: request.method, headers: request.headers, body: await request.text(), + signal: request.signal, }; apiRequests.push(recorded); const organization = /^\/api\/0\/organizations\/([^/]+)\/$/.exec( url.pathname, ); if (organization) { + await beforeRegionResponse?.(); return Response.json({ id: "100", slug: organization[1], @@ -225,13 +237,168 @@ beforeEach(() => { if (url.pathname === "/api/0/projects/code-mode-org/web/") { return Response.json({ id: "200", slug: "web", name: "Web" }); } - return apiResponse(recorded); + return await apiResponse(recorded); }); }); -afterEach(() => vi.restoreAllMocks()); +afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +function deferred() { + let resolve!: () => void; + const promise = new Promise((finish) => { + resolve = finish; + }); + return { promise, resolve }; +} describe("Code Mode on the authenticated MCP", () => { + it.each(["return", "throw"])( + "closes unawaited API work when the program exits with %s", + async (exit) => { + const started = deferred(); + const finish = deferred(); + let write: ApiRequest | undefined; + apiResponse = async (request) => { + if (request.method === "POST") { + write = request; + started.resolve(); + await finish.promise; + } else { + await started.promise; + } + return Response.json({ ok: true }); + }; + const { response, ctx } = await dispatchRpc( + "tools/call", + { + name: "execute_sentry_code", + arguments: { + code: `async () => { + sentry.request({method: "POST", path: "/organizations/code-mode-org/dashboards/", body: {title: "Copy"}}).catch(() => {}); + await sentry.request({path: "/organizations/"}); + ${exit === "return" ? 'return "done";' : 'throw new Error("Program failed");'} + }`, + }, + }, + { props: { grantedSkills: ["api-write"] }, keepAlive: finish.promise }, + ); + try { + const result = await readRpcResult(response); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain( + exit === "return" ? "Await every sentry.request()" : "Program failed", + ); + expect(result.content[0].text).toContain( + "writes may already have completed", + ); + expect(write?.signal.aborted).toBe(true); + } finally { + finish.resolve(); + await waitOnExecutionContext(ctx); + } + }, + ); + + it("does not send a write when authorization finishes after the program", async () => { + const authorizing = deferred(); + const finish = deferred(); + beforeRegionResponse = async () => { + authorizing.resolve(); + await finish.promise; + }; + apiResponse = async () => { + await authorizing.promise; + return Response.json({ ok: true }); + }; + const { response, ctx } = await dispatchRpc( + "tools/call", + { + name: "execute_sentry_code", + arguments: { + code: `async () => { + sentry.request({method: "POST", path: "/organizations/code-mode-org/dashboards/", body: {title: "Copy"}}).catch(() => {}); + await sentry.request({path: "/organizations/"}); + return "done"; + }`, + }, + }, + { props: { grantedSkills: ["api-write"] }, keepAlive: finish.promise }, + ); + try { + const result = await readRpcResult(response); + finish.resolve(); + await waitOnExecutionContext(ctx); + // Drain the released lookup and any continuation into the API broker. + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(apiRequests.filter(({ method }) => method === "POST")).toEqual([]); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain("Await every sentry.request()"); + } finally { + finish.resolve(); + await waitOnExecutionContext(ctx); + } + }); + + it.each(["headers", "body"])( + "aborts an awaited API request stalled before its %s at the execution deadline", + async (phase) => { + vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout"] }); + const started = deferred(); + const finish = deferred(); + let pending: ApiRequest | undefined; + apiResponse = async (request) => { + pending = request; + started.resolve(); + if (phase === "headers") { + await finish.promise; + return Response.json({ ok: true }); + } + return new Response( + new ReadableStream({ + start(controller) { + request.signal.addEventListener( + "abort", + () => controller.error(request.signal.reason), + { once: true }, + ); + void finish.promise.then(() => { + if (!request.signal.aborted) controller.close(); + }); + }, + }), + { headers: { "content-type": "application/json" } }, + ); + }; + const dispatched = dispatchRpc( + "tools/call", + { + name: "execute_sentry_code", + arguments: { + code: 'async () => sentry.request({path: "/organizations/"})', + }, + }, + { keepAlive: finish.promise }, + ); + await started.promise; + await vi.advanceTimersByTimeAsync(30_000); + const { response, ctx } = await dispatched; + try { + const result = await readRpcResult(response); + expect(result.isError).toBe(true); + expect(result.content[0].text).toContain("Execution deadline exceeded"); + expect(pending?.signal.aborted).toBe(true); + expect(apiRequests).toHaveLength(1); + } finally { + finish.resolve(); + vi.useRealTimers(); + await waitOnExecutionContext(ctx); + } + }, + ); + it("rejects excessive nested argument members before executing code", async () => { const result = await rpc("tools/call", { name: "execute_sentry_code", diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index 4c08642a7..9dec89dd2 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -651,7 +651,7 @@ "tools": [ { "name": "execute_sentry_code", - "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are disabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", + "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Await every sentry.request(), including parallel calls, before returning. Pending calls are cancelled when execution ends or times out; already accepted writes are not rolled back. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are disabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", "requiredScopes": [] }, { @@ -692,7 +692,7 @@ "tools": [ { "name": "execute_sentry_code", - "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are enabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", + "description": "Execute a JavaScript async arrow function with sentry.request({method?, path, query?, body?}). Discover endpoints and schemas with search_sentry_api. method defaults to GET. path is relative to the configured API prefix; full /api/0 paths are also accepted. query accepts string, number or boolean values and arrays of those values; arrays produce repeated parameters, and null/undefined values are omitted. body is JSON. Each request returns {status, headers, body}; headers.link preserves pagination. Non-2xx responses throw an Error with status, method, path, headers, data. On 403, headers[\"www-authenticate\"] may provide insufficient_scope evidence; a denial without that evidence does not identify its cause. Credentials are injected outside the sandbox. Direct network access is disabled; use sentry.request for HTTP. Only documented operations within this connection’s API read/write permission and organization constraint are allowed. Sentry separately enforces the upstream token scopes and user/resource permissions. A permission denial cannot be bypassed by changing routes or using fetch directly.\nBefore writing, discover the operation and inspect its request contract, defaults and update semantics. For edits, read the current resource through HTTP: other MCP tools may return renamed, enriched or filtered views. Even a raw GET response is not a write template: sending the same field and value back can trigger effects, including propagating an aggregate value to child resources. Build the body explicitly from required fields and requested changes. Include other fields only when the request contract or documented update behavior requires them; do not resend unchanged optional fields merely because GET returned them. Do not assume PUT replaces everything or that omitted fields always stay unchanged, and do not turn a missing field into an empty list or null. When the contract requires resending nested collections, retain existing IDs, values and relationships unless the user requested changing them. If preservation is ambiguous, obtain more documentation or report the limitation rather than inventing write semantics. Re-read after a write to verify requested changes and preserved settings. If an unexpected side effect occurs, do not probe speculative write bodies or routes to repair it; report the actual state unless a documented repair is available.\nCombine dependent requests, loops, pagination and verification in code. Await every sentry.request(), including parallel calls, before returning. Pending calls are cancelled when execution ends or times out; already accepted writes are not rolled back. Keep intermediate responses inside the program and return only the fields needed to answer the user, such as IDs, URLs, counts or a concise verification result. Return full objects only when the user needs their full contents.\nLimits: 20 API calls, 30 seconds, 50,000 output bytes, 2 MB per API response. Writes are enabled. No automatic retries. Execution is not atomic; inspect state before retrying a failed write.\nExample: async () => { const response = await sentry.request({path: \"/organizations/\"}); return {organizations: response.body.map(({slug, name}) => ({slug, name})), pagination: response.headers.link}; }", "requiredScopes": [] }, {