#
tokens: 48926/50000 55/493 files (page 3/20)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 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
│   │   │   │   │   ├── docs
│   │   │   │   │   │   └── toc.tsx
│   │   │   │   │   ├── 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
│   │   │   │   │   │   │   ├── gemini.tsx
│   │   │   │   │   │   │   └── sentry.tsx
│   │   │   │   │   │   ├── interactive-markdown.tsx
│   │   │   │   │   │   ├── json-schema-params.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

--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Test
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [main]
 6 |   pull_request:
 7 | 
 8 | jobs:
 9 |   test:
10 |     runs-on: ubuntu-latest
11 |     steps:
12 |       - uses: actions/checkout@v4
13 | 
14 |       - name: Setup Node.js
15 |         uses: actions/setup-node@v4
16 |         with:
17 |           node-version: "20"
18 | 
19 |       # pnpm/action-setup@v4
20 |       - uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
21 |         name: Install pnpm
22 |         with:
23 |           run_install: false
24 | 
25 |       - name: Get pnpm store directory
26 |         shell: bash
27 |         run: |
28 |           echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
29 | 
30 |       - uses: actions/cache@v4
31 |         name: Setup pnpm cache
32 |         with:
33 |           path: ${{ env.STORE_PATH }}
34 |           key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
35 |           restore-keys: |
36 |             ${{ runner.os }}-pnpm-store-
37 | 
38 |       - name: Install dependencies
39 |         run: pnpm install --frozen-lockfile
40 | 
41 |       - name: Run build
42 |         run: pnpm build
43 | 
44 |       - name: Run type checking
45 |         run: pnpm -w run tsc
46 | 
47 |       - name: Run linter
48 |         run: pnpm lint
49 | 
50 |       - name: Run tests
51 |         run: pnpm test:ci
52 | 
53 |       - name: Upload coverage reports to Codecov
54 |         uses: codecov/codecov-action@v4
55 |         env:
56 |           CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
57 |         with:
58 |           flags: unittests
59 |           name: codecov-unittests
60 |           fail_ci_if_error: false
61 | 
62 |       - name: Upload results to Codecov
63 |         if: ${{ !cancelled() }}
64 |         uses: codecov/test-results-action@v1
65 |         with:
66 |           token: ${{ secrets.CODECOV_TOKEN }}
67 | 
68 |       - name: Publish Test Report
69 |         uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857
70 |         if: ${{ !cancelled() }}
71 |         with:
72 |           report_paths: "**/*.junit.xml"
73 |           comment: false
74 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-meta.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "logs": 0,
 3 |   "errors": 0,
 4 |   "performance_issues": 0,
 5 |   "span_count": 112.0,
 6 |   "transaction_child_count_map": [
 7 |     {
 8 |       "transaction.event_id": "0daf40dc453a429c8c57e4c215c4e82c",
 9 |       "count()": 10.0
10 |     },
11 |     {
12 |       "transaction.event_id": "845c0f1eb7544741a9d08cf28e144f42",
13 |       "count()": 1.0
14 |     },
15 |     {
16 |       "transaction.event_id": "b49a5b53cba046a2bab9323d8f00de96",
17 |       "count()": 7.0
18 |     },
19 |     {
20 |       "transaction.event_id": "c0db3d88529744d393f091b692c024ca",
21 |       "count()": 11.0
22 |     },
23 |     {
24 |       "transaction.event_id": "ee6e7f39107847f980e06119bf116d38",
25 |       "count()": 7.0
26 |     },
27 |     {
28 |       "transaction.event_id": "efa09ae9091e400e8865fe48aaae80d6",
29 |       "count()": 12.0
30 |     },
31 |     {
32 |       "transaction.event_id": "f398bbc635c64e2091d94679dade2957",
33 |       "count()": 3.0
34 |     },
35 |     {
36 |       "transaction.event_id": "f531c48c3eaa43be9587dd880b8ec4bb",
37 |       "count()": 6.0
38 |     },
39 |     {
40 |       "transaction.event_id": "f779775e1c6a4f09b62d68818a25d7b5",
41 |       "count()": 55.0
42 |     }
43 |   ],
44 |   "span_count_map": {
45 |     "cache.get": 41.0,
46 |     "middleware.django": 24.0,
47 |     "db": 11.0,
48 |     "function": 6.0,
49 |     "db.redis": 4.0,
50 |     "feature.flagpole.batch_has": 4.0,
51 |     "processor": 2.0,
52 |     "execute": 2.0,
53 |     "fetch_organization_projects": 2.0,
54 |     "other": 1.0,
55 |     "db.clickhouse": 1.0,
56 |     "validator": 1.0,
57 |     "serialize": 1.0,
58 |     "http.client": 1.0,
59 |     "base.paginate.on_results": 1.0,
60 |     "ratelimit.__call__": 1.0,
61 |     "base.dispatch.request": 1.0,
62 |     "discover.endpoint": 1.0,
63 |     "serialize.iterate": 1.0,
64 |     "base.dispatch.setup": 1.0,
65 |     "serialize.get_attrs": 1.0,
66 |     "build_plan.storage_query_plan_builder": 1.0,
67 |     "serialize.get_attrs.project.options": 1.0,
68 |     "check_object_permissions_on_organization": 1.0,
69 |     "allocation_policy.get_quota_allowance": 1.0
70 |   }
71 | }
72 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/mcp.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "mcp",
 3 |   "description": "Model Context Protocol attributes for MCP tool calls and sessions",
 4 |   "attributes": {
 5 |     "mcp.tool.name": {
 6 |       "description": "Tool name (e.g., search_issues, search_events)",
 7 |       "type": "string",
 8 |       "examples": [
 9 |         "search_issues",
10 |         "search_events",
11 |         "get_issue_details",
12 |         "update_issue"
13 |       ]
14 |     },
15 |     "mcp.session.id": {
16 |       "description": "MCP session identifier",
17 |       "type": "string"
18 |     },
19 |     "mcp.transport": {
20 |       "description": "MCP transport protocol used",
21 |       "type": "string",
22 |       "examples": ["stdio", "http", "websocket"]
23 |     },
24 |     "mcp.request.id": {
25 |       "description": "MCP request identifier",
26 |       "type": "string"
27 |     },
28 |     "mcp.response.status": {
29 |       "description": "MCP response status",
30 |       "type": "string",
31 |       "examples": ["success", "error"]
32 |     },
33 |     "mcp.client.name": {
34 |       "description": "Name of the MCP client application",
35 |       "type": "string",
36 |       "examples": [
37 |         "Cursor",
38 |         "Claude Code",
39 |         "VSCode MCP Extension",
40 |         "sentry-mcp-stdio"
41 |       ]
42 |     },
43 |     "mcp.client.version": {
44 |       "description": "Version of the MCP client application",
45 |       "type": "string",
46 |       "examples": ["0.16.0", "1.0.0", "2.3.1"]
47 |     },
48 |     "mcp.server.name": {
49 |       "description": "Name of the MCP server application",
50 |       "type": "string",
51 |       "examples": ["Sentry MCP Server", "GitHub MCP Server", "Slack MCP Server"]
52 |     },
53 |     "mcp.server.version": {
54 |       "description": "Version of the MCP server application",
55 |       "type": "string",
56 |       "examples": ["0.1.0", "1.2.3", "2.0.0"]
57 |     },
58 |     "mcp.protocol.version": {
59 |       "description": "MCP protocol version being used",
60 |       "type": "string",
61 |       "examples": ["2024-11-05", "1.0", "2.0"]
62 |     }
63 |   },
64 |   "custom": true
65 | }
66 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/cloudevents.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "cloudevents",
 3 |   "description": "This document defines attributes for CloudEvents.\n",
 4 |   "attributes": {
 5 |     "cloudevents.event_id": {
 6 |       "description": "The [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#id) uniquely identifies the event.\n",
 7 |       "type": "string",
 8 |       "stability": "development",
 9 |       "examples": ["123e4567-e89b-12d3-a456-426614174000", "0001"]
10 |     },
11 |     "cloudevents.event_source": {
12 |       "description": "The [source](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#source-1) identifies the context in which an event happened.\n",
13 |       "type": "string",
14 |       "stability": "development",
15 |       "examples": [
16 |         "https://github.com/cloudevents",
17 |         "/cloudevents/spec/pull/123",
18 |         "my-service"
19 |       ]
20 |     },
21 |     "cloudevents.event_spec_version": {
22 |       "description": "The [version of the CloudEvents specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#specversion) which the event uses.\n",
23 |       "type": "string",
24 |       "stability": "development",
25 |       "examples": ["1.0"]
26 |     },
27 |     "cloudevents.event_type": {
28 |       "description": "The [event_type](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#type) contains a value describing the type of event related to the originating occurrence.\n",
29 |       "type": "string",
30 |       "stability": "development",
31 |       "examples": [
32 |         "com.github.pull_request.opened",
33 |         "com.example.object.deleted.v2"
34 |       ]
35 |     },
36 |     "cloudevents.event_subject": {
37 |       "description": "The [subject](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#subject) of the event in the context of the event producer (identified by source).\n",
38 |       "type": "string",
39 |       "stability": "development",
40 |       "examples": ["mynewfile.jpg"]
41 |     }
42 |   }
43 | }
44 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/aspnetcore.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "aspnetcore",
 3 |   "description": "ASP.NET Core attributes",
 4 |   "attributes": {
 5 |     "aspnetcore.rate_limiting.policy": {
 6 |       "description": "Rate limiting policy name.",
 7 |       "type": "string",
 8 |       "stability": "stable",
 9 |       "examples": ["fixed", "sliding", "token"]
10 |     },
11 |     "aspnetcore.rate_limiting.result": {
12 |       "description": "Rate-limiting result, shows whether the lease was acquired or contains a rejection reason",
13 |       "type": "string",
14 |       "stability": "stable",
15 |       "examples": [
16 |         "acquired",
17 |         "endpoint_limiter",
18 |         "global_limiter",
19 |         "request_canceled"
20 |       ]
21 |     },
22 |     "aspnetcore.routing.is_fallback": {
23 |       "description": "A value that indicates whether the matched route is a fallback route.",
24 |       "type": "boolean",
25 |       "stability": "stable",
26 |       "examples": ["true"]
27 |     },
28 |     "aspnetcore.diagnostics.handler.type": {
29 |       "description": "Full type name of the [`IExceptionHandler`](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.diagnostics.iexceptionhandler) implementation that handled the exception.",
30 |       "type": "string",
31 |       "stability": "stable",
32 |       "examples": ["Contoso.MyHandler"]
33 |     },
34 |     "aspnetcore.request.is_unhandled": {
35 |       "description": "Flag indicating if request was handled by the application pipeline.",
36 |       "type": "boolean",
37 |       "stability": "stable",
38 |       "examples": ["true"]
39 |     },
40 |     "aspnetcore.routing.match_status": {
41 |       "description": "Match result - success or failure",
42 |       "type": "string",
43 |       "stability": "stable",
44 |       "examples": ["success", "failure"]
45 |     },
46 |     "aspnetcore.diagnostics.exception.result": {
47 |       "description": "ASP.NET Core exception middleware handling result",
48 |       "type": "string",
49 |       "stability": "stable",
50 |       "examples": ["handled", "unhandled", "skipped", "aborted"]
51 |     }
52 |   }
53 | }
54 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/whoami.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { defineTool } from "../internal/tool-helpers/define";
 2 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 3 | import type { ServerContext } from "../types";
 4 | import { ALL_SKILLS } from "../skills";
 5 | 
 6 | export default defineTool({
 7 |   name: "whoami",
 8 |   description: [
 9 |     "Identify the authenticated user in Sentry.",
10 |     "",
11 |     "Use this tool when you need to:",
12 |     "- Get the user's name and email address.",
13 |   ].join("\n"),
14 |   inputSchema: {},
15 |   skills: ALL_SKILLS, // Foundational tool - available to all skills
16 |   requiredScopes: [], // No specific scopes required - uses authentication token
17 |   annotations: {
18 |     readOnlyHint: true,
19 |     openWorldHint: true,
20 |   },
21 |   async handler(params, context: ServerContext) {
22 |     // User data endpoints (like /auth/) should never use regionUrl
23 |     // as they must always query the main API server, not region-specific servers
24 |     const apiService = apiServiceFromContext(context);
25 |     // API client will throw ApiClientError/ApiServerError which the MCP server wrapper handles
26 |     const user = await apiService.getAuthenticatedUser();
27 | 
28 |     let output = `You are authenticated as ${user.name} (${user.email}).\n\nYour Sentry User ID is ${user.id}.`;
29 | 
30 |     // Add constraints information
31 |     const constraints = context.constraints;
32 |     if (
33 |       constraints.organizationSlug ||
34 |       constraints.projectSlug ||
35 |       constraints.regionUrl
36 |     ) {
37 |       output += "\n\n## Session Constraints\n\n";
38 | 
39 |       if (constraints.organizationSlug) {
40 |         output += `- **Organization**: ${constraints.organizationSlug}\n`;
41 |       }
42 |       if (constraints.projectSlug) {
43 |         output += `- **Project**: ${constraints.projectSlug}\n`;
44 |       }
45 |       if (constraints.regionUrl) {
46 |         output += `- **Region URL**: ${constraints.regionUrl}\n`;
47 |       }
48 | 
49 |       output += "\nThese constraints limit the scope of this MCP session.";
50 |     }
51 | 
52 |     return output;
53 |   },
54 | });
55 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/seer-noisefilter.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | export default function SeerNoiseFilter() {
 2 |   return (
 3 |     <svg
 4 |       xmlns="http://www.w3.org/2000/svg"
 5 |       width="0"
 6 |       height="0"
 7 |       aria-hidden="true"
 8 |     >
 9 |       <defs>
10 |         <filter
11 |           id="nnnoise-darken-fine"
12 |           filterUnits="objectBoundingBox"
13 |           primitiveUnits="objectBoundingBox"
14 |           x="-50%"
15 |           y="-50%"
16 |           width="200%"
17 |           height="200%"
18 |           colorInterpolationFilters="linearRGB"
19 |         >
20 |           {/* <!-- 1) Fine monochrome noise --> */}
21 |           <feTurbulence
22 |             type="fractalNoise"
23 |             baseFrequency="0.69"
24 |             numOctaves="1"
25 |             seed="9"
26 |             result="noise"
27 |           />
28 |           <feColorMatrix in="noise" type="saturate" values="0" result="g" />
29 | 
30 |           {/* <!-- 2) Shape the noise -> mostly near 0 with occasional spikes (speckles) --> */}
31 |           {/* <!--    gamma < 1 = more speckles; > 1 = fewer --> */}
32 |           <feComponentTransfer in="g" result="mask">
33 |             <feFuncR type="gamma" amplitude="1" exponent="0.65" offset="0" />
34 |             <feFuncG type="gamma" amplitude="1" exponent="0.65" offset="0" />
35 |             <feFuncB type="gamma" amplitude="1" exponent="0.65" offset="0" />
36 |           </feComponentTransfer>
37 | 
38 |           {/* <!-- 3) Keep noise only where the element is opaque (transparent areas stay clean) --> */}
39 |           <feComposite
40 |             in="mask"
41 |             in2="SourceAlpha"
42 |             operator="in"
43 |             result="maskedNoise"
44 |           />
45 | 
46 |           {/* <!-- 4) Darken-only: out = SourceGraphic * (1 - strength * maskedNoise) --> */}
47 |           {/* <!--    arithmetic: k1=-strength, k2=1, k3=0, k4=0 --> */}
48 |           <feComposite
49 |             in="SourceGraphic"
50 |             in2="maskedNoise"
51 |             operator="arithmetic"
52 |             k1="-1"
53 |             k2="1"
54 |             k3="0"
55 |             k4="0"
56 |           />
57 |         </filter>
58 |       </defs>
59 |     </svg>
60 |   );
61 | }
62 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/update-issue.eval.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describeEval } from "vitest-evals";
 2 | import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
 3 | 
 4 | describeEval("update-issue", {
 5 |   data: async () => {
 6 |     return [
 7 |       // Core use case: Resolve an issue
 8 |       {
 9 |         input: `Resolve the issue ${FIXTURES.issueId} in organization ${FIXTURES.organizationSlug}. Output only the new status as a single word.`,
10 |         expectedTools: [
11 |           {
12 |             name: "find_organizations",
13 |             arguments: {},
14 |           },
15 |           {
16 |             name: "update_issue",
17 |             arguments: {
18 |               organizationSlug: FIXTURES.organizationSlug,
19 |               issueId: FIXTURES.issueId,
20 |               status: "resolved",
21 |               regionUrl: "https://us.sentry.io",
22 |             },
23 |           },
24 |         ],
25 |       },
26 |       // Core use case: Assign an issue
27 |       {
28 |         input: `Assign the issue ${FIXTURES.issueId} in organization ${FIXTURES.organizationSlug} to 'john.doe'. Output only the assigned username.`,
29 |         expectedTools: [
30 |           {
31 |             name: "find_organizations",
32 |             arguments: {},
33 |           },
34 |           {
35 |             name: "update_issue",
36 |             arguments: {
37 |               organizationSlug: FIXTURES.organizationSlug,
38 |               issueId: FIXTURES.issueId,
39 |               assignedTo: "john.doe",
40 |               regionUrl: "https://us.sentry.io",
41 |             },
42 |           },
43 |         ],
44 |       },
45 |       // Core use case: Using issue URL (alternative input method)
46 |       {
47 |         input: `Resolve the issue at ${FIXTURES.issueUrl}. Output only the new status as a single word.`,
48 |         expectedTools: [
49 |           {
50 |             name: "update_issue",
51 |             arguments: {
52 |               issueUrl: FIXTURES.issueUrl,
53 |               status: "resolved",
54 |             },
55 |           },
56 |         ],
57 |       },
58 |     ];
59 |   },
60 |   task: NoOpTaskRunner(),
61 |   scorers: [ToolPredictionScorer()],
62 |   threshold: 0.6,
63 |   timeout: 30000,
64 | });
65 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/index.html:
--------------------------------------------------------------------------------

```html
 1 | <!doctype html>
 2 | <html lang="en">
 3 | 
 4 | <head>
 5 |   <meta charset="UTF-8" />
 6 |   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 7 |   <title>Sentry MCP</title>
 8 |   <link href="./src/client/index.css" rel="stylesheet" />
 9 |   <link rel="icon" href="/favicon.ico" />
10 |   <!-- Primary Meta Tags -->
11 |   <meta name="title" content="Sentry MCP" />
12 |   <meta name="description" content="A Model Context Protocol implementation for interacting with Sentry." />
13 | 
14 |   <!-- Open Graph / Facebook -->
15 |   <meta property="og:type" content="website" />
16 |   <meta property="og:url" content="https://mcp.sentry.dev/" />
17 |   <meta property="og:title" content="A model context protocol implementation for interacting with Sentry." />
18 |   <meta property="og:description"
19 |     content="Simply put, its a way to plug Sentry's API into an LLM, letting you ask questions about your data in context of the LLM itself. This lets you take an agent that you already use, like Cursor, and pull in additional information from Sentry to help with tasks like debugging, code generation, and more." />
20 |   <meta property="og:image" content="https://mcp.sentry.dev/flow.jpg" />
21 | 
22 |   <!-- Twitter -->
23 |   <meta property="twitter:card" content="summary_large_image" />
24 |   <meta property="twitter:url" content="https://mcp.sentry.dev/" />
25 |   <meta property="twitter:title" content="A model context protocol implementation for interacting with Sentry." />
26 |   <meta property="twitter:description"
27 |     content="Simply put, its a way to plug Sentry's API into an LLM, letting you ask questions about your data in context of the LLM itself. This lets you take an agent that you already use, like Cursor, and pull in additional information from Sentry to help with tasks like debugging, code generation, and more." />
28 |   <meta property="twitter:image" content="https://mcp.sentry.dev/flow.jpg" />
29 | </head>
30 | 
31 | <body>
32 |   <div id="root"></div>
33 |   <script type="module" src="./src/client/main.tsx"></script>
34 | </body>
35 | 
36 | </html>
37 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { UserInputError } from "../../errors";
 2 | import { SENTRY_ALLOWED_REGION_DOMAINS } from "../../constants";
 3 | 
 4 | /**
 5 |  * Validates that a regionUrl is valid.
 6 |  * Prevents SSRF attacks by only allowing the base host itself or domains from an allowlist.
 7 |  *
 8 |  * Rules:
 9 |  * 1. By default, only the base host itself is allowed as regionUrl
10 |  * 2. For other domains, they must be in SENTRY_ALLOWED_REGION_DOMAINS
11 |  * 3. Protocol MUST be HTTPS for security
12 |  *
13 |  * @param regionUrl - The region URL to validate
14 |  * @param baseHost - The base host to validate against
15 |  * @returns The validated host if valid
16 |  * @throws {UserInputError} If the regionUrl is invalid or not allowed
17 |  */
18 | export function validateRegionUrl(regionUrl: string, baseHost: string): string {
19 |   let parsedUrl: URL;
20 |   try {
21 |     parsedUrl = new URL(regionUrl);
22 |   } catch {
23 |     throw new UserInputError(
24 |       `Invalid regionUrl provided: ${regionUrl}. Must be a valid URL.`,
25 |     );
26 |   }
27 | 
28 |   // Validate protocol - MUST be HTTPS for security
29 |   if (parsedUrl.protocol !== "https:") {
30 |     throw new UserInputError(
31 |       `Invalid regionUrl provided: ${regionUrl}. Must use HTTPS protocol for security.`,
32 |     );
33 |   }
34 | 
35 |   // Validate that the host is not just the protocol name
36 |   if (parsedUrl.host === "https" || parsedUrl.host === "http") {
37 |     throw new UserInputError(
38 |       `Invalid regionUrl provided: ${regionUrl}. The host cannot be just a protocol name.`,
39 |     );
40 |   }
41 | 
42 |   const regionHost = parsedUrl.host.toLowerCase();
43 |   const baseLower = baseHost.toLowerCase();
44 | 
45 |   // First, allow if it's the same as the base host
46 |   if (regionHost === baseLower) {
47 |     return regionHost;
48 |   }
49 | 
50 |   // Otherwise, check against the allowlist
51 |   if (!SENTRY_ALLOWED_REGION_DOMAINS.has(regionHost)) {
52 |     throw new UserInputError(
53 |       `Invalid regionUrl: ${regionUrl}. The domain '${regionHost}' is not allowed. Allowed domains are: ${Array.from(SENTRY_ALLOWED_REGION_DOMAINS).join(", ")}`,
54 |     );
55 |   }
56 | 
57 |   return regionHost;
58 | }
59 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-mcp-metadata.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Custom hook to fetch and manage MCP metadata
 3 |  *
 4 |  * Provides immediate access to prompts and tools without waiting for chat stream
 5 |  */
 6 | import { useState, useEffect, useCallback } from "react";
 7 | 
 8 | export interface McpMetadata {
 9 |   type: "mcp-metadata";
10 |   prompts: Array<{
11 |     name: string;
12 |     description: string;
13 |     parameters: Record<
14 |       string,
15 |       {
16 |         type: string;
17 |         required: boolean;
18 |         description?: string;
19 |       }
20 |     >;
21 |   }>;
22 |   tools: string[];
23 |   resources?: Array<{
24 |     name: string;
25 |     description: string;
26 |   }>;
27 |   timestamp: string;
28 | }
29 | 
30 | interface UseMcpMetadataResult {
31 |   metadata: McpMetadata | null;
32 |   isLoading: boolean;
33 |   error: string | null;
34 |   refetch: () => Promise<void>;
35 | }
36 | 
37 | export function useMcpMetadata(enabled = true): UseMcpMetadataResult {
38 |   const [metadata, setMetadata] = useState<McpMetadata | null>(null);
39 |   const [isLoading, setIsLoading] = useState(false);
40 |   const [error, setError] = useState<string | null>(null);
41 | 
42 |   const fetchMetadata = useCallback(async () => {
43 |     if (!enabled) {
44 |       return;
45 |     }
46 | 
47 |     setIsLoading(true);
48 |     setError(null);
49 | 
50 |     try {
51 |       const response = await fetch("/api/metadata", {
52 |         credentials: "include", // Include cookies
53 |       });
54 | 
55 |       if (!response.ok) {
56 |         const errorData = await response.json().catch(() => ({}));
57 |         throw new Error(errorData.error || `HTTP ${response.status}`);
58 |       }
59 | 
60 |       const data = await response.json();
61 |       setMetadata(data);
62 |     } catch (err) {
63 |       const errorMessage =
64 |         err instanceof Error ? err.message : "Failed to fetch metadata";
65 |       setError(errorMessage);
66 |       console.error("Failed to fetch MCP metadata:", err);
67 |     } finally {
68 |       setIsLoading(false);
69 |     }
70 |   }, [enabled]);
71 | 
72 |   // Fetch metadata when auth token changes or component mounts
73 |   useEffect(() => {
74 |     fetchMetadata();
75 |   }, [fetchMetadata]);
76 | 
77 |   return {
78 |     metadata,
79 |     isLoading,
80 |     error,
81 |     refetch: fetchMetadata,
82 |   };
83 | }
84 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/whoami.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import whoami from "./whoami.js";
 3 | import {
 4 |   createTestContext,
 5 |   createTestContextWithConstraints,
 6 | } from "../test-utils/context.js";
 7 | 
 8 | describe("whoami", () => {
 9 |   it("serializes without constraints", async () => {
10 |     const result = await whoami.handler(
11 |       {},
12 |       createTestContext({
13 |         constraints: {},
14 |         accessToken: "access-token",
15 |         userId: "123456",
16 |       }),
17 |     );
18 |     expect(result).toMatchInlineSnapshot(
19 |       `
20 |       "You are authenticated as Test User ([email protected]).
21 | 
22 |       Your Sentry User ID is 123456."
23 |     `,
24 |     );
25 |   });
26 | 
27 |   it("serializes with constraints", async () => {
28 |     const result = await whoami.handler(
29 |       {},
30 |       createTestContextWithConstraints(
31 |         {
32 |           organizationSlug: "sentry",
33 |           projectSlug: "mcp-server",
34 |           regionUrl: "https://us.sentry.io",
35 |         },
36 |         {
37 |           accessToken: "access-token",
38 |           userId: "123456",
39 |         },
40 |       ),
41 |     );
42 |     expect(result).toMatchInlineSnapshot(
43 |       `
44 |       "You are authenticated as Test User ([email protected]).
45 | 
46 |       Your Sentry User ID is 123456.
47 | 
48 |       ## Session Constraints
49 | 
50 |       - **Organization**: sentry
51 |       - **Project**: mcp-server
52 |       - **Region URL**: https://us.sentry.io
53 | 
54 |       These constraints limit the scope of this MCP session."
55 |     `,
56 |     );
57 |   });
58 | 
59 |   it("serializes with partial constraints", async () => {
60 |     const result = await whoami.handler(
61 |       {},
62 |       createTestContextWithConstraints(
63 |         {
64 |           organizationSlug: "sentry",
65 |         },
66 |         {
67 |           accessToken: "access-token",
68 |           userId: "123456",
69 |         },
70 |       ),
71 |     );
72 |     expect(result).toMatchInlineSnapshot(
73 |       `
74 |       "You are authenticated as Test User ([email protected]).
75 | 
76 |       Your Sentry User ID is 123456.
77 | 
78 |       ## Session Constraints
79 | 
80 |       - **Organization**: sentry
81 | 
82 |       These constraints limit the scope of this MCP session."
83 |     `,
84 |     );
85 |   });
86 | });
87 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/seer.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { useId } from "react";
 2 | import RootCause from "./RootCause";
 3 | import SeerNoiseFilter from "./seer-noisefilter";
 4 | import SeerClipMask from "./seer-clipmask";
 5 | 
 6 | export default function Seer({ step }: { step: number }) {
 7 |   const id = useId().replace(/:/g, "");
 8 |   return (
 9 |     <div
10 |       className={`${
11 |         step === 2
12 |           ? "scale-100 opacity-100 duration-300"
13 |           : "scale-90 opacity-0 pointer-events-none"
14 |       } absolute overflow-hidden top-0 right-0 z-10 h-full w-full bg-600 flex flex-col justify-center p-4 pb-0 ease-out`}
15 |     >
16 |       <div
17 |         className={`inset-0 absolute bg-background rounded-tl-xl rounded-br-3xl border-t border-l border-white/20 ${
18 |           step === 2
19 |             ? "duration-300 opacity-80 translate-x-0"
20 |             : "duration-0 opacity-0 motion-safe:translate-x-1/2"
21 |         }`}
22 |       />
23 |       <SeerNoiseFilter />
24 |       <SeerClipMask id={id} />
25 |       {/* ⚠️ Seer */}
26 |       <div
27 |         className="relative z-10 mx-auto aspect-square w-36 overflow-hidden bg-gradient-to-b from-pink-600 to-pink-400 -translate-y-64"
28 |         style={{
29 |           clipPath: `url(#${id})`,
30 |         }}
31 |       >
32 |         <div className="bg-pink-300 [mask-image:linear-gradient(to_top,red,transparent)] absolute inset-0 [filter:url(#nnnoise-darken-fine)]" />
33 |         {/* eye mask */}
34 |         <div className="-translate-x-1/2 absolute left-1/2 mt-16 w-full shadow-2xl shadow-amber-500 [mask-image:radial-gradient(ellipse_100%_200%_at_top,red_50%,transparent_50%)]">
35 |           <div className="bg-amber-100 [mask-image:radial-gradient(ellipse_at_bottom,red_50%,transparent_50%)]">
36 |             {/* 👁️ Eye of the Seer */}
37 |             <div
38 |               className={`mx-auto h-8 w-8 translate-y-1/2 rounded-full bg-blue-700 ${
39 |                 step === 2
40 |                   ? "translate-x-6 delay-1200 duration-1500"
41 |                   : "-translate-x-6"
42 |               }`}
43 |             />
44 |           </div>
45 |         </div>
46 |       </div>
47 | 
48 |       <RootCause step={step} />
49 |     </div>
50 |   );
51 | }
52 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/code-snippet.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { Copy, Check } from "lucide-react";
 2 | import { useState, useEffect, useRef } from "react";
 3 | import { Button } from "./button";
 4 | 
 5 | export default function CodeSnippet({
 6 |   snippet,
 7 |   noMargin,
 8 | }: {
 9 |   snippet: string;
10 |   noMargin?: boolean;
11 | }) {
12 |   const [copied, setCopied] = useState(false);
13 |   const timeoutRef = useRef<number | null>(null);
14 | 
15 |   // Clean up timeout on unmount
16 |   useEffect(() => {
17 |     return () => {
18 |       if (timeoutRef.current) {
19 |         clearTimeout(timeoutRef.current);
20 |       }
21 |     };
22 |   }, []);
23 | 
24 |   const handleCopy = async () => {
25 |     try {
26 |       await navigator.clipboard.writeText(snippet);
27 |       // Only show success if the operation actually succeeded
28 |       setCopied(true);
29 | 
30 |       // Clear any existing timeout before setting a new one
31 |       if (timeoutRef.current) {
32 |         clearTimeout(timeoutRef.current);
33 |       }
34 | 
35 |       timeoutRef.current = setTimeout(() => {
36 |         setCopied(false);
37 |         timeoutRef.current = null;
38 |       }, 2000);
39 |     } catch (error) {
40 |       // Handle clipboard write failure silently or you could show an error state
41 |       console.error("Failed to copy to clipboard:", error);
42 |     }
43 |   };
44 | 
45 |   return (
46 |     <div className={`relative text-white max-w-full ${!noMargin && "mb-6"}`}>
47 |       <div className="absolute top-2.5 right-2.5 flex items-center justify-end">
48 |         <Button
49 |           variant="ghost"
50 |           size="icon"
51 |           className="h-8 w-8 text-neutral-500 cursor-pointer duration-300 hover:bg-violet-300/25 rounded-md"
52 |           onClick={handleCopy}
53 |         >
54 |           {copied ? (
55 |             <Check className="h-4 w-4 text-green-500" />
56 |           ) : (
57 |             <Copy className="h-4 w-4 text-violet-300/50" />
58 |           )}
59 |           <span className="sr-only">Copy Snippet</span>
60 |         </Button>
61 |       </div>
62 |       <pre
63 |         className="p-4 border border-violet-300/25 pr-12 overflow-x-auto text-slate-200 text-sm bg-background rounded-xl"
64 |         style={{ margin: 0 }}
65 |       >
66 |         {snippet}
67 |       </pre>
68 |     </div>
69 |   );
70 | }
71 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/update-project.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import updateProject from "./update-project.js";
 3 | 
 4 | describe("update_project", () => {
 5 |   it("updates name and platform", async () => {
 6 |     const result = await updateProject.handler(
 7 |       {
 8 |         organizationSlug: "sentry-mcp-evals",
 9 |         projectSlug: "cloudflare-mcp",
10 |         name: "New Project Name",
11 |         slug: null,
12 |         platform: "python",
13 |         teamSlug: null,
14 |         regionUrl: null,
15 |       },
16 |       {
17 |         constraints: {
18 |           organizationSlug: null,
19 |         },
20 |         accessToken: "access-token",
21 |         userId: "1",
22 |       },
23 |     );
24 |     expect(result).toMatchInlineSnapshot(`
25 |       "# Updated Project in **sentry-mcp-evals**
26 | 
27 |       **ID**: 4509109104082945
28 |       **Slug**: cloudflare-mcp
29 |       **Name**: New Project Name
30 |       **Platform**: python
31 | 
32 |       ## Updates Applied
33 |       - Updated name to "New Project Name"
34 |       - Updated platform to "python"
35 | 
36 |       # Using this information
37 | 
38 |       - The project is now accessible at slug: \`cloudflare-mcp\`
39 |       "
40 |     `);
41 |   });
42 | 
43 |   it("assigns project to new team", async () => {
44 |     const result = await updateProject.handler(
45 |       {
46 |         organizationSlug: "sentry-mcp-evals",
47 |         projectSlug: "cloudflare-mcp",
48 |         name: null,
49 |         slug: null,
50 |         platform: null,
51 |         teamSlug: "backend-team",
52 |         regionUrl: null,
53 |       },
54 |       {
55 |         constraints: {
56 |           organizationSlug: null,
57 |         },
58 |         accessToken: "access-token",
59 |         userId: "1",
60 |       },
61 |     );
62 |     expect(result).toMatchInlineSnapshot(`
63 |       "# Updated Project in **sentry-mcp-evals**
64 | 
65 |       **ID**: 4509106749636608
66 |       **Slug**: cloudflare-mcp
67 |       **Name**: cloudflare-mcp
68 |       **Platform**: node
69 | 
70 |       ## Updates Applied
71 |       - Updated team assignment to "backend-team"
72 | 
73 |       # Using this information
74 | 
75 |       - The project is now accessible at slug: \`cloudflare-mcp\`
76 |       - The project is now assigned to the \`backend-team\` team
77 |       "
78 |     `);
79 |   });
80 | });
81 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/error.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "error",
 3 |   "description": "This document defines the shared attributes used to report an error.\n",
 4 |   "attributes": {
 5 |     "error.type": {
 6 |       "description": "Describes a class of error the operation ended with.\n",
 7 |       "type": "string",
 8 |       "note": "The `error.type` SHOULD be predictable, and SHOULD have low cardinality.\n\nWhen `error.type` is set to a type (e.g., an exception type), its\ncanonical class name identifying the type within the artifact SHOULD be used.\n\nInstrumentations SHOULD document the list of errors they report.\n\nThe cardinality of `error.type` within one instrumentation library SHOULD be low.\nTelemetry consumers that aggregate data from multiple instrumentation libraries and applications\nshould be prepared for `error.type` to have high cardinality at query time when no\nadditional filters are applied.\n\nIf the operation has completed successfully, instrumentations SHOULD NOT set `error.type`.\n\nIf a specific domain defines its own set of error identifiers (such as HTTP or gRPC status codes),\nit's RECOMMENDED to:\n\n- Use a domain-specific attribute\n- Set `error.type` to capture all errors, regardless of whether they are defined within the domain-specific set or not.\n",
 9 |       "stability": "stable",
10 |       "examples": ["_OTHER"]
11 |     },
12 |     "error.message": {
13 |       "description": "A message providing more detail about an error in human-readable form.",
14 |       "type": "string",
15 |       "note": "`error.message` should provide additional context and detail about an error.\nIt is NOT RECOMMENDED to duplicate the value of `error.type` in `error.message`.\nIt is also NOT RECOMMENDED to duplicate the value of `exception.message` in `error.message`.\n\n`error.message` is NOT RECOMMENDED for metrics or spans due to its unbounded cardinality and overlap with span status.\n",
16 |       "stability": "development",
17 |       "examples": [
18 |         "Unexpected input type: string",
19 |         "The user has exceeded their storage quota"
20 |       ]
21 |     }
22 |   }
23 | }
24 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-streaming-simulation.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Hook for simulating streaming animation for local messages (like slash commands)
 3 |  * This provides the same UX as AI-generated responses for locally generated content
 4 |  */
 5 | import { useState, useCallback, useRef, useEffect } from "react";
 6 | 
 7 | interface StreamingSimulationState {
 8 |   isStreaming: boolean;
 9 |   streamingMessageId: string | null;
10 | }
11 | 
12 | export function useStreamingSimulation() {
13 |   const [state, setState] = useState<StreamingSimulationState>({
14 |     isStreaming: false,
15 |     streamingMessageId: null,
16 |   });
17 | 
18 |   const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
19 | 
20 |   // Start streaming simulation for a specific message
21 |   const startStreaming = useCallback((messageId: string, duration = 1000) => {
22 |     setState({
23 |       isStreaming: true,
24 |       streamingMessageId: messageId,
25 |     });
26 | 
27 |     // Clear any existing timeout
28 |     if (timeoutRef.current) {
29 |       clearTimeout(timeoutRef.current);
30 |     }
31 | 
32 |     // Stop streaming after the specified duration
33 |     timeoutRef.current = setTimeout(() => {
34 |       setState({
35 |         isStreaming: false,
36 |         streamingMessageId: null,
37 |       });
38 |     }, duration);
39 |   }, []);
40 | 
41 |   // Stop streaming simulation immediately
42 |   const stopStreaming = useCallback(() => {
43 |     if (timeoutRef.current) {
44 |       clearTimeout(timeoutRef.current);
45 |       timeoutRef.current = null;
46 |     }
47 |     setState({
48 |       isStreaming: false,
49 |       streamingMessageId: null,
50 |     });
51 |   }, []);
52 | 
53 |   // Check if a specific message is currently streaming
54 |   const isMessageStreaming = useCallback(
55 |     (messageId: string) => {
56 |       return state.isStreaming && state.streamingMessageId === messageId;
57 |     },
58 |     [state.isStreaming, state.streamingMessageId],
59 |   );
60 | 
61 |   // Cleanup on unmount
62 |   useEffect(() => {
63 |     return () => {
64 |       if (timeoutRef.current) {
65 |         clearTimeout(timeoutRef.current);
66 |       }
67 |     };
68 |   }, []);
69 | 
70 |   return {
71 |     isStreaming: state.isStreaming,
72 |     streamingMessageId: state.streamingMessageId,
73 |     startStreaming,
74 |     stopStreaming,
75 |     isMessageStreaming,
76 |   };
77 | }
78 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-items-attributes.json:
--------------------------------------------------------------------------------

```json
  1 | [
  2 |   {
  3 |     "key": "span.op",
  4 |     "name": "Span Operation"
  5 |   },
  6 |   {
  7 |     "key": "span.description",
  8 |     "name": "Span Description"
  9 |   },
 10 |   {
 11 |     "key": "span.status",
 12 |     "name": "Span Status"
 13 |   },
 14 |   {
 15 |     "key": "span.duration",
 16 |     "name": "Span Duration"
 17 |   },
 18 |   {
 19 |     "key": "transaction",
 20 |     "name": "Transaction"
 21 |   },
 22 |   {
 23 |     "key": "transaction.op",
 24 |     "name": "Transaction Operation"
 25 |   },
 26 |   {
 27 |     "key": "transaction.duration",
 28 |     "name": "Transaction Duration"
 29 |   },
 30 |   {
 31 |     "key": "transaction.status",
 32 |     "name": "Transaction Status"
 33 |   },
 34 |   {
 35 |     "key": "project",
 36 |     "name": "Project"
 37 |   },
 38 |   {
 39 |     "key": "environment",
 40 |     "name": "Environment"
 41 |   },
 42 |   {
 43 |     "key": "release",
 44 |     "name": "Release"
 45 |   },
 46 |   {
 47 |     "key": "user.id",
 48 |     "name": "User ID"
 49 |   },
 50 |   {
 51 |     "key": "user.email",
 52 |     "name": "User Email"
 53 |   },
 54 |   {
 55 |     "key": "user.username",
 56 |     "name": "Username"
 57 |   },
 58 |   {
 59 |     "key": "error.type",
 60 |     "name": "Error Type"
 61 |   },
 62 |   {
 63 |     "key": "error.value",
 64 |     "name": "Error Value"
 65 |   },
 66 |   {
 67 |     "key": "error.handled",
 68 |     "name": "Error Handled"
 69 |   },
 70 |   {
 71 |     "key": "message",
 72 |     "name": "Message"
 73 |   },
 74 |   {
 75 |     "key": "level",
 76 |     "name": "Level"
 77 |   },
 78 |   {
 79 |     "key": "platform",
 80 |     "name": "Platform"
 81 |   },
 82 |   {
 83 |     "key": "sdk.name",
 84 |     "name": "SDK Name"
 85 |   },
 86 |   {
 87 |     "key": "sdk.version",
 88 |     "name": "SDK Version"
 89 |   },
 90 |   {
 91 |     "key": "http.method",
 92 |     "name": "HTTP Method"
 93 |   },
 94 |   {
 95 |     "key": "http.status_code",
 96 |     "name": "HTTP Status Code"
 97 |   },
 98 |   {
 99 |     "key": "http.url",
100 |     "name": "HTTP URL"
101 |   },
102 |   {
103 |     "key": "browser.name",
104 |     "name": "Browser Name"
105 |   },
106 |   {
107 |     "key": "os.name",
108 |     "name": "OS Name"
109 |   },
110 |   {
111 |     "key": "device",
112 |     "name": "Device"
113 |   },
114 |   {
115 |     "key": "geo.country_code",
116 |     "name": "Country Code"
117 |   },
118 |   {
119 |     "key": "geo.region",
120 |     "name": "Geographic Region"
121 |   },
122 |   {
123 |     "key": "geo.city",
124 |     "name": "City"
125 |   },
126 |   {
127 |     "key": "custom.tier",
128 |     "name": "Customer Tier"
129 |   },
130 |   {
131 |     "key": "custom.feature_flag",
132 |     "name": "Feature Flag"
133 |   }
134 | ]
135 | 
```

--------------------------------------------------------------------------------
/packages/mcp-test-client/src/logger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Simple terminal output logger for the MCP client.
 3 |  *
 4 |  * In an ideal world, this would use a state manager to track the last active logger,
 5 |  * and it'd accept streams to the log functions. It'd then handle automatically
 6 |  * terminating a previous block, inserting a new block, and restarting the previous
 7 |  * block when streams receive new data. This is just a simplified version as this is
 8 |  * not a big concern in this project.
 9 |  */
10 | 
11 | import chalk from "chalk";
12 | 
13 | let responseStarted = false;
14 | 
15 | export const logError = (msg: string, detail?: any) =>
16 |   process.stdout.write(
17 |     `\n${chalk.red("●")} ${msg}${detail ? `\n  ⎿  ${chalk.gray(detail instanceof Error ? detail.message : detail)}` : ""}\n`,
18 |   );
19 | 
20 | export const logSuccess = (msg: string, detail?: string) =>
21 |   process.stdout.write(
22 |     `\n${chalk.green("●")} ${msg}${detail ? `\n  ⎿  ${chalk.gray(detail)}` : ""}\n`,
23 |   );
24 | 
25 | export const logInfo = (msg: string, detail?: string) =>
26 |   process.stdout.write(
27 |     `\n${chalk.blue("●")} ${msg}${detail ? `\n  ⎿  ${chalk.gray(detail)}` : ""}\n`,
28 |   );
29 | 
30 | export const logUser = (msg: string) =>
31 |   process.stdout.write(`\n${chalk.gray(">")} ${chalk.gray(msg)}\n`);
32 | 
33 | export const logTool = (name: string, args?: any) => {
34 |   const params =
35 |     args && Object.keys(args).length > 0
36 |       ? `(${Object.entries(args)
37 |           .map(
38 |             ([k, v]) =>
39 |               `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`,
40 |           )
41 |           .join(", ")})`
42 |       : "()";
43 |   process.stdout.write(`\n${chalk.green("●")} ${name}${params}\n`);
44 | };
45 | 
46 | export const logToolResult = (msg: string) =>
47 |   process.stdout.write(`  ⎿  ${chalk.white(msg)}\n`);
48 | 
49 | export const logStreamStart = () => {
50 |   if (!responseStarted) {
51 |     process.stdout.write(`\n${chalk.white("●")} `);
52 |     responseStarted = true;
53 |   }
54 | };
55 | 
56 | export const logStreamWrite = (chunk: string) =>
57 |   process.stdout.write(chunk.replace(/\n/g, "\n  "));
58 | 
59 | export const logStreamEnd = () => {
60 |   if (responseStarted) {
61 |     process.stdout.write("\n");
62 |     responseStarted = false;
63 |   }
64 | };
65 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/create-team.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import { setTag } from "@sentry/core";
 3 | import { defineTool } from "../internal/tool-helpers/define";
 4 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 5 | import type { ServerContext } from "../types";
 6 | import { ParamOrganizationSlug, ParamRegionUrl } from "../schema";
 7 | 
 8 | export default defineTool({
 9 |   name: "create_team",
10 |   skills: ["project-management"], // Only available in project-management skill
11 |   requiredScopes: ["team:write"],
12 |   description: [
13 |     "Create a new team in Sentry.",
14 |     "",
15 |     "USE THIS TOOL WHEN USERS WANT TO:",
16 |     "- 'Create a new team'",
17 |     "- 'Set up a team called [X]'",
18 |     "- 'I need a team for my project'",
19 |     "",
20 |     "Be careful when using this tool!",
21 |     "",
22 |     "<examples>",
23 |     "### Create a new team",
24 |     "```",
25 |     "create_team(organizationSlug='my-organization', name='the-goats')",
26 |     "```",
27 |     "</examples>",
28 |     "",
29 |     "<hints>",
30 |     "- If any parameter is ambiguous, you should clarify with the user what they meant.",
31 |     "</hints>",
32 |   ].join("\n"),
33 |   inputSchema: {
34 |     organizationSlug: ParamOrganizationSlug,
35 |     regionUrl: ParamRegionUrl.nullable().default(null),
36 |     name: z.string().trim().describe("The name of the team to create."),
37 |   },
38 |   annotations: {
39 |     readOnlyHint: false,
40 |     destructiveHint: false,
41 |     openWorldHint: true,
42 |   },
43 |   async handler(params, context: ServerContext) {
44 |     const apiService = apiServiceFromContext(context, {
45 |       regionUrl: params.regionUrl ?? undefined,
46 |     });
47 |     const organizationSlug = params.organizationSlug;
48 | 
49 |     setTag("organization.slug", organizationSlug);
50 | 
51 |     const team = await apiService.createTeam({
52 |       organizationSlug,
53 |       name: params.name,
54 |     });
55 |     let output = `# New Team in **${organizationSlug}**\n\n`;
56 |     output += `**ID**: ${team.id}\n`;
57 |     output += `**Slug**: ${team.slug}\n`;
58 |     output += `**Name**: ${team.name}\n`;
59 |     output += "# Using this information\n\n";
60 |     output += `- You should always inform the user of the Team Slug value.\n`;
61 |     return output;
62 |   },
63 | });
64 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-scroll-lock.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Hook to lock body scroll when a component is active
 3 |  * Handles edge cases like iOS Safari and nested locks
 4 |  */
 5 | 
 6 | import { useEffect, useRef } from "react";
 7 | 
 8 | // Track active locks to handle nested components
 9 | let activeLocks = 0;
10 | let originalStyles: {
11 |   overflow?: string;
12 |   position?: string;
13 |   top?: string;
14 |   width?: string;
15 | } = {};
16 | 
17 | export function useScrollLock(enabled = true) {
18 |   const scrollPositionRef = useRef(0);
19 | 
20 |   useEffect(() => {
21 |     if (!enabled) return;
22 | 
23 |     // Save scroll position and lock scroll
24 |     const lockScroll = () => {
25 |       // First lock - save original styles
26 |       if (activeLocks === 0) {
27 |         scrollPositionRef.current = window.scrollY;
28 | 
29 |         originalStyles = {
30 |           overflow: document.body.style.overflow,
31 |           position: document.body.style.position,
32 |           top: document.body.style.top,
33 |           width: document.body.style.width,
34 |         };
35 | 
36 |         // Apply scroll lock styles
37 |         document.body.style.overflow = "hidden";
38 | 
39 |         // iOS Safari fix - prevent rubber band scrolling
40 |         if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
41 |           document.body.style.position = "fixed";
42 |           document.body.style.top = `-${scrollPositionRef.current}px`;
43 |           document.body.style.width = "100%";
44 |         }
45 |       }
46 | 
47 |       activeLocks++;
48 |     };
49 | 
50 |     // Restore scroll position and unlock
51 |     const unlockScroll = () => {
52 |       activeLocks--;
53 | 
54 |       // Last lock removed - restore original styles
55 |       if (activeLocks === 0) {
56 |         document.body.style.overflow = originalStyles.overflow || "";
57 |         document.body.style.position = originalStyles.position || "";
58 |         document.body.style.top = originalStyles.top || "";
59 |         document.body.style.width = originalStyles.width || "";
60 | 
61 |         // Restore scroll position for iOS
62 |         if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
63 |           window.scrollTo(0, scrollPositionRef.current);
64 |         }
65 | 
66 |         originalStyles = {};
67 |       }
68 |     };
69 | 
70 |     lockScroll();
71 | 
72 |     // Cleanup
73 |     return () => {
74 |       unlockScroll();
75 |     };
76 |   }, [enabled]);
77 | }
78 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/sliding-panel.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Reusable sliding panel component
 3 |  * Handles responsive slide-out behavior
 4 |  */
 5 | 
 6 | import type { ReactNode } from "react";
 7 | import { useScrollLock } from "../../hooks/use-scroll-lock";
 8 | 
 9 | interface SlidingPanelProps {
10 |   isOpen: boolean;
11 |   onClose?: () => void;
12 |   children: ReactNode;
13 |   className?: string;
14 | }
15 | 
16 | export function SlidingPanel({
17 |   isOpen,
18 |   onClose,
19 |   children,
20 |   className = "",
21 | }: SlidingPanelProps) {
22 |   // Lock body scroll when panel is open on mobile
23 |   useScrollLock(isOpen && window.innerWidth < 768);
24 | 
25 |   return (
26 |     <div className="fixed inset-0 bg-transparent max-w-none max-h-none w-full h-full z-50 pointer-events-none">
27 |       {/* Backdrop */}
28 |       <div
29 |         className={`fixed xl:hidden inset-0 bg-black/50 backdrop-blur-sm transition-opacity ${
30 |           isOpen
31 |             ? "opacity-100 pointer-events-auto duration-200"
32 |             : "opacity-0 select-none pointer-events-none duration-300"
33 |         }`}
34 |         onClick={isOpen ? onClose : undefined}
35 |         onKeyDown={
36 |           isOpen ? (e) => e.key === "Escape" && onClose?.() : undefined
37 |         }
38 |         role={isOpen ? "button" : undefined}
39 |         tabIndex={isOpen ? 0 : -1}
40 |         aria-label={isOpen ? "Close panel" : undefined}
41 |       />
42 | 
43 |       {/* Panel */}
44 |       <div
45 |         className={`fixed xl:hidden inset-y-0 right-0 w-full max-w-2xl bg-background-3 border-l border-slate-800 z-50 shadow-2xl flex flex-col ease-out motion-safe:duration-300 ${
46 |           isOpen
47 |             ? "translate-x-0 pointer-events-auto"
48 |             : "translate-x-full pointer-events-none"
49 |         } ${className}`}
50 |       >
51 |         {children}
52 |       </div>
53 |       <div
54 |         className={`fixed hidden xl:flex inset-y-0 right-0 w-full max-w-[50vw] bg-background-2 flex-col ease-out ${
55 |           isOpen
56 |             ? "translate-x-0 scale-100 opacity-100 pointer-events-auto motion-safe:duration-300 delay-150 transition-[opacity,filter,scale]"
57 |             : "translate-x-full motion-safe:scale-90 opacity-0 pointer-events-none duration-0"
58 |         } ${className}`}
59 |       >
60 |         {children}
61 |       </div>
62 |     </div>
63 |   );
64 | }
65 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/telemetry.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "telemetry",
 3 |   "description": "This document defines attributes for telemetry SDK.\n",
 4 |   "attributes": {
 5 |     "telemetry.sdk.name": {
 6 |       "description": "The name of the telemetry SDK as defined above.\n",
 7 |       "type": "string",
 8 |       "note": "The OpenTelemetry SDK MUST set the `telemetry.sdk.name` attribute to `opentelemetry`.\nIf another SDK, like a fork or a vendor-provided implementation, is used, this SDK MUST set the\n`telemetry.sdk.name` attribute to the fully-qualified class or module name of this SDK's main entry point\nor another suitable identifier depending on the language.\nThe identifier `opentelemetry` is reserved and MUST NOT be used in this case.\nAll custom identifiers SHOULD be stable across different versions of an implementation.\n",
 9 |       "stability": "stable",
10 |       "examples": ["opentelemetry"]
11 |     },
12 |     "telemetry.sdk.language": {
13 |       "description": "The language of the telemetry SDK.\n",
14 |       "type": "string",
15 |       "stability": "stable",
16 |       "examples": [
17 |         "cpp",
18 |         "dotnet",
19 |         "erlang",
20 |         "go",
21 |         "java",
22 |         "nodejs",
23 |         "php",
24 |         "python",
25 |         "ruby",
26 |         "rust",
27 |         "swift",
28 |         "webjs"
29 |       ]
30 |     },
31 |     "telemetry.sdk.version": {
32 |       "description": "The version string of the telemetry SDK.\n",
33 |       "type": "string",
34 |       "stability": "stable",
35 |       "examples": ["1.2.3"]
36 |     },
37 |     "telemetry.distro.name": {
38 |       "description": "The name of the auto instrumentation agent or distribution, if used.\n",
39 |       "type": "string",
40 |       "note": "Official auto instrumentation agents and distributions SHOULD set the `telemetry.distro.name` attribute to\na string starting with `opentelemetry-`, e.g. `opentelemetry-java-instrumentation`.\n",
41 |       "stability": "development",
42 |       "examples": ["parts-unlimited-java"]
43 |     },
44 |     "telemetry.distro.version": {
45 |       "description": "The version string of the auto instrumentation agent or distribution, if used.\n",
46 |       "type": "string",
47 |       "stability": "development",
48 |       "examples": ["1.2.3"]
49 |     }
50 |   }
51 | }
52 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/permissions.parseScopes.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import { parseScopes, expandScopes, type Scope } from "./permissions";
 3 | 
 4 | describe("parseScopes", () => {
 5 |   it("parses comma-separated string with trimming and de-dup", () => {
 6 |     const { valid, invalid } = parseScopes(
 7 |       "event:write, foo, org:admin, , event:write",
 8 |     );
 9 |     const v = new Set<Scope>(valid);
10 |     expect(v.has("event:write")).toBe(true);
11 |     expect(v.has("org:admin")).toBe(true);
12 |     expect(invalid).toEqual(["foo"]);
13 |   });
14 | 
15 |   it("parses arrays and filters non-strings", () => {
16 |     const { valid, invalid } = parseScopes([
17 |       "member:read",
18 |       "x",
19 |       123 as unknown,
20 |       " team:write ",
21 |       "",
22 |     ]);
23 |     const v = new Set<Scope>(valid);
24 |     expect(v.has("member:read")).toBe(true);
25 |     expect(v.has("team:write")).toBe(true);
26 |     expect(invalid).toEqual(["x"]);
27 |   });
28 | 
29 |   it("handles empty or undefined inputs", () => {
30 |     expect(parseScopes("")).toEqual({ valid: new Set<Scope>(), invalid: [] });
31 |     expect(parseScopes(undefined)).toEqual({
32 |       valid: new Set<Scope>(),
33 |       invalid: [],
34 |     });
35 |     expect(parseScopes([])).toEqual({ valid: new Set<Scope>(), invalid: [] });
36 |   });
37 | });
38 | 
39 | // Consolidated strict-like parseScopes cases
40 | describe("parseScopes (strict-like cases)", () => {
41 |   it("returns invalid tokens for unknown scopes", () => {
42 |     const { valid, invalid } = parseScopes("foo,bar,org:admin");
43 |     expect(invalid).toEqual(["foo", "bar"]);
44 |     expect([...valid]).toContain("org:admin");
45 |   });
46 | 
47 |   it("returns only valid set when all are valid", () => {
48 |     const { valid, invalid } = parseScopes("event:admin,org:read");
49 |     expect(invalid).toEqual([]);
50 |     const out = new Set<Scope>(valid);
51 |     expect(out.has("event:admin")).toBe(true);
52 |     expect(out.has("org:read")).toBe(true);
53 |   });
54 | });
55 | 
56 | // Related behavior validation for expandScopes
57 | describe("expandScopes", () => {
58 |   it("includes implied lower scopes", () => {
59 |     const expanded = expandScopes(new Set<Scope>(["event:write"]));
60 |     expect(expanded.has("event:read")).toBe(true);
61 |     expect(expanded.has("event:write")).toBe(true);
62 |   });
63 | });
64 | 
```

--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://turbo.build/schema.json",
 3 |   "daemon": false,
 4 |   "ui": "stream",
 5 |   "tasks": {
 6 |     "setup": {
 7 |       "dependsOn": ["build"],
 8 |       "cache": false
 9 |     },
10 |     "generate-definitions": {
11 |       "dependsOn": [],
12 |       "inputs": [
13 |         "src/tools/**/*.ts",
14 |         "src/skills.ts",
15 |         "scripts/generate-definitions.ts",
16 |         "!src/**/*.test.ts"
17 |       ],
18 |       "outputs": ["src/toolDefinitions.json", "src/skillDefinitions.json"],
19 |       "cache": true
20 |     },
21 |     "dev": {
22 |       "dependsOn": ["^build"],
23 |       "cache": false,
24 |       "persistent": true
25 |     },
26 |     "deploy": {
27 |       "dependsOn": ["^build"],
28 |       "outputs": [],
29 |       "cache": true
30 |     },
31 |     "lint": {
32 |       "dependsOn": ["^build"],
33 |       "inputs": ["**/*.{ts,tsx,js,jsx}", "biome.json", "package.json"],
34 |       "cache": true
35 |     },
36 |     "test": {
37 |       "dependsOn": ["^build"],
38 |       "inputs": [
39 |         "src/**/*.ts",
40 |         "**/*.test.ts",
41 |         "**/*.spec.ts",
42 |         "vitest.config.ts",
43 |         "package.json"
44 |       ],
45 |       "outputs": ["coverage/**", "*.junit.xml"],
46 |       "cache": true
47 |     },
48 |     "tsc": {
49 |       "dependsOn": ["^build"],
50 |       "inputs": ["src/**/*.{ts,tsx}", "tsconfig*.json"],
51 |       "outputs": [],
52 |       "cache": true
53 |     },
54 |     "eval": {
55 |       "dependsOn": ["^build"],
56 |       "outputs": [],
57 |       "cache": true
58 |     },
59 |     "build": {
60 |       "dependsOn": ["^build"],
61 |       "inputs": [
62 |         "src/**/*.{ts,tsx,js,jsx,json}",
63 |         "package.json",
64 |         "tsconfig*.json",
65 |         "tsdown.config.ts",
66 |         "vite.config.ts",
67 |         "wrangler.jsonc",
68 |         "!**/*.test.{ts,tsx}",
69 |         "!**/*.spec.{ts,tsx}"
70 |       ],
71 |       "outputs": [".next/**", "!.next/cache/**", "dist/**", "*.tsbuildinfo"],
72 |       "cache": true
73 |     },
74 |     "after-build": {
75 |       "dependsOn": ["build"],
76 |       "cache": false
77 |     }
78 |   },
79 |   "globalPassThroughEnv": [
80 |     "NODE_ENV",
81 |     "CI",
82 |     "OPENAI_API_KEY",
83 |     "COOKIE_SECRET",
84 |     "SENTRY_CLIENT_ID",
85 |     "SENTRY_CLIENT_SECRET",
86 |     "SENTRY_AUTH_TOKEN",
87 |     "SENTRY_ACCESS_TOKEN",
88 |     "SENTRY_SPOTLIGHT",
89 |     "VITE_SENTRY_DSN",
90 |     "VITE_SENTRY_ENVIRONMENT"
91 |   ]
92 | }
93 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/button.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import type * as React from "react";
 2 | import { Slot } from "@radix-ui/react-slot";
 3 | import { cva, type VariantProps } from "class-variance-authority";
 4 | import { cn } from "../../lib/utils";
 5 | 
 6 | const buttonVariants = cva(
 7 |   "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
 8 |   {
 9 |     variants: {
10 |       variant: {
11 |         default: "bg-violet-300 text-black shadow-xs hover:bg-violet-300/90",
12 |         destructive:
13 |           "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20",
14 |         outline:
15 |           "bg-slate-800/50 border border-slate-600/50 shadow-xs hover:bg-slate-700/50 hover:text-white ",
16 |         secondary:
17 |           "bg-background-3 shadow-xs hover:bg-violet-300 hover:text-black",
18 |         ghost: "hover:text-white hover:bg-slate-700/50 ",
19 |         link: "text-primary hover:underline hover:text-violet-300 cursor-pointer",
20 |       },
21 |       size: {
22 |         default: "h-9 px-4 py-2 has-[>svg]:px-3",
23 |         sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
24 |         xs: "h-7 gap-1.5 px-2 has-[>svg]:px-1.5",
25 |         lg: "h-10 px-6 has-[>svg]:px-4",
26 |         icon: "size-9",
27 |       },
28 |       active: {
29 |         true: "text-violet-300 underline",
30 |       },
31 |     },
32 |     defaultVariants: {
33 |       variant: "default",
34 |       size: "default",
35 |     },
36 |   },
37 | );
38 | 
39 | function Button({
40 |   className,
41 |   variant,
42 |   size,
43 |   active = false,
44 |   asChild = false,
45 |   ...props
46 | }: React.ComponentProps<"button"> &
47 |   VariantProps<typeof buttonVariants> & {
48 |     asChild?: boolean;
49 |     active?: boolean;
50 |   }) {
51 |   const Comp = asChild ? Slot : "button";
52 | 
53 |   return (
54 |     <Comp
55 |       data-slot="button"
56 |       className={cn(buttonVariants({ variant, size, className, active }))}
57 |       {...props}
58 |     />
59 |   );
60 | }
61 | 
62 | export { Button, buttonVariants };
63 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/generic-event.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "id": "a6251c18f0194b8e8158518b8ee99545",
 3 |   "groupID": "6898891101",
 4 |   "eventID": "a6251c18f0194b8e8158518b8ee99545",
 5 |   "projectID": "4509062593708032",
 6 |   "size": 547,
 7 |   "entries": [],
 8 |   "dist": null,
 9 |   "message": "",
10 |   "title": "Endpoint Regression",
11 |   "location": null,
12 |   "user": null,
13 |   "contexts": {},
14 |   "sdk": null,
15 |   "context": {},
16 |   "packages": {},
17 |   "type": "generic",
18 |   "metadata": {
19 |     "title": "Endpoint Regression"
20 |   },
21 |   "tags": [
22 |     {
23 |       "key": "level",
24 |       "value": "info"
25 |     },
26 |     {
27 |       "key": "transaction",
28 |       "value": "POST /oauth/token"
29 |     }
30 |   ],
31 |   "platform": "python",
32 |   "dateReceived": "2025-11-18T06:01:22.186680Z",
33 |   "dateCreated": "2025-11-18T06:01:20Z",
34 |   "errors": [],
35 |   "occurrence": {
36 |     "id": "ae3754a99b294006b8d13ad59bb84d0f",
37 |     "projectId": 4509062593708032,
38 |     "eventId": "a6251c18f0194b8e8158518b8ee99545",
39 |     "fingerprint": ["ddf744fc1a47831ed53d9a489160fa7a"],
40 |     "issueTitle": "Endpoint Regression",
41 |     "subtitle": "Increased from 909.77ms to 1711.36ms (P95)",
42 |     "resourceId": null,
43 |     "evidenceData": {
44 |       "absolutePercentageChange": 1.8810815660491678,
45 |       "aggregateRange1": 909.7721153846148,
46 |       "aggregateRange2": 1711.3555555555554,
47 |       "breakpoint": 1763416800,
48 |       "change": "regression",
49 |       "transaction": "POST /oauth/token"
50 |     },
51 |     "evidenceDisplay": [
52 |       {
53 |         "name": "Regression",
54 |         "value": "POST /oauth/token duration increased from 909.77ms to 1711.36ms (P95)",
55 |         "important": true
56 |       },
57 |       {
58 |         "name": "Transaction",
59 |         "value": "POST /oauth/token",
60 |         "important": true
61 |       }
62 |     ],
63 |     "type": 1018,
64 |     "detectionTime": 1763445680.827214,
65 |     "level": "info",
66 |     "culprit": "POST /oauth/token",
67 |     "priority": 50,
68 |     "assignee": null
69 |   },
70 |   "crashFile": null,
71 |   "culprit": "POST /oauth/token",
72 |   "fingerprints": ["d41d8cd98f00b204e9800998ecf8427e"],
73 |   "groupingConfig": {
74 |     "id": "newstyle:2023-01-11",
75 |     "enhancements": "test"
76 |   },
77 |   "release": null,
78 |   "userReport": null,
79 |   "sdkUpdates": [],
80 |   "resolvedWith": [],
81 |   "nextEventID": null,
82 |   "previousEventID": "65d7c166833945efad0a4d38a4fd3665"
83 | }
84 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-releases.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from "vitest";
 2 | import findReleases from "./find-releases.js";
 3 | 
 4 | describe("find_releases", () => {
 5 |   it("works without project", async () => {
 6 |     const result = await findReleases.handler(
 7 |       {
 8 |         organizationSlug: "sentry-mcp-evals",
 9 |         projectSlug: null,
10 |         regionUrl: null,
11 |         query: null,
12 |       },
13 |       {
14 |         constraints: {
15 |           organizationSlug: null,
16 |         },
17 |         accessToken: "access-token",
18 |         userId: "1",
19 |       },
20 |     );
21 |     expect(result).toMatchInlineSnapshot(`
22 |       "# Releases in **sentry-mcp-evals**
23 | 
24 |       ## 8ce89484-0fec-4913-a2cd-e8e2d41dee36
25 | 
26 |       **Created**: 2025-04-13T19:54:21.764Z
27 |       **First Event**: 2025-04-13T19:54:21.000Z
28 |       **Last Event**: 2025-04-13T20:28:23.000Z
29 |       **New Issues**: 0
30 |       **Projects**: cloudflare-mcp
31 | 
32 |       # Using this information
33 | 
34 |       - You can reference the Release version in commit messages or documentation.
35 |       - You can search for issues in a specific release using the \`find_errors()\` tool with the query \`release:8ce89484-0fec-4913-a2cd-e8e2d41dee36\`.
36 |       "
37 |     `);
38 |   });
39 | 
40 |   it("works with project", async () => {
41 |     const result = await findReleases.handler(
42 |       {
43 |         organizationSlug: "sentry-mcp-evals",
44 |         projectSlug: "cloudflare-mcp",
45 |         regionUrl: null,
46 |         query: null,
47 |       },
48 |       {
49 |         constraints: {
50 |           organizationSlug: null,
51 |         },
52 |         accessToken: "access-token",
53 |         userId: "1",
54 |       },
55 |     );
56 |     expect(result).toMatchInlineSnapshot(`
57 |       "# Releases in **sentry-mcp-evals/cloudflare-mcp**
58 | 
59 |       ## 8ce89484-0fec-4913-a2cd-e8e2d41dee36
60 | 
61 |       **Created**: 2025-04-13T19:54:21.764Z
62 |       **First Event**: 2025-04-13T19:54:21.000Z
63 |       **Last Event**: 2025-04-13T20:28:23.000Z
64 |       **New Issues**: 0
65 |       **Projects**: cloudflare-mcp
66 | 
67 |       # Using this information
68 | 
69 |       - You can reference the Release version in commit messages or documentation.
70 |       - You can search for issues in a specific release using the \`find_errors()\` tool with the query \`release:8ce89484-0fec-4913-a2cd-e8e2d41dee36\`.
71 |       "
72 |     `);
73 |   });
74 | });
75 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/browser.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "browser",
 3 |   "description": "The web browser attributes\n",
 4 |   "attributes": {
 5 |     "browser.brands": {
 6 |       "description": "Array of brand name and version separated by a space",
 7 |       "type": "string",
 8 |       "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.brands`).\n",
 9 |       "stability": "development",
10 |       "examples": ["[\" Not A;Brand 99\",\"Chromium 99\",\"Chrome 99\"]"]
11 |     },
12 |     "browser.platform": {
13 |       "description": "The platform on which the browser is running",
14 |       "type": "string",
15 |       "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.platform`). If unavailable, the legacy `navigator.platform` API SHOULD NOT be used instead and this attribute SHOULD be left unset in order for the values to be consistent.\nThe list of possible values is defined in the [W3C User-Agent Client Hints specification](https://wicg.github.io/ua-client-hints/#sec-ch-ua-platform). Note that some (but not all) of these values can overlap with values in the [`os.type` and `os.name` attributes](./os.md). However, for consistency, the values in the `browser.platform` attribute should capture the exact value that the user agent provides.\n",
16 |       "stability": "development",
17 |       "examples": ["Windows", "macOS", "Android"]
18 |     },
19 |     "browser.mobile": {
20 |       "description": "A boolean that is true if the browser is running on a mobile device",
21 |       "type": "boolean",
22 |       "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.mobile`). If unavailable, this attribute SHOULD be left unset.\n",
23 |       "stability": "development"
24 |     },
25 |     "browser.language": {
26 |       "description": "Preferred language of the user using the browser",
27 |       "type": "string",
28 |       "note": "This value is intended to be taken from the Navigator API `navigator.language`.\n",
29 |       "stability": "development",
30 |       "examples": ["en", "en-US", "fr", "fr-FR"]
31 |     }
32 |   }
33 | }
34 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/errors.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from "vitest";
 2 | import { UserInputError, ConfigurationError } from "./errors";
 3 | 
 4 | describe("UserInputError", () => {
 5 |   it("should create a UserInputError with the correct message and name", () => {
 6 |     const message = "Invalid input provided";
 7 |     const error = new UserInputError(message);
 8 | 
 9 |     expect(error.message).toBe(message);
10 |     expect(error.name).toBe("UserInputError");
11 |     expect(error instanceof Error).toBe(true);
12 |     expect(error instanceof UserInputError).toBe(true);
13 |   });
14 | 
15 |   it("should be distinguishable from regular Error", () => {
16 |     const userInputError = new UserInputError("User input error");
17 |     const regularError = new Error("Regular error");
18 | 
19 |     expect(userInputError instanceof UserInputError).toBe(true);
20 |     expect(regularError instanceof UserInputError).toBe(false);
21 |   });
22 | 
23 |   it("should support error cause", () => {
24 |     const cause = new Error("Original error");
25 |     const error = new UserInputError("User input error", { cause });
26 | 
27 |     expect(error.cause).toBe(cause);
28 |   });
29 | });
30 | 
31 | describe("ConfigurationError", () => {
32 |   it("should create a ConfigurationError with the correct message and name", () => {
33 |     const message = "Invalid configuration";
34 |     const error = new ConfigurationError(message);
35 | 
36 |     expect(error.message).toBe(message);
37 |     expect(error.name).toBe("ConfigurationError");
38 |     expect(error instanceof Error).toBe(true);
39 |     expect(error instanceof ConfigurationError).toBe(true);
40 |   });
41 | 
42 |   it("should be distinguishable from regular Error and UserInputError", () => {
43 |     const configError = new ConfigurationError("Config error");
44 |     const userInputError = new UserInputError("User input error");
45 |     const regularError = new Error("Regular error");
46 | 
47 |     expect(configError instanceof ConfigurationError).toBe(true);
48 |     expect(userInputError instanceof ConfigurationError).toBe(false);
49 |     expect(regularError instanceof ConfigurationError).toBe(false);
50 |   });
51 | 
52 |   it("should support error cause", () => {
53 |     const cause = new Error("DNS resolution failed");
54 |     const error = new ConfigurationError("Unable to connect to server", {
55 |       cause,
56 |     });
57 | 
58 |     expect(error.cause).toBe(cause);
59 |   });
60 | });
61 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/fetch-utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Fetch with timeout using AbortController
 3 |  * @param url - The URL to fetch
 4 |  * @param options - Standard fetch options
 5 |  * @param timeoutMs - Timeout in milliseconds (default: 30000)
 6 |  * @returns Promise<Response>
 7 |  * @throws Error if request times out
 8 |  */
 9 | export async function fetchWithTimeout(
10 |   url: string | URL,
11 |   options: RequestInit = {},
12 |   timeoutMs = 30000,
13 | ): Promise<Response> {
14 |   const controller = new AbortController();
15 |   const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
16 | 
17 |   try {
18 |     const response = await fetch(url, {
19 |       ...options,
20 |       signal: controller.signal,
21 |     });
22 |     return response;
23 |   } catch (error) {
24 |     if (error instanceof Error && error.name === "AbortError") {
25 |       throw new Error(`Request timeout after ${timeoutMs}ms`);
26 |     }
27 |     throw error;
28 |   } finally {
29 |     clearTimeout(timeoutId);
30 |   }
31 | }
32 | 
33 | /**
34 |  * Retry a function with exponential backoff
35 |  * @param fn - The async function to retry
36 |  * @param options - Retry options
37 |  * @param options.maxRetries - Maximum number of retries (default: 3)
38 |  * @param options.initialDelay - Initial delay in milliseconds (default: 1000)
39 |  * @param options.shouldRetry - Predicate to determine if error should be retried (default: always retry)
40 |  * @returns Promise with the function result
41 |  * @throws The last error if all retries are exhausted
42 |  */
43 | export async function retryWithBackoff<T>(
44 |   fn: () => Promise<T>,
45 |   {
46 |     maxRetries = 3,
47 |     initialDelay = 1000,
48 |     shouldRetry = (error: unknown) => true,
49 |   }: {
50 |     maxRetries?: number;
51 |     initialDelay?: number;
52 |     shouldRetry?: (error: unknown) => boolean;
53 |   } = {},
54 | ): Promise<T> {
55 |   let lastError: unknown;
56 |   let delay = initialDelay;
57 | 
58 |   for (let attempt = 0; attempt <= maxRetries; attempt++) {
59 |     try {
60 |       return await fn();
61 |     } catch (error) {
62 |       lastError = error;
63 | 
64 |       // Don't retry if we've exhausted attempts or if the error is non-retryable
65 |       if (attempt === maxRetries || !shouldRetry(error)) {
66 |         throw error;
67 |       }
68 | 
69 |       // Wait before retrying with exponential backoff
70 |       await new Promise((resolve) => setTimeout(resolve, delay));
71 |       delay = Math.min(delay * 2, 30000); // Cap at 30 seconds
72 |     }
73 |   }
74 | 
75 |   throw lastError;
76 | }
77 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/chat/tool-invocation.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import { useState } from "react";
 2 | import { Bolt, ChevronDown, ChevronRight } from "lucide-react";
 3 | import type { ToolMessage, ToolInvocationProps } from "./types";
 4 | import { isTextMessage } from "./types";
 5 | 
 6 | function getTokenCount(content: ToolMessage[]): number {
 7 |   return content.reduce((acc, message) => {
 8 |     if (isTextMessage(message)) {
 9 |       return acc + message.text.length;
10 |     }
11 |     return acc;
12 |   }, 0);
13 | }
14 | 
15 | export function ToolInvocation({
16 |   tool,
17 |   messageId,
18 |   index,
19 | }: ToolInvocationProps) {
20 |   const [isExpanded, setIsExpanded] = useState(false);
21 | 
22 |   return (
23 |     <div className="border border-slate-900 rounded overflow-hidden">
24 |       <button
25 |         type="button"
26 |         onClick={() => setIsExpanded(!isExpanded)}
27 |         className="w-full p-3 text-left cursor-pointer hover:bg-slate-900/50 transition-colors"
28 |       >
29 |         <div className="flex items-center gap-2 text-violet-400">
30 |           {isExpanded ? (
31 |             <ChevronDown className="h-3 w-3" />
32 |           ) : (
33 |             <ChevronRight className="h-3 w-3" />
34 |           )}
35 |           <Bolt className="h-3 w-3" />
36 |           <span className="font-mono">{tool.toolName}</span>
37 |           {tool.state === "result" && (
38 |             <span className="text-xs text-slate-500 ml-auto">
39 |               {`~${getTokenCount(tool.result?.content ?? []).toLocaleString()}
40 |               tokens`}
41 |             </span>
42 |           )}
43 |         </div>
44 |       </button>
45 | 
46 |       {isExpanded && tool.state === "result" && tool.result && (
47 |         <div className="px-3 pb-3 border-t border-slate-600/30 text-slate-300">
48 |           <div className="mt-2">
49 |             <ToolContent content={tool.result.content} />
50 |           </div>
51 |         </div>
52 |       )}
53 |     </div>
54 |   );
55 | }
56 | 
57 | export function ToolContent({ content }: { content: ToolMessage[] }) {
58 |   return (
59 |     <div className="space-y-3">
60 |       {content.map((message: ToolMessage, index: number) => (
61 |         <div key={`message-${message.type}-${index}`} className="space-y-2">
62 |           <pre className="text-slate-400 text-sm whitespace-pre-wrap overflow-x-auto">
63 |             {isTextMessage(message)
64 |               ? message.text
65 |               : JSON.stringify(message, null, 2)}
66 |           </pre>
67 |         </div>
68 |       ))}
69 |     </div>
70 |   );
71 | }
72 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@sentry/mcp-cloudflare",
 3 |   "version": "0.24.0",
 4 |   "private": true,
 5 |   "type": "module",
 6 |   "license": "FSL-1.1-ALv2",
 7 |   "files": [
 8 |     "./dist/*"
 9 |   ],
10 |   "exports": {
11 |     ".": {
12 |       "types": "./dist/index.ts",
13 |       "default": "./dist/index.js"
14 |     }
15 |   },
16 |   "scripts": {
17 |     "build": "tsc -b && vite build",
18 |     "dev": "vite",
19 |     "deploy": "pnpm exec wrangler deploy",
20 |     "cf:versions:upload": "npx wrangler versions upload",
21 |     "preview": "vite preview",
22 |     "cf-typegen": "wrangler types",
23 |     "test": "vitest run",
24 |     "test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile=tests.junit.xml",
25 |     "test:watch": "vitest",
26 |     "tsc": "tsc --noEmit"
27 |   },
28 |   "devDependencies": {
29 |     "@cloudflare/vite-plugin": "^1.13.15",
30 |     "@cloudflare/workers-types": "catalog:",
31 |     "@sentry/mcp-core": "workspace:*",
32 |     "@sentry/mcp-server-mocks": "workspace:*",
33 |     "@sentry/mcp-server-tsconfig": "workspace:*",
34 |     "@sentry/vite-plugin": "catalog:",
35 |     "@tailwindcss/typography": "catalog:",
36 |     "@tailwindcss/vite": "catalog:",
37 |     "@types/react": "catalog:",
38 |     "@types/react-dom": "catalog:",
39 |     "@types/react-scroll-to-bottom": "^4.2.5",
40 |     "@vitejs/plugin-react": "catalog:",
41 |     "tailwindcss": "catalog:",
42 |     "urlpattern-polyfill": "^10.1.0",
43 |     "vite": "catalog:",
44 |     "vitest": "catalog:",
45 |     "wrangler": "^4.45.0"
46 |   },
47 |   "dependencies": {
48 |     "@ai-sdk/openai": "catalog:",
49 |     "@ai-sdk/react": "catalog:",
50 |     "@cloudflare/workers-oauth-provider": "catalog:",
51 |     "@modelcontextprotocol/sdk": "catalog:",
52 |     "@radix-ui/react-accordion": "catalog:",
53 |     "@radix-ui/react-slot": "catalog:",
54 |     "@sentry/cloudflare": "catalog:",
55 |     "@sentry/react": "catalog:",
56 |     "agents": "catalog:",
57 |     "ai": "catalog:",
58 |     "asciinema-player": "^3.10.0",
59 |     "better-sqlite3": "catalog:",
60 |     "class-variance-authority": "catalog:",
61 |     "clsx": "catalog:",
62 |     "hono": "catalog:",
63 |     "lucide-react": "catalog:",
64 |     "react": "catalog:",
65 |     "react-dom": "catalog:",
66 |     "react-markdown": "catalog:",
67 |     "react-scroll-to-bottom": "^4.2.0",
68 |     "remark-gfm": "catalog:",
69 |     "tailwind-merge": "catalog:",
70 |     "tw-animate-css": "catalog:",
71 |     "workers-mcp": "catalog:",
72 |     "zod": "catalog:"
73 |   }
74 | }
75 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import type * as React from "react";
 2 | import * as AccordionPrimitive from "@radix-ui/react-accordion";
 3 | import { ChevronDownIcon } from "lucide-react";
 4 | 
 5 | import { cn } from "../../lib/utils";
 6 | 
 7 | function Accordion({
 8 |   className,
 9 |   ...props
10 | }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
11 |   return (
12 |     <AccordionPrimitive.Root
13 |       data-slot="accordion"
14 |       className="space-y-1"
15 |       {...props}
16 |     />
17 |   );
18 | }
19 | 
20 | function AccordionItem({
21 |   className,
22 |   ...props
23 | }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
24 |   return (
25 |     <AccordionPrimitive.Item
26 |       data-slot="accordion-item"
27 |       className={cn("px-4 bg-background-2 rounded-xl", className)}
28 |       {...props}
29 |     />
30 |   );
31 | }
32 | 
33 | function AccordionTrigger({
34 |   className,
35 |   children,
36 |   ...props
37 | }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
38 |   return (
39 |     <AccordionPrimitive.Header className="flex m-0">
40 |       <AccordionPrimitive.Trigger
41 |         data-slot="accordion-trigger"
42 |         className={cn(
43 |           "focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 py-4 text-left font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180 text-lg text-white hover:text-violet-300 cursor-pointer ",
44 |           className,
45 |         )}
46 |         {...props}
47 |       >
48 |         {children}
49 |         <ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 motion-safe:transition-transform motion-safe:duration-200" />
50 |       </AccordionPrimitive.Trigger>
51 |     </AccordionPrimitive.Header>
52 |   );
53 | }
54 | 
55 | function AccordionContent({
56 |   className,
57 |   children,
58 |   ...props
59 | }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
60 |   return (
61 |     <AccordionPrimitive.Content
62 |       data-slot="accordion-content"
63 |       className="motion-safe:data-[state=closed]:animate-accordion-up motion-safe:data-[state=open]:animate-accordion-down overflow-hidden text-sm border-t border-t-slate-800"
64 |       {...props}
65 |     >
66 |       <div className={cn("pt-0 pb-4", className)}>{children}</div>
67 |     </AccordionPrimitive.Content>
68 |   );
69 | }
70 | 
71 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
72 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/search-issues/agent.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import type { SentryApiService } from "../../api-client";
 3 | import { ConfigurationError } from "../../errors";
 4 | import { callEmbeddedAgent } from "../../internal/agents/callEmbeddedAgent";
 5 | import { createDatasetFieldsTool } from "../../internal/agents/tools/dataset-fields";
 6 | import { createWhoamiTool } from "../../internal/agents/tools/whoami";
 7 | import { systemPrompt } from "./config";
 8 | 
 9 | // OpenAI structured outputs (used by GPT-5) require all properties to be in the 'required' array.
10 | // Avoid .optional()/.default() so the generated JSON Schema keeps every field required.
11 | // Tracking: https://github.com/getsentry/sentry-mcp/issues/623
12 | export const searchIssuesAgentOutputSchema = z.object({
13 |   query: z.string().describe("The Sentry issue search query"),
14 |   sort: z
15 |     .enum(["date", "freq", "new", "user"])
16 |     .nullable()
17 |     .describe("How to sort the results"),
18 |   explanation: z
19 |     .string()
20 |     .describe("Brief explanation of how you translated this query."),
21 | });
22 | 
23 | export interface SearchIssuesAgentOptions {
24 |   query: string;
25 |   organizationSlug: string;
26 |   apiService: SentryApiService;
27 |   projectId?: string;
28 | }
29 | 
30 | /**
31 |  * Search issues agent - single entry point for translating natural language queries to Sentry issue search syntax
32 |  * This returns both the translated query result AND the tool calls made by the agent
33 |  */
34 | export async function searchIssuesAgent(
35 |   options: SearchIssuesAgentOptions,
36 | ): Promise<{
37 |   result: z.output<typeof searchIssuesAgentOutputSchema>;
38 |   toolCalls: any[];
39 | }> {
40 |   if (!process.env.OPENAI_API_KEY) {
41 |     throw new ConfigurationError(
42 |       "OPENAI_API_KEY environment variable is required for semantic search",
43 |     );
44 |   }
45 | 
46 |   // Create tools pre-bound with the provided API service and organization
47 |   return await callEmbeddedAgent<
48 |     z.output<typeof searchIssuesAgentOutputSchema>,
49 |     typeof searchIssuesAgentOutputSchema
50 |   >({
51 |     system: systemPrompt,
52 |     prompt: options.query,
53 |     tools: {
54 |       issueFields: createDatasetFieldsTool({
55 |         apiService: options.apiService,
56 |         organizationSlug: options.organizationSlug,
57 |         dataset: "search_issues",
58 |         projectId: options.projectId,
59 |       }),
60 |       whoami: createWhoamiTool({ apiService: options.apiService }),
61 |     },
62 |     schema: searchIssuesAgentOutputSchema,
63 |   });
64 | }
65 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/geo.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "geo",
 3 |   "description": "Geo fields can carry data about a specific location related to an event. This geolocation information can be derived from techniques such as Geo IP, or be user-supplied.\nNote: Geo attributes are typically used under another namespace, such as client.* and describe the location of the corresponding entity (device, end-user, etc). Semantic conventions that reference geo attributes (as a root namespace) or embed them (under their own namespace) SHOULD document what geo attributes describe in the scope of that convention.\n",
 4 |   "attributes": {
 5 |     "geo.locality.name": {
 6 |       "description": "Locality name. Represents the name of a city, town, village, or similar populated place.\n",
 7 |       "type": "string",
 8 |       "stability": "development",
 9 |       "examples": ["Montreal", "Berlin"]
10 |     },
11 |     "geo.continent.code": {
12 |       "description": "Two-letter code representing continent’s name.\n",
13 |       "type": "string",
14 |       "stability": "development",
15 |       "examples": ["AF", "AN", "AS", "EU", "NA", "OC", "SA"]
16 |     },
17 |     "geo.country.iso_code": {
18 |       "description": "Two-letter ISO Country Code ([ISO 3166-1 alpha2](https://wikipedia.org/wiki/ISO_3166-1#Codes)).\n",
19 |       "type": "string",
20 |       "stability": "development",
21 |       "examples": ["CA"]
22 |     },
23 |     "geo.location.lon": {
24 |       "description": "Longitude of the geo location in [WGS84](https://wikipedia.org/wiki/World_Geodetic_System#WGS84).\n",
25 |       "type": "number",
26 |       "stability": "development",
27 |       "examples": ["-73.61483"]
28 |     },
29 |     "geo.location.lat": {
30 |       "description": "Latitude of the geo location in [WGS84](https://wikipedia.org/wiki/World_Geodetic_System#WGS84).\n",
31 |       "type": "number",
32 |       "stability": "development",
33 |       "examples": ["45.505918"]
34 |     },
35 |     "geo.postal_code": {
36 |       "description": "Postal code associated with the location. Values appropriate for this field may also be known as a postcode or ZIP code and will vary widely from country to country.\n",
37 |       "type": "string",
38 |       "stability": "development",
39 |       "examples": ["94040"]
40 |     },
41 |     "geo.region.iso_code": {
42 |       "description": "Region ISO code ([ISO 3166-2](https://wikipedia.org/wiki/ISO_3166-2)).\n",
43 |       "type": "string",
44 |       "stability": "development",
45 |       "examples": ["CA-QC"]
46 |     }
47 |   }
48 | }
49 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-dsns.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { setTag } from "@sentry/core";
 2 | import { defineTool } from "../internal/tool-helpers/define";
 3 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 4 | import type { ServerContext } from "../types";
 5 | import {
 6 |   ParamOrganizationSlug,
 7 |   ParamRegionUrl,
 8 |   ParamProjectSlug,
 9 | } from "../schema";
10 | 
11 | export default defineTool({
12 |   name: "find_dsns",
13 |   skills: ["project-management"], // DSN management is part of project setup
14 |   requiredScopes: ["project:read"],
15 |   description: [
16 |     "List all Sentry DSNs for a specific project.",
17 |     "",
18 |     "Use this tool when you need to:",
19 |     "- Retrieve a SENTRY_DSN for a specific project",
20 |     "",
21 |     "<hints>",
22 |     "- If the user passes a parameter in the form of name/otherName, its likely in the format of <organizationSlug>/<projectSlug>.",
23 |     "- If only one parameter is provided, and it could be either `organizationSlug` or `projectSlug`, its probably `organizationSlug`, but if you're really uncertain you might want to call `find_organizations()` first.",
24 |     "</hints>",
25 |   ].join("\n"),
26 |   inputSchema: {
27 |     organizationSlug: ParamOrganizationSlug,
28 |     regionUrl: ParamRegionUrl.nullable().default(null),
29 |     projectSlug: ParamProjectSlug,
30 |   },
31 |   annotations: {
32 |     readOnlyHint: true,
33 |     openWorldHint: true,
34 |   },
35 |   async handler(params, context: ServerContext) {
36 |     const apiService = apiServiceFromContext(context, {
37 |       regionUrl: params.regionUrl ?? undefined,
38 |     });
39 |     const organizationSlug = params.organizationSlug;
40 | 
41 |     setTag("organization.slug", organizationSlug);
42 |     setTag("project.slug", params.projectSlug);
43 | 
44 |     const clientKeys = await apiService.listClientKeys({
45 |       organizationSlug,
46 |       projectSlug: params.projectSlug,
47 |     });
48 |     let output = `# DSNs in **${organizationSlug}/${params.projectSlug}**\n\n`;
49 |     if (clientKeys.length === 0) {
50 |       output +=
51 |         "No DSNs were found.\n\nYou can create new one using the `create_dsn` tool.";
52 |       return output;
53 |     }
54 |     for (const clientKey of clientKeys) {
55 |       output += `## ${clientKey.name}\n`;
56 |       output += `**ID**: ${clientKey.id}\n`;
57 |       output += `**DSN**: ${clientKey.dsn.public}\n\n`;
58 |     }
59 |     output += "# Using this information\n\n";
60 |     output +=
61 |       "- The `SENTRY_DSN` value is a URL that you can use to initialize Sentry's SDKs.\n";
62 |     return output;
63 |   },
64 | });
65 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server/src/transports/stdio.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Standard I/O Transport for MCP Server.
 3 |  *
 4 |  * Provides stdio-based communication for the Sentry MCP server, typically used
 5 |  * when the server runs as a subprocess communicating via stdin/stdout pipes.
 6 |  *
 7 |  * @example Basic Usage
 8 |  * ```typescript
 9 |  * import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10 |  * import { startStdio } from "./transports/stdio.js";
11 |  *
12 |  * const server = new Server();
13 |  * const context = {
14 |  *   accessToken: process.env.SENTRY_TOKEN,
15 |  *   host: "sentry.io"
16 |  * };
17 |  *
18 |  * await startStdio(server, context);
19 |  * ```
20 |  */
21 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23 | import * as Sentry from "@sentry/node";
24 | import { LIB_VERSION } from "@sentry/mcp-core/version";
25 | import type { ServerContext } from "@sentry/mcp-core/types";
26 | 
27 | /**
28 |  * Starts the MCP server with stdio transport and telemetry.
29 |  *
30 |  * Connects the server using stdio transport for process-based communication.
31 |  * Context is already captured in tool handler closures during buildServer().
32 |  * All operations are wrapped in Sentry tracing for observability.
33 |  *
34 |  * @param server - Configured and instrumented MCP server instance (with context in closures)
35 |  * @param context - Server context with authentication and configuration (for telemetry attributes)
36 |  *
37 |  * @example CLI Integration
38 |  * ```typescript
39 |  * import { buildServer } from "./server.js";
40 |  * import { startStdio } from "./transports/stdio.js";
41 |  *
42 |  * const context = {
43 |  *   accessToken: userToken,
44 |  *   sentryHost: "sentry.io",
45 |  *   userId: "user-123",
46 |  *   clientId: "cursor-ide",
47 |  *   constraints: {}
48 |  * };
49 |  *
50 |  * const server = buildServer({ context }); // Context captured in closures
51 |  * await startStdio(server, context);
52 |  * ```
53 |  */
54 | export async function startStdio(server: McpServer, context: ServerContext) {
55 |   await Sentry.startNewTrace(async () => {
56 |     return await Sentry.startSpan(
57 |       {
58 |         name: "mcp.server/stdio",
59 |         attributes: {
60 |           "mcp.transport": "stdio",
61 |           "network.transport": "pipe",
62 |           "service.version": LIB_VERSION,
63 |         },
64 |       },
65 |       async () => {
66 |         // Context already captured in tool handler closures during buildServer()
67 |         const transport = new StdioServerTransport();
68 |         await server.connect(transport);
69 |       },
70 |     );
71 |   });
72 | }
73 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-teams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { setTag } from "@sentry/core";
 2 | import { defineTool } from "../internal/tool-helpers/define";
 3 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 4 | import { UserInputError } from "../errors";
 5 | import type { ServerContext } from "../types";
 6 | import {
 7 |   ParamOrganizationSlug,
 8 |   ParamRegionUrl,
 9 |   ParamSearchQuery,
10 | } from "../schema";
11 | 
12 | const RESULT_LIMIT = 25;
13 | 
14 | export default defineTool({
15 |   name: "find_teams",
16 |   skills: ["inspect", "triage", "project-management"], // Team viewing and management
17 |   requiredScopes: ["team:read"],
18 |   description: [
19 |     "Find teams in an organization in Sentry.",
20 |     "",
21 |     "Use this tool when you need to:",
22 |     "- View teams in a Sentry organization",
23 |     "- Find a team's slug to aid other tool requests",
24 |     "- Search for specific teams by name or slug",
25 |     "",
26 |     `Returns up to ${RESULT_LIMIT} results. If you hit this limit, use the query parameter to narrow down results.`,
27 |   ].join("\n"),
28 |   inputSchema: {
29 |     organizationSlug: ParamOrganizationSlug,
30 |     regionUrl: ParamRegionUrl.nullable().default(null),
31 |     query: ParamSearchQuery.nullable().default(null),
32 |   },
33 |   annotations: {
34 |     readOnlyHint: true,
35 |     openWorldHint: true,
36 |   },
37 |   async handler(params, context: ServerContext) {
38 |     const apiService = apiServiceFromContext(context, {
39 |       regionUrl: params.regionUrl ?? undefined,
40 |     });
41 |     const organizationSlug = params.organizationSlug;
42 | 
43 |     if (!organizationSlug) {
44 |       throw new UserInputError(
45 |         "Organization slug is required. Please provide an organizationSlug parameter.",
46 |       );
47 |     }
48 | 
49 |     setTag("organization.slug", organizationSlug);
50 | 
51 |     const teams = await apiService.listTeams(organizationSlug, {
52 |       query: params.query ?? undefined,
53 |     });
54 | 
55 |     let output = `# Teams in **${organizationSlug}**\n\n`;
56 | 
57 |     if (params.query) {
58 |       output += `**Search query:** "${params.query}"\n\n`;
59 |     }
60 | 
61 |     if (teams.length === 0) {
62 |       output += params.query
63 |         ? `No teams found matching "${params.query}".\n`
64 |         : "No teams found.\n";
65 |       return output;
66 |     }
67 | 
68 |     output += teams.map((team) => `- ${team.slug}\n`).join("");
69 | 
70 |     if (teams.length === RESULT_LIMIT) {
71 |       output += `\n---\n\n**Note:** Showing ${RESULT_LIMIT} results (maximum). There may be more teams available. Use the \`query\` parameter to search for specific teams.`;
72 |     }
73 | 
74 |     return output;
75 |   },
76 | });
77 | 
```

--------------------------------------------------------------------------------
/docs/github-actions.md:
--------------------------------------------------------------------------------

```markdown
 1 | # GitHub Actions
 2 | 
 3 | CI/CD workflows for the Sentry MCP project.
 4 | 
 5 | ## Workflows
 6 | 
 7 | ### test.yml
 8 | Runs on all pushes to main and pull requests:
 9 | - Build, lint, unit tests
10 | - Code coverage reporting
11 | 
12 | ### deploy.yml
13 | Runs after tests pass on main branch:
14 | - **Canary deployment**: Deploy to `sentry-mcp-canary` worker with isolated resources
15 | - **Smoke tests**: Test canary deployment
16 | - **Production deployment**: Deploy to `sentry-mcp` worker (only if canary tests pass)
17 | - **Production smoke tests**: Test production deployment
18 | - **Automatic rollback**: Rollback production if smoke tests fail
19 | 
20 | ### eval.yml
21 | Runs evaluation tests against the MCP server.
22 | 
23 | ## Required Secrets
24 | 
25 | Repository secrets (no environment needed):
26 | 
27 | - **`CLOUDFLARE_API_TOKEN`** - Cloudflare API token with Workers deployment permissions
28 | - **`CLOUDFLARE_ACCOUNT_ID`** - Your Cloudflare account ID  
29 | - **`SENTRY_AUTH_TOKEN`** - For Sentry release tracking
30 | - **`SENTRY_CLIENT_SECRET`** - Sentry OAuth client secret
31 | - **`COOKIE_SECRET`** - Session cookie encryption secret
32 | - **`OPENAI_API_KEY`** - For AI-powered search features
33 | 
34 | ## Deployment Architecture
35 | 
36 | ### Workers
37 | - **`sentry-mcp`** - Production worker at `https://mcp.sentry.dev`
38 | - **`sentry-mcp-canary`** - Canary worker at `https://sentry-mcp-canary.getsentry.workers.dev`
39 | 
40 | ### Resource Isolation
41 | Canary and production use separate resources for complete isolation:
42 | 
43 | | Resource | Production | Canary |
44 | |----------|------------|---------|
45 | | KV Namespace | `8dd5e9bafe1945298e2d5ca3b408a553` | `a3fe0d23b2d34416930e284362a88a3b` |
46 | | Rate Limiter IDs | `1001`, `1002` | `2001`, `2002` |
47 | | Wrangler Config | `wrangler.jsonc` | `wrangler.canary.jsonc` |
48 | 
49 | ### Deployment Flow
50 | 1. **Build once** - Single build for both deployments
51 | 2. **Deploy canary** - `wrangler deploy --config wrangler.canary.jsonc`
52 | 3. **Wait 30s** - Allow propagation
53 | 4. **Test canary** - Run smoke tests against canary worker
54 | 5. **Deploy production** - `wrangler deploy` (only if canary tests pass)
55 | 6. **Wait 30s** - Allow propagation  
56 | 7. **Test production** - Run smoke tests against production worker
57 | 8. **Rollback** - `wrangler rollback` if production tests fail
58 | 
59 | ## Manual Deployment
60 | 
61 | Trigger via GitHub Actions → Deploy to Cloudflare → "Run workflow"
62 | 
63 | ## Troubleshooting
64 | 
65 | 1. **Authentication failed** - Check `CLOUDFLARE_API_TOKEN` permissions
66 | 2. **Build failures** - Review TypeScript/build logs
67 | 3. **Smoke test failures** - Check worker logs in Cloudflare dashboard
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-projects.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { setTag } from "@sentry/core";
 2 | import { defineTool } from "../internal/tool-helpers/define";
 3 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 4 | import { UserInputError } from "../errors";
 5 | import type { ServerContext } from "../types";
 6 | import {
 7 |   ParamOrganizationSlug,
 8 |   ParamRegionUrl,
 9 |   ParamSearchQuery,
10 | } from "../schema";
11 | import { ALL_SKILLS } from "../skills";
12 | 
13 | const RESULT_LIMIT = 25;
14 | 
15 | export default defineTool({
16 |   name: "find_projects",
17 |   skills: ALL_SKILLS, // Foundational tool - available to all skills
18 |   requiredScopes: ["project:read"],
19 |   description: [
20 |     "Find projects in Sentry.",
21 |     "",
22 |     "Use this tool when you need to:",
23 |     "- View projects in a Sentry organization",
24 |     "- Find a project's slug to aid other tool requests",
25 |     "- Search for specific projects by name or slug",
26 |     "",
27 |     `Returns up to ${RESULT_LIMIT} results. If you hit this limit, use the query parameter to narrow down results.`,
28 |   ].join("\n"),
29 |   inputSchema: {
30 |     organizationSlug: ParamOrganizationSlug,
31 |     regionUrl: ParamRegionUrl.nullable().default(null),
32 |     query: ParamSearchQuery.nullable().default(null),
33 |   },
34 |   annotations: {
35 |     readOnlyHint: true,
36 |     openWorldHint: true,
37 |   },
38 |   async handler(params, context: ServerContext) {
39 |     const apiService = apiServiceFromContext(context, {
40 |       regionUrl: params.regionUrl ?? undefined,
41 |     });
42 |     const organizationSlug = params.organizationSlug;
43 | 
44 |     if (!organizationSlug) {
45 |       throw new UserInputError(
46 |         "Organization slug is required. Please provide an organizationSlug parameter.",
47 |       );
48 |     }
49 | 
50 |     setTag("organization.slug", organizationSlug);
51 | 
52 |     const projects = await apiService.listProjects(organizationSlug, {
53 |       query: params.query ?? undefined,
54 |     });
55 | 
56 |     let output = `# Projects in **${organizationSlug}**\n\n`;
57 | 
58 |     if (params.query) {
59 |       output += `**Search query:** "${params.query}"\n\n`;
60 |     }
61 | 
62 |     if (projects.length === 0) {
63 |       output += params.query
64 |         ? `No projects found matching "${params.query}".\n`
65 |         : "No projects found.\n";
66 |       return output;
67 |     }
68 | 
69 |     output += projects.map((project) => `- **${project.slug}**\n`).join("");
70 | 
71 |     if (projects.length === RESULT_LIMIT) {
72 |       output += `\n---\n\n**Note:** Showing ${RESULT_LIMIT} results (maximum). There may be more projects available. Use the \`query\` parameter to search for specific projects.`;
73 |     }
74 | 
75 |     return output;
76 |   },
77 | });
78 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/keys-copy.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | export default function KeysCopy({ step }: { step?: number }) {
 2 |   return (
 3 |     <div className="-translate-1/2 absolute top-1/2 left-1/2 z-10 flex items-center gap-3 pointer-events-none">
 4 |       <div
 5 |         className={`${
 6 |           step === 0 && "animate-keycap"
 7 |         } relative size-fit opacity-0 translate-x-8 -translate-y-4`}
 8 |         style={{ ["--delay" as any]: "1.00s" }}
 9 |       >
10 |         {/* <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background" /> */}
11 |         <div className="[mask-image:url('/keycap-⌘.png')] [mask-size:100%] bg-clip-content size-fit relative [filter:drop-shadow(inset_0_-1rem_1rem_rgba(0,0,0,1))] translate-y-2">
12 |           <div className="absolute bottom-0 left-1/2 h-[69%] w-[75%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl rounded-tr-2xl bg-background/50 -z-10" />
13 |           <img
14 |             className={`${
15 |               step === 0 && "animate-keycap-inner-meta"
16 |             } bg-clip-content select-none`}
17 |             src="/keycap-⌘.png"
18 |             draggable={false}
19 |             alt="keycap-⌘"
20 |             style={{ ["--delay" as any]: "1.20s" }}
21 |           />
22 |           <div className="absolute inset-0 bg-gradient-to-t from-black/50 via-30% via-transparent to-transparent z-10" />
23 |         </div>
24 |       </div>
25 |       <div
26 |         className={`${
27 |           step === 0 && "animate-keycap"
28 |         } relative size-fit opacity-0 -translate-x-8 translate-y-4`}
29 |         style={{ ["--delay" as any]: "1.15s" }}
30 |       >
31 |         {/* <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background" /> */}
32 |         <div className="[mask-image:url('/keycap-c.png')] [mask-size:100%] bg-clip-content size-fit relative [filter:drop-shadow(inset_0_-1rem_1rem_rgba(0,0,0,1))] translate-y-2">
33 |           <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background/50 -z-10" />
34 |           <img
35 |             className={`${
36 |               step === 0 && "animate-keycap-inner"
37 |             } bg-clip-content select-none`}
38 |             src="/keycap-c.png"
39 |             draggable={false}
40 |             alt="keycap-c"
41 |             style={{ ["--delay" as any]: "1.35s" }}
42 |           />
43 |           <div className="absolute inset-0 bg-gradient-to-t from-black/50 via-30% via-transparent to-transparent z-10" />
44 |         </div>
45 |       </div>
46 |     </div>
47 |   );
48 | }
49 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/terminal-ui/keys-paste.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | export default function KeysPaste({ step }: { step?: number }) {
 2 |   return (
 3 |     <div className="-translate-1/2 absolute top-1/2 left-1/2 z-50 hidden items-center gap-3 md:flex pointer-events-none">
 4 |       <div
 5 |         className={`${
 6 |           step === 0 && "animate-keycap"
 7 |         } relative size-fit opacity-0 translate-x-8 -translate-y-4`}
 8 |         style={{ ["--delay" as any]: "1.75s" }}
 9 |       >
10 |         {/* <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background" /> */}
11 |         <div className="[mask-image:url('/keycap-⌘.png')] [mask-size:100%] bg-clip-content size-fit relative [filter:drop-shadow(inset_0_-1rem_1rem_rgba(0,0,0,1))] translate-y-2">
12 |           <div className="absolute bottom-0 left-1/2 h-[69%] w-[75%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl rounded-tr-2xl bg-background/50 -z-10" />
13 |           <img
14 |             className={`${
15 |               step === 0 && "animate-keycap-inner-meta"
16 |             } bg-clip-content select-none`}
17 |             src="/keycap-⌘.png"
18 |             draggable={false}
19 |             alt="keycap-⌘"
20 |             style={{ ["--delay" as any]: "2.05s" }}
21 |           />
22 |           <div className="absolute inset-0 bg-gradient-to-t from-black/50 via-30% via-transparent to-transparent z-10" />
23 |         </div>
24 |       </div>
25 |       <div
26 |         className={`${
27 |           step === 0 && "animate-keycap"
28 |         } relative size-fit opacity-0 -translate-x-8 translate-y-4`}
29 |         style={{ ["--delay" as any]: "2.00s" }}
30 |       >
31 |         {/* <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background" /> */}
32 |         <div className="[mask-image:url('/keycap-v.png')] [mask-size:100%] bg-clip-content size-fit relative [filter:drop-shadow(inset_0_-1rem_1rem_rgba(0,0,0,1))] translate-y-2">
33 |           <div className="absolute bottom-0 left-1/2 h-[69%] w-[72.5%] -translate-x-[51%] rotate-x-50 rotate-z-27 rounded-3xl rounded-br-2xl bg-background/50 -z-10" />
34 |           <img
35 |             className={`${
36 |               step === 0 && "animate-keycap-inner"
37 |             } bg-clip-content select-none`}
38 |             src="/keycap-v.png"
39 |             draggable={false}
40 |             alt="keycap-v"
41 |             style={{ ["--delay" as any]: "2.30s" }}
42 |           />
43 |           <div className="absolute inset-0 bg-gradient-to-t from-black/50 via-30% via-transparent to-transparent z-10" />
44 |         </div>
45 |       </div>
46 |     </div>
47 |   );
48 | }
49 | 
```

--------------------------------------------------------------------------------
/docs/quality-checks.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Quality Checks
 2 | 
 3 | Required quality checks that MUST pass before completing any code changes.
 4 | 
 5 | ## Critical Quality Checks
 6 | 
 7 | **After ANY code changes, you MUST run:**
 8 | 
 9 | ```bash
10 | pnpm -w run lint:fix    # Fix linting issues
11 | pnpm tsc --noEmit       # Check TypeScript types
12 | pnpm test               # Run all tests
13 | ```
14 | 
15 | **DO NOT proceed if any check fails.**
16 | 
17 | ## Tool Testing Requirements
18 | 
19 | **ALL tools MUST have comprehensive tests that verify:**
20 | 
21 | - **Input validation** - Required/optional parameters, type checking, edge cases
22 | - **Output formatting** - Markdown structure, content accuracy, error messages
23 | - **API integration** - Mock server responses, error handling, parameter passing
24 | - **Snapshot testing** - Use inline snapshots to verify formatted output
25 | 
26 | **Required test patterns:**
27 | - Unit tests in individual `{tool-name}.test.ts` files using Vitest and MSW mocks
28 | - Input/output validation with inline snapshots
29 | - Error case testing (API failures, invalid params)
30 | - Mock server setup in `packages/mcp-server-mocks`
31 | 
32 | See `docs/testing.md` for detailed testing patterns and `docs/adding-tools.md` for the testing workflow.
33 | 
34 | ## Tool Count Limits
35 | 
36 | **IMPORTANT**: AI agents have a hard cap of 45 total tools. Sentry MCP must:
37 | - Target ~20 tools (current best practice)
38 | - Never exceed 25 tools (absolute maximum)
39 | - This limit exists in Cursor and possibly other tools
40 | 
41 | **Current status**: 19 tools (within target range)
42 | 
43 | ## Build Verification
44 | 
45 | Ensure the build process works correctly:
46 | 
47 | ```bash
48 | npm run build              # Build all packages
49 | npm run generate-tool-definitions  # Generate tool definitions
50 | ```
51 | 
52 | Tool definitions must generate without errors for client consumption.
53 | 
54 | ## Code Quality Standards
55 | 
56 | - **TypeScript strict mode** - All code must compile without errors
57 | - **Linting compliance** - Follow established code style patterns
58 | - **Test coverage** - All new tools must have comprehensive tests
59 | - **Error handling** - Use patterns from `common-patterns.md#error-handling`
60 | - **API patterns** - Follow patterns from `api-patterns.md`
61 | 
62 | ## Pre-Commit Checklist
63 | 
64 | Before completing any task:
65 | 
66 | - [ ] All quality checks pass (`pnpm -w run lint:fix`, `pnpm tsc --noEmit`, `pnpm test`)
67 | - [ ] Tool count within limits (≤20 target, ≤25 absolute max)
68 | - [ ] New tools have comprehensive tests
69 | - [ ] Build process generates tool definitions successfully
70 | - [ ] Documentation updated if patterns changed
71 | 
72 | ## References
73 | 
74 | - Testing patterns: `testing.md`
75 | - Tool development: `adding-tools.md`
76 | - Code patterns: `common-patterns.md`
77 | - API usage: `api-patterns.md`
78 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/oauth/state.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | 
  3 | // Minimal, stateless HMAC-signed OAuth state utilities
  4 | // Format: `${signatureHex}.${base64(payloadJson)}`
  5 | 
  6 | // Safe envelope: keep the full downstream AuthRequest (+permissions) under `req`
  7 | // and include only iat/exp metadata at top-level to avoid collisions.
  8 | export const OAuthStateSchema = z.object({
  9 |   req: z.record(z.unknown()),
 10 |   iat: z.number().int(),
 11 |   exp: z.number().int(),
 12 | });
 13 | 
 14 | export type OAuthState = z.infer<typeof OAuthStateSchema> & {
 15 |   req: Record<string, unknown>;
 16 | };
 17 | 
 18 | async function importKey(secret: string): Promise<CryptoKey> {
 19 |   if (!secret) {
 20 |     throw new Error(
 21 |       "COOKIE_SECRET is not defined. A secret key is required for signing state.",
 22 |     );
 23 |   }
 24 |   const enc = new TextEncoder();
 25 |   return crypto.subtle.importKey(
 26 |     "raw",
 27 |     enc.encode(secret),
 28 |     { name: "HMAC", hash: "SHA-256" },
 29 |     false,
 30 |     ["sign", "verify"],
 31 |   );
 32 | }
 33 | 
 34 | async function signHex(key: CryptoKey, data: string): Promise<string> {
 35 |   const enc = new TextEncoder();
 36 |   const signatureBuffer = await crypto.subtle.sign(
 37 |     "HMAC",
 38 |     key,
 39 |     enc.encode(data),
 40 |   );
 41 |   return Array.from(new Uint8Array(signatureBuffer))
 42 |     .map((b) => b.toString(16).padStart(2, "0"))
 43 |     .join("");
 44 | }
 45 | 
 46 | async function verifyHex(
 47 |   key: CryptoKey,
 48 |   signatureHex: string,
 49 |   data: string,
 50 | ): Promise<boolean> {
 51 |   try {
 52 |     const enc = new TextEncoder();
 53 |     const signatureBytes = new Uint8Array(
 54 |       signatureHex.match(/.{1,2}/g)!.map((byte) => Number.parseInt(byte, 16)),
 55 |     );
 56 |     return await crypto.subtle.verify(
 57 |       "HMAC",
 58 |       key,
 59 |       signatureBytes.buffer,
 60 |       enc.encode(data),
 61 |     );
 62 |   } catch {
 63 |     return false;
 64 |   }
 65 | }
 66 | 
 67 | export async function signState(
 68 |   payload: OAuthState,
 69 |   secret: string,
 70 | ): Promise<string> {
 71 |   const key = await importKey(secret);
 72 |   const json = JSON.stringify(payload);
 73 |   const sig = await signHex(key, json);
 74 |   // Using standard base64 to match other usage in the codebase
 75 |   const b64 = btoa(json);
 76 |   return `${sig}.${b64}`;
 77 | }
 78 | 
 79 | export async function verifyAndParseState(
 80 |   compact: string,
 81 |   secret: string,
 82 | ): Promise<OAuthState> {
 83 |   const [sig, b64] = compact.split(".");
 84 |   if (!sig || !b64) {
 85 |     throw new Error("Invalid state format");
 86 |   }
 87 |   const json = atob(b64);
 88 |   const key = await importKey(secret);
 89 |   const ok = await verifyHex(key, sig, json);
 90 |   if (!ok) {
 91 |     throw new Error("Invalid state signature");
 92 |   }
 93 |   const parsed = OAuthStateSchema.parse(JSON.parse(json));
 94 |   const now = Date.now();
 95 |   if (parsed.exp <= now) {
 96 |     throw new Error("State expired");
 97 |   }
 98 |   return parsed;
 99 | }
100 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/WindowHeader.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   ArrowLeft,
 3 |   ArrowRight,
 4 |   ChevronLeft,
 5 |   ChevronRight,
 6 |   Copy,
 7 |   Lock,
 8 |   PanelBottom,
 9 |   PanelLeft,
10 |   PanelRight,
11 |   Plus,
12 |   RotateCcw,
13 |   Search,
14 |   Settings,
15 |   Share,
16 | } from "lucide-react";
17 | 
18 | export default function WindowHeader({
19 |   step,
20 |   ide = false,
21 | }: {
22 |   step?: number;
23 |   ide?: boolean;
24 | }) {
25 |   return (
26 |     <div
27 |       className={`flex items-center gap-2 max-w-full p-2 pr-2 pl-4 ${
28 |         ide && "pr-1"
29 |       }`}
30 |     >
31 |       <div className="size-3 flex-shrink-0 rounded-full border border-white/20 bg-pink-300/50" />
32 |       <div className="size-3 flex-shrink-0 rounded-full border border-white/20 bg-amber-300/50" />
33 |       <div className="mr-4 size-3 flex-shrink-0 rounded-full border border-white/20 bg-emerald-300/50" />
34 |       {/*<PanelLeft />*/}
35 |       {ide ? (
36 |         <>
37 |           <ArrowLeft className="size-5" />
38 |           <ArrowRight className="size-5" />
39 |         </>
40 |       ) : (
41 |         <>
42 |           <ChevronLeft className="size-5" />
43 |           <ChevronRight className="size-5" />
44 |         </>
45 |       )}
46 |       {/*<ShieldCheck className="ml-auto"/>*/}
47 |       <div
48 |         className={`mx-auto flex items-center ${
49 |           ide && "justify-center"
50 |         } h-8 w-full max-w-1/2 cursor-pointer gap-2 rounded-xl border border-white/20 bg-white/5 p-3 duration-200 hover:bg-white/10 active:bg-white/50 active:duration-75`}
51 |         style={{ ["--delay" as any]: "0.45s" }}
52 |       >
53 |         {!ide && <Lock className="size-4 flex-shrink-0" />}
54 |         {ide && <Search className="size-4 flex-shrink-0" />}
55 |         <div className="truncate relative">
56 |           <div
57 |             className={`absolute w-full h-[1lh] bg-pink-500 -z-10 ${
58 |               step === 0
59 |                 ? "animate-select opacity-100"
60 |                 : "opacity-0 duration-300"
61 |             }`}
62 |           />
63 |           {ide
64 |             ? "Search"
65 |             : "https://sentry.sentry.io/issues/6811213890/?environment=cloudflare&project=4509062593708032&query=is%3Aunresolved&referrer=issue-stream&seerDrawer=true"}
66 |         </div>
67 |         {!ide && <RotateCcw className="size-4 flex-shrink-0" />}
68 |       </div>
69 |       {ide ? (
70 |         <>
71 |           <PanelLeft className="ml-1 size-4" />
72 |           <PanelBottom className="ml-1 hidden size-4 2xl:block" />
73 |           <PanelRight className="ml-1 hidden size-4 2xl:block" />
74 |           <Settings className="mr-2 ml-1 size-4" />
75 |         </>
76 |       ) : (
77 |         <>
78 |           <Share className="ml-2 size-4" />
79 |           <Plus className="ml-2 hidden size-4 2xl:block" />
80 |           <Copy className="mr-3 ml-2 size-4" />
81 |         </>
82 |       )}
83 |     </div>
84 |   );
85 | }
86 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "sentry-mcp",
 3 |   "version": "0.0.1",
 4 |   "private": true,
 5 |   "type": "module",
 6 |   "packageManager": "[email protected]",
 7 |   "engines": {
 8 |     "node": ">=20"
 9 |   },
10 |   "license": "FSL-1.1-ALv2",
11 |   "author": "Sentry",
12 |   "description": "Sentry MCP Server",
13 |   "homepage": "https://github.com/getsentry/sentry-mcp",
14 |   "keywords": ["sentry"],
15 |   "bugs": {
16 |     "url": "https://github.com/getsentry/sentry-mcp/issues"
17 |   },
18 |   "repository": {
19 |     "type": "git",
20 |     "url": "[email protected]:getsentry/sentry-mcp.git"
21 |   },
22 |   "scripts": {
23 |     "docs:check": "node scripts/check-doc-links.mjs",
24 |     "dev": "dotenv -e .env -e .env.local -- turbo dev --filter=!@sentry/mcp-server",
25 |     "dev:stdio": "dotenv -e .env -e .env.local -- turbo dev --filter=!@sentry/mcp-cloudflare",
26 |     "build": "turbo build after-build",
27 |     "deploy": "turbo deploy",
28 |     "eval": "dotenv -e .env -e .env.local -- turbo eval",
29 |     "eval:ci": "CI=true dotenv -e .env -e .env.local -- pnpm --stream -r run eval:ci",
30 |     "format": "biome format --write",
31 |     "lint": "biome lint",
32 |     "lint:fix": "biome lint --fix",
33 |     "inspector": "pnpx @modelcontextprotocol/inspector@latest",
34 |     "measure-tokens": "pnpm run --filter ./packages/mcp-core measure-tokens",
35 |     "prepare": "simple-git-hooks",
36 |     "cli": "pnpm run --filter ./packages/mcp-test-client start",
37 |     "start:stdio": "pnpm --stream run --filter ./packages/mcp-server start",
38 |     "test": "dotenv -e .env -e .env.local -- turbo test",
39 |     "test:ci": "CI=true dotenv -e .env -e .env.local -- pnpm --stream -r run test:ci",
40 |     "test:watch": "dotenv -e .env -e .env.local -- turbo test:watch",
41 |     "tsc": "turbo tsc"
42 |   },
43 |   "dependencies": {
44 |     "@biomejs/biome": "catalog:",
45 |     "@types/node": "catalog:",
46 |     "@vitest/coverage-v8": "catalog:",
47 |     "dotenv": "catalog:",
48 |     "dotenv-cli": "catalog:",
49 |     "lint-staged": "catalog:",
50 |     "simple-git-hooks": "catalog:",
51 |     "tsdown": "catalog:",
52 |     "tsx": "catalog:",
53 |     "turbo": "catalog:",
54 |     "typescript": "catalog:",
55 |     "vitest": "catalog:",
56 |     "vitest-evals": "catalog:"
57 |   },
58 |   "simple-git-hooks": {
59 |     "pre-commit": "pnpm exec lint-staged --concurrent false"
60 |   },
61 |   "lint-staged": {
62 |     "*": [
63 |       "biome format --write --no-errors-on-unmatched --files-ignore-unknown=true",
64 |       "biome lint --fix --no-errors-on-unmatched --files-ignore-unknown=true"
65 |     ]
66 |   },
67 |   "pnpm": {
68 |     "onlyBuiltDependencies": [
69 |       "@biomejs/biome",
70 |       "better-sqlite3",
71 |       "esbuild",
72 |       "sharp",
73 |       "simple-git-hooks",
74 |       "workerd"
75 |     ]
76 |   },
77 |   "devDependencies": {
78 |     "@types/json-schema": "^7.0.15"
79 |   }
80 | }
81 | 
```

--------------------------------------------------------------------------------
/scripts/check-doc-links.mjs:
--------------------------------------------------------------------------------

```
 1 | #!/usr/bin/env node
 2 | import { readdirSync, readFileSync, statSync } from "node:fs";
 3 | import { join, resolve } from "node:path";
 4 | 
 5 | const root = resolve(process.cwd());
 6 | const docsDir = join(root, "docs");
 7 | 
 8 | /** Recursively collect docs files */
 9 | function walk(dir) {
10 |   const entries = readdirSync(dir);
11 |   const files = [];
12 |   for (const e of entries) {
13 |     const p = join(dir, e);
14 |     const s = statSync(p);
15 |     if (s.isDirectory()) files.push(...walk(p));
16 |     else if (e.endsWith(".md") || e.endsWith(".mdc")) files.push(p);
17 |   }
18 |   return files;
19 | }
20 | 
21 | const files = walk(docsDir);
22 | 
23 | const problems = [];
24 | 
25 | for (const file of files) {
26 |   const rel = file.slice(root.length + 1);
27 |   const content = readFileSync(file, "utf8");
28 | 
29 |   // Strip fenced code blocks to avoid false positives in examples
30 |   const contentNoFences = content.replace(/```[\s\S]*?```/g, "");
31 | 
32 |   // 1) Flag local Markdown links like [text](./file.md) or [text](../file.mdc)
33 |   const localLinkRe = /\[[^\]]+\]\((\.\.?\/[^)]+)\)/g;
34 |   for (const m of contentNoFences.matchAll(localLinkRe)) {
35 |     // Skip illustrative placeholders
36 |     if (m[1].includes("...")) continue;
37 |     problems.push({
38 |       file: rel,
39 |       type: "local-markdown-link",
40 |       message: `Use @path for local docs instead of Markdown links: ${m[0]}`,
41 |     });
42 |   }
43 | 
44 |   // 1b) Flag Markdown links that point to @path
45 |   const atMarkdownLinkRe = /\[[^\]]+\]\(@[^)]+\)/g;
46 |   for (const m of contentNoFences.matchAll(atMarkdownLinkRe)) {
47 |     problems.push({
48 |       file: rel,
49 |       type: "atpath-markdown-link",
50 |       message: `Do not wrap @paths in Markdown links: ${m[0]}`,
51 |     });
52 |   }
53 | 
54 |   // 2) Validate @path references point to real files (only for clear file tokens)
55 |   // Matches @path segments with known extensions or obvious repo files
56 |   const atPathRe = /@([A-Za-z0-9_.\-\/]+\.(?:mdc|md|ts|tsx|js|json))/g;
57 |   for (const m of contentNoFences.matchAll(atPathRe)) {
58 |     const relPath = m[1];
59 |     const abs = join(root, relPath);
60 |     try {
61 |       const st = statSync(abs);
62 |       if (!st.isFile()) {
63 |         problems.push({
64 |           file: rel,
65 |           type: "missing-file",
66 |           message: `@${relPath} is not a file`,
67 |         });
68 |       }
69 |     } catch {
70 |       problems.push({
71 |         file: rel,
72 |         type: "missing-file",
73 |         message: `@${relPath} does not exist`,
74 |       });
75 |     }
76 |   }
77 | }
78 | 
79 | if (problems.length) {
80 |   console.error("[docs:check] Problems found:\n");
81 |   for (const p of problems) {
82 |     console.error(`- ${p.type}: ${p.file} -> ${p.message}`);
83 |   }
84 |   process.exit(1);
85 | } else {
86 |   console.log("[docs:check] OK: no local Markdown links and all @paths exist.");
87 | }
88 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/create-dsn.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | import { setTag } from "@sentry/core";
 3 | import { defineTool } from "../internal/tool-helpers/define";
 4 | import { apiServiceFromContext } from "../internal/tool-helpers/api";
 5 | import type { ServerContext } from "../types";
 6 | import {
 7 |   ParamOrganizationSlug,
 8 |   ParamRegionUrl,
 9 |   ParamProjectSlug,
10 | } from "../schema";
11 | 
12 | export default defineTool({
13 |   name: "create_dsn",
14 |   skills: ["project-management"], // Only available in project-management skill
15 |   requiredScopes: ["project:write"],
16 |   description: [
17 |     "Create an additional DSN for an EXISTING project.",
18 |     "",
19 |     "USE THIS TOOL WHEN:",
20 |     "- Project already exists and needs additional DSN",
21 |     "- 'Create another DSN for project X'",
22 |     "- 'I need a production DSN for existing project'",
23 |     "",
24 |     "DO NOT USE for new projects (use create_project instead)",
25 |     "",
26 |     "Be careful when using this tool!",
27 |     "",
28 |     "<examples>",
29 |     "### Create additional DSN for existing project",
30 |     "```",
31 |     "create_dsn(organizationSlug='my-organization', projectSlug='my-project', name='Production')",
32 |     "```",
33 |     "</examples>",
34 |     "",
35 |     "<hints>",
36 |     "- If the user passes a parameter in the form of name/otherName, its likely in the format of <organizationSlug>/<projectSlug>.",
37 |     "- If any parameter is ambiguous, you should clarify with the user what they meant.",
38 |     "</hints>",
39 |   ].join("\n"),
40 |   inputSchema: {
41 |     organizationSlug: ParamOrganizationSlug,
42 |     regionUrl: ParamRegionUrl.nullable().default(null),
43 |     projectSlug: ParamProjectSlug,
44 |     name: z
45 |       .string()
46 |       .trim()
47 |       .describe("The name of the DSN to create, for example 'Production'."),
48 |   },
49 |   annotations: {
50 |     readOnlyHint: false,
51 |     destructiveHint: false,
52 |     openWorldHint: true,
53 |   },
54 |   async handler(params, context: ServerContext) {
55 |     const apiService = apiServiceFromContext(context, {
56 |       regionUrl: params.regionUrl ?? undefined,
57 |     });
58 |     const organizationSlug = params.organizationSlug;
59 | 
60 |     setTag("organization.slug", organizationSlug);
61 |     setTag("project.slug", params.projectSlug);
62 | 
63 |     const clientKey = await apiService.createClientKey({
64 |       organizationSlug,
65 |       projectSlug: params.projectSlug,
66 |       name: params.name,
67 |     });
68 |     let output = `# New DSN in **${organizationSlug}/${params.projectSlug}**\n\n`;
69 |     output += `**DSN**: ${clientKey.dsn.public}\n`;
70 |     output += `**Name**: ${clientKey.name}\n\n`;
71 |     output += "# Using this information\n\n";
72 |     output +=
73 |       "- The `SENTRY_DSN` value is a URL that you can use to initialize Sentry's SDKs.\n";
74 |     return output;
75 |   },
76 | });
77 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/openai-provider.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createOpenAI } from "@ai-sdk/openai";
 2 | import type { LanguageModelV1 } from "ai";
 3 | import { USER_AGENT } from "../../version";
 4 | 
 5 | // Default configuration constants
 6 | const DEFAULT_OPENAI_MODEL = "gpt-5";
 7 | const VALID_REASONING_EFFORTS = ["low", "medium", "high"] as const;
 8 | const DEFAULT_REASONING_EFFORT: (typeof VALID_REASONING_EFFORTS)[number] =
 9 |   "low";
10 | 
11 | type ReasoningEffort = (typeof VALID_REASONING_EFFORTS)[number];
12 | 
13 | // Module-level state for baseURL (set only via explicit configuration, not env vars)
14 | let configuredBaseUrl: string | undefined;
15 | 
16 | /**
17 |  * Configure the OpenAI base URL (CLI flag only, not environment variable).
18 |  * This must be called explicitly - it cannot be set via environment variables for security.
19 |  */
20 | export function setOpenAIBaseUrl(baseUrl: string | undefined): void {
21 |   configuredBaseUrl = baseUrl;
22 | }
23 | 
24 | /**
25 |  * Retrieve an OpenAI language model configured from environment variables and explicit config.
26 |  *
27 |  * Configuration:
28 |  * - OPENAI_MODEL: Model to use (default: "gpt-5") - env var OK
29 |  * - OPENAI_REASONING_EFFORT: Reasoning effort for o1 models: "low", "medium", "high", or "" to disable (default: "low") - env var OK
30 |  * - Base URL: Must be set via setOpenAIBaseUrl() - NOT from env vars (security risk)
31 |  */
32 | export function getOpenAIModel(model?: string): LanguageModelV1 {
33 |   const defaultModel = process.env.OPENAI_MODEL || DEFAULT_OPENAI_MODEL;
34 | 
35 |   // Handle reasoning effort: empty string explicitly disables it, undefined uses default
36 |   const envReasoningEffort = process.env.OPENAI_REASONING_EFFORT;
37 |   let reasoningEffort: ReasoningEffort | undefined;
38 | 
39 |   if (envReasoningEffort === "") {
40 |     // Empty string explicitly disables reasoning effort
41 |     reasoningEffort = undefined;
42 |   } else if (envReasoningEffort === undefined) {
43 |     // Not set - use default
44 |     reasoningEffort = DEFAULT_REASONING_EFFORT;
45 |   } else if (
46 |     VALID_REASONING_EFFORTS.includes(envReasoningEffort as ReasoningEffort)
47 |   ) {
48 |     // Valid value
49 |     reasoningEffort = envReasoningEffort as ReasoningEffort;
50 |   } else {
51 |     // Invalid value - provide helpful error with all valid options
52 |     const validValues = VALID_REASONING_EFFORTS.map((v) => `"${v}"`).join(", ");
53 |     throw new Error(
54 |       `Invalid OPENAI_REASONING_EFFORT value: "${envReasoningEffort}". Must be one of: ${validValues}, or "" (empty string to disable). Default is "${DEFAULT_REASONING_EFFORT}".`,
55 |     );
56 |   }
57 | 
58 |   const factory = createOpenAI({
59 |     ...(configuredBaseUrl && { baseURL: configuredBaseUrl }),
60 |     headers: {
61 |       "User-Agent": USER_AGENT,
62 |     },
63 |   });
64 | 
65 |   return factory(model ?? defaultModel, {
66 |     ...(reasoningEffort && { reasoningEffort }),
67 |   });
68 | }
69 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/search-issues.eval.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describeEval } from "vitest-evals";
 2 | import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
 3 | 
 4 | // Note: This eval requires OPENAI_API_KEY to be set in the environment
 5 | // The search_issues tool uses the AI SDK to translate natural language queries
 6 | describeEval("search-issues", {
 7 |   data: async () => {
 8 |     return [
 9 |       // Core test: Basic issue search
10 |       {
11 |         input: `Show me unresolved issues in ${FIXTURES.organizationSlug}`,
12 |         expectedTools: [
13 |           {
14 |             name: "find_organizations",
15 |             arguments: {},
16 |           },
17 |           {
18 |             name: "search_issues",
19 |             arguments: {
20 |               organizationSlug: FIXTURES.organizationSlug,
21 |               naturalLanguageQuery: "unresolved issues",
22 |             },
23 |           },
24 |         ],
25 |       },
26 |       // Core test: Search with 'me' reference (tests whoami integration)
27 |       {
28 |         input: `Find issues assigned to me in ${FIXTURES.organizationSlug}`,
29 |         expectedTools: [
30 |           {
31 |             name: "find_organizations",
32 |             arguments: {},
33 |           },
34 |           {
35 |             name: "whoami",
36 |             arguments: {},
37 |           },
38 |           {
39 |             name: "search_issues",
40 |             arguments: {
41 |               organizationSlug: FIXTURES.organizationSlug,
42 |               naturalLanguageQuery: "issues assigned to me",
43 |             },
44 |           },
45 |         ],
46 |       },
47 |       // Core test: Project-specific search
48 |       {
49 |         input: `Search for database errors in ${FIXTURES.organizationSlug}/${FIXTURES.projectSlug}`,
50 |         expectedTools: [
51 |           {
52 |             name: "find_organizations",
53 |             arguments: {},
54 |           },
55 |           {
56 |             name: "search_issues",
57 |             arguments: {
58 |               organizationSlug: FIXTURES.organizationSlug,
59 |               projectSlugOrId: FIXTURES.projectSlug,
60 |               naturalLanguageQuery: "database errors",
61 |             },
62 |           },
63 |         ],
64 |       },
65 |       // Core test: Complex natural language query
66 |       {
67 |         input: `Find critical production errors affecting more than 100 users in ${FIXTURES.organizationSlug}`,
68 |         expectedTools: [
69 |           {
70 |             name: "find_organizations",
71 |             arguments: {},
72 |           },
73 |           {
74 |             name: "search_issues",
75 |             arguments: {
76 |               organizationSlug: FIXTURES.organizationSlug,
77 |               naturalLanguageQuery:
78 |                 "critical production errors affecting more than 100 users",
79 |             },
80 |           },
81 |         ],
82 |       },
83 |     ];
84 |   },
85 |   task: NoOpTaskRunner(),
86 |   scorers: [ToolPredictionScorer()],
87 |   threshold: 0.6,
88 |   timeout: 30000,
89 | });
90 | 
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/device.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "namespace": "device",
 3 |   "description": "Describes device attributes.\n",
 4 |   "attributes": {
 5 |     "device.id": {
 6 |       "description": "A unique identifier representing the device\n",
 7 |       "type": "string",
 8 |       "note": "Its value SHOULD be identical for all apps on a device and it SHOULD NOT change if an app is uninstalled and re-installed.\nHowever, it might be resettable by the user for all apps on a device.\nHardware IDs (e.g. vendor-specific serial number, IMEI or MAC address) MAY be used as values.\n\nMore information about Android identifier best practices can be found [here](https://developer.android.com/training/articles/user-data-ids).\n\n> [!WARNING]\n>\n> This attribute may contain sensitive (PII) information. Caution should be taken when storing personal data or anything which can identify a user. GDPR and data protection laws may apply,\n> ensure you do your own due diligence.\n>\n> Due to these reasons, this identifier is not recommended for consumer applications and will likely result in rejection from both Google Play and App Store.\n> However, it may be appropriate for specific enterprise scenarios, such as kiosk devices or enterprise-managed devices, with appropriate compliance clearance.\n> Any instrumentation providing this identifier MUST implement it as an opt-in feature.\n>\n> See [`app.installation.id`](/docs/registry/attributes/app.md#app-installation-id) for a more privacy-preserving alternative.\n",
 9 |       "stability": "development",
10 |       "examples": ["123456789012345", "01:23:45:67:89:AB"]
11 |     },
12 |     "device.manufacturer": {
13 |       "description": "The name of the device manufacturer\n",
14 |       "type": "string",
15 |       "note": "The Android OS provides this field via [Build](https://developer.android.com/reference/android/os/Build#MANUFACTURER). iOS apps SHOULD hardcode the value `Apple`.\n",
16 |       "stability": "development",
17 |       "examples": ["Apple", "Samsung"]
18 |     },
19 |     "device.model.identifier": {
20 |       "description": "The model identifier for the device\n",
21 |       "type": "string",
22 |       "note": "It's recommended this value represents a machine-readable version of the model identifier rather than the market or consumer-friendly name of the device.\n",
23 |       "stability": "development",
24 |       "examples": ["iPhone3,4", "SM-G920F"]
25 |     },
26 |     "device.model.name": {
27 |       "description": "The marketing name for the device model\n",
28 |       "type": "string",
29 |       "note": "It's recommended this value represents a human-readable version of the device model rather than a machine-readable alternative.\n",
30 |       "stability": "development",
31 |       "examples": ["iPhone 6s Plus", "Samsung Galaxy S6"]
32 |     }
33 |   }
34 | }
35 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/app.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Hono } from "hono";
 2 | import { csrf } from "hono/csrf";
 3 | import { secureHeaders } from "hono/secure-headers";
 4 | import * as Sentry from "@sentry/cloudflare";
 5 | import type { Env } from "./types";
 6 | import sentryOauth from "./oauth";
 7 | import chatOauth from "./routes/chat-oauth";
 8 | import chat from "./routes/chat";
 9 | import search from "./routes/search";
10 | import metadata from "./routes/metadata";
11 | import { logIssue } from "@sentry/mcp-core/telem/logging";
12 | import { createRequestLogger } from "./logging";
13 | import mcpRoutes from "./routes/mcp";
14 | import { getClientIp } from "./utils/client-ip";
15 | 
16 | const app = new Hono<{
17 |   Bindings: Env;
18 | }>()
19 |   .use("*", createRequestLogger())
20 |   // Set user IP address for Sentry (optional in local dev)
21 |   .use("*", async (c, next) => {
22 |     const clientIP = getClientIp(c.req.raw);
23 | 
24 |     if (clientIP) {
25 |       Sentry.setUser({ ip_address: clientIP });
26 |     }
27 |     // In local development, IP extraction may fail - this is expected and safe to ignore
28 |     // as it's only used for Sentry telemetry context
29 | 
30 |     await next();
31 |   })
32 |   // Apply security middleware globally
33 |   .use(
34 |     "*",
35 |     secureHeaders({
36 |       xFrameOptions: "DENY",
37 |       xContentTypeOptions: "nosniff",
38 |       referrerPolicy: "strict-origin-when-cross-origin",
39 |       strictTransportSecurity: "max-age=31536000; includeSubDomains",
40 |     }),
41 |   )
42 |   .use(
43 |     "*",
44 |     csrf({
45 |       origin: (origin, c) => {
46 |         if (!origin) {
47 |           return true;
48 |         }
49 |         const requestUrl = new URL(c.req.url);
50 |         return origin === requestUrl.origin;
51 |       },
52 |     }),
53 |   )
54 |   .get("/robots.txt", (c) => {
55 |     return c.text(["User-agent: *", "Allow: /$", "Disallow: /"].join("\n"));
56 |   })
57 |   .get("/llms.txt", (c) => {
58 |     return c.text(
59 |       [
60 |         "# sentry-mcp",
61 |         "",
62 |         "This service implements the Model Context Protocol for interacting with Sentry (https://sentry.io/welcome/).",
63 |         "",
64 |         `The MCP's server address is: ${new URL("/mcp", c.req.url).href}`,
65 |         "",
66 |       ].join("\n"),
67 |     );
68 |   })
69 |   .route("/oauth", sentryOauth)
70 |   .route("/api/auth", chatOauth)
71 |   .route("/api/chat", chat)
72 |   .route("/api/search", search)
73 |   .route("/api/metadata", metadata)
74 |   .route("/.mcp", mcpRoutes)
75 |   .get("/sse", (c) => {
76 |     return c.json(
77 |       {
78 |         error: "SSE transport has been removed",
79 |         message:
80 |           "The SSE transport endpoint is no longer supported. Please use the HTTP transport at /mcp instead.",
81 |         migrationGuide: "https://mcp.sentry.dev",
82 |       },
83 |       410,
84 |     );
85 |   });
86 | 
87 | // TODO: propagate the error as sentry isnt injecting into hono
88 | app.onError((err, c) => {
89 |   logIssue(err);
90 |   return c.text("Internal Server Error", 500);
91 | });
92 | 
93 | export default app;
94 | 
```

--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/list-issues.eval.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describeEval } from "vitest-evals";
  2 | import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
  3 | 
  4 | describeEval("list-issues", {
  5 |   data: async () => {
  6 |     return [
  7 |       {
  8 |         input: `What are the most common production errors in ${FIXTURES.organizationSlug}?`,
  9 |         expectedTools: [
 10 |           {
 11 |             name: "find_organizations",
 12 |             arguments: {},
 13 |           },
 14 |           {
 15 |             name: "find_issues",
 16 |             arguments: {
 17 |               organizationSlug: FIXTURES.organizationSlug,
 18 |               query: "is:unresolved",
 19 |               sortBy: "count",
 20 |               regionUrl: "https://us.sentry.io",
 21 |             },
 22 |           },
 23 |         ],
 24 |       },
 25 |       {
 26 |         input: `Show me the top issues in ${FIXTURES.organizationSlug} organization`,
 27 |         expectedTools: [
 28 |           {
 29 |             name: "find_organizations",
 30 |             arguments: {},
 31 |           },
 32 |           {
 33 |             name: "find_issues",
 34 |             arguments: {
 35 |               organizationSlug: FIXTURES.organizationSlug,
 36 |               sortBy: "count",
 37 |               regionUrl: "https://us.sentry.io",
 38 |             },
 39 |           },
 40 |         ],
 41 |       },
 42 |       {
 43 |         input: `What are the most recent issues in ${FIXTURES.organizationSlug}?`,
 44 |         expectedTools: [
 45 |           {
 46 |             name: "find_organizations",
 47 |             arguments: {},
 48 |           },
 49 |           {
 50 |             name: "find_issues",
 51 |             arguments: {
 52 |               organizationSlug: FIXTURES.organizationSlug,
 53 |               sortBy: "last_seen",
 54 |               regionUrl: "https://us.sentry.io",
 55 |             },
 56 |           },
 57 |         ],
 58 |       },
 59 |       {
 60 |         input: `Find the newest production issues in ${FIXTURES.organizationSlug}`,
 61 |         expectedTools: [
 62 |           {
 63 |             name: "find_organizations",
 64 |             arguments: {},
 65 |           },
 66 |           {
 67 |             name: "find_issues",
 68 |             arguments: {
 69 |               organizationSlug: FIXTURES.organizationSlug,
 70 |               sortBy: "first_seen",
 71 |               regionUrl: "https://us.sentry.io",
 72 |             },
 73 |           },
 74 |         ],
 75 |       },
 76 |       {
 77 |         input: `What issues is [email protected] experiencing in ${FIXTURES.organizationSlug}?`,
 78 |         expectedTools: [
 79 |           {
 80 |             name: "find_organizations",
 81 |             arguments: {},
 82 |           },
 83 |           {
 84 |             name: "find_issues",
 85 |             arguments: {
 86 |               organizationSlug: FIXTURES.organizationSlug,
 87 |               query: "user.email:[email protected]",
 88 |               regionUrl: "https://us.sentry.io",
 89 |             },
 90 |           },
 91 |         ],
 92 |       },
 93 |     ];
 94 |   },
 95 |   task: NoOpTaskRunner(),
 96 |   scorers: [ToolPredictionScorer()],
 97 |   threshold: 0.6,
 98 |   timeout: 30000,
 99 | });
100 | 
```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/header.tsx:
--------------------------------------------------------------------------------

```typescript
 1 | import type React from "react";
 2 | import { SentryIcon } from "./icons/sentry";
 3 | import { Github, PanelLeftClose } from "lucide-react";
 4 | import { Button } from "./button";
 5 | import { Badge } from "./badge";
 6 | 
 7 | interface HeaderProps {
 8 |   toggleChat?: (open: boolean) => void;
 9 |   isChatOpen?: boolean;
10 | }
11 | 
12 | export const Header: React.FC<HeaderProps> = ({ toggleChat, isChatOpen }) => {
13 |   return (
14 |     <header className="pl-4 container mx-auto w-full px-4 sm:px-8 sticky py-4 top-0 z-30 backdrop-blur-xl bg-gradient-to-b from-background to-1% to-background/80">
15 |       {/* <div className="absolute inset-0 h-full w-full -z-10 pointer-events-none bg-gradient-to-r from-transparent to-background-2 hidden md:block" /> */}
16 |       <div className="absolute inset-0 h-full w-screen left-1/2 -translate-x-1/2 [mask-image:linear-gradient(to_right,transparent,red_4rem,red_calc(100%-4rem),transparent)] border-b-[1px] border-white/15 pointer-events-none -z-10" />
17 |       <div className="flex items-center justify-between w-full">
18 |         <div className="flex items-center gap-2 flex-shrink-0">
19 |           <SentryIcon className="h-8 w-8" />
20 |           <div className="flex items-center gap-3">
21 |             <h1 className="text-2xl font-semibold whitespace-nowrap">
22 |               Sentry MCP
23 |             </h1>
24 |             <Badge
25 |               variant="outline"
26 |               className="text-xs bg-background-3 font-normal"
27 |             >
28 |               Beta
29 |             </Badge>
30 |           </div>
31 |         </div>
32 |         <div
33 |           className={`flex items-center gap-4 motion-safe:duration-300 [--x:0] xl:[--x:30rem] 2xl:[--x:38rem] ${
34 |             isChatOpen ? "-translate-x-[var(--x)]" : ""
35 |           }`}
36 |         >
37 |           <Button
38 |             className="rounded-xl max-md:!py-3 max-md:!px-2.25"
39 |             variant="secondary"
40 |             asChild
41 |           >
42 |             <a
43 |               href="https://github.com/getsentry/sentry-mcp"
44 |               target="_blank"
45 |               rel="noopener noreferrer"
46 |             >
47 |               <Github className="h-5 w-5" />
48 |               <span className="max-sm:sr-only">GitHub</span>
49 |             </a>
50 |           </Button>
51 |           {toggleChat && isChatOpen !== undefined && (
52 |             <Button
53 |               type="button"
54 |               onClick={() => toggleChat(!isChatOpen)}
55 |               className="cursor-pointer pl-3 pr-3.5 py-2 rounded-xl max-md:!py-3 max-md:!px-2.25 flex items-center bg-violet-300 text-background hover:bg-white/90 transition font-bold font-sans border border-background"
56 |             >
57 |               <PanelLeftClose className="size-4" />
58 |               <span className="max-sm:sr-only">Live Demo</span>
59 |             </Button>
60 |           )}
61 |         </div>
62 |       </div>
63 |     </header>
64 |   );
65 | };
66 | 
```
Page 3/20FirstPrevNextLast