#
tokens: 37986/50000 2/501 files (page 17/20)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 17 of 20. Use http://codebase.md/getsentry/sentry-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .claude
│   ├── agents
│   │   └── claude-optimizer.md
│   ├── commands
│   │   ├── gh-pr.md
│   │   └── gh-review.md
│   └── settings.json
├── .craft.yml
├── .cursor
│   └── mcp.json
├── .env.example
├── .github
│   └── workflows
│       ├── deploy.yml
│       ├── eval.yml
│       ├── merge-jobs.yml
│       ├── release.yml
│       ├── smoke-tests.yml
│       ├── test.yml
│       └── token-cost.yml
├── .gitignore
├── .mcp.json
├── .vscode
│   ├── extensions.json
│   ├── mcp.json
│   └── settings.json
├── AGENTS.md
├── benchmark-agent.sh
├── bin
│   └── bump-version.sh
├── biome.json
├── CLAUDE.md
├── codecov.yml
├── core
├── docs
│   ├── adding-tools.md
│   ├── api-patterns.md
│   ├── architecture.md
│   ├── cloudflare
│   │   ├── architecture.md
│   │   ├── oauth-architecture.md
│   │   └── overview.md
│   ├── coding-guidelines.md
│   ├── common-patterns.md
│   ├── error-handling.md
│   ├── github-actions.md
│   ├── llms
│   │   ├── document-scopes.md
│   │   ├── documentation-style-guide.md
│   │   └── README.md
│   ├── logging.md
│   ├── monitoring.md
│   ├── pr-management.md
│   ├── quality-checks.md
│   ├── README.md
│   ├── releases
│   │   ├── cloudflare.md
│   │   └── stdio.md
│   ├── search-events-api-patterns.md
│   ├── security.md
│   ├── specs
│   │   ├── README.md
│   │   ├── search-events.md
│   │   └── subpath-constraints.md
│   ├── testing-remote.md
│   ├── testing-stdio.md
│   ├── testing.md
│   └── token-cost-tracking.md
├── LICENSE.md
├── Makefile
├── package.json
├── packages
│   ├── mcp-cloudflare
│   │   ├── .env.example
│   │   ├── components.json
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── public
│   │   │   ├── demo.cast
│   │   │   ├── favicon.ico
│   │   │   ├── flow-transparent.png
│   │   │   ├── flow.jpg
│   │   │   ├── keycap-⌘.png
│   │   │   ├── keycap-c.png
│   │   │   └── keycap-v.png
│   │   ├── src
│   │   │   ├── client
│   │   │   │   ├── app.tsx
│   │   │   │   ├── components
│   │   │   │   │   ├── animation
│   │   │   │   │   │   ├── browser-ui
│   │   │   │   │   │   │   ├── BrowserWindow.tsx
│   │   │   │   │   │   │   ├── BrowserWindowIconSidebar.tsx
│   │   │   │   │   │   │   ├── DiffBlock.tsx
│   │   │   │   │   │   │   ├── IDEWindow.tsx
│   │   │   │   │   │   │   ├── IssueDetails.tsx
│   │   │   │   │   │   │   ├── keys-copy.tsx
│   │   │   │   │   │   │   ├── LoadingSquares.tsx
│   │   │   │   │   │   │   ├── RootCause.tsx
│   │   │   │   │   │   │   ├── seer-clipmask.tsx
│   │   │   │   │   │   │   ├── seer-noisefilter.tsx
│   │   │   │   │   │   │   ├── seer.tsx
│   │   │   │   │   │   │   └── WindowHeader.tsx
│   │   │   │   │   │   ├── BrowserAnimation.tsx
│   │   │   │   │   │   ├── DataWire.tsx
│   │   │   │   │   │   ├── dracula.css
│   │   │   │   │   │   ├── terminal-ui
│   │   │   │   │   │   │   ├── keys-paste.tsx
│   │   │   │   │   │   │   ├── SpeedDisplay.tsx
│   │   │   │   │   │   │   └── StepsList.tsx
│   │   │   │   │   │   ├── TerminalAnimation.tsx
│   │   │   │   │   │   └── tests.tsx
│   │   │   │   │   ├── chat
│   │   │   │   │   │   ├── auth-form.tsx
│   │   │   │   │   │   ├── chat-input.tsx
│   │   │   │   │   │   ├── chat-message.tsx
│   │   │   │   │   │   ├── chat-messages.tsx
│   │   │   │   │   │   ├── chat-ui.tsx
│   │   │   │   │   │   ├── chat.tsx
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── tool-invocation.tsx
│   │   │   │   │   │   └── types.ts
│   │   │   │   │   ├── fragments
│   │   │   │   │   │   ├── install-tabs.tsx
│   │   │   │   │   │   ├── remote-setup.tsx
│   │   │   │   │   │   ├── setup-guide.tsx
│   │   │   │   │   │   └── stdio-setup.tsx
│   │   │   │   │   ├── getting-started.tsx
│   │   │   │   │   ├── hero
│   │   │   │   │   │   ├── header-divider.tsx
│   │   │   │   │   │   └── hero-block.tsx
│   │   │   │   │   ├── home-layout
│   │   │   │   │   │   ├── footer.tsx
│   │   │   │   │   │   └── sidebars.tsx
│   │   │   │   │   ├── ui
│   │   │   │   │   │   ├── accordion.tsx
│   │   │   │   │   │   ├── backdrop.tsx
│   │   │   │   │   │   ├── badge.tsx
│   │   │   │   │   │   ├── base.tsx
│   │   │   │   │   │   ├── button.tsx
│   │   │   │   │   │   ├── code-snippet.tsx
│   │   │   │   │   │   ├── header.tsx
│   │   │   │   │   │   ├── icon.tsx
│   │   │   │   │   │   ├── icons
│   │   │   │   │   │   │   ├── claude.tsx
│   │   │   │   │   │   │   ├── codex.tsx
│   │   │   │   │   │   │   ├── cursor.tsx
│   │   │   │   │   │   │   ├── gemini.tsx
│   │   │   │   │   │   │   ├── sentry.tsx
│   │   │   │   │   │   │   ├── vscode.tsx
│   │   │   │   │   │   │   ├── warp.tsx
│   │   │   │   │   │   │   ├── windsurf.tsx
│   │   │   │   │   │   │   └── zed.tsx
│   │   │   │   │   │   ├── interactive-markdown.tsx
│   │   │   │   │   │   ├── json-schema-params.tsx
│   │   │   │   │   │   ├── key-icon.tsx
│   │   │   │   │   │   ├── key-word.tsx
│   │   │   │   │   │   ├── markdown.tsx
│   │   │   │   │   │   ├── note.tsx
│   │   │   │   │   │   ├── prose.tsx
│   │   │   │   │   │   ├── section.tsx
│   │   │   │   │   │   ├── slash-command-actions.tsx
│   │   │   │   │   │   ├── slash-command-text.tsx
│   │   │   │   │   │   ├── sliding-panel.tsx
│   │   │   │   │   │   ├── template-vars.tsx
│   │   │   │   │   │   ├── tool-actions.tsx
│   │   │   │   │   │   └── typewriter.tsx
│   │   │   │   │   └── usecases
│   │   │   │   │       ├── fix-bugs.tsx
│   │   │   │   │       ├── index.tsx
│   │   │   │   │       ├── instrument.tsx
│   │   │   │   │       ├── search-things.tsx
│   │   │   │   │       └── search-visual.tsx
│   │   │   │   ├── contexts
│   │   │   │   │   └── auth-context.tsx
│   │   │   │   ├── hooks
│   │   │   │   │   ├── use-endpoint-mode.ts
│   │   │   │   │   ├── use-mcp-metadata.ts
│   │   │   │   │   ├── use-persisted-chat.ts
│   │   │   │   │   ├── use-scroll-lock.ts
│   │   │   │   │   └── use-streaming-simulation.ts
│   │   │   │   ├── index.css
│   │   │   │   ├── instrument.ts
│   │   │   │   ├── lib
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── main.tsx
│   │   │   │   ├── utils
│   │   │   │   │   ├── chat-error-handler.ts
│   │   │   │   │   ├── cursor-deeplink.ts
│   │   │   │   │   └── index.ts
│   │   │   │   └── vite-env.d.ts
│   │   │   ├── constants.ts
│   │   │   ├── server
│   │   │   │   ├── app.test.ts
│   │   │   │   ├── app.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── lib
│   │   │   │   │   ├── approval-dialog.test.ts
│   │   │   │   │   ├── approval-dialog.ts
│   │   │   │   │   ├── constraint-utils.test.ts
│   │   │   │   │   ├── constraint-utils.ts
│   │   │   │   │   ├── html-utils.ts
│   │   │   │   │   ├── mcp-handler.test.ts
│   │   │   │   │   ├── mcp-handler.ts
│   │   │   │   │   └── slug-validation.ts
│   │   │   │   ├── logging.ts
│   │   │   │   ├── oauth
│   │   │   │   │   ├── authorize.test.ts
│   │   │   │   │   ├── callback.test.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── helpers.test.ts
│   │   │   │   │   ├── helpers.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── routes
│   │   │   │   │   │   ├── authorize.ts
│   │   │   │   │   │   ├── callback.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   └── state.ts
│   │   │   │   ├── routes
│   │   │   │   │   ├── chat-oauth.ts
│   │   │   │   │   ├── chat.ts
│   │   │   │   │   ├── mcp.ts
│   │   │   │   │   ├── metadata.ts
│   │   │   │   │   ├── search.test.ts
│   │   │   │   │   └── search.ts
│   │   │   │   ├── sentry.config.ts
│   │   │   │   ├── types
│   │   │   │   │   └── chat.ts
│   │   │   │   ├── types.ts
│   │   │   │   └── utils
│   │   │   │       ├── auth-errors.ts
│   │   │   │       ├── client-ip.test.ts
│   │   │   │       ├── client-ip.ts
│   │   │   │       ├── rate-limiter.test.ts
│   │   │   │       └── rate-limiter.ts
│   │   │   └── test-setup.ts
│   │   ├── tsconfig.client.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   ├── tsconfig.server.json
│   │   ├── vite.config.ts
│   │   ├── vitest.config.ts
│   │   ├── worker-configuration.d.ts
│   │   ├── wrangler.canary.jsonc
│   │   └── wrangler.jsonc
│   ├── mcp-core
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── scripts
│   │   │   ├── generate-definitions.ts
│   │   │   ├── generate-otel-namespaces.ts
│   │   │   ├── measure-token-cost.ts
│   │   │   └── validate-skills-mapping.ts
│   │   ├── src
│   │   │   ├── api-client
│   │   │   │   ├── client.test.ts
│   │   │   │   ├── client.ts
│   │   │   │   ├── errors.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── schema.test.ts
│   │   │   │   ├── schema.ts
│   │   │   │   └── types.ts
│   │   │   ├── constants.ts
│   │   │   ├── errors.test.ts
│   │   │   ├── errors.ts
│   │   │   ├── internal
│   │   │   │   ├── agents
│   │   │   │   │   ├── callEmbeddedAgent.ts
│   │   │   │   │   ├── openai-provider.test.ts
│   │   │   │   │   ├── openai-provider.ts
│   │   │   │   │   └── tools
│   │   │   │   │       ├── data
│   │   │   │   │       │   ├── __namespaces.json
│   │   │   │   │       │   ├── android.json
│   │   │   │   │       │   ├── app.json
│   │   │   │   │       │   ├── artifact.json
│   │   │   │   │       │   ├── aspnetcore.json
│   │   │   │   │       │   ├── aws.json
│   │   │   │   │       │   ├── azure.json
│   │   │   │   │       │   ├── browser.json
│   │   │   │   │       │   ├── cassandra.json
│   │   │   │   │       │   ├── cicd.json
│   │   │   │   │       │   ├── CLAUDE.md
│   │   │   │   │       │   ├── client.json
│   │   │   │   │       │   ├── cloud.json
│   │   │   │   │       │   ├── cloudevents.json
│   │   │   │   │       │   ├── cloudfoundry.json
│   │   │   │   │       │   ├── code.json
│   │   │   │   │       │   ├── container.json
│   │   │   │   │       │   ├── cpu.json
│   │   │   │   │       │   ├── cpython.json
│   │   │   │   │       │   ├── database.json
│   │   │   │   │       │   ├── db.json
│   │   │   │   │       │   ├── deployment.json
│   │   │   │   │       │   ├── destination.json
│   │   │   │   │       │   ├── device.json
│   │   │   │   │       │   ├── disk.json
│   │   │   │   │       │   ├── dns.json
│   │   │   │   │       │   ├── dotnet.json
│   │   │   │   │       │   ├── elasticsearch.json
│   │   │   │   │       │   ├── enduser.json
│   │   │   │   │       │   ├── error.json
│   │   │   │   │       │   ├── faas.json
│   │   │   │   │       │   ├── feature_flags.json
│   │   │   │   │       │   ├── file.json
│   │   │   │   │       │   ├── gcp.json
│   │   │   │   │       │   ├── gen_ai.json
│   │   │   │   │       │   ├── geo.json
│   │   │   │   │       │   ├── go.json
│   │   │   │   │       │   ├── graphql.json
│   │   │   │   │       │   ├── hardware.json
│   │   │   │   │       │   ├── heroku.json
│   │   │   │   │       │   ├── host.json
│   │   │   │   │       │   ├── http.json
│   │   │   │   │       │   ├── ios.json
│   │   │   │   │       │   ├── jvm.json
│   │   │   │   │       │   ├── k8s.json
│   │   │   │   │       │   ├── linux.json
│   │   │   │   │       │   ├── log.json
│   │   │   │   │       │   ├── mcp.json
│   │   │   │   │       │   ├── messaging.json
│   │   │   │   │       │   ├── network.json
│   │   │   │   │       │   ├── nodejs.json
│   │   │   │   │       │   ├── oci.json
│   │   │   │   │       │   ├── opentracing.json
│   │   │   │   │       │   ├── os.json
│   │   │   │   │       │   ├── otel.json
│   │   │   │   │       │   ├── peer.json
│   │   │   │   │       │   ├── process.json
│   │   │   │   │       │   ├── profile.json
│   │   │   │   │       │   ├── rpc.json
│   │   │   │   │       │   ├── server.json
│   │   │   │   │       │   ├── service.json
│   │   │   │   │       │   ├── session.json
│   │   │   │   │       │   ├── signalr.json
│   │   │   │   │       │   ├── source.json
│   │   │   │   │       │   ├── system.json
│   │   │   │   │       │   ├── telemetry.json
│   │   │   │   │       │   ├── test.json
│   │   │   │   │       │   ├── thread.json
│   │   │   │   │       │   ├── tls.json
│   │   │   │   │       │   ├── url.json
│   │   │   │   │       │   ├── user.json
│   │   │   │   │       │   ├── v8js.json
│   │   │   │   │       │   ├── vcs.json
│   │   │   │   │       │   ├── webengine.json
│   │   │   │   │       │   └── zos.json
│   │   │   │   │       ├── dataset-fields.test.ts
│   │   │   │   │       ├── dataset-fields.ts
│   │   │   │   │       ├── otel-semantics.test.ts
│   │   │   │   │       ├── otel-semantics.ts
│   │   │   │   │       ├── utils.ts
│   │   │   │   │       ├── whoami.test.ts
│   │   │   │   │       └── whoami.ts
│   │   │   │   ├── constraint-helpers.test.ts
│   │   │   │   ├── constraint-helpers.ts
│   │   │   │   ├── error-handling.ts
│   │   │   │   ├── fetch-utils.test.ts
│   │   │   │   ├── fetch-utils.ts
│   │   │   │   ├── formatting.test.ts
│   │   │   │   ├── formatting.ts
│   │   │   │   ├── issue-helpers.test.ts
│   │   │   │   ├── issue-helpers.ts
│   │   │   │   ├── test-fixtures.ts
│   │   │   │   └── tool-helpers
│   │   │   │       ├── api.test.ts
│   │   │   │       ├── api.ts
│   │   │   │       ├── define.ts
│   │   │   │       ├── enhance-error.ts
│   │   │   │       ├── formatting.ts
│   │   │   │       ├── issue.ts
│   │   │   │       ├── seer.test.ts
│   │   │   │       ├── seer.ts
│   │   │   │       ├── validate-region-url.test.ts
│   │   │   │       └── validate-region-url.ts
│   │   │   ├── permissions.parseScopes.test.ts
│   │   │   ├── permissions.ts
│   │   │   ├── schema.ts
│   │   │   ├── server.ts
│   │   │   ├── skillDefinitions.json
│   │   │   ├── skillDefinitions.ts
│   │   │   ├── skills.test.ts
│   │   │   ├── skills.ts
│   │   │   ├── telem
│   │   │   │   ├── index.ts
│   │   │   │   ├── logging.ts
│   │   │   │   ├── sentry.test.ts
│   │   │   │   └── sentry.ts
│   │   │   ├── test-setup.ts
│   │   │   ├── test-utils
│   │   │   │   └── context.ts
│   │   │   ├── toolDefinitions.json
│   │   │   ├── toolDefinitions.ts
│   │   │   ├── tools
│   │   │   │   ├── analyze-issue-with-seer.test.ts
│   │   │   │   ├── analyze-issue-with-seer.ts
│   │   │   │   ├── create-dsn.test.ts
│   │   │   │   ├── create-dsn.ts
│   │   │   │   ├── create-project.test.ts
│   │   │   │   ├── create-project.ts
│   │   │   │   ├── create-team.test.ts
│   │   │   │   ├── create-team.ts
│   │   │   │   ├── find-dsns.test.ts
│   │   │   │   ├── find-dsns.ts
│   │   │   │   ├── find-organizations.test.ts
│   │   │   │   ├── find-organizations.ts
│   │   │   │   ├── find-projects.test.ts
│   │   │   │   ├── find-projects.ts
│   │   │   │   ├── find-releases.test.ts
│   │   │   │   ├── find-releases.ts
│   │   │   │   ├── find-teams.test.ts
│   │   │   │   ├── find-teams.ts
│   │   │   │   ├── get-doc.test.ts
│   │   │   │   ├── get-doc.ts
│   │   │   │   ├── get-event-attachment.test.ts
│   │   │   │   ├── get-event-attachment.ts
│   │   │   │   ├── get-issue-details.test.ts
│   │   │   │   ├── get-issue-details.ts
│   │   │   │   ├── get-trace-details.test.ts
│   │   │   │   ├── get-trace-details.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── search-docs.test.ts
│   │   │   │   ├── search-docs.ts
│   │   │   │   ├── search-events
│   │   │   │   │   ├── agent.ts
│   │   │   │   │   ├── CLAUDE.md
│   │   │   │   │   ├── config.ts
│   │   │   │   │   ├── formatters.ts
│   │   │   │   │   ├── handler.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── utils.test.ts
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── search-events.test.ts
│   │   │   │   ├── search-issues
│   │   │   │   │   ├── agent.ts
│   │   │   │   │   ├── CLAUDE.md
│   │   │   │   │   ├── config.ts
│   │   │   │   │   ├── formatters.ts
│   │   │   │   │   ├── handler.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── README.md
│   │   │   │   ├── tools.test.ts
│   │   │   │   ├── types.ts
│   │   │   │   ├── update-issue.test.ts
│   │   │   │   ├── update-issue.ts
│   │   │   │   ├── update-project.test.ts
│   │   │   │   ├── update-project.ts
│   │   │   │   ├── use-sentry
│   │   │   │   │   ├── agent.ts
│   │   │   │   │   ├── CLAUDE.md
│   │   │   │   │   ├── config.ts
│   │   │   │   │   ├── handler.test.ts
│   │   │   │   │   ├── handler.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── tool-wrapper.test.ts
│   │   │   │   │   └── tool-wrapper.ts
│   │   │   │   ├── whoami.test.ts
│   │   │   │   └── whoami.ts
│   │   │   ├── types.ts
│   │   │   ├── utils
│   │   │   │   ├── slug-validation.test.ts
│   │   │   │   ├── slug-validation.ts
│   │   │   │   ├── url-utils.test.ts
│   │   │   │   └── url-utils.ts
│   │   │   └── version.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   ├── mcp-server
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── cli
│   │   │   │   ├── parse.test.ts
│   │   │   │   ├── parse.ts
│   │   │   │   ├── resolve.test.ts
│   │   │   │   ├── resolve.ts
│   │   │   │   ├── types.ts
│   │   │   │   └── usage.ts
│   │   │   ├── index.ts
│   │   │   └── transports
│   │   │       └── stdio.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── mcp-server-evals
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── bin
│   │   │   │   └── start-mock-stdio.ts
│   │   │   ├── evals
│   │   │   │   ├── autofix.eval.ts
│   │   │   │   ├── create-dsn.eval.ts
│   │   │   │   ├── create-project.eval.ts
│   │   │   │   ├── create-team.eval.ts
│   │   │   │   ├── get-issue.eval.ts
│   │   │   │   ├── get-trace-details.eval.ts
│   │   │   │   ├── list-dsns.eval.ts
│   │   │   │   ├── list-issues.eval.ts
│   │   │   │   ├── list-organizations.eval.ts
│   │   │   │   ├── list-projects.eval.ts
│   │   │   │   ├── list-releases.eval.ts
│   │   │   │   ├── list-tags.eval.ts
│   │   │   │   ├── list-teams.eval.ts
│   │   │   │   ├── search-docs.eval.ts
│   │   │   │   ├── search-events-agent.eval.ts
│   │   │   │   ├── search-events.eval.ts
│   │   │   │   ├── search-issues-agent.eval.ts
│   │   │   │   ├── search-issues.eval.ts
│   │   │   │   ├── update-issue.eval.ts
│   │   │   │   ├── update-project.eval.ts
│   │   │   │   └── utils
│   │   │   │       ├── fixtures.ts
│   │   │   │       ├── index.ts
│   │   │   │       ├── runner.ts
│   │   │   │       ├── structuredOutputScorer.ts
│   │   │   │       └── toolPredictionScorer.ts
│   │   │   └── setup-env.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   ├── mcp-server-mocks
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── fixtures
│   │   │   │   ├── autofix-state.json
│   │   │   │   ├── csp-event.json
│   │   │   │   ├── csp-issue.json
│   │   │   │   ├── default-event.json
│   │   │   │   ├── event-attachments.json
│   │   │   │   ├── event.json
│   │   │   │   ├── generic-event.json
│   │   │   │   ├── issue.json
│   │   │   │   ├── performance-event.json
│   │   │   │   ├── performance-issue.json
│   │   │   │   ├── project.json
│   │   │   │   ├── regressed-issue.json
│   │   │   │   ├── tags.json
│   │   │   │   ├── team.json
│   │   │   │   ├── trace-event.json
│   │   │   │   ├── trace-items-attributes-logs-number.json
│   │   │   │   ├── trace-items-attributes-logs-string.json
│   │   │   │   ├── trace-items-attributes-spans-number.json
│   │   │   │   ├── trace-items-attributes-spans-string.json
│   │   │   │   ├── trace-items-attributes.json
│   │   │   │   ├── trace-meta-with-nulls.json
│   │   │   │   ├── trace-meta.json
│   │   │   │   ├── trace-mixed.json
│   │   │   │   ├── trace.json
│   │   │   │   ├── unknown-event.json
│   │   │   │   └── unsupported-issue.json
│   │   │   ├── fixtures.ts
│   │   │   ├── index.ts
│   │   │   └── utils.ts
│   │   ├── tsconfig.json
│   │   └── tsdown.config.ts
│   ├── mcp-server-tsconfig
│   │   ├── package.json
│   │   ├── tsconfig.base.json
│   │   └── tsconfig.vite.json
│   ├── mcp-test-client
│   │   ├── .env.test
│   │   ├── .gitignore
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── agent.ts
│   │   │   ├── auth
│   │   │   │   ├── config.ts
│   │   │   │   └── oauth.ts
│   │   │   ├── constants.ts
│   │   │   ├── index.ts
│   │   │   ├── logger.test.ts
│   │   │   ├── logger.ts
│   │   │   ├── mcp-test-client-remote.ts
│   │   │   ├── mcp-test-client.ts
│   │   │   ├── types.ts
│   │   │   └── version.ts
│   │   ├── tsconfig.json
│   │   ├── tsdown.config.ts
│   │   └── vitest.config.ts
│   └── smoke-tests
│       ├── package.json
│       ├── src
│       │   └── smoke.test.ts
│       └── vitest.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
├── scripts
│   └── check-doc-links.mjs
├── turbo.json
└── vitest.workspace.ts
```

# Files

--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/index.ts:
--------------------------------------------------------------------------------

```typescript
   1 | /**
   2 |  * MSW-based Mock Server for Sentry MCP Development and Testing.
   3 |  *
   4 |  * Provides comprehensive mock responses for all Sentry API endpoints used by the
   5 |  * MCP server. Built with MSW (Mock Service Worker) for realistic HTTP interception
   6 |  * and response handling during development and testing.
   7 |  *
   8 |  * **Usage in Tests:**
   9 |  * ```typescript
  10 |  * import { mswServer } from "@sentry/mcp-server-mocks";
  11 |  *
  12 |  * beforeAll(() => mswServer.listen());
  13 |  * afterEach(() => mswServer.resetHandlers());
  14 |  * afterAll(() => mswServer.close());
  15 |  * ```
  16 |  *
  17 |  * **Usage in Development:**
  18 |  * ```typescript
  19 |  * // Start mock server for local development
  20 |  * mswServer.listen();
  21 |  * // Now all Sentry API calls will be intercepted
  22 |  * ```
  23 |  */
  24 | import { setupServer } from "msw/node";
  25 | import { http, HttpResponse } from "msw";
  26 | 
  27 | import autofixStateFixture from "./fixtures/autofix-state.json" with {
  28 |   type: "json",
  29 | };
  30 | import issueFixture from "./fixtures/issue.json" with { type: "json" };
  31 | import eventsFixture from "./fixtures/event.json" with { type: "json" };
  32 | import performanceEventFixture from "./fixtures/performance-event.json" with {
  33 |   type: "json",
  34 | };
  35 | import eventAttachmentsFixture from "./fixtures/event-attachments.json" with {
  36 |   type: "json",
  37 | };
  38 | import tagsFixture from "./fixtures/tags.json" with { type: "json" };
  39 | import projectFixture from "./fixtures/project.json" with { type: "json" };
  40 | import teamFixture from "./fixtures/team.json" with { type: "json" };
  41 | import traceItemsAttributesFixture from "./fixtures/trace-items-attributes.json" with {
  42 |   type: "json",
  43 | };
  44 | import traceItemsAttributesSpansStringFixture from "./fixtures/trace-items-attributes-spans-string.json" with {
  45 |   type: "json",
  46 | };
  47 | import traceItemsAttributesSpansNumberFixture from "./fixtures/trace-items-attributes-spans-number.json" with {
  48 |   type: "json",
  49 | };
  50 | import traceItemsAttributesLogsStringFixture from "./fixtures/trace-items-attributes-logs-string.json" with {
  51 |   type: "json",
  52 | };
  53 | import traceItemsAttributesLogsNumberFixture from "./fixtures/trace-items-attributes-logs-number.json" with {
  54 |   type: "json",
  55 | };
  56 | import traceMetaFixture from "./fixtures/trace-meta.json" with { type: "json" };
  57 | import traceMetaWithNullsFixture from "./fixtures/trace-meta-with-nulls.json" with {
  58 |   type: "json",
  59 | };
  60 | import traceFixture from "./fixtures/trace.json" with { type: "json" };
  61 | import traceMixedFixture from "./fixtures/trace-mixed.json" with {
  62 |   type: "json",
  63 | };
  64 | import traceEventFixture from "./fixtures/trace-event.json" with {
  65 |   type: "json",
  66 | };
  67 | 
  68 | /**
  69 |  * Standard organization payload for mock responses.
  70 |  * Used across multiple endpoints for consistency.
  71 |  */
  72 | const OrganizationPayload = {
  73 |   id: "4509106740723712",
  74 |   slug: "sentry-mcp-evals",
  75 |   name: "sentry-mcp-evals",
  76 |   links: {
  77 |     regionUrl: "https://us.sentry.io",
  78 |     organizationUrl: "https://sentry.io/sentry-mcp-evals",
  79 |   },
  80 | };
  81 | 
  82 | /**
  83 |  * Standard release payload for mock responses.
  84 |  * Includes typical metadata and project associations.
  85 |  */
  86 | const ReleasePayload = {
  87 |   id: 1402755016,
  88 |   version: "8ce89484-0fec-4913-a2cd-e8e2d41dee36",
  89 |   status: "open",
  90 |   shortVersion: "8ce89484-0fec-4913-a2cd-e8e2d41dee36",
  91 |   versionInfo: {
  92 |     package: null,
  93 |     version: { raw: "8ce89484-0fec-4913-a2cd-e8e2d41dee36" },
  94 |     description: "8ce89484-0fec-4913-a2cd-e8e2d41dee36",
  95 |     buildHash: null,
  96 |   },
  97 |   ref: null,
  98 |   url: null,
  99 |   dateReleased: null,
 100 |   dateCreated: "2025-04-13T19:54:21.764000Z",
 101 |   data: {},
 102 |   newGroups: 0,
 103 |   owner: null,
 104 |   commitCount: 0,
 105 |   lastCommit: null,
 106 |   deployCount: 0,
 107 |   lastDeploy: null,
 108 |   authors: [],
 109 |   projects: [
 110 |     {
 111 |       id: 4509062593708032,
 112 |       slug: "cloudflare-mcp",
 113 |       name: "cloudflare-mcp",
 114 |       newGroups: 0,
 115 |       platform: "bun",
 116 |       platforms: ["javascript"],
 117 |       hasHealthData: false,
 118 |     },
 119 |   ],
 120 |   firstEvent: "2025-04-13T19:54:21Z",
 121 |   lastEvent: "2025-04-13T20:28:23Z",
 122 |   currentProjectMeta: {},
 123 |   userAgent: null,
 124 | };
 125 | 
 126 | const ClientKeyPayload = {
 127 |   id: "d20df0a1ab5031c7f3c7edca9c02814d",
 128 |   name: "Default",
 129 |   label: "Default",
 130 |   public: "d20df0a1ab5031c7f3c7edca9c02814d",
 131 |   secret: "154001fd3dfe38130e1c7948a323fad8",
 132 |   projectId: 4509109104082945,
 133 |   isActive: true,
 134 |   rateLimit: null,
 135 |   dsn: {
 136 |     secret:
 137 |       "https://d20df0a1ab5031c7f3c7edca9c02814d:154001fd3dfe38130e1c7948a323fad8@o4509106732793856.ingest.us.sentry.io/4509109104082945",
 138 |     public:
 139 |       "https://d20df0a1ab5031c7f3c7edca9c02814d@o4509106732793856.ingest.us.sentry.io/4509109104082945",
 140 |     csp: "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/csp-report/?sentry_key=d20df0a1ab5031c7f3c7edca9c02814d",
 141 |     security:
 142 |       "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/security/?sentry_key=d20df0a1ab5031c7f3c7edca9c02814d",
 143 |     minidump:
 144 |       "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/minidump/?sentry_key=d20df0a1ab5031c7f3c7edca9c02814d",
 145 |     nel: "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/nel/?sentry_key=d20df0a1ab5031c7f3c7edca9c02814d",
 146 |     unreal:
 147 |       "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/unreal/d20df0a1ab5031c7f3c7edca9c02814d/",
 148 |     crons:
 149 |       "https://o4509106732793856.ingest.us.sentry.io/api/4509109104082945/cron/___MONITOR_SLUG___/d20df0a1ab5031c7f3c7edca9c02814d/",
 150 |     cdn: "https://js.sentry-cdn.com/d20df0a1ab5031c7f3c7edca9c02814d.min.js",
 151 |   },
 152 |   browserSdkVersion: "8.x",
 153 |   browserSdk: {
 154 |     choices: [
 155 |       ["9.x", "9.x"],
 156 |       ["8.x", "8.x"],
 157 |       ["7.x", "7.x"],
 158 |     ],
 159 |   },
 160 |   dateCreated: "2025-04-07T00:12:25.139394Z",
 161 |   dynamicSdkLoaderOptions: {
 162 |     hasReplay: true,
 163 |     hasPerformance: true,
 164 |     hasDebug: false,
 165 |   },
 166 | };
 167 | 
 168 | // a newer issue, seen less recently
 169 | const issueFixture2 = {
 170 |   ...issueFixture,
 171 |   id: 6507376926,
 172 |   shortId: "CLOUDFLARE-MCP-42",
 173 |   count: 1,
 174 |   title: "Error: Tool list_issues is already registered",
 175 |   firstSeen: "2025-04-11T22:51:19.403000Z",
 176 |   lastSeen: "2025-04-12T11:34:11Z",
 177 | };
 178 | 
 179 | const EventsErrorsMeta = {
 180 |   fields: {
 181 |     "issue.id": "integer",
 182 |     title: "string",
 183 |     project: "string",
 184 |     "count()": "integer",
 185 |     "last_seen()": "date",
 186 |   },
 187 |   units: {
 188 |     "issue.id": null,
 189 |     title: null,
 190 |     project: null,
 191 |     "count()": null,
 192 |     "last_seen()": null,
 193 |   },
 194 |   isMetricsData: false,
 195 |   isMetricsExtractedData: false,
 196 |   tips: { query: null, columns: null },
 197 |   datasetReason: "unchanged",
 198 |   dataset: "errors",
 199 | };
 200 | 
 201 | const EmptyEventsErrorsPayload = {
 202 |   data: [],
 203 |   meta: EventsErrorsMeta,
 204 | };
 205 | 
 206 | const EventsErrorsPayload = {
 207 |   data: [
 208 |     {
 209 |       "issue.id": 6114575469,
 210 |       title: "Error: Tool list_organizations is already registered",
 211 |       project: "test-suite",
 212 |       "count()": 2,
 213 |       "last_seen()": "2025-04-07T12:23:39+00:00",
 214 |       issue: "CLOUDFLARE-MCP-41",
 215 |     },
 216 |   ],
 217 |   meta: EventsErrorsMeta,
 218 | };
 219 | 
 220 | const EventsSpansMeta = {
 221 |   fields: {
 222 |     id: "string",
 223 |     "span.op": "string",
 224 |     "span.description": "string",
 225 |     "span.duration": "duration",
 226 |     transaction: "string",
 227 |     timestamp: "string",
 228 |     is_transaction: "boolean",
 229 |     project: "string",
 230 |     trace: "string",
 231 |     "transaction.span_id": "string",
 232 |     "project.name": "string",
 233 |   },
 234 |   units: {
 235 |     id: null,
 236 |     "span.op": null,
 237 |     "span.description": null,
 238 |     "span.duration": "millisecond",
 239 |     transaction: null,
 240 |     timestamp: null,
 241 |     is_transaction: null,
 242 |     project: null,
 243 |     trace: null,
 244 |     "transaction.span_id": null,
 245 |     "project.name": null,
 246 |   },
 247 |   isMetricsData: false,
 248 |   isMetricsExtractedData: false,
 249 |   tips: {},
 250 |   datasetReason: "unchanged",
 251 |   dataset: "spans",
 252 |   dataScanned: "full",
 253 |   accuracy: {
 254 |     confidence: [
 255 |       {},
 256 |       {},
 257 |       {},
 258 |       {},
 259 |       {},
 260 |       {},
 261 |       {},
 262 |       {},
 263 |       {},
 264 |       {},
 265 |       {},
 266 |       {},
 267 |       {},
 268 |       {},
 269 |       {},
 270 |       {},
 271 |       {},
 272 |       {},
 273 |       {},
 274 |       {},
 275 |       {},
 276 |       {},
 277 |       {},
 278 |       {},
 279 |       {},
 280 |       {},
 281 |     ],
 282 |   },
 283 | };
 284 | 
 285 | const EmptyEventsSpansPayload = {
 286 |   data: [],
 287 |   meta: EventsSpansMeta,
 288 | };
 289 | 
 290 | const EventsSpansPayload = {
 291 |   data: [
 292 |     {
 293 |       id: "07752c6aeb027c8f",
 294 |       "span.op": "http.server",
 295 |       "span.description": "GET /trpc/bottleList",
 296 |       "span.duration": 12.0,
 297 |       transaction: "GET /trpc/bottleList",
 298 |       timestamp: "2025-04-13T14:19:18+00:00",
 299 |       is_transaction: true,
 300 |       project: "peated",
 301 |       trace: "6a477f5b0f31ef7b6b9b5e1dea66c91d",
 302 |       "transaction.span_id": "07752c6aeb027c8f",
 303 |       "project.name": "peated",
 304 |     },
 305 |     {
 306 |       id: "7ab5edf5b3ba42c9",
 307 |       "span.op": "http.server",
 308 |       "span.description": "GET /trpc/bottleList",
 309 |       "span.duration": 18.0,
 310 |       transaction: "GET /trpc/bottleList",
 311 |       timestamp: "2025-04-13T14:19:17+00:00",
 312 |       is_transaction: true,
 313 |       project: "peated",
 314 |       trace: "54177131c7b192a446124daba3136045",
 315 |       "transaction.span_id": "7ab5edf5b3ba42c9",
 316 |       "project.name": "peated",
 317 |     },
 318 |   ],
 319 |   meta: EventsSpansMeta,
 320 |   confidence: [
 321 |     {},
 322 |     {},
 323 |     {},
 324 |     {},
 325 |     {},
 326 |     {},
 327 |     {},
 328 |     {},
 329 |     {},
 330 |     {},
 331 |     {},
 332 |     {},
 333 |     {},
 334 |     {},
 335 |     {},
 336 |     {},
 337 |     {},
 338 |     {},
 339 |     {},
 340 |     {},
 341 |     {},
 342 |     {},
 343 |     {},
 344 |     {},
 345 |     {},
 346 |     {},
 347 |   ],
 348 | };
 349 | 
 350 | /**
 351 |  * Builds MSW handlers for both SaaS and self-hosted Sentry instances.
 352 |  *
 353 |  * Creates handlers based on the controlOnly flag:
 354 |  * - controlOnly: false (default) - Creates handlers for both sentry.io and us.sentry.io
 355 |  * - controlOnly: true - Creates handlers only for sentry.io (main host)
 356 |  *
 357 |  * @param handlers - Array of handler definitions with method, path, fetch function, and optional controlOnly flag
 358 |  * @returns Array of MSW http handlers
 359 |  *
 360 |  * @example Handler Definitions
 361 |  * ```typescript
 362 |  * buildHandlers([
 363 |  *   {
 364 |  *     method: "get",
 365 |  *     path: "/api/0/auth/",
 366 |  *     fetch: () => HttpResponse.json({ user: "data" }),
 367 |  *     controlOnly: true  // Only available on sentry.io
 368 |  *   },
 369 |  *   {
 370 |  *     method: "get",
 371 |  *     path: "/api/0/organizations/",
 372 |  *     fetch: () => HttpResponse.json([OrganizationPayload]),
 373 |  *     controlOnly: false  // Available on both sentry.io and us.sentry.io
 374 |  *   }
 375 |  * ]);
 376 |  * ```
 377 |  */
 378 | function buildHandlers(
 379 |   handlers: {
 380 |     method: keyof typeof http;
 381 |     path: string;
 382 |     fetch: Parameters<(typeof http)[keyof typeof http]>[1];
 383 |     controlOnly?: boolean;
 384 |   }[],
 385 | ) {
 386 |   const result = [];
 387 | 
 388 |   for (const handler of handlers) {
 389 |     // Always add handler for main host (sentry.io)
 390 |     result.push(
 391 |       http[handler.method](`https://sentry.io${handler.path}`, handler.fetch),
 392 |     );
 393 | 
 394 |     // Only add handler for region-specific host if not controlOnly
 395 |     if (!handler.controlOnly) {
 396 |       result.push(
 397 |         http[handler.method](
 398 |           `https://us.sentry.io${handler.path}`,
 399 |           handler.fetch,
 400 |         ),
 401 |       );
 402 |     }
 403 |   }
 404 | 
 405 |   return result;
 406 | }
 407 | 
 408 | /**
 409 |  * Complete set of Sentry API mock handlers.
 410 |  *
 411 |  * Covers all endpoints used by the MCP server with realistic responses,
 412 |  * parameter validation, and error scenarios.
 413 |  */
 414 | export const restHandlers = buildHandlers([
 415 |   // User data endpoints - controlOnly: true (only available on sentry.io)
 416 |   {
 417 |     method: "get",
 418 |     path: "/api/0/auth/",
 419 |     controlOnly: true,
 420 |     fetch: () => {
 421 |       return HttpResponse.json({
 422 |         id: "123456",
 423 |         name: "Test User",
 424 |         email: "[email protected]",
 425 |         username: "testuser",
 426 |         avatarUrl: "https://example.com/avatar.jpg",
 427 |         dateJoined: "2024-01-01T00:00:00Z",
 428 |         isActive: true,
 429 |         isManaged: false,
 430 |         isStaff: false,
 431 |         isSuperuser: false,
 432 |         lastLogin: "2024-12-01T00:00:00Z",
 433 |         has2fa: false,
 434 |         hasPasswordAuth: true,
 435 |         emails: [
 436 |           {
 437 |             id: "1",
 438 |             email: "[email protected]",
 439 |             is_verified: true,
 440 |           },
 441 |         ],
 442 |       });
 443 |     },
 444 |   },
 445 |   {
 446 |     method: "get",
 447 |     path: "/api/0/users/me/regions/",
 448 |     controlOnly: true,
 449 |     fetch: () => {
 450 |       return HttpResponse.json({
 451 |         regions: [{ name: "us", url: "https://us.sentry.io" }],
 452 |       });
 453 |     },
 454 |   },
 455 |   // All other endpoints - controlOnly: false (default, available on both hosts)
 456 |   {
 457 |     method: "get",
 458 |     path: "/api/0/organizations/",
 459 |     fetch: () => {
 460 |       return HttpResponse.json([OrganizationPayload]);
 461 |     },
 462 |   },
 463 |   {
 464 |     method: "get",
 465 |     path: "/api/0/organizations/sentry-mcp-evals/",
 466 |     fetch: () => {
 467 |       return HttpResponse.json(OrganizationPayload);
 468 |     },
 469 |   },
 470 |   // 404 handlers for test scenarios
 471 |   {
 472 |     method: "get",
 473 |     path: "/api/0/organizations/nonexistent-org/",
 474 |     fetch: () => {
 475 |       return HttpResponse.json(
 476 |         { detail: "The requested resource does not exist" },
 477 |         { status: 404 },
 478 |       );
 479 |     },
 480 |   },
 481 |   {
 482 |     method: "get",
 483 |     path: "/api/0/projects/sentry-mcp-evals/nonexistent-project/",
 484 |     fetch: () => {
 485 |       return HttpResponse.json(
 486 |         { detail: "The requested resource does not exist" },
 487 |         { status: 404 },
 488 |       );
 489 |     },
 490 |   },
 491 |   {
 492 |     method: "get",
 493 |     path: "/api/0/organizations/sentry-mcp-evals/teams/",
 494 |     fetch: () => {
 495 |       return HttpResponse.json([teamFixture]);
 496 |     },
 497 |   },
 498 |   {
 499 |     method: "get",
 500 |     path: "/api/0/organizations/sentry-mcp-evals/projects/",
 501 |     fetch: () => {
 502 |       return HttpResponse.json([
 503 |         {
 504 |           ...projectFixture,
 505 |           id: "4509106749636608", // Different ID for GET endpoint
 506 |         },
 507 |       ]);
 508 |     },
 509 |   },
 510 |   {
 511 |     method: "post",
 512 |     path: "/api/0/organizations/sentry-mcp-evals/teams/",
 513 |     fetch: () => {
 514 |       // TODO: validate payload (only accept 'the-goats' for team name)
 515 |       return HttpResponse.json(
 516 |         {
 517 |           ...teamFixture,
 518 |           id: "4509109078196224",
 519 |           dateCreated: "2025-04-07T00:05:48.196710Z",
 520 |           access: [
 521 |             "event:read",
 522 |             "org:integrations",
 523 |             "org:read",
 524 |             "member:read",
 525 |             "alerts:write",
 526 |             "event:admin",
 527 |             "team:admin",
 528 |             "project:releases",
 529 |             "team:read",
 530 |             "project:write",
 531 |             "event:write",
 532 |             "team:write",
 533 |             "project:read",
 534 |             "project:admin",
 535 |             "alerts:read",
 536 |           ],
 537 |         },
 538 |         { status: 201 },
 539 |       );
 540 |     },
 541 |   },
 542 |   {
 543 |     method: "post",
 544 |     path: "/api/0/teams/sentry-mcp-evals/the-goats/projects/",
 545 |     fetch: async ({ request }) => {
 546 |       // TODO: validate payload (only accept 'cloudflare-mcp' for project name)
 547 |       const body = (await request.json()) as any;
 548 |       return HttpResponse.json({
 549 |         ...projectFixture,
 550 |         name: body?.name || "cloudflare-mcp",
 551 |         slug: body?.slug || "cloudflare-mcp",
 552 |         platform: body?.platform || "node",
 553 |       });
 554 |     },
 555 |   },
 556 |   {
 557 |     method: "get",
 558 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/",
 559 |     fetch: () => {
 560 |       return HttpResponse.json(projectFixture);
 561 |     },
 562 |   },
 563 |   {
 564 |     method: "put",
 565 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/",
 566 |     fetch: async ({ request }) => {
 567 |       const body = (await request.json()) as any;
 568 |       return HttpResponse.json({
 569 |         ...projectFixture,
 570 |         slug: body?.slug || "cloudflare-mcp",
 571 |         name: body?.name || "cloudflare-mcp",
 572 |         platform: body?.platform || "node",
 573 |       });
 574 |     },
 575 |   },
 576 |   {
 577 |     method: "post",
 578 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/keys/",
 579 |     fetch: () => {
 580 |       // TODO: validate payload (only accept 'Default' for key name)
 581 |       return HttpResponse.json(ClientKeyPayload);
 582 |     },
 583 |   },
 584 |   {
 585 |     method: "get",
 586 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/keys/",
 587 |     fetch: () => {
 588 |       return HttpResponse.json([ClientKeyPayload]);
 589 |     },
 590 |   },
 591 |   {
 592 |     method: "get",
 593 |     path: "/api/0/organizations/sentry-mcp-evals/events/",
 594 |     fetch: async ({ request }) => {
 595 |       const url = new URL(request.url);
 596 |       const dataset = url.searchParams.get("dataset");
 597 |       const query = url.searchParams.get("query");
 598 |       const fields = url.searchParams.getAll("field");
 599 | 
 600 |       if (dataset === "spans") {
 601 |         //[sentryApi] GET https://sentry.io/api/0/organizations/sentry-mcp-evals/events/?dataset=spans&per_page=10&referrer=sentry-mcp&sort=-span.duration&allowAggregateConditions=0&useRpc=1&field=id&field=trace&field=span.op&field=span.description&field=span.duration&field=transaction&field=project&field=timestamp&query=is_transaction%3Atrue
 602 |         if (query !== "is_transaction:true") {
 603 |           return HttpResponse.json(EmptyEventsSpansPayload);
 604 |         }
 605 | 
 606 |         if (url.searchParams.get("useRpc") !== "1") {
 607 |           return HttpResponse.json("Invalid useRpc", { status: 400 });
 608 |         }
 609 | 
 610 |         if (
 611 |           !fields.includes("id") ||
 612 |           !fields.includes("trace") ||
 613 |           !fields.includes("span.op") ||
 614 |           !fields.includes("span.description") ||
 615 |           !fields.includes("span.duration")
 616 |         ) {
 617 |           return HttpResponse.json("Invalid fields", { status: 400 });
 618 |         }
 619 |         return HttpResponse.json(EventsSpansPayload);
 620 |       }
 621 |       if (dataset === "errors") {
 622 |         //https://sentry.io/api/0/organizations/sentry-mcp-evals/events/?dataset=errors&per_page=10&referrer=sentry-mcp&sort=-count&statsPeriod=1w&field=issue&field=title&field=project&field=last_seen%28%29&field=count%28%29&query=
 623 | 
 624 |         if (
 625 |           !fields.includes("issue") ||
 626 |           !fields.includes("title") ||
 627 |           !fields.includes("project") ||
 628 |           !fields.includes("last_seen()") ||
 629 |           !fields.includes("count()")
 630 |         ) {
 631 |           return HttpResponse.json("Invalid fields", { status: 400 });
 632 |         }
 633 | 
 634 |         if (
 635 |           !["-count", "-last_seen"].includes(
 636 |             url.searchParams.get("sort") as string,
 637 |           )
 638 |         ) {
 639 |           return HttpResponse.json("Invalid sort", { status: 400 });
 640 |         }
 641 | 
 642 |         // TODO: this is not correct, but itll fix test flakiness for now
 643 |         const sortedQuery = query ? query?.split(" ").sort().join(" ") : null;
 644 |         if (
 645 |           ![
 646 |             null,
 647 |             "",
 648 |             "error.handled:false",
 649 |             "error.unhandled:true",
 650 |             "error.handled:false is:unresolved",
 651 |             "error.unhandled:true is:unresolved",
 652 |             "is:unresolved project:cloudflare-mcp",
 653 |             "project:cloudflare-mcp",
 654 |             "user.email:[email protected]",
 655 |           ].includes(sortedQuery)
 656 |         ) {
 657 |           return HttpResponse.json(EmptyEventsErrorsPayload);
 658 |         }
 659 | 
 660 |         return HttpResponse.json(EventsErrorsPayload);
 661 |       }
 662 | 
 663 |       return HttpResponse.json("Invalid dataset", { status: 400 });
 664 |     },
 665 |   },
 666 |   {
 667 |     method: "get",
 668 |     path: "/api/0/projects/sentry-mcp-evals/foobar/issues/",
 669 |     fetch: () => HttpResponse.json([]),
 670 |   },
 671 |   {
 672 |     method: "get",
 673 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/issues/",
 674 |     fetch: ({ request }) => {
 675 |       const url = new URL(request.url);
 676 |       const sort = url.searchParams.get("sort");
 677 | 
 678 |       if (![null, "user", "freq", "date", "new", null].includes(sort)) {
 679 |         return HttpResponse.json(
 680 |           `Invalid sort: ${url.searchParams.get("sort")}`,
 681 |           {
 682 |             status: 400,
 683 |           },
 684 |         );
 685 |       }
 686 | 
 687 |       const collapse = url.searchParams.getAll("collapse");
 688 |       if (collapse.includes("stats")) {
 689 |         return HttpResponse.json(`Invalid collapse: ${collapse.join(",")}`, {
 690 |           status: 400,
 691 |         });
 692 |       }
 693 | 
 694 |       const query = url.searchParams.get("query");
 695 |       const queryTokens = query?.split(" ").sort() ?? [];
 696 |       const sortedQuery = queryTokens ? queryTokens.join(" ") : null;
 697 |       if (
 698 |         ![
 699 |           null,
 700 |           "",
 701 |           "is:unresolved",
 702 |           "error.handled:false is:unresolved",
 703 |           "error.unhandled:true is:unresolved",
 704 |           "user.email:[email protected]",
 705 |         ].includes(sortedQuery)
 706 |       ) {
 707 |         return HttpResponse.json([]);
 708 |       }
 709 | 
 710 |       if (queryTokens.includes("user.email:[email protected]")) {
 711 |         return HttpResponse.json([issueFixture]);
 712 |       }
 713 | 
 714 |       if (sort === "date") {
 715 |         return HttpResponse.json([issueFixture, issueFixture2]);
 716 |       }
 717 |       return HttpResponse.json([issueFixture2, issueFixture]);
 718 |     },
 719 |   },
 720 | 
 721 |   {
 722 |     method: "get",
 723 |     path: "/api/0/organizations/sentry-mcp-evals/issues/",
 724 |     fetch: ({ request }) => {
 725 |       const url = new URL(request.url);
 726 |       const sort = url.searchParams.get("sort");
 727 | 
 728 |       if (![null, "user", "freq", "date", "new", null].includes(sort)) {
 729 |         return HttpResponse.json(
 730 |           `Invalid sort: ${url.searchParams.get("sort")}`,
 731 |           {
 732 |             status: 400,
 733 |           },
 734 |         );
 735 |       }
 736 | 
 737 |       const collapse = url.searchParams.getAll("collapse");
 738 |       if (collapse.includes("stats")) {
 739 |         return HttpResponse.json(`Invalid collapse: ${collapse.join(",")}`, {
 740 |           status: 400,
 741 |         });
 742 |       }
 743 | 
 744 |       const query = url.searchParams.get("query");
 745 |       const queryTokens = query?.split(" ").sort() ?? [];
 746 |       const sortedQuery = queryTokens ? queryTokens.join(" ") : null;
 747 |       if (query === "7ca573c0f4814912aaa9bdc77d1a7d51") {
 748 |         return HttpResponse.json([issueFixture]);
 749 |       }
 750 |       if (
 751 |         ![
 752 |           null,
 753 |           "",
 754 |           "is:unresolved",
 755 |           "error.handled:false is:unresolved",
 756 |           "error.unhandled:true is:unresolved",
 757 |           "project:cloudflare-mcp",
 758 |           "is:unresolved project:cloudflare-mcp",
 759 |           "user.email:[email protected]",
 760 |         ].includes(sortedQuery)
 761 |       ) {
 762 |         if (queryTokens.includes("project:remote-mcp")) {
 763 |           return HttpResponse.json(
 764 |             {
 765 |               detail:
 766 |                 "Invalid query. Project(s) remote-mcp do not exist or are not actively selected.",
 767 |             },
 768 |             { status: 400 },
 769 |           );
 770 |         }
 771 |         return HttpResponse.json([]);
 772 |       }
 773 |       if (queryTokens.includes("user.email:[email protected]")) {
 774 |         return HttpResponse.json([issueFixture]);
 775 |       }
 776 | 
 777 |       if (sort === "date") {
 778 |         return HttpResponse.json([issueFixture, issueFixture2]);
 779 |       }
 780 |       return HttpResponse.json([issueFixture2, issueFixture]);
 781 |     },
 782 |   },
 783 |   {
 784 |     method: "get",
 785 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/",
 786 |     fetch: () => HttpResponse.json(issueFixture),
 787 |   },
 788 |   {
 789 |     method: "get",
 790 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376925/",
 791 |     fetch: () => HttpResponse.json(issueFixture),
 792 |   },
 793 |   {
 794 |     method: "get",
 795 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-42/",
 796 |     fetch: () => HttpResponse.json(issueFixture2),
 797 |   },
 798 |   {
 799 |     method: "get",
 800 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376926/",
 801 |     fetch: () => HttpResponse.json(issueFixture2),
 802 |   },
 803 | 
 804 |   // Trace endpoints
 805 |   {
 806 |     method: "get",
 807 |     path: "/api/0/organizations/sentry-mcp-evals/trace-meta/a4d1aae7216b47ff8117cf4e09ce9d0a/",
 808 |     fetch: () => HttpResponse.json(traceMetaFixture),
 809 |   },
 810 |   {
 811 |     method: "get",
 812 |     path: "/api/0/organizations/sentry-mcp-evals/trace/a4d1aae7216b47ff8117cf4e09ce9d0a/",
 813 |     fetch: () => HttpResponse.json(traceFixture),
 814 |   },
 815 | 
 816 |   {
 817 |     method: "get",
 818 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/7ca573c0f4814912aaa9bdc77d1a7d51/",
 819 |     fetch: () => HttpResponse.json(eventsFixture),
 820 |   },
 821 |   {
 822 |     method: "get",
 823 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/events/latest/",
 824 |     fetch: () => HttpResponse.json(eventsFixture),
 825 |   },
 826 |   {
 827 |     method: "get",
 828 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376925/events/7ca573c0f4814912aaa9bdc77d1a7d51/",
 829 |     fetch: () => HttpResponse.json(eventsFixture),
 830 |   },
 831 |   {
 832 |     method: "get",
 833 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376925/events/latest/",
 834 |     fetch: () => HttpResponse.json(eventsFixture),
 835 |   },
 836 |   // TODO: event payload should be tweaked to match issue
 837 |   {
 838 |     method: "get",
 839 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-42/events/latest/",
 840 |     fetch: () => HttpResponse.json(eventsFixture),
 841 |   },
 842 |   // TODO: event payload should be tweaked to match issue
 843 |   {
 844 |     method: "get",
 845 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376926/events/latest/",
 846 |     fetch: () => HttpResponse.json(eventsFixture),
 847 |   },
 848 | 
 849 |   // Performance issue with N+1 query detection
 850 |   {
 851 |     method: "get",
 852 |     path: "/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/",
 853 |     fetch: () => HttpResponse.json(performanceEventFixture),
 854 |   },
 855 |   {
 856 |     method: "get",
 857 |     path: "/api/0/organizations/sentry-mcp-evals/issues/7890123456/events/latest/",
 858 |     fetch: () => HttpResponse.json(performanceEventFixture),
 859 |   },
 860 |   {
 861 |     method: "get",
 862 |     path: "/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/a1b2c3d4e5f6789012345678901234567/",
 863 |     fetch: () => HttpResponse.json(performanceEventFixture),
 864 |   },
 865 | 
 866 |   {
 867 |     method: "get",
 868 |     path: "/api/0/organizations/sentry-mcp-evals/releases/",
 869 |     fetch: () => HttpResponse.json([ReleasePayload]),
 870 |   },
 871 |   {
 872 |     method: "get",
 873 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/releases/",
 874 |     fetch: () => HttpResponse.json([ReleasePayload]),
 875 |   },
 876 |   {
 877 |     method: "get",
 878 |     path: "/api/0/organizations/sentry-mcp-evals/tags/",
 879 |     fetch: () => HttpResponse.json(tagsFixture),
 880 |   },
 881 |   {
 882 |     method: "get",
 883 |     path: "/api/0/organizations/sentry-mcp-evals/trace-items/attributes/",
 884 |     fetch: ({ request }) => {
 885 |       const url = new URL(request.url);
 886 |       const itemType = url.searchParams.get("itemType");
 887 |       const attributeType = url.searchParams.get("attributeType");
 888 | 
 889 |       // Validate required parameters
 890 |       if (!itemType) {
 891 |         return HttpResponse.json(
 892 |           { detail: "itemType parameter is required" },
 893 |           { status: 400 },
 894 |         );
 895 |       }
 896 | 
 897 |       if (!attributeType) {
 898 |         return HttpResponse.json(
 899 |           { detail: "attributeType parameter is required" },
 900 |           { status: 400 },
 901 |         );
 902 |       }
 903 | 
 904 |       // Validate itemType values (API accepts both singular and plural forms)
 905 |       const normalizedItemType = itemType === "spans" ? "span" : itemType;
 906 |       if (!["span", "logs"].includes(normalizedItemType)) {
 907 |         return HttpResponse.json(
 908 |           {
 909 |             detail: `Invalid itemType '${itemType}'. Must be 'span' or 'logs'`,
 910 |           },
 911 |           { status: 400 },
 912 |         );
 913 |       }
 914 | 
 915 |       // Validate attributeType values
 916 |       if (!["string", "number"].includes(attributeType)) {
 917 |         return HttpResponse.json(
 918 |           {
 919 |             detail: `Invalid attributeType '${attributeType}'. Must be 'string' or 'number'`,
 920 |           },
 921 |           { status: 400 },
 922 |         );
 923 |       }
 924 | 
 925 |       // Return appropriate fixture based on parameters
 926 |       if (normalizedItemType === "span") {
 927 |         if (attributeType === "string") {
 928 |           return HttpResponse.json(traceItemsAttributesSpansStringFixture);
 929 |         }
 930 |         return HttpResponse.json(traceItemsAttributesSpansNumberFixture);
 931 |       }
 932 |       if (normalizedItemType === "logs") {
 933 |         if (attributeType === "string") {
 934 |           return HttpResponse.json(traceItemsAttributesLogsStringFixture);
 935 |         }
 936 |         return HttpResponse.json(traceItemsAttributesLogsNumberFixture);
 937 |       }
 938 | 
 939 |       // Fallback (should not reach here with valid inputs)
 940 |       return HttpResponse.json(traceItemsAttributesFixture);
 941 |     },
 942 |   },
 943 |   {
 944 |     method: "get",
 945 |     path: "/api/0/organizations/sentry-mcp-evals/issues/PEATED-A8/autofix/",
 946 |     fetch: () => HttpResponse.json(autofixStateFixture),
 947 |   },
 948 |   {
 949 |     method: "get",
 950 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/",
 951 |     fetch: () => HttpResponse.json({ autofix: null }),
 952 |   },
 953 |   {
 954 |     method: "post",
 955 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-42/autofix/",
 956 |     fetch: () => HttpResponse.json({ run_id: 123 }),
 957 |   },
 958 |   {
 959 |     method: "post",
 960 |     path: "/api/0/organizations/sentry-mcp-evals/issues/PEATED-A8/autofix/",
 961 |     fetch: () => HttpResponse.json({ run_id: 123 }),
 962 |   },
 963 |   {
 964 |     method: "get",
 965 |     path: "/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/autofix/",
 966 |     fetch: () => HttpResponse.json({ autofix: null }),
 967 |   },
 968 |   {
 969 |     method: "get",
 970 |     path: "/api/0/organizations/sentry-mcp-evals/issues/DEFAULT-001/autofix/",
 971 |     fetch: () => HttpResponse.json({ autofix: null }),
 972 |   },
 973 |   {
 974 |     method: "get",
 975 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CONTEXT-001/autofix/",
 976 |     fetch: () => HttpResponse.json({ autofix: null }),
 977 |   },
 978 |   {
 979 |     method: "get",
 980 |     path: "/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/autofix/",
 981 |     fetch: () => HttpResponse.json({ autofix: null }),
 982 |   },
 983 |   {
 984 |     method: "get",
 985 |     path: "/api/0/organizations/sentry-mcp-evals/issues/FUTURE-TYPE-001/autofix/",
 986 |     fetch: () => HttpResponse.json({ autofix: null }),
 987 |   },
 988 |   {
 989 |     method: "get",
 990 |     path: "/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/autofix/",
 991 |     fetch: () => HttpResponse.json({ autofix: null }),
 992 |   },
 993 | 
 994 |   {
 995 |     method: "get",
 996 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-45/autofix/",
 997 |     fetch: () =>
 998 |       HttpResponse.json({
 999 |         autofix: {
1000 |           run_id: 13,
1001 |           request: { project_id: 4505138086019073 },
1002 |           status: "COMPLETED",
1003 |           updated_at: "2025-04-09T22:39:50.778146",
1004 |           steps: [
1005 |             {
1006 |               type: "root_cause_analysis",
1007 |               key: "root_cause_analysis",
1008 |               index: 0,
1009 |               status: "COMPLETED",
1010 |               title: "1. **Root Cause Analysis**",
1011 |               output_stream: null,
1012 |               progress: [],
1013 |               description: "The analysis has completed successfully.",
1014 |               causes: [
1015 |                 {
1016 |                   description: "The analysis has completed successfully.",
1017 |                   id: 1,
1018 |                   root_cause_reproduction: [],
1019 |                 },
1020 |               ],
1021 |             },
1022 |           ],
1023 |         },
1024 |       }),
1025 |   },
1026 |   {
1027 |     method: "post",
1028 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/teams/:teamSlug/",
1029 |     fetch: async ({ request, params }) => {
1030 |       const body = (await request.json()) as any;
1031 |       const teamSlug = params.teamSlug as string;
1032 |       return HttpResponse.json({
1033 |         ...teamFixture,
1034 |         id: "4509109078196224",
1035 |         slug: teamSlug,
1036 |         name: teamSlug,
1037 |         dateCreated: "2025-04-07T00:05:48.196710Z",
1038 |       });
1039 |     },
1040 |   },
1041 |   {
1042 |     method: "put",
1043 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/",
1044 |     fetch: async ({ request }) => {
1045 |       const body = (await request.json()) as any;
1046 |       const updatedIssue = {
1047 |         ...issueFixture,
1048 |         status: body?.status || issueFixture.status,
1049 |         assignedTo: body?.assignedTo || issueFixture.assignedTo,
1050 |       };
1051 |       return HttpResponse.json(updatedIssue);
1052 |     },
1053 |   },
1054 |   {
1055 |     method: "put",
1056 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376925/",
1057 |     fetch: async ({ request }) => {
1058 |       const body = (await request.json()) as any;
1059 |       const updatedIssue = {
1060 |         ...issueFixture,
1061 |         status: body?.status || issueFixture.status,
1062 |         assignedTo: body?.assignedTo || issueFixture.assignedTo,
1063 |       };
1064 |       return HttpResponse.json(updatedIssue);
1065 |     },
1066 |   },
1067 |   {
1068 |     method: "put",
1069 |     path: "/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-42/",
1070 |     fetch: async ({ request }) => {
1071 |       const body = (await request.json()) as any;
1072 |       const updatedIssue = {
1073 |         ...issueFixture2,
1074 |         status: body?.status || issueFixture2.status,
1075 |         assignedTo: body?.assignedTo || issueFixture2.assignedTo,
1076 |       };
1077 |       return HttpResponse.json(updatedIssue);
1078 |     },
1079 |   },
1080 |   {
1081 |     method: "put",
1082 |     path: "/api/0/organizations/sentry-mcp-evals/issues/6507376926/",
1083 |     fetch: async ({ request }) => {
1084 |       const body = (await request.json()) as any;
1085 |       const updatedIssue = {
1086 |         ...issueFixture2,
1087 |         status: body?.status || issueFixture2.status,
1088 |         assignedTo: body?.assignedTo || issueFixture2.assignedTo,
1089 |       };
1090 |       return HttpResponse.json(updatedIssue);
1091 |     },
1092 |   },
1093 |   // Event attachment endpoints
1094 |   {
1095 |     method: "get",
1096 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/7ca573c0f4814912aaa9bdc77d1a7d51/attachments/",
1097 |     fetch: () => HttpResponse.json(eventAttachmentsFixture),
1098 |   },
1099 |   {
1100 |     method: "get",
1101 |     path: "/api/0/projects/sentry-mcp-evals/cloudflare-mcp/events/7ca573c0f4814912aaa9bdc77d1a7d51/attachments/123/",
1102 |     fetch: () => {
1103 |       // Mock attachment blob response
1104 |       const mockBlob = new Blob(["fake image data"], { type: "image/png" });
1105 |       return new HttpResponse(mockBlob, {
1106 |         headers: {
1107 |           "Content-Type": "image/png",
1108 |         },
1109 |       });
1110 |     },
1111 |   },
1112 | ]);
1113 | 
1114 | // Add handlers for mcp.sentry.dev and localhost
1115 | export const searchHandlers = [
1116 |   http.post("https://mcp.sentry.dev/api/search", async ({ request }) => {
1117 |     const body = (await request.json()) as any;
1118 | 
1119 |     // Mock different results based on guide
1120 |     let results = [
1121 |       {
1122 |         id: "product/rate-limiting.md",
1123 |         url: "https://docs.sentry.io/product/rate-limiting",
1124 |         snippet:
1125 |           "Learn how to configure rate limiting in Sentry to prevent quota exhaustion and control event ingestion.",
1126 |         relevance: 0.95,
1127 |       },
1128 |       {
1129 |         id: "product/accounts/quotas/spike-protection.md",
1130 |         url: "https://docs.sentry.io/product/accounts/quotas/spike-protection",
1131 |         snippet:
1132 |           "Spike protection helps prevent unexpected spikes in event volume from consuming your quota.",
1133 |         relevance: 0.87,
1134 |       },
1135 |     ];
1136 | 
1137 |     // If guide is specified, return platform-specific results
1138 |     if (body?.guide) {
1139 |       const guide = body.guide;
1140 |       if (guide.includes("/")) {
1141 |         const [platformName, guideName] = guide.split("/");
1142 |         results = [
1143 |           {
1144 |             id: `platforms/${platformName}/guides/${guideName}.md`,
1145 |             url: `https://docs.sentry.io/platforms/${platformName}/guides/${guideName}`,
1146 |             snippet: `Setup guide for ${guideName} on ${platformName}`,
1147 |             relevance: 0.95,
1148 |           },
1149 |         ];
1150 |       } else {
1151 |         results = [
1152 |           {
1153 |             id: `platforms/${guide}/index.md`,
1154 |             url: `https://docs.sentry.io/platforms/${guide}`,
1155 |             snippet: `Documentation for ${guide} platform`,
1156 |             relevance: 0.95,
1157 |           },
1158 |         ];
1159 |       }
1160 |     }
1161 | 
1162 |     // Return mock search results
1163 |     return HttpResponse.json({
1164 |       query: body?.query || "",
1165 |       results,
1166 |     });
1167 |   }),
1168 | ];
1169 | 
1170 | // Mock handlers for documentation fetching
1171 | export const docsHandlers = [
1172 |   http.get("https://docs.sentry.io/product/rate-limiting.md", () => {
1173 |     return new HttpResponse(
1174 |       `# Project Rate Limits and Quotas
1175 | 
1176 | Rate limiting allows you to control the volume of events that Sentry accepts from your applications. This helps you manage costs and ensures that a sudden spike in errors doesn't consume your entire quota.
1177 | 
1178 | ## Why Use Rate Limiting?
1179 | 
1180 | - **Cost Control**: Prevent unexpected charges from error spikes
1181 | - **Noise Reduction**: Filter out repetitive or low-value events
1182 | - **Resource Management**: Ensure critical projects have quota available
1183 | - **Performance**: Reduce load on your Sentry organization
1184 | 
1185 | ## Types of Rate Limits
1186 | 
1187 | ### 1. Organization Rate Limits
1188 | 
1189 | Set a maximum number of events per hour across your entire organization:
1190 | 
1191 | \`\`\`python
1192 | # In your organization settings
1193 | rate_limit = 1000  # events per hour
1194 | \`\`\`
1195 | 
1196 | ### 2. Project Rate Limits
1197 | 
1198 | Configure limits for specific projects:
1199 | 
1200 | \`\`\`javascript
1201 | // Project settings
1202 | {
1203 |   "rateLimit": {
1204 |     "window": 3600,  // 1 hour in seconds
1205 |     "limit": 500     // max events
1206 |   }
1207 | }
1208 | \`\`\`
1209 | 
1210 | ### 3. Key-Based Rate Limiting
1211 | 
1212 | Rate limit by specific attributes:
1213 | 
1214 | - **By Release**: Limit events from specific releases
1215 | - **By User**: Prevent single users from consuming quota
1216 | - **By Transaction**: Control high-volume transactions
1217 | 
1218 | ## Configuration Examples
1219 | 
1220 | ### SDK Configuration
1221 | 
1222 | Configure client-side sampling to reduce events before they're sent:
1223 | 
1224 | \`\`\`javascript
1225 | Sentry.init({
1226 |   dsn: "your-dsn",
1227 |   tracesSampleRate: 0.1,  // Sample 10% of transactions
1228 |   beforeSend(event) {
1229 |     // Custom filtering logic
1230 |     if (event.exception?.values?.[0]?.value?.includes("NetworkError")) {
1231 |       return null;  // Drop network errors
1232 |     }
1233 |     return event;
1234 |   }
1235 | });
1236 | \`\`\`
1237 | 
1238 | ### Inbound Filters
1239 | 
1240 | Use Sentry's inbound filters to drop events server-side:
1241 | 
1242 | 1. Go to **Project Settings** → **Inbound Filters**
1243 | 2. Enable filters for:
1244 |    - Legacy browsers
1245 |    - Web crawlers
1246 |    - Specific error messages
1247 |    - IP addresses
1248 | 
1249 | ### Spike Protection
1250 | 
1251 | Enable spike protection to automatically limit events during traffic spikes:
1252 | 
1253 | \`\`\`python
1254 | # Project settings
1255 | spike_protection = {
1256 |   "enabled": True,
1257 |   "max_events_per_hour": 10000,
1258 |   "detection_window": 300  # 5 minutes
1259 | }
1260 | \`\`\`
1261 | 
1262 | ## Best Practices
1263 | 
1264 | 1. **Start Conservative**: Begin with lower limits and increase as needed
1265 | 2. **Monitor Usage**: Regularly review your quota consumption
1266 | 3. **Use Sampling**: Implement transaction sampling for high-volume apps
1267 | 4. **Filter Noise**: Drop known low-value events at the SDK level
1268 | 5. **Set Alerts**: Configure notifications for quota thresholds
1269 | 
1270 | ## Rate Limit Headers
1271 | 
1272 | Sentry returns rate limit information in response headers:
1273 | 
1274 | \`\`\`
1275 | X-Sentry-Rate-Limit: 60
1276 | X-Sentry-Rate-Limit-Remaining: 42
1277 | X-Sentry-Rate-Limit-Reset: 1634567890
1278 | \`\`\`
1279 | 
1280 | ## Quota Management
1281 | 
1282 | ### Viewing Quota Usage
1283 | 
1284 | 1. Navigate to **Settings** → **Subscription**
1285 | 2. View usage by:
1286 |    - Project
1287 |    - Event type
1288 |    - Time period
1289 | 
1290 | ### On-Demand Budgets
1291 | 
1292 | Purchase additional events when approaching limits:
1293 | 
1294 | \`\`\`bash
1295 | # Via API
1296 | curl -X POST https://sentry.io/api/0/organizations/{org}/quotas/ \\
1297 |   -H 'Authorization: Bearer <token>' \\
1298 |   -d '{"events": 100000}'
1299 | \`\`\`
1300 | 
1301 | ## Troubleshooting
1302 | 
1303 | ### Events Being Dropped?
1304 | 
1305 | Check:
1306 | 1. Organization and project rate limits
1307 | 2. Spike protection status
1308 | 3. SDK sampling configuration
1309 | 4. Inbound filter settings
1310 | 
1311 | ### Rate Limit Errors
1312 | 
1313 | If you see 429 errors:
1314 | - Review your rate limit configuration
1315 | - Implement exponential backoff
1316 | - Consider event buffering
1317 | 
1318 | ## Related Documentation
1319 | 
1320 | - [SDK Configuration Guide](/platforms/javascript/configuration)
1321 | - [Quotas and Billing](/product/quotas)
1322 | - [Filtering Events](/product/data-management/filtering)`,
1323 |       {
1324 |         headers: {
1325 |           "Content-Type": "text/markdown",
1326 |         },
1327 |       },
1328 |     );
1329 |   }),
1330 |   http.get(
1331 |     "https://docs.sentry.io/product/accounts/quotas/spike-protection.md",
1332 |     () => {
1333 |       return new HttpResponse(
1334 |         `# Spike Protection
1335 | 
1336 | Spike protection prevents sudden spikes in event volume from consuming your entire quota.
1337 | 
1338 | ## How it works
1339 | 
1340 | When Sentry detects an abnormal spike in events, it automatically activates spike protection...`,
1341 |         {
1342 |           headers: {
1343 |             "Content-Type": "text/markdown",
1344 |           },
1345 |         },
1346 |       );
1347 |     },
1348 |   ),
1349 |   // Catch-all for other doc paths - return 404
1350 |   http.get("https://docs.sentry.io/*.md", () => {
1351 |     return new HttpResponse(null, { status: 404 });
1352 |   }),
1353 | ];
1354 | 
1355 | /**
1356 |  * Configured MSW server instance with all Sentry API mock handlers.
1357 |  *
1358 |  * Ready-to-use mock server for testing and development. Includes all endpoints
1359 |  * with realistic data, parameter validation, and error scenarios.
1360 |  *
1361 |  * @example Test Setup
1362 |  * ```typescript
1363 |  * import { mswServer } from "@sentry/mcp-server-mocks";
1364 |  *
1365 |  * beforeAll(() => mswServer.listen({ onUnhandledRequest: 'error' }));
1366 |  * afterEach(() => mswServer.resetHandlers());
1367 |  * afterAll(() => mswServer.close());
1368 |  * ```
1369 |  *
1370 |  * @example Development Usage
1371 |  * ```typescript
1372 |  * import { mswServer } from "@sentry/mcp-server-mocks";
1373 |  *
1374 |  * // Start intercepting requests
1375 |  * mswServer.listen();
1376 |  *
1377 |  * // Your MCP server will now use mock responses
1378 |  * const apiService = new SentryApiService({ host: "sentry.io" });
1379 |  * const orgs = await apiService.listOrganizations();
1380 |  * console.log(orgs); // Returns mock organization data
1381 |  * ```
1382 |  *
1383 |  * @note User Data Endpoint Restrictions
1384 |  * The following endpoints are configured with `controlOnly: true` to work ONLY
1385 |  * with the main host (sentry.io) and will NOT respond to requests from
1386 |  * region-specific hosts (us.sentry.io, de.sentry.io):
1387 |  * - `/api/0/auth/` (whoami endpoint)
1388 |  * - `/api/0/users/me/regions/` (find_organizations endpoint)
1389 |  *
1390 |  * This matches the real Sentry API behavior where user data must always be queried
1391 |  * from the main API server.
1392 |  */
1393 | export const mswServer = setupServer(
1394 |   ...restHandlers,
1395 |   ...searchHandlers,
1396 |   ...docsHandlers,
1397 | );
1398 | 
1399 | // Export fixtures for use in tests
1400 | export {
1401 |   autofixStateFixture,
1402 |   traceMetaFixture,
1403 |   traceMetaWithNullsFixture,
1404 |   performanceEventFixture,
1405 |   traceFixture,
1406 |   traceMixedFixture,
1407 |   traceEventFixture,
1408 | };
1409 | 
1410 | // Export fixture factories
1411 | export {
1412 |   createDefaultEvent,
1413 |   createGenericEvent,
1414 |   createUnknownEvent,
1415 |   createPerformanceEvent,
1416 |   createPerformanceIssue,
1417 |   createRegressedIssue,
1418 |   createUnsupportedIssue,
1419 |   createCspIssue,
1420 |   createCspEvent,
1421 | } from "./fixtures";
1422 | 
1423 | // Export utilities for creating mock servers
1424 | export { setupMockServer, startMockServer } from "./utils";
1425 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/get-issue-details.test.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import { describe, it, expect } from "vitest";
   2 | import { http, HttpResponse } from "msw";
   3 | import {
   4 |   mswServer,
   5 |   createDefaultEvent,
   6 |   createGenericEvent,
   7 |   createUnknownEvent,
   8 |   createPerformanceEvent,
   9 |   createPerformanceIssue,
  10 |   createRegressedIssue,
  11 |   createUnsupportedIssue,
  12 |   createCspIssue,
  13 |   createCspEvent,
  14 | } from "@sentry/mcp-server-mocks";
  15 | import getIssueDetails from "./get-issue-details.js";
  16 | 
  17 | const baseContext = {
  18 |   constraints: {
  19 |     organizationSlug: undefined,
  20 |   },
  21 |   accessToken: "access-token",
  22 |   userId: "1",
  23 | };
  24 | 
  25 | // Removed - now using createPerformanceIssue() factory from mocks
  26 | 
  27 | // Removed - now using createPerformanceEvent() factory from mocks with overrides
  28 | 
  29 | function createTraceResponseFixture() {
  30 |   return [
  31 |     {
  32 |       span_id: "root-span",
  33 |       event_id: "root-span",
  34 |       transaction_id: "root-span",
  35 |       project_id: "4509062593708032",
  36 |       project_slug: "cloudflare-mcp",
  37 |       profile_id: "",
  38 |       profiler_id: "",
  39 |       parent_span_id: null,
  40 |       start_timestamp: 0,
  41 |       end_timestamp: 1,
  42 |       measurements: {},
  43 |       duration: 1000,
  44 |       transaction: "/api/users",
  45 |       is_transaction: true,
  46 |       description: "GET /api/users",
  47 |       sdk_name: "sentry.python",
  48 |       op: "http.server",
  49 |       name: "GET /api/users",
  50 |       event_type: "transaction",
  51 |       additional_attributes: {},
  52 |       errors: [],
  53 |       occurrences: [],
  54 |       children: [
  55 |         {
  56 |           span_id: "parent123",
  57 |           event_id: "parent123",
  58 |           transaction_id: "parent123",
  59 |           project_id: "4509062593708032",
  60 |           project_slug: "cloudflare-mcp",
  61 |           profile_id: "",
  62 |           profiler_id: "",
  63 |           parent_span_id: "root-span",
  64 |           start_timestamp: 0.1,
  65 |           end_timestamp: 0.35,
  66 |           measurements: {},
  67 |           duration: 250,
  68 |           transaction: "/api/users",
  69 |           is_transaction: false,
  70 |           description: "GET /api/users handler",
  71 |           sdk_name: "sentry.python",
  72 |           op: "http.server",
  73 |           name: "GET /api/users handler",
  74 |           event_type: "span",
  75 |           additional_attributes: {},
  76 |           errors: [],
  77 |           occurrences: [],
  78 |           children: [
  79 |             {
  80 |               span_id: "span001",
  81 |               event_id: "span001",
  82 |               transaction_id: "span001",
  83 |               project_id: "4509062593708032",
  84 |               project_slug: "cloudflare-mcp",
  85 |               profile_id: "",
  86 |               profiler_id: "",
  87 |               parent_span_id: "parent123",
  88 |               start_timestamp: 0.15,
  89 |               end_timestamp: 0.16,
  90 |               measurements: {},
  91 |               duration: 10,
  92 |               transaction: "/api/users",
  93 |               is_transaction: false,
  94 |               description: "SELECT * FROM users WHERE id = 1",
  95 |               sdk_name: "sentry.python",
  96 |               op: "db.query",
  97 |               name: "SELECT * FROM users WHERE id = 1",
  98 |               event_type: "span",
  99 |               additional_attributes: {},
 100 |               errors: [],
 101 |               occurrences: [],
 102 |               children: [],
 103 |             },
 104 |             {
 105 |               span_id: "span002",
 106 |               event_id: "span002",
 107 |               transaction_id: "span002",
 108 |               project_id: "4509062593708032",
 109 |               project_slug: "cloudflare-mcp",
 110 |               profile_id: "",
 111 |               profiler_id: "",
 112 |               parent_span_id: "parent123",
 113 |               start_timestamp: 0.2,
 114 |               end_timestamp: 0.212,
 115 |               measurements: {},
 116 |               duration: 12,
 117 |               transaction: "/api/users",
 118 |               is_transaction: false,
 119 |               description: "SELECT * FROM users WHERE id = 2",
 120 |               sdk_name: "sentry.python",
 121 |               op: "db.query",
 122 |               name: "SELECT * FROM users WHERE id = 2",
 123 |               event_type: "span",
 124 |               additional_attributes: {},
 125 |               errors: [],
 126 |               occurrences: [],
 127 |               children: [],
 128 |             },
 129 |             {
 130 |               span_id: "span003",
 131 |               event_id: "span003",
 132 |               transaction_id: "span003",
 133 |               project_id: "4509062593708032",
 134 |               project_slug: "cloudflare-mcp",
 135 |               profile_id: "",
 136 |               profiler_id: "",
 137 |               parent_span_id: "parent123",
 138 |               start_timestamp: 0.24,
 139 |               end_timestamp: 0.255,
 140 |               measurements: {},
 141 |               duration: 15,
 142 |               transaction: "/api/users",
 143 |               is_transaction: false,
 144 |               description: "SELECT * FROM users WHERE id = 3",
 145 |               sdk_name: "sentry.python",
 146 |               op: "db.query",
 147 |               name: "SELECT * FROM users WHERE id = 3",
 148 |               event_type: "span",
 149 |               additional_attributes: {},
 150 |               errors: [],
 151 |               occurrences: [],
 152 |               children: [],
 153 |             },
 154 |           ],
 155 |         },
 156 |       ],
 157 |     },
 158 |   ];
 159 | }
 160 | 
 161 | describe("get_issue_details", () => {
 162 |   it("serializes with issueId", async () => {
 163 |     const result = await getIssueDetails.handler(
 164 |       {
 165 |         organizationSlug: "sentry-mcp-evals",
 166 |         issueId: "CLOUDFLARE-MCP-41",
 167 |         eventId: undefined,
 168 |         issueUrl: undefined,
 169 |         regionUrl: null,
 170 |       },
 171 |       {
 172 |         constraints: {
 173 |           organizationSlug: undefined,
 174 |         },
 175 |         accessToken: "access-token",
 176 |         userId: "1",
 177 |       },
 178 |     );
 179 |     expect(result).toMatchInlineSnapshot(`
 180 |       "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals**
 181 | 
 182 |       **Description**: Error: Tool list_organizations is already registered
 183 |       **Culprit**: Object.fetch(index)
 184 |       **First Seen**: 2025-04-03T22:51:19.403Z
 185 |       **Last Seen**: 2025-04-12T11:34:11.000Z
 186 |       **Occurrences**: 25
 187 |       **Users Impacted**: 1
 188 |       **Status**: unresolved
 189 |       **Substatus**: ongoing
 190 |       **Assigned To**: Jane Developer (User)
 191 |       **Issue Type**: error
 192 |       **Issue Category**: error
 193 |       **Platform**: javascript
 194 |       **Project**: CLOUDFLARE-MCP
 195 |       **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41
 196 | 
 197 |       ## Event Details
 198 | 
 199 |       **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51
 200 |       **Type**: error
 201 |       **Occurred At**: 2025-04-08T21:15:04.000Z
 202 | 
 203 |       ### Error
 204 | 
 205 |       \`\`\`
 206 |       Error: Tool list_organizations is already registered
 207 |       \`\`\`
 208 | 
 209 |       **Stacktrace:**
 210 |       \`\`\`
 211 |       index.js:7809:27
 212 |       index.js:8029:24 (OAuthProviderImpl.fetch)
 213 |       index.js:19631:28 (Object.fetch)
 214 |       \`\`\`
 215 | 
 216 |       ### HTTP Request
 217 | 
 218 |       **Method:** GET
 219 |       **URL:** https://mcp.sentry.dev/sse
 220 | 
 221 |       ### Tags
 222 | 
 223 |       **environment**: development
 224 |       **handled**: no
 225 |       **level**: error
 226 |       **mechanism**: cloudflare
 227 |       **runtime.name**: cloudflare
 228 |       **url**: https://mcp.sentry.dev/sse
 229 | 
 230 |       ### Additional Context
 231 | 
 232 |       These are additional context provided by the user when they're instrumenting their application.
 233 | 
 234 |       **cloud_resource**
 235 |       cloud.provider: "cloudflare"
 236 | 
 237 |       **culture**
 238 |       timezone: "Europe/London"
 239 | 
 240 |       **runtime**
 241 |       name: "cloudflare"
 242 | 
 243 |       **trace**
 244 |       trace_id: "3032af8bcdfe4423b937fc5c041d5d82"
 245 |       span_id: "953da703d2a6f4c7"
 246 |       status: "unknown"
 247 |       client_sample_rate: 1
 248 |       sampled: true
 249 | 
 250 |       # Using this information
 251 | 
 252 |       - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged.
 253 |       - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code.
 254 |       "
 255 |     `);
 256 |   });
 257 | 
 258 |   it("displays team assignment correctly", async () => {
 259 |     // Override the issue fixture with a team assignment
 260 |     mswServer.use(
 261 |       http.get(
 262 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/TEAM-ISSUE-001/",
 263 |         () =>
 264 |           HttpResponse.json({
 265 |             id: "123456789",
 266 |             shortId: "TEAM-ISSUE-001",
 267 |             title: "Test issue with team assignment",
 268 |             firstSeen: "2025-04-03T22:51:19.403Z",
 269 |             lastSeen: "2025-04-12T11:34:11Z",
 270 |             count: "10",
 271 |             userCount: 5,
 272 |             permalink:
 273 |               "https://sentry-mcp-evals.sentry.io/issues/TEAM-ISSUE-001",
 274 |             project: {
 275 |               id: "4509062593708032",
 276 |               slug: "test-project",
 277 |               name: "Test Project",
 278 |             },
 279 |             platform: "javascript",
 280 |             status: "unresolved",
 281 |             substatus: "ongoing",
 282 |             culprit: "app.main",
 283 |             type: "error",
 284 |             issueType: "error",
 285 |             issueCategory: "error",
 286 |             assignedTo: {
 287 |               type: "team",
 288 |               id: "99999",
 289 |               name: "Platform Team",
 290 |             },
 291 |           }),
 292 |         { once: true },
 293 |       ),
 294 |       http.get(
 295 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/TEAM-ISSUE-001/events/latest/",
 296 |         () => HttpResponse.json(createDefaultEvent()),
 297 |         { once: true },
 298 |       ),
 299 |     );
 300 | 
 301 |     const result = await getIssueDetails.handler(
 302 |       {
 303 |         organizationSlug: "sentry-mcp-evals",
 304 |         issueId: "TEAM-ISSUE-001",
 305 |         eventId: undefined,
 306 |         issueUrl: undefined,
 307 |         regionUrl: null,
 308 |       },
 309 |       baseContext,
 310 |     );
 311 | 
 312 |     // Verify that team assignment is displayed with "(Team)" suffix
 313 |     expect(result).toContain("**Assigned To**: Platform Team (Team)");
 314 |   });
 315 | 
 316 |   it("serializes with issueUrl", async () => {
 317 |     const result = await getIssueDetails.handler(
 318 |       {
 319 |         organizationSlug: undefined,
 320 |         issueId: undefined,
 321 |         eventId: undefined,
 322 |         issueUrl: "https://sentry-mcp-evals.sentry.io/issues/6507376925",
 323 |         regionUrl: null,
 324 |       },
 325 |       {
 326 |         constraints: {
 327 |           organizationSlug: undefined,
 328 |         },
 329 |         accessToken: "access-token",
 330 |         userId: "1",
 331 |       },
 332 |     );
 333 | 
 334 |     expect(result).toMatchInlineSnapshot(`
 335 |       "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals**
 336 | 
 337 |       **Description**: Error: Tool list_organizations is already registered
 338 |       **Culprit**: Object.fetch(index)
 339 |       **First Seen**: 2025-04-03T22:51:19.403Z
 340 |       **Last Seen**: 2025-04-12T11:34:11.000Z
 341 |       **Occurrences**: 25
 342 |       **Users Impacted**: 1
 343 |       **Status**: unresolved
 344 |       **Substatus**: ongoing
 345 |       **Assigned To**: Jane Developer (User)
 346 |       **Issue Type**: error
 347 |       **Issue Category**: error
 348 |       **Platform**: javascript
 349 |       **Project**: CLOUDFLARE-MCP
 350 |       **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41
 351 | 
 352 |       ## Event Details
 353 | 
 354 |       **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51
 355 |       **Type**: error
 356 |       **Occurred At**: 2025-04-08T21:15:04.000Z
 357 | 
 358 |       ### Error
 359 | 
 360 |       \`\`\`
 361 |       Error: Tool list_organizations is already registered
 362 |       \`\`\`
 363 | 
 364 |       **Stacktrace:**
 365 |       \`\`\`
 366 |       index.js:7809:27
 367 |       index.js:8029:24 (OAuthProviderImpl.fetch)
 368 |       index.js:19631:28 (Object.fetch)
 369 |       \`\`\`
 370 | 
 371 |       ### HTTP Request
 372 | 
 373 |       **Method:** GET
 374 |       **URL:** https://mcp.sentry.dev/sse
 375 | 
 376 |       ### Tags
 377 | 
 378 |       **environment**: development
 379 |       **handled**: no
 380 |       **level**: error
 381 |       **mechanism**: cloudflare
 382 |       **runtime.name**: cloudflare
 383 |       **url**: https://mcp.sentry.dev/sse
 384 | 
 385 |       ### Additional Context
 386 | 
 387 |       These are additional context provided by the user when they're instrumenting their application.
 388 | 
 389 |       **cloud_resource**
 390 |       cloud.provider: "cloudflare"
 391 | 
 392 |       **culture**
 393 |       timezone: "Europe/London"
 394 | 
 395 |       **runtime**
 396 |       name: "cloudflare"
 397 | 
 398 |       **trace**
 399 |       trace_id: "3032af8bcdfe4423b937fc5c041d5d82"
 400 |       span_id: "953da703d2a6f4c7"
 401 |       status: "unknown"
 402 |       client_sample_rate: 1
 403 |       sampled: true
 404 | 
 405 |       # Using this information
 406 | 
 407 |       - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged.
 408 |       - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code.
 409 |       "
 410 |     `);
 411 |   });
 412 | 
 413 |   it("renders related trace spans when trace fetch succeeds", async () => {
 414 |     mswServer.use(
 415 |       http.get(
 416 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/",
 417 |         () => HttpResponse.json(createPerformanceIssue()),
 418 |         { once: true },
 419 |       ),
 420 |       http.get(
 421 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/",
 422 |         () => {
 423 |           // Create event with specific evidence data for this test
 424 |           const event = createPerformanceEvent();
 425 |           const offenderSpanIds =
 426 |             event.occurrence.evidenceData.offenderSpanIds.slice(0, 3);
 427 |           event.occurrence.evidenceData.offenderSpanIds = offenderSpanIds;
 428 |           event.occurrence.evidenceData.numberRepeatingSpans = String(
 429 |             offenderSpanIds.length,
 430 |           );
 431 |           event.occurrence.evidenceData.repeatingSpansCompact = undefined;
 432 |           event.occurrence.evidenceData.repeatingSpans = [
 433 |             'db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"',
 434 |             "function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file",
 435 |             'db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21',
 436 |           ];
 437 |           const spansEntry = event.entries.find(
 438 |             (entry: { type: string; data?: unknown }) => entry.type === "spans",
 439 |           );
 440 |           if (spansEntry?.data) {
 441 |             spansEntry.data = spansEntry.data.slice(0, 4);
 442 |           }
 443 |           return HttpResponse.json(event);
 444 |         },
 445 |         { once: true },
 446 |       ),
 447 |       http.get(
 448 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/trace/abcdef1234567890abcdef1234567890/",
 449 |         () => HttpResponse.json(createTraceResponseFixture()),
 450 |         { once: true },
 451 |       ),
 452 |     );
 453 | 
 454 |     const result = await getIssueDetails.handler(
 455 |       {
 456 |         organizationSlug: "sentry-mcp-evals",
 457 |         issueId: "PERF-N1-001",
 458 |         eventId: undefined,
 459 |         issueUrl: undefined,
 460 |         regionUrl: null,
 461 |       },
 462 |       baseContext,
 463 |     );
 464 | 
 465 |     if (typeof result !== "string") {
 466 |       throw new Error("Expected string result");
 467 |     }
 468 | 
 469 |     const performanceSection = result
 470 |       .slice(result.indexOf("### Repeated Database Queries"))
 471 |       .split("### Tags")[0]
 472 |       .trim();
 473 | 
 474 |     expect(performanceSection).toMatchInlineSnapshot(`
 475 |       "### Repeated Database Queries
 476 | 
 477 |       **Query executed 3 times:**
 478 |       **Repeated operations:**
 479 |       - db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"
 480 |       - function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file
 481 |       - db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21
 482 | 
 483 |       ### Span Tree (Limited to 10 spans)
 484 | 
 485 |       \`\`\`
 486 |       GET /api/users [parent12 · http.server · 250ms]
 487 |          ├─ SELECT * FROM users WHERE id = 1 [span001 · db.query · 5ms] [N+1]
 488 |          ├─ SELECT * FROM users WHERE id = 2 [span002 · db.query · 5ms] [N+1]
 489 |          └─ SELECT * FROM users WHERE id = 3 [span003 · db.query · 5ms] [N+1]
 490 |       \`\`\`
 491 | 
 492 |       **Transaction:**
 493 |       /api/users
 494 | 
 495 |       **Offending Spans:**
 496 |       SELECT * FROM users WHERE id = %s
 497 | 
 498 |       **Repeated:**
 499 |       3 times"
 500 |     `);
 501 |   });
 502 | 
 503 |   it("falls back to offending span list when trace fetch fails", async () => {
 504 |     mswServer.use(
 505 |       http.get(
 506 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/",
 507 |         () => HttpResponse.json(createPerformanceIssue()),
 508 |         { once: true },
 509 |       ),
 510 |       http.get(
 511 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/",
 512 |         () => {
 513 |           // Create event with specific evidence data for this test
 514 |           const event = createPerformanceEvent();
 515 |           const offenderSpanIds =
 516 |             event.occurrence.evidenceData.offenderSpanIds.slice(0, 3);
 517 |           event.occurrence.evidenceData.offenderSpanIds = offenderSpanIds;
 518 |           event.occurrence.evidenceData.numberRepeatingSpans = String(
 519 |             offenderSpanIds.length,
 520 |           );
 521 |           event.occurrence.evidenceData.repeatingSpansCompact = undefined;
 522 |           event.occurrence.evidenceData.repeatingSpans = [
 523 |             'db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"',
 524 |             "function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file",
 525 |             'db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21',
 526 |           ];
 527 |           const spansEntry = event.entries.find(
 528 |             (entry: { type: string; data?: unknown }) => entry.type === "spans",
 529 |           );
 530 |           if (spansEntry?.data) {
 531 |             spansEntry.data = spansEntry.data.slice(0, 4);
 532 |           }
 533 |           return HttpResponse.json(event);
 534 |         },
 535 |         { once: true },
 536 |       ),
 537 |       http.get(
 538 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/trace/abcdef1234567890abcdef1234567890/",
 539 |         () => HttpResponse.json({ detail: "Trace not found" }, { status: 404 }),
 540 |         { once: true },
 541 |       ),
 542 |     );
 543 | 
 544 |     const result = await getIssueDetails.handler(
 545 |       {
 546 |         organizationSlug: "sentry-mcp-evals",
 547 |         issueId: "PERF-N1-001",
 548 |         eventId: undefined,
 549 |         issueUrl: undefined,
 550 |         regionUrl: null,
 551 |       },
 552 |       baseContext,
 553 |     );
 554 | 
 555 |     if (typeof result !== "string") {
 556 |       throw new Error("Expected string result");
 557 |     }
 558 | 
 559 |     const performanceSection = result
 560 |       .slice(result.indexOf("### Repeated Database Queries"))
 561 |       .split("### Tags")[0]
 562 |       .trim();
 563 | 
 564 |     expect(performanceSection).toMatchInlineSnapshot(`
 565 |       "### Repeated Database Queries
 566 | 
 567 |       **Query executed 3 times:**
 568 |       **Repeated operations:**
 569 |       - db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"
 570 |       - function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file
 571 |       - db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21
 572 | 
 573 |       ### Span Tree (Limited to 10 spans)
 574 | 
 575 |       \`\`\`
 576 |       GET /api/users [parent12 · http.server · 250ms]
 577 |          ├─ SELECT * FROM users WHERE id = 1 [span001 · db.query · 5ms] [N+1]
 578 |          ├─ SELECT * FROM users WHERE id = 2 [span002 · db.query · 5ms] [N+1]
 579 |          └─ SELECT * FROM users WHERE id = 3 [span003 · db.query · 5ms] [N+1]
 580 |       \`\`\`
 581 | 
 582 |       **Transaction:**
 583 |       /api/users
 584 | 
 585 |       **Offending Spans:**
 586 |       SELECT * FROM users WHERE id = %s
 587 | 
 588 |       **Repeated:**
 589 |       3 times"
 590 |     `);
 591 |   });
 592 | 
 593 |   it("serializes with eventId", async () => {
 594 |     const result = await getIssueDetails.handler(
 595 |       {
 596 |         organizationSlug: "sentry-mcp-evals",
 597 |         issueId: undefined,
 598 |         issueUrl: undefined,
 599 |         eventId: "7ca573c0f4814912aaa9bdc77d1a7d51",
 600 |         regionUrl: null,
 601 |       },
 602 |       {
 603 |         constraints: {
 604 |           organizationSlug: undefined,
 605 |         },
 606 |         accessToken: "access-token",
 607 |         userId: "1",
 608 |       },
 609 |     );
 610 |     expect(result).toMatchInlineSnapshot(`
 611 |       "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals**
 612 | 
 613 |       **Description**: Error: Tool list_organizations is already registered
 614 |       **Culprit**: Object.fetch(index)
 615 |       **First Seen**: 2025-04-03T22:51:19.403Z
 616 |       **Last Seen**: 2025-04-12T11:34:11.000Z
 617 |       **Occurrences**: 25
 618 |       **Users Impacted**: 1
 619 |       **Status**: unresolved
 620 |       **Substatus**: ongoing
 621 |       **Assigned To**: Jane Developer (User)
 622 |       **Issue Type**: error
 623 |       **Issue Category**: error
 624 |       **Platform**: javascript
 625 |       **Project**: CLOUDFLARE-MCP
 626 |       **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41
 627 | 
 628 |       ## Event Details
 629 | 
 630 |       **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51
 631 |       **Type**: error
 632 |       **Occurred At**: 2025-04-08T21:15:04.000Z
 633 | 
 634 |       ### Error
 635 | 
 636 |       \`\`\`
 637 |       Error: Tool list_organizations is already registered
 638 |       \`\`\`
 639 | 
 640 |       **Stacktrace:**
 641 |       \`\`\`
 642 |       index.js:7809:27
 643 |       index.js:8029:24 (OAuthProviderImpl.fetch)
 644 |       index.js:19631:28 (Object.fetch)
 645 |       \`\`\`
 646 | 
 647 |       ### HTTP Request
 648 | 
 649 |       **Method:** GET
 650 |       **URL:** https://mcp.sentry.dev/sse
 651 | 
 652 |       ### Tags
 653 | 
 654 |       **environment**: development
 655 |       **handled**: no
 656 |       **level**: error
 657 |       **mechanism**: cloudflare
 658 |       **runtime.name**: cloudflare
 659 |       **url**: https://mcp.sentry.dev/sse
 660 | 
 661 |       ### Additional Context
 662 | 
 663 |       These are additional context provided by the user when they're instrumenting their application.
 664 | 
 665 |       **cloud_resource**
 666 |       cloud.provider: "cloudflare"
 667 | 
 668 |       **culture**
 669 |       timezone: "Europe/London"
 670 | 
 671 |       **runtime**
 672 |       name: "cloudflare"
 673 | 
 674 |       **trace**
 675 |       trace_id: "3032af8bcdfe4423b937fc5c041d5d82"
 676 |       span_id: "953da703d2a6f4c7"
 677 |       status: "unknown"
 678 |       client_sample_rate: 1
 679 |       sampled: true
 680 | 
 681 |       # Using this information
 682 | 
 683 |       - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged.
 684 |       - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code.
 685 |       "
 686 |     `);
 687 |   });
 688 | 
 689 |   it("throws error for malformed regionUrl", async () => {
 690 |     await expect(
 691 |       getIssueDetails.handler(
 692 |         {
 693 |           organizationSlug: "sentry-mcp-evals",
 694 |           issueId: "CLOUDFLARE-MCP-41",
 695 |           eventId: undefined,
 696 |           issueUrl: undefined,
 697 |           regionUrl: "https",
 698 |         },
 699 |         {
 700 |           constraints: {
 701 |             organizationSlug: undefined,
 702 |           },
 703 |           accessToken: "access-token",
 704 |           userId: "1",
 705 |         },
 706 |       ),
 707 |     ).rejects.toThrow(
 708 |       "Invalid regionUrl provided: https. Must be a valid URL.",
 709 |     );
 710 |   });
 711 | 
 712 |   it("enhances 404 error with parameter context for non-existent issue", async () => {
 713 |     // This test demonstrates the enhance-error functionality:
 714 |     // When a 404 occurs, enhanceNotFoundError() adds parameter context to help users
 715 |     // understand what went wrong (organizationSlug + issueId in this case)
 716 | 
 717 |     // Mock a 404 response for a non-existent issue
 718 |     mswServer.use(
 719 |       http.get(
 720 |         "https://sentry.io/api/0/organizations/test-org/issues/NONEXISTENT-ISSUE-123/",
 721 |         () => {
 722 |           return new HttpResponse(
 723 |             JSON.stringify({ detail: "The requested resource does not exist" }),
 724 |             { status: 404 },
 725 |           );
 726 |         },
 727 |         { once: true },
 728 |       ),
 729 |     );
 730 | 
 731 |     await expect(
 732 |       getIssueDetails.handler(
 733 |         {
 734 |           organizationSlug: "test-org",
 735 |           issueId: "NONEXISTENT-ISSUE-123",
 736 |           eventId: undefined,
 737 |           issueUrl: undefined,
 738 |           regionUrl: null,
 739 |         },
 740 |         {
 741 |           constraints: {
 742 |             organizationSlug: undefined,
 743 |           },
 744 |           accessToken: "access-token",
 745 |           userId: "1",
 746 |         },
 747 |       ),
 748 |     ).rejects.toThrowErrorMatchingInlineSnapshot(`
 749 |       [ApiNotFoundError: The requested resource does not exist
 750 |       Please verify these parameters are correct:
 751 |         - organizationSlug: 'test-org'
 752 |         - issueId: 'NONEXISTENT-ISSUE-123']
 753 |     `);
 754 |   });
 755 | 
 756 |   // These tests verify that Seer analysis is properly formatted when available
 757 |   // Note: The autofix endpoint needs to be mocked for each test
 758 | 
 759 |   it("includes Seer analysis when available - COMPLETED state", async () => {
 760 |     // This test currently passes without Seer data since the autofix endpoint
 761 |     // returns an error that is caught silently. The functionality is implemented
 762 |     // and will work when Seer data is available.
 763 |     const result = await getIssueDetails.handler(
 764 |       {
 765 |         organizationSlug: "sentry-mcp-evals",
 766 |         issueId: "CLOUDFLARE-MCP-41",
 767 |         eventId: undefined,
 768 |         issueUrl: undefined,
 769 |         regionUrl: null,
 770 |       },
 771 |       {
 772 |         constraints: {
 773 |           organizationSlug: undefined,
 774 |         },
 775 |         accessToken: "access-token",
 776 |         userId: "1",
 777 |       },
 778 |     );
 779 | 
 780 |     // Verify the basic issue output is present
 781 |     expect(result).toContain("# Issue CLOUDFLARE-MCP-41");
 782 |     expect(result).toContain(
 783 |       "Error: Tool list_organizations is already registered",
 784 |     );
 785 |     // When Seer data is available, these would pass:
 786 |     // expect(result).toContain("## Seer AI Analysis");
 787 |     // expect(result).toContain("For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`");
 788 |   });
 789 | 
 790 |   it.skip("includes Seer analysis when in progress - PROCESSING state", async () => {
 791 |     const inProgressFixture = {
 792 |       autofix: {
 793 |         run_id: 12345,
 794 |         status: "PROCESSING",
 795 |         updated_at: "2025-04-09T22:39:50.778146",
 796 |         request: {},
 797 |         steps: [
 798 |           {
 799 |             id: "step-1",
 800 |             type: "root_cause_analysis",
 801 |             status: "COMPLETED",
 802 |             title: "Root Cause Analysis",
 803 |             index: 0,
 804 |             causes: [
 805 |               {
 806 |                 id: 0,
 807 |                 description:
 808 |                   "The bottleById query fails because the input ID doesn't exist in the database.",
 809 |                 root_cause_reproduction: [],
 810 |               },
 811 |             ],
 812 |             progress: [],
 813 |             queued_user_messages: [],
 814 |             selection: null,
 815 |           },
 816 |           {
 817 |             id: "step-2",
 818 |             type: "solution",
 819 |             status: "IN_PROGRESS",
 820 |             title: "Generating Solution",
 821 |             index: 1,
 822 |             description: null,
 823 |             solution: [],
 824 |             progress: [],
 825 |             queued_user_messages: [],
 826 |           },
 827 |         ],
 828 |       },
 829 |     };
 830 | 
 831 |     // Use mswServer.use to prepend a handler - MSW uses LIFO order
 832 |     mswServer.use(
 833 |       http.get(
 834 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/",
 835 |         () => HttpResponse.json(inProgressFixture),
 836 |         { once: true }, // Ensure this handler is only used once for this test
 837 |       ),
 838 |     );
 839 | 
 840 |     const result = await getIssueDetails.handler(
 841 |       {
 842 |         organizationSlug: "sentry-mcp-evals",
 843 |         issueId: "CLOUDFLARE-MCP-41",
 844 |         eventId: undefined,
 845 |         issueUrl: undefined,
 846 |         regionUrl: null,
 847 |       },
 848 |       {
 849 |         constraints: {
 850 |           organizationSlug: undefined,
 851 |         },
 852 |         accessToken: "access-token",
 853 |         userId: "1",
 854 |       },
 855 |     );
 856 | 
 857 |     expect(result).toContain("## Seer Analysis");
 858 |     expect(result).toContain("**Status:** Processing");
 859 |     expect(result).toContain("**Root Cause Identified:**");
 860 |     expect(result).toContain(
 861 |       "The bottleById query fails because the input ID doesn't exist in the database.",
 862 |     );
 863 |     expect(result).toContain(
 864 |       "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`",
 865 |     );
 866 |   });
 867 | 
 868 |   it.skip("includes Seer analysis when failed - FAILED state", async () => {
 869 |     const failedFixture = {
 870 |       autofix: {
 871 |         run_id: 12346,
 872 |         status: "FAILED",
 873 |         updated_at: "2025-04-09T22:39:50.778146",
 874 |         request: {},
 875 |         steps: [],
 876 |       },
 877 |     };
 878 | 
 879 |     mswServer.use(
 880 |       http.get(
 881 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/",
 882 |         () => HttpResponse.json(failedFixture),
 883 |         { once: true },
 884 |       ),
 885 |     );
 886 | 
 887 |     const result = await getIssueDetails.handler(
 888 |       {
 889 |         organizationSlug: "sentry-mcp-evals",
 890 |         issueId: "CLOUDFLARE-MCP-41",
 891 |         eventId: undefined,
 892 |         issueUrl: undefined,
 893 |         regionUrl: null,
 894 |       },
 895 |       {
 896 |         constraints: {
 897 |           organizationSlug: undefined,
 898 |         },
 899 |         accessToken: "access-token",
 900 |         userId: "1",
 901 |       },
 902 |     );
 903 | 
 904 |     expect(result).toContain("## Seer Analysis");
 905 |     expect(result).toContain("**Status:** Analysis failed.");
 906 |     expect(result).toContain(
 907 |       "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`",
 908 |     );
 909 |   });
 910 | 
 911 |   it.skip("includes Seer analysis when needs information - NEED_MORE_INFORMATION state", async () => {
 912 |     const needsInfoFixture = {
 913 |       autofix: {
 914 |         run_id: 12347,
 915 |         status: "NEED_MORE_INFORMATION",
 916 |         updated_at: "2025-04-09T22:39:50.778146",
 917 |         request: {},
 918 |         steps: [
 919 |           {
 920 |             id: "step-1",
 921 |             type: "root_cause_analysis",
 922 |             status: "COMPLETED",
 923 |             title: "Root Cause Analysis",
 924 |             index: 0,
 925 |             causes: [
 926 |               {
 927 |                 id: 0,
 928 |                 description:
 929 |                   "Partial analysis completed but more context needed.",
 930 |                 root_cause_reproduction: [],
 931 |               },
 932 |             ],
 933 |             progress: [],
 934 |             queued_user_messages: [],
 935 |             selection: null,
 936 |           },
 937 |         ],
 938 |       },
 939 |     };
 940 | 
 941 |     mswServer.use(
 942 |       http.get(
 943 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/",
 944 |         () => HttpResponse.json(needsInfoFixture),
 945 |         { once: true },
 946 |       ),
 947 |     );
 948 | 
 949 |     const result = await getIssueDetails.handler(
 950 |       {
 951 |         organizationSlug: "sentry-mcp-evals",
 952 |         issueId: "CLOUDFLARE-MCP-41",
 953 |         eventId: undefined,
 954 |         issueUrl: undefined,
 955 |         regionUrl: null,
 956 |       },
 957 |       {
 958 |         constraints: {
 959 |           organizationSlug: undefined,
 960 |         },
 961 |         accessToken: "access-token",
 962 |         userId: "1",
 963 |       },
 964 |     );
 965 | 
 966 |     expect(result).toContain("## Seer Analysis");
 967 |     expect(result).toContain("**Root Cause Identified:**");
 968 |     expect(result).toContain(
 969 |       "Partial analysis completed but more context needed.",
 970 |     );
 971 |     expect(result).toContain(
 972 |       "**Status:** Analysis paused - additional information needed.",
 973 |     );
 974 |     expect(result).toContain(
 975 |       "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`",
 976 |     );
 977 |   });
 978 | 
 979 |   it("handles default event type (error without exception data)", async () => {
 980 |     // Mock a "default" event type - represents errors without exception data
 981 |     const defaultEvent = createDefaultEvent();
 982 | 
 983 |     mswServer.use(
 984 |       http.get(
 985 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/DEFAULT-001/events/latest/",
 986 |         () => HttpResponse.json(defaultEvent),
 987 |       ),
 988 |       http.get(
 989 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/DEFAULT-001/",
 990 |         () => {
 991 |           return HttpResponse.json({
 992 |             id: "123456",
 993 |             shortId: "DEFAULT-001",
 994 |             title: "Error without exception data",
 995 |             firstSeen: "2025-10-02T10:00:00.000Z",
 996 |             lastSeen: "2025-10-02T12:00:00.000Z",
 997 |             count: "5",
 998 |             userCount: 2,
 999 |             permalink: "https://sentry-mcp-evals.sentry.io/issues/123456/",
1000 |             project: {
1001 |               id: "4509062593708032",
1002 |               name: "TEST-PROJECT",
1003 |               slug: "test-project",
1004 |               platform: "python",
1005 |             },
1006 |             status: "unresolved",
1007 |             culprit: "unknown",
1008 |             type: "default",
1009 |             platform: "python",
1010 |           });
1011 |         },
1012 |       ),
1013 |     );
1014 | 
1015 |     const result = await getIssueDetails.handler(
1016 |       {
1017 |         organizationSlug: "sentry-mcp-evals",
1018 |         issueId: "DEFAULT-001",
1019 |         eventId: undefined,
1020 |         issueUrl: undefined,
1021 |         regionUrl: null,
1022 |       },
1023 |       {
1024 |         constraints: {
1025 |           organizationSlug: undefined,
1026 |         },
1027 |         accessToken: "access-token",
1028 |         userId: "1",
1029 |       },
1030 |     );
1031 | 
1032 |     // Verify the event was processed successfully
1033 |     expect(result).toContain("# Issue DEFAULT-001 in **sentry-mcp-evals**");
1034 |     expect(result).toContain("Error without exception data");
1035 |     expect(result).toContain("**Event ID**: abc123def456");
1036 |     // Default events should show dateCreated just like error events
1037 |     expect(result).toContain("**Occurred At**: 2025-10-02T12:00:00.000Z");
1038 |     expect(result).toContain("### Error");
1039 |     expect(result).toContain("Something went wrong");
1040 |   });
1041 | 
1042 |   it("handles CSP (Content Security Policy) violation events", async () => {
1043 |     // Mock a CSP violation event and issue
1044 |     const cspEvent = createCspEvent();
1045 |     const cspIssue = createCspIssue();
1046 | 
1047 |     mswServer.use(
1048 |       http.get(
1049 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/events/latest/",
1050 |         () => HttpResponse.json(cspEvent),
1051 |       ),
1052 |       http.get(
1053 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/",
1054 |         () => HttpResponse.json(cspIssue),
1055 |       ),
1056 |     );
1057 | 
1058 |     const result = await getIssueDetails.handler(
1059 |       {
1060 |         organizationSlug: "sentry-mcp-evals",
1061 |         issueId: "BLOG-CSP-4XC",
1062 |         eventId: undefined,
1063 |         issueUrl: undefined,
1064 |         regionUrl: null,
1065 |       },
1066 |       baseContext,
1067 |     );
1068 | 
1069 |     // Verify CSP-specific content is included
1070 |     expect(result).toContain("# Issue BLOG-CSP-4XC in **sentry-mcp-evals**");
1071 |     expect(result).toContain("Blocked 'image' from 'blob:'");
1072 |     expect(result).toContain("**Event ID**: bf5b6c7fd49f4f8da94085a43393051d");
1073 |     expect(result).toContain("**Type**: csp");
1074 |     // Should show the CSP entry data
1075 |     expect(result).toContain("### CSP Violation");
1076 |     expect(result).toContain("**Blocked URI**: blob");
1077 |     expect(result).toContain("**Violated Directive**: img-src");
1078 |     expect(result).toContain("**Document URI**: https://blog.sentry.io");
1079 |   });
1080 | 
1081 |   it("displays context (extra) data when present", async () => {
1082 |     const eventWithContext = {
1083 |       id: "abc123def456",
1084 |       type: "error",
1085 |       title: "TypeError",
1086 |       culprit: "app.js in processData",
1087 |       message: "Cannot read property 'value' of undefined",
1088 |       dateCreated: "2025-10-02T12:00:00.000Z",
1089 |       platform: "javascript",
1090 |       entries: [
1091 |         {
1092 |           type: "message",
1093 |           data: {
1094 |             formatted: "Cannot read property 'value' of undefined",
1095 |           },
1096 |         },
1097 |       ],
1098 |       context: {
1099 |         custom_field: "custom_value",
1100 |         user_action: "submit_form",
1101 |         session_data: {
1102 |           session_id: "sess_12345",
1103 |           user_id: "user_67890",
1104 |         },
1105 |         environment_info: "production",
1106 |       },
1107 |       contexts: {
1108 |         runtime: {
1109 |           name: "node",
1110 |           version: "18.0.0",
1111 |           type: "runtime",
1112 |         },
1113 |       },
1114 |       tags: [
1115 |         { key: "environment", value: "production" },
1116 |         { key: "level", value: "error" },
1117 |       ],
1118 |     };
1119 | 
1120 |     mswServer.use(
1121 |       http.get(
1122 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CONTEXT-001/",
1123 |         () => {
1124 |           return HttpResponse.json({
1125 |             id: "123456",
1126 |             shortId: "CONTEXT-001",
1127 |             title: "TypeError",
1128 |             firstSeen: "2025-10-02T10:00:00.000Z",
1129 |             lastSeen: "2025-10-02T12:00:00.000Z",
1130 |             count: "5",
1131 |             userCount: 2,
1132 |             permalink: "https://sentry-mcp-evals.sentry.io/issues/123456/",
1133 |             project: {
1134 |               id: "4509062593708032",
1135 |               name: "TEST-PROJECT",
1136 |               slug: "test-project",
1137 |               platform: "javascript",
1138 |             },
1139 |             status: "unresolved",
1140 |             culprit: "app.js in processData",
1141 |             type: "error",
1142 |             platform: "javascript",
1143 |           });
1144 |         },
1145 |       ),
1146 |       http.get(
1147 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CONTEXT-001/events/latest/",
1148 |         () => {
1149 |           return HttpResponse.json(eventWithContext);
1150 |         },
1151 |       ),
1152 |     );
1153 | 
1154 |     const result = await getIssueDetails.handler(
1155 |       {
1156 |         organizationSlug: "sentry-mcp-evals",
1157 |         issueId: "CONTEXT-001",
1158 |         eventId: undefined,
1159 |         issueUrl: undefined,
1160 |         regionUrl: null,
1161 |       },
1162 |       {
1163 |         constraints: {
1164 |           organizationSlug: undefined,
1165 |         },
1166 |         accessToken: "access-token",
1167 |         userId: "1",
1168 |       },
1169 |     );
1170 | 
1171 |     // Verify the context (extra) data is displayed
1172 |     expect(result).toContain("### Extra Data");
1173 |     expect(result).toContain("Additional data attached to this event");
1174 |     expect(result).toContain('**custom_field**: "custom_value"');
1175 |     expect(result).toContain('**user_action**: "submit_form"');
1176 |     expect(result).toContain("**session_data**:");
1177 |     expect(result).toContain('"session_id": "sess_12345"');
1178 |     expect(result).toContain('"user_id": "user_67890"');
1179 |     expect(result).toContain('**environment_info**: "production"');
1180 |     // Verify contexts are still displayed
1181 |     expect(result).toContain("### Additional Context");
1182 |   });
1183 | 
1184 |   it("handles regressed performance issues (generic type with empty entries)", async () => {
1185 |     // This tests the actual structure from issue #633
1186 |     // Regressed performance issues have:
1187 |     // - type: "generic"
1188 |     // - entries: [] (empty array)
1189 |     // - occurrence field with evidenceData
1190 | 
1191 |     const regressedIssueFixture = createRegressedIssue();
1192 | 
1193 |     // Use the generic event fixture factory (baseline already matches this test's needs)
1194 |     const regressedEventFixture = createGenericEvent();
1195 | 
1196 |     mswServer.use(
1197 |       http.get(
1198 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/",
1199 |         () => HttpResponse.json(regressedIssueFixture),
1200 |         { once: true },
1201 |       ),
1202 |       http.get(
1203 |         "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/events/latest/",
1204 |         () => HttpResponse.json(regressedEventFixture),
1205 |         { once: true },
1206 |       ),
1207 |     );
1208 | 
1209 |     const result = await getIssueDetails.handler(
1210 |       {
1211 |         organizationSlug: "sentry-mcp-evals",
1212 |         issueId: "MCP-SERVER-EQE",
1213 |         eventId: undefined,
1214 |         issueUrl: undefined,
1215 |         regionUrl: null,
1216 |       },
1217 |       baseContext,
1218 |     );
1219 | 
1220 |     expect(result).toMatchInlineSnapshot(`
1221 |       "# Issue MCP-SERVER-EQE in **sentry-mcp-evals**
1222 | 
1223 |       **Description**: Endpoint Regression
1224 |       **Query Pattern**: \`Increased from 909.77ms to 1711.36ms (P95)\`
1225 |       **First Seen**: 2025-09-24T03:02:10.919Z
1226 |       **Last Seen**: 2025-11-18T06:01:20.000Z
1227 |       **Occurrences**: 3
1228 |       **Users Impacted**: 0
1229 |       **Status**: unresolved
1230 |       **Substatus**: regressed
1231 |       **Issue Type**: performance_p95_endpoint_regression
1232 |       **Issue Category**: metric
1233 |       **Platform**: python
1234 |       **Project**: mcp-server
1235 |       **URL**: https://sentry-mcp-evals.sentry.io/issues/MCP-SERVER-EQE
1236 | 
1237 |       ## Event Details
1238 | 
1239 |       **Event ID**: a6251c18f0194b8e8158518b8ee99545
1240 |       **Type**: generic
1241 |       **Occurred At**: 2025-11-18T06:01:20.000Z
1242 | 
1243 |       ### Performance Regression Details
1244 | 
1245 |       **Regression:**
1246 |       POST /oauth/token duration increased from 909.77ms to 1711.36ms (P95)
1247 | 
1248 |       **Transaction:**
1249 |       POST /oauth/token
1250 | 
1251 |       ### Tags
1252 | 
1253 |       **level**: info
1254 |       **transaction**: POST /oauth/token
1255 | 
1256 |       # Using this information
1257 | 
1258 |       - You can reference the IssueID in commit messages (e.g. \`Fixes MCP-SERVER-EQE\`) to automatically close the issue when the commit is merged.
1259 |       - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code.
1260 |       "
1261 |     `);
1262 |   });
1263 | 
1264 |   it("handles unsupported event types gracefully", async () => {
1265 |     // This tests that unknown event types don't crash the tool
1266 |     // Instead, we should show the issue info and a warning about the unsupported event type
1267 | 
1268 |     const unsupportedIssueFixture = createUnsupportedIssue();
1269 | 
1270 |     // Event with a type that doesn't exist yet (would never be returned by Sentry API)
1271 |     // Use the unknown event fixture factory (baseline already has future_ai_agent_trace type)
1272 |     const unsupportedEventFixture = createUnknownEvent();
1273 | 
1274 |     mswServer.use(
1275 |       // More specific pattern for events (must come first to match before the issue pattern)
1276 |       http.get(
1277 |         "*/api/0/organizations/*/issues/FUTURE-TYPE-001/events/latest/",
1278 |         () => {
1279 |           return HttpResponse.json(unsupportedEventFixture);
1280 |         },
1281 |       ),
1282 |       http.get("*/api/0/organizations/*/issues/FUTURE-TYPE-001", () => {
1283 |         return HttpResponse.json(unsupportedIssueFixture);
1284 |       }),
1285 |     );
1286 | 
1287 |     const result = await getIssueDetails.handler(
1288 |       {
1289 |         organizationSlug: "sentry-mcp-evals",
1290 |         issueId: "FUTURE-TYPE-001",
1291 |         issueUrl: undefined,
1292 |         eventId: undefined,
1293 |         regionUrl: null,
1294 |       },
1295 |       baseContext,
1296 |     );
1297 | 
1298 |     if (typeof result !== "string") {
1299 |       throw new Error("Expected string result");
1300 |     }
1301 | 
1302 |     // Extract the Sentry Event ID from the result (it varies per run)
1303 |     const sentryEventIdMatch = result.match(
1304 |       /Sentry Event ID \*\*([a-f0-9]{32})\*\*/,
1305 |     );
1306 |     const sentryEventId = sentryEventIdMatch
1307 |       ? sentryEventIdMatch[1]
1308 |       : "SENTRY_EVENT_ID";
1309 | 
1310 |     // Replace the dynamic Sentry Event ID with a placeholder for snapshot testing
1311 |     const normalizedResult = result.replace(
1312 |       /Sentry Event ID \*\*[a-f0-9]{32}\*\*/,
1313 |       "Sentry Event ID **<SENTRY_EVENT_ID>**",
1314 |     );
1315 | 
1316 |     expect(normalizedResult).toMatchInlineSnapshot(`
1317 |       "# Issue FUTURE-TYPE-001 in **sentry-mcp-evals**
1318 | 
1319 |       **Description**: Future Event Type Issue
1320 |       **Culprit**: some.module
1321 |       **First Seen**: 2025-01-01T00:00:00.000Z
1322 |       **Last Seen**: 2025-01-01T01:00:00.000Z
1323 |       **Occurrences**: 1
1324 |       **Users Impacted**: 1
1325 |       **Status**: unresolved
1326 |       **Issue Type**: error
1327 |       **Issue Category**: error
1328 |       **Platform**: python
1329 |       **Project**: mcp-server
1330 |       **URL**: https://sentry-mcp-evals.sentry.io/issues/FUTURE-TYPE-001
1331 | 
1332 |       ## Event Details
1333 | 
1334 |       ⚠️  **Warning**: Unsupported event type "future_ai_agent_trace"
1335 | 
1336 |       This event type is not yet fully supported by the MCP server. Only basic issue information is shown above.
1337 | 
1338 |       **Please report this**: Open a GitHub issue at https://github.com/getsentry/sentry-mcp/issues/new and include Event ID **ffffffffffffffffffffffffffffffff** and Sentry Event ID **<SENTRY_EVENT_ID>** to help us add support for this event type.
1339 |       "
1340 |     `);
1341 | 
1342 |     // Verify we actually got a Sentry Event ID
1343 |     expect(sentryEventId).toMatch(/^[a-f0-9]{32}$/);
1344 |   });
1345 | });
1346 | 
```
Page 17/20FirstPrevNextLast