This is page 2 of 16. Use http://codebase.md/getsentry/sentry-mcp?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
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/session.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "session",
"description": "Session is defined as the period of time encompassing all activities performed by the application and the actions executed by the end user.\nConsequently, a Session is represented as a collection of Logs, Events, and Spans emitted by the Client Application throughout the Session's duration. Each Session is assigned a unique identifier, which is included as an attribute in the Logs, Events, and Spans generated during the Session's lifecycle.\nWhen a session reaches end of life, typically due to user inactivity or session timeout, a new session identifier will be assigned. The previous session identifier may be provided by the instrumentation so that telemetry backends can link the two sessions.\n",
"attributes": {
"session.id": {
"description": "A unique id to identify a session.",
"type": "string",
"stability": "development",
"examples": ["00112233-4455-6677-8899-aabbccddeeff"]
},
"session.previous_id": {
"description": "The previous `session.id` for this user, when known.",
"type": "string",
"stability": "development",
"examples": ["00112233-4455-6677-8899-aabbccddeeff"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/default-event.json:
--------------------------------------------------------------------------------
```json
{
"id": "abc123def456",
"groupID": "1234567890",
"eventID": "abc123def456",
"projectID": "4509062593708032",
"size": 300,
"title": "Error without exception data",
"message": "Something went wrong",
"platform": "python",
"type": "default",
"dateCreated": "2025-10-02T12:00:00.000Z",
"dateReceived": "2025-10-02T12:00:01.000Z",
"culprit": "unknown",
"dist": null,
"location": null,
"user": null,
"contexts": {},
"sdk": null,
"context": {},
"packages": {},
"errors": [],
"crashFile": null,
"fingerprints": ["default-event-fingerprint"],
"groupingConfig": {
"id": "newstyle:2023-01-11",
"enhancements": "test"
},
"release": null,
"userReport": null,
"sdkUpdates": [],
"resolvedWith": [],
"nextEventID": null,
"previousEventID": null,
"entries": [
{
"type": "message",
"data": {
"formatted": "Something went wrong",
"message": "Something went wrong"
}
}
],
"tags": [
{
"key": "level",
"value": "error"
},
{
"key": "environment",
"value": "production"
}
],
"metadata": {
"title": "Error without exception data",
"type": "default",
"value": "Something went wrong"
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-meta-with-nulls.json:
--------------------------------------------------------------------------------
```json
{
"logs": 0,
"errors": 2,
"performance_issues": 0,
"span_count": 85,
"transaction_child_count_map": [
{
"transaction.event_id": "0daf40dc453a429c8c57e4c215c4e82c",
"count()": 10.0
},
{
"transaction.event_id": null,
"count()": 15.0
},
{
"transaction.event_id": "b49a5b53cba046a2bab9323d8f00de96",
"count()": 7.0
},
{
"transaction.event_id": null,
"count()": 8.0
},
{
"transaction.event_id": "ee6e7f39107847f980e06119bf116d38",
"count()": 7.0
},
{
"transaction.event_id": null,
"count()": 20.0
},
{
"transaction.event_id": "f398bbc635c64e2091d94679dade2957",
"count()": 3.0
},
{
"transaction.event_id": "f779775e1c6a4f09b62d68818a25d7b5",
"count()": 15.0
}
],
"span_count_map": {
"cache.get": 30.0,
"middleware.django": 20.0,
"db": 10.0,
"function": 5.0,
"db.redis": 4.0,
"feature.flagpole.batch_has": 4.0,
"processor": 2.0,
"execute": 2.0,
"fetch_organization_projects": 2.0,
"other": 1.0,
"db.clickhouse": 1.0,
"validator": 1.0,
"serialize": 1.0,
"http.client": 1.0,
"base.paginate.on_results": 1.0
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/terminal-ui/SpeedDisplay.tsx:
--------------------------------------------------------------------------------
```typescript
"use client";
import { ChevronsRight } from "lucide-react";
import { useEffect, useState } from "react";
type SpeedDisplayProps = {
speed: number | string;
/** total animation time; keep in sync with CSS via a CSS var */
durationMs?: number;
className?: string;
};
export default function SpeedDisplay({
speed,
durationMs = 500,
className = "",
}: SpeedDisplayProps) {
const [animate, setAnimate] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies: change in speed triggers the animation
useEffect(() => {
setAnimate(true);
const t = setTimeout(() => setAnimate(false), durationMs);
return () => clearTimeout(t);
}, [speed, durationMs]);
return (
<div
aria-live="polite"
className={`absolute top-6 right-4 sm:top-8 sm:right-10 z-20 flex items-center font-bold text-xl sm:text-3xl opacity-50 ${
animate ? "speed-animate" : ""
} ${className}`}
style={{ ["--speed-pop-dur" as any]: `${durationMs}ms` }}
>
<div className="absolute right-0 top-0 -translate-y-full font-mono text-xs opacity-50 text-nowrap">
sped up
</div>
<ChevronsRight className="size-6 sm:size-10 stroke-3" />
{speed}x
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/BrowserWindow.tsx:
--------------------------------------------------------------------------------
```typescript
import BrowserWindowIconSidebar from "./BrowserWindowIconSidebar";
import KeysCopy from "./keys-copy";
import IssueDetails from "./IssueDetails";
import Seer from "./seer";
import WindowHeader from "./WindowHeader";
export default function BrowserWindow({ step }: { step: number }) {
return (
<div
// isometric option: rotate-x-60 -rotate-z-45 scale-75 perspective-distant
className={`${
step >= 3
? "pointer-events-none motion-safe:translate-x-32 motion-safe:scale-75 opacity-0"
: step === 1
? "border-orange-400/50"
: step === 2
? "border-pink-400/50"
: "border-white/10"
} absolute inset-0 flex h-full w-full flex-col rounded-3xl border bg-white/5 duration-300 backdrop-blur`}
id="window"
>
<KeysCopy step={step} />
<WindowHeader step={step} />
<div className={`flex h-full w-full ${step > 1 && "overflow-hidden"}`}>
<BrowserWindowIconSidebar />
<div
className={`relative flex w-full flex-col gap-3 p-0 ${
step === 2 && "overflow-hidden"
}`}
>
<IssueDetails step={step} />
<Seer step={step} />
</div>
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/whoami.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { getCurrentUser } from "./whoami";
import type { SentryApiService } from "../../../api-client";
describe("whoami agent tool", () => {
let mockApiService: SentryApiService;
beforeEach(() => {
vi.clearAllMocks();
mockApiService = {
getAuthenticatedUser: vi.fn(),
} as unknown as SentryApiService;
});
describe("getCurrentUser", () => {
it("should return current user information", async () => {
const mockUser = {
id: "123",
name: "John Doe",
email: "[email protected]",
};
(mockApiService.getAuthenticatedUser as any).mockResolvedValue(mockUser);
const result = await getCurrentUser(mockApiService);
expect(result).toEqual({
id: "123",
name: "John Doe",
email: "[email protected]",
});
expect(mockApiService.getAuthenticatedUser).toHaveBeenCalledOnce();
});
it("should handle API errors gracefully", async () => {
(mockApiService.getAuthenticatedUser as any).mockRejectedValue(
new Error("Unauthorized"),
);
await expect(getCurrentUser(mockApiService)).rejects.toThrow(
"Unauthorized",
);
});
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/search-docs.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("search-docs", {
data: async () => {
return [
{
input:
"I need documentation on how to set up error tracking with Sentry in JavaScript",
expectedTools: [
{
name: "search_docs",
arguments: {
query: "set up error tracking JavaScript",
maxResults: 3,
},
},
],
},
{
input:
"I need help configuring Sentry with React components and error boundaries",
expectedTools: [
{
name: "search_docs",
arguments: {
query: "React components error boundaries",
maxResults: 3,
},
},
],
},
{
input: "What is Sentry's rate limiting and how does it work?",
expectedTools: [
{
name: "search_docs",
arguments: {
query: "rate limiting",
maxResults: 3,
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-endpoint-mode.ts:
--------------------------------------------------------------------------------
```typescript
import { useState, useEffect } from "react";
export type EndpointMode = "standard" | "agent";
const STORAGE_KEY = "sentry-mcp-endpoint-mode";
/**
* Hook to manage MCP endpoint mode preference.
* Toggles between "/mcp" (standard) and "/mcp?agent=1" (agent mode).
*
* The preference is persisted in localStorage.
*/
export function useEndpointMode() {
const [endpointMode, setEndpointModeState] = useState<EndpointMode>(() => {
// Initialize from localStorage on mount
if (typeof window !== "undefined") {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored === "agent" || stored === "standard") {
return stored;
}
}
return "standard"; // Default to standard mode
});
// Persist to localStorage when changed
useEffect(() => {
if (typeof window !== "undefined") {
localStorage.setItem(STORAGE_KEY, endpointMode);
}
}, [endpointMode]);
const setEndpointMode = (mode: EndpointMode) => {
setEndpointModeState(mode);
};
const toggleEndpointMode = () => {
setEndpointModeState((prev) =>
prev === "standard" ? "agent" : "standard",
);
};
return {
endpointMode,
setEndpointMode,
toggleEndpointMode,
isAgentMode: endpointMode === "agent",
};
}
```
--------------------------------------------------------------------------------
/packages/mcp-server/src/cli/types.ts:
--------------------------------------------------------------------------------
```typescript
import type { Skill } from "@sentry/mcp-core/skills";
export type CliArgs = {
accessToken?: string;
host?: string;
url?: string;
mcpUrl?: string;
sentryDsn?: string;
openaiBaseUrl?: string;
openaiModel?: string;
skills?: string;
agent?: boolean;
organizationSlug?: string;
projectSlug?: string;
help?: boolean;
version?: boolean;
unknownArgs: string[];
};
export type EnvArgs = {
accessToken?: string;
host?: string; // parsed from SENTRY_HOST or SENTRY_URL (raw value)
url?: string; // raw URL if provided (SENTRY_URL)
mcpUrl?: string;
sentryDsn?: string;
openaiModel?: string;
skills?: string;
};
export type MergedArgs = {
accessToken?: string;
host?: string;
url?: string;
mcpUrl?: string;
sentryDsn?: string;
openaiBaseUrl?: string;
openaiModel?: string;
skills?: string;
agent?: boolean;
organizationSlug?: string;
projectSlug?: string;
help?: boolean;
version?: boolean;
unknownArgs: string[];
};
export type ResolvedConfig = {
accessToken: string;
sentryHost: string;
mcpUrl?: string;
sentryDsn?: string;
openaiBaseUrl?: string;
openaiModel?: string;
/** Primary authorization method */
finalSkills?: Set<Skill>;
organizationSlug?: string;
projectSlug?: string;
};
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/get-trace-details.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("get-trace-details", {
data: async () => {
return [
{
input: `Show me trace ${FIXTURES.traceId} from Sentry in ${FIXTURES.organizationSlug}.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "get_trace_details",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
traceId: FIXTURES.traceId,
regionUrl: "https://us.sentry.io",
},
},
],
},
{
input: `Explain trace ${FIXTURES.traceId} in ${FIXTURES.organizationSlug}.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "get_trace_details",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
traceId: FIXTURES.traceId,
regionUrl: "https://us.sentry.io",
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/test.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "test",
"description": "This group describes attributes specific to [software tests](https://wikipedia.org/wiki/Software_testing).\n",
"attributes": {
"test.suite.name": {
"description": "The human readable name of a [test suite](https://wikipedia.org/wiki/Test_suite).\n",
"type": "string",
"stability": "development",
"examples": ["TestSuite1"]
},
"test.suite.run.status": {
"description": "The status of the test suite run.\n",
"type": "string",
"stability": "development",
"examples": [
"success",
"failure",
"skipped",
"aborted",
"timed_out",
"in_progress"
]
},
"test.case.name": {
"description": "The fully qualified human readable name of the [test case](https://wikipedia.org/wiki/Test_case).\n",
"type": "string",
"stability": "development",
"examples": [
"org.example.TestCase1.test1",
"example/tests/TestCase1.test1",
"ExampleTestCase1_test1"
]
},
"test.case.result.status": {
"description": "The status of the actual test case result from test execution.\n",
"type": "string",
"stability": "development",
"examples": ["pass", "fail"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server/src/cli/usage.ts:
--------------------------------------------------------------------------------
```typescript
import type { Skill } from "@sentry/mcp-core/skills";
export function buildUsage(
packageName: string,
allSkills: ReadonlyArray<Skill>,
): string {
return `Usage: ${packageName} --access-token=<token> [--host=<host>]
Required:
--access-token <token> Sentry User Auth Token with API access
Common optional flags:
--host <host> Change Sentry host (self-hosted)
--sentry-dsn <dsn> Override DSN used for telemetry reporting
--openai-base-url <url> Override OpenAI API base URL for embedded agents
--openai-model <model> Override OpenAI model (default: gpt-5, reasoning effort: low)
--agent Agent mode: only expose use_sentry tool (for AI agents)
Session constraints:
--organization-slug <slug> Force all calls to an organization
--project-slug <slug> Optional project constraint
Skill controls:
--skills <list> Specify which skills to grant (default: all skills)
All skills: ${allSkills.join(", ")}
Examples:
${packageName} --access-token=TOKEN
${packageName} --access-token=TOKEN --skills=inspect,triage
${packageName} --access-token=TOKEN --host=sentry.example.com
${packageName} --access-token=TOKEN --openai-model=o1-mini
${packageName} --access-token=TOKEN --openai-base-url=https://proxy.example.com/v1`;
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/source.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "source",
"description": "These attributes may be used to describe the sender of a network exchange/packet. These should be used when there is no client/server relationship between the two sides, or when that relationship is unknown. This covers low-level network interactions (e.g. packet tracing) where you don't know if there was a connection or which side initiated it. This also covers unidirectional UDP flows and peer-to-peer communication where the \"user-facing\" surface of the protocol / API doesn't expose a clear notion of client and server.\n",
"attributes": {
"source.address": {
"description": "Source address - domain name if available without reverse DNS lookup; otherwise, IP address or Unix domain socket name.",
"type": "string",
"note": "When observed from the destination side, and when communicating through an intermediary, `source.address` SHOULD represent the source address behind any intermediaries, for example proxies, if it's available.\n",
"stability": "development",
"examples": ["source.example.com", "10.1.2.80", "/tmp/my.sock"]
},
"source.port": {
"description": "Source port number",
"type": "number",
"stability": "development",
"examples": ["3389", "2888"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/regressed-issue.json:
--------------------------------------------------------------------------------
```json
{
"id": "6898891101",
"shareId": null,
"shortId": "MCP-SERVER-EQE",
"title": "Endpoint Regression",
"culprit": "POST /oauth/token",
"permalink": "https://sentry.sentry.io/issues/6898891101/",
"logger": null,
"level": "info",
"status": "unresolved",
"statusDetails": {},
"substatus": "regressed",
"isPublic": false,
"platform": "python",
"project": {
"id": "4509062593708032",
"name": "mcp-server",
"slug": "mcp-server",
"platform": "node-cloudflare-workers"
},
"type": "generic",
"metadata": {
"title": "Endpoint Regression",
"value": "Increased from 909.77ms to 1711.36ms (P95)",
"initial_priority": 50
},
"numComments": 0,
"assignedTo": null,
"isBookmarked": false,
"isSubscribed": false,
"subscriptionDetails": null,
"hasSeen": true,
"annotations": [],
"issueType": "performance_p95_endpoint_regression",
"issueCategory": "metric",
"priority": "medium",
"priorityLockedAt": null,
"isUnhandled": false,
"count": "3",
"userCount": 0,
"firstSeen": "2025-09-24T03:02:10.919020Z",
"lastSeen": "2025-11-18T06:01:20Z",
"firstRelease": null,
"lastRelease": null,
"activity": [],
"seenBy": [],
"pluginActions": [],
"pluginIssues": [],
"pluginContexts": [],
"userReportCount": 0,
"stats": {},
"participants": []
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/get-issue.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("get-issue", {
data: async () => {
return [
{
input: `Explain CLOUDFLARE-MCP-41 from Sentry in ${FIXTURES.organizationSlug}.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "get_issue_details",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
issueId: "CLOUDFLARE-MCP-41",
regionUrl: "https://us.sentry.io",
},
},
],
},
{
input: `Explain the event with ID 7ca573c0f4814912aaa9bdc77d1a7d51 from Sentry in ${FIXTURES.organizationSlug}.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "get_issue_details",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
eventId: "7ca573c0f4814912aaa9bdc77d1a7d51",
regionUrl: "https://us.sentry.io",
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/destination.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "destination",
"description": "These attributes may be used to describe the receiver of a network exchange/packet. These should be used when there is no client/server relationship between the two sides, or when that relationship is unknown. This covers low-level network interactions (e.g. packet tracing) where you don't know if there was a connection or which side initiated it. This also covers unidirectional UDP flows and peer-to-peer communication where the \"user-facing\" surface of the protocol / API doesn't expose a clear notion of client and server.\n",
"attributes": {
"destination.address": {
"description": "Destination address - domain name if available without reverse DNS lookup; otherwise, IP address or Unix domain socket name.",
"type": "string",
"note": "When observed from the source side, and when communicating through an intermediary, `destination.address` SHOULD represent the destination address behind any intermediaries, for example proxies, if it's available.\n",
"stability": "development",
"examples": ["destination.example.com", "10.1.2.80", "/tmp/my.sock"]
},
"destination.port": {
"description": "Destination port number",
"type": "number",
"stability": "development",
"examples": ["3389", "2888"]
}
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
```yaml
name: Release
on:
workflow_dispatch:
inputs:
version:
description: Version to release
required: true
force:
description: Force a release even when there are release-blockers (empty string == false)
required: false
jobs:
release:
runs-on: ubuntu-latest
name: "Release a new version"
steps:
- name: Get auth token
id: token
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
with:
app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ steps.token.outputs.token }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
# pnpm/action-setup@v4
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
name: Install pnpm
with:
run_install: false
- name: Prepare release
uses: getsentry/action-prepare-release@v1
env:
GITHUB_TOKEN: ${{ steps.token.outputs.token }}
with:
version: ${{ github.event.inputs.version }}
force: ${{ github.event.inputs.force }}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/utils/cursor-deeplink.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Generates a Cursor deep link for MCP server installation.
* @param endpoint - The MCP endpoint URL (defaults to current origin + /mcp)
* @returns The cursor:// deep link URL
*/
export function getCursorDeepLink(endpoint?: string): string {
const url = endpoint ?? new URL("/mcp", window.location.href).href;
const config = btoa(JSON.stringify({ url }));
return `cursor://anysphere.cursor-deeplink/mcp/install?name=Sentry&config=${encodeURIComponent(config)}`;
}
/**
* Navigates to Cursor via deep link, with fallback to anchor if Cursor isn't installed.
* @param endpoint - The MCP endpoint URL (defaults to current origin + /mcp)
* @param fallbackAnchor - The anchor to navigate to if Cursor doesn't open (default: #getting-started)
*/
export function openCursorDeepLink(
endpoint?: string,
fallbackAnchor = "#getting-started",
): void {
const clickedTime = Date.now();
const deepLink = getCursorDeepLink(endpoint);
window.location.href = deepLink;
// Fallback if Cursor is not installed
const timeout = setTimeout(() => {
if (document.hasFocus() && Date.now() - clickedTime < 2000) {
window.location.href = fallbackAnchor;
}
}, 123);
document.addEventListener(
"visibilitychange",
() => {
if (document.visibilityState === "hidden") {
clearTimeout(timeout);
}
},
{ once: true },
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/user.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "user",
"description": "Describes information about the user.",
"attributes": {
"user.email": {
"description": "User email address.\n",
"type": "string",
"stability": "development",
"examples": ["[email protected]"]
},
"user.full_name": {
"description": "User's full name\n",
"type": "string",
"stability": "development",
"examples": ["Albert Einstein"]
},
"user.hash": {
"description": "Unique user hash to correlate information for a user in anonymized form.\n",
"type": "string",
"note": "Useful if `user.id` or `user.name` contain confidential information and cannot be used.\n",
"stability": "development",
"examples": ["364fc68eaf4c8acec74a4e52d7d1feaa"]
},
"user.id": {
"description": "Unique identifier of the user.\n",
"type": "string",
"stability": "development",
"examples": ["S-1-5-21-202424912787-2692429404-2351956786-1000"]
},
"user.name": {
"description": "Short name or login/username of the user.\n",
"type": "string",
"stability": "development",
"examples": ["a.einstein"]
},
"user.roles": {
"description": "Array of user roles at the time of the event.\n",
"type": "string",
"stability": "development",
"examples": ["[\"admin\",\"reporting_user\"]"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/performance-issue.json:
--------------------------------------------------------------------------------
```json
{
"id": "7890123456",
"shareId": null,
"shortId": "PERF-N1-001",
"title": "N+1 Query: SELECT * FROM users WHERE id = %s",
"culprit": "GET /api/users",
"permalink": "https://sentry-mcp-evals.sentry.io/issues/7890123456/",
"logger": null,
"level": "warning",
"status": "unresolved",
"statusDetails": {},
"substatus": "ongoing",
"isPublic": false,
"platform": "python",
"project": {
"id": "4509062593708032",
"name": "CLOUDFLARE-MCP",
"slug": "CLOUDFLARE-MCP",
"platform": "python"
},
"type": "performance_n_plus_one_db_queries",
"metadata": {
"title": "N+1 Query: SELECT * FROM users WHERE id = %s",
"location": "GET /api/users",
"value": "SELECT * FROM users WHERE id = %s"
},
"numComments": 0,
"assignedTo": null,
"isBookmarked": false,
"isSubscribed": false,
"subscriptionDetails": null,
"hasSeen": true,
"annotations": [],
"issueType": "performance_n_plus_one_db_queries",
"issueCategory": "performance",
"priority": "medium",
"priorityLockedAt": null,
"isUnhandled": false,
"count": "25",
"userCount": 5,
"firstSeen": "2025-08-05T12:00:00.000Z",
"lastSeen": "2025-08-06T12:00:00.000Z",
"firstRelease": null,
"lastRelease": null,
"activity": [],
"openPeriods": [],
"seenBy": [],
"pluginActions": [],
"pluginIssues": [],
"pluginContexts": [],
"userReportCount": 0,
"stats": {},
"participants": []
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/BrowserWindowIconSidebar.tsx:
--------------------------------------------------------------------------------
```typescript
import {
ChartNoAxesCombined,
Compass,
LayoutDashboard,
Settings,
Shield,
} from "lucide-react";
import { SentryIcon } from "../../ui/icons/sentry";
export default function BrowserWindowIconSidebar() {
return (
<div className="flex flex-col gap-3 px-4 pt-2 overflow-clip max-h-full">
<div className="size-12 flex-shrink-0 rounded-xl border-[0.5px] border-violet-300/20 bg-gradient-to-tr from-[#362e5a] to-[#885091] grid place-content-center text-white bg-clip-padding">
<SentryIcon className="h-8 w-8 text-white" />
</div>
<div className="size-12 flex-shrink-0 rounded-xl border-[0.5px] border-violet-300/20 bg-violet-300/13 grid place-content-center">
<Compass className="h-8 w-8 stroke-1 text-violet-300" />
</div>
<div className="size-12 flex-shrink-0 rounded-xl grid place-content-center opacity-50">
<LayoutDashboard className="h-8 w-8 stroke-1" />
</div>
<div className="size-12 flex-shrink-0 rounded-xl grid place-content-center opacity-50">
<ChartNoAxesCombined className="h-8 w-8 stroke-1" />
</div>
<div className="size-12 flex-shrink-0 rounded-xl grid place-content-center opacity-50">
<Shield className="h-8 w-8 stroke-1" />
</div>
<div className="size-12 flex-shrink-0 rounded-xl grid place-content-center opacity-50">
<Settings className="h-8 w-8 stroke-1" />
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/list-releases.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("list-releases", {
data: async () => {
return [
{
input: `Show me the releases in ${FIXTURES.organizationSlug}`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "find_releases",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
regionUrl: "https://us.sentry.io",
},
},
],
},
{
input: `Show me a list of versions in ${FIXTURES.organizationSlug}/${FIXTURES.projectSlug}`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "find_projects",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
regionUrl: "https://us.sentry.io",
},
},
{
name: "find_releases",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
projectSlug: FIXTURES.projectSlug,
regionUrl: "https://us.sentry.io",
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/deployment.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "deployment",
"description": "This document defines attributes for software deployments.\n",
"attributes": {
"deployment.name": {
"description": "The name of the deployment.\n",
"type": "string",
"stability": "development",
"examples": ["deploy my app", "deploy-frontend"]
},
"deployment.id": {
"description": "The id of the deployment.\n",
"type": "string",
"stability": "development",
"examples": ["1208"]
},
"deployment.status": {
"description": "The status of the deployment.\n",
"type": "string",
"stability": "development",
"examples": ["failed", "succeeded"]
},
"deployment.environment.name": {
"description": "Name of the [deployment environment](https://wikipedia.org/wiki/Deployment_environment) (aka deployment tier).\n",
"type": "string",
"note": "`deployment.environment.name` does not affect the uniqueness constraints defined through\nthe `service.namespace`, `service.name` and `service.instance.id` resource attributes.\nThis implies that resources carrying the following attribute combinations MUST be\nconsidered to be identifying the same service:\n\n- `service.name=frontend`, `deployment.environment.name=production`\n- `service.name=frontend`, `deployment.environment.name=staging`.\n",
"stability": "development",
"examples": ["staging", "production"]
}
}
}
```
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
```yaml
packages:
- packages/*
catalog:
"@ai-sdk/openai": ^1.3.22
"@ai-sdk/react": ^1.2.12
"@biomejs/biome": ^1.9.4
"@cloudflare/vite-plugin": ^1.13.15
"@cloudflare/vitest-pool-workers": ^0.8.47
"@cloudflare/workers-oauth-provider": ^0.0.12
"@cloudflare/workers-types": ^4.20251014.0
"@modelcontextprotocol/sdk": ^1.21.0
"@radix-ui/react-accordion": ^1.2.11
"@radix-ui/react-slot": ^1.2.3
"@sentry/cloudflare": 10.28.0
"@sentry/core": 10.28.0
"@sentry/node": 10.28.0
"@sentry/react": 10.28.0
"@sentry/vite-plugin": ^4.6.1
"@tailwindcss/typography": ^0.5.16
"@tailwindcss/vite": ^4.1.11
"@types/node": ^22.15.33
"@types/react": ^19.1.8
"@types/react-dom": ^19.1.6
"@vitejs/plugin-react": ^4.6.0
"@vitest/coverage-v8": ^3.2.4
agents: ^0.2.23
ai: ^4.3.16
better-sqlite3: ^11.10.0
chalk: ^5.4.1
class-variance-authority: ^0.7.1
clsx: ^2.1.1
commander: ^14.0.0
dotenv: ^16.6.1
dotenv-cli: ^8.0.0
hono: ^4.10.3
lint-staged: ^15.5.2
lucide-react: ^0.503.0
msw: ^2.10.2
open: ^10.1.2
react: ^19.1.0
react-dom: ^19.1.0
react-markdown: ^9.1.0
remark-gfm: ^4.0.1
simple-git-hooks: ^2.13.0
tailwind-merge: ^3.3.1
tailwindcss: ^4.1.11
tsdown: ^0.12.9
tsx: ^4.20.3
turbo: ^2.5.4
tw-animate-css: ^1.3.4
typescript: ^5.8.3
vite: ^6.3.5
vitest: ^3.2.4
vitest-evals: ^0.4.0
workers-mcp: 0.1.0-3
wrangler: ^4.45.0
zod: ^3.25.67
zod-to-json-schema: ^3.24.6
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/update-project.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("update-project", {
data: async () => {
return [
{
input: `Update the project '${FIXTURES.projectSlug}' in organization '${FIXTURES.organizationSlug}' to change its name to 'Updated Project Name' and slug to 'updated-project-slug'. Output only the new project slug as plain text without any formatting:\nupdated-project-slug`,
expectedTools: [
{
name: "update_project",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
projectSlug: FIXTURES.projectSlug,
name: "Updated Project Name",
slug: "updated-project-slug",
},
},
],
},
{
input: `Assign the project '${FIXTURES.projectSlug}' in organization '${FIXTURES.organizationSlug}' to the team '${FIXTURES.teamSlug}'. Output only the team slug as plain text without any formatting:\nthe-goats`,
expectedTools: [
{
name: "update_project",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
projectSlug: FIXTURES.projectSlug,
teamSlug: FIXTURES.teamSlug,
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/sentry.config.ts:
--------------------------------------------------------------------------------
```typescript
import type { Env } from "./types";
import { LIB_VERSION } from "@sentry/mcp-core/version";
import * as Sentry from "@sentry/cloudflare";
import { sentryBeforeSend } from "@sentry/mcp-core/telem/sentry";
type SentryConfig = ReturnType<Parameters<typeof Sentry.withSentry>[0]>;
export default function getSentryConfig(env: Env): SentryConfig {
const { id: versionId } = env.CF_VERSION_METADATA;
return {
dsn: env.SENTRY_DSN,
tracesSampleRate: 1,
sendDefaultPii: true,
beforeSend: sentryBeforeSend,
initialScope: {
tags: {
"mcp.server_version": LIB_VERSION,
"sentry.host": env.SENTRY_HOST,
},
},
release: versionId,
environment:
env.SENTRY_ENVIRONMENT ??
(process.env.NODE_ENV !== "production" ? "development" : "production"),
enableLogs: true,
integrations: [
Sentry.consoleLoggingIntegration(),
Sentry.zodErrorsIntegration(),
Sentry.vercelAIIntegration(),
],
};
}
getSentryConfig.partial = (config: Partial<SentryConfig>) => {
return (env: Env) => {
const defaultConfig = getSentryConfig(env);
return {
...defaultConfig,
...config,
initialScope: {
...defaultConfig.initialScope,
...config.initialScope,
tags: {
// idk I can't typescript
...((defaultConfig.initialScope ?? {}) as any).tags,
...((config.initialScope ?? {}) as any).tags,
},
},
};
};
};
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/use-sentry/agent.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import type { Tool } from "ai";
import { ConfigurationError } from "../../errors";
import {
callEmbeddedAgent,
type ToolCall,
} from "../../internal/agents/callEmbeddedAgent";
import { systemPrompt } from "./config";
/**
* Output schema for the embedded agent.
* The agent returns the final result string directly from its tool calls.
*/
const outputSchema = z.object({
result: z
.string()
.describe(
"The final result from your tool calls that answers the user's request",
),
});
export interface UseSentryAgentOptions {
request: string;
tools: Record<string, Tool>;
}
/**
* use_sentry agent - executes natural language requests using Sentry MCP tools
* This returns the final result AND the tool calls made by the agent
*/
export async function useSentryAgent(options: UseSentryAgentOptions): Promise<{
result: z.infer<typeof outputSchema>;
toolCalls: ToolCall[];
}> {
if (!process.env.OPENAI_API_KEY) {
throw new ConfigurationError(
"OPENAI_API_KEY environment variable is required for use_sentry tool",
);
}
// Frame the request to make clear we're asking the agent to use tools
// Don't just pass the raw request as it might trigger safety responses
const prompt = options.request;
// Use callEmbeddedAgent with all pre-wrapped MCP tools
return await callEmbeddedAgent({
system: systemPrompt,
prompt: prompt,
tools: options.tools,
schema: outputSchema,
});
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/markdown.tsx:
--------------------------------------------------------------------------------
```typescript
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { cn } from "@/client/lib/utils";
interface MarkdownProps {
children: string;
className?: string;
}
export function Markdown({ children, className }: MarkdownProps) {
return (
<ReactMarkdown
className={cn(
"prose prose-invert prose-slate max-w-none",
"prose-p:my-2 prose-p:leading-relaxed",
"prose-pre:bg-slate-900 prose-pre:border prose-pre:border-slate-700",
"prose-code:bg-slate-800 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm",
"prose-code:before:content-none prose-code:after:content-none",
"prose-strong:text-slate-100",
"prose-em:text-slate-200",
"prose-a:text-violet-300",
"prose-blockquote:border-l-violet-500 prose-blockquote:bg-slate-800/50 prose-blockquote:py-2 prose-blockquote:px-4",
"prose-h1:text-slate-100 prose-h2:text-slate-100 prose-h3:text-slate-100",
"prose-h4:text-slate-100 prose-h5:text-slate-100 prose-h6:text-slate-100",
"prose-ul:my-2 prose-ol:my-2",
"prose-li:my-1",
"prose-hr:border-slate-700",
"prose-table:border-slate-700",
"prose-th:border-slate-700 prose-td:border-slate-700",
className,
)}
remarkPlugins={[remarkGfm]}
disallowedElements={["script", "style", "iframe", "object", "embed"]}
unwrapDisallowed={true}
>
{children}
</ReactMarkdown>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/tags.json:
--------------------------------------------------------------------------------
```json
[
{
"key": "transaction",
"name": "Transaction",
"totalValues": 1080
},
{
"key": "runtime.name",
"name": "Runtime.Name",
"totalValues": 1080
},
{
"key": "level",
"name": "Level",
"totalValues": 1144
},
{
"key": "device",
"name": "Device",
"totalValues": 25
},
{
"key": "os",
"name": "OS",
"totalValues": 1133
},
{
"key": "user",
"name": "User",
"totalValues": 1080
},
{
"key": "runtime",
"name": "Runtime",
"totalValues": 1080
},
{
"key": "release",
"name": "Release",
"totalValues": 1135
},
{
"key": "url",
"name": "URL",
"totalValues": 1080
},
{
"key": "uptime_rule",
"name": "Uptime Rule",
"totalValues": 9
},
{
"key": "server_name",
"name": "Server",
"totalValues": 1080
},
{
"key": "browser",
"name": "Browser",
"totalValues": 56
},
{
"key": "os.name",
"name": "Os.Name",
"totalValues": 1135
},
{
"key": "device.family",
"name": "Device.Family",
"totalValues": 25
},
{
"key": "replayId",
"name": "Replayid",
"totalValues": 55
},
{
"key": "client_os.name",
"name": "Client Os.Name",
"totalValues": 1
},
{
"key": "environment",
"name": "Environment",
"totalValues": 1144
},
{
"key": "service",
"name": "Service",
"totalValues": 1135
},
{
"key": "browser.name",
"name": "Browser.Name",
"totalValues": 56
}
]
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-items-attributes-spans-string.json:
--------------------------------------------------------------------------------
```json
[
{
"key": "span.op",
"name": "Span Operation"
},
{
"key": "span.description",
"name": "Span Description"
},
{
"key": "span.status",
"name": "Span Status"
},
{
"key": "transaction",
"name": "Transaction"
},
{
"key": "transaction.op",
"name": "Transaction Operation"
},
{
"key": "transaction.status",
"name": "Transaction Status"
},
{
"key": "project",
"name": "Project"
},
{
"key": "environment",
"name": "Environment"
},
{
"key": "release",
"name": "Release"
},
{
"key": "user.id",
"name": "User ID"
},
{
"key": "user.email",
"name": "User Email"
},
{
"key": "user.username",
"name": "Username"
},
{
"key": "platform",
"name": "Platform"
},
{
"key": "sdk.name",
"name": "SDK Name"
},
{
"key": "sdk.version",
"name": "SDK Version"
},
{
"key": "http.method",
"name": "HTTP Method"
},
{
"key": "http.url",
"name": "HTTP URL"
},
{
"key": "browser.name",
"name": "Browser Name"
},
{
"key": "os.name",
"name": "OS Name"
},
{
"key": "device",
"name": "Device"
},
{
"key": "geo.country_code",
"name": "Country Code"
},
{
"key": "geo.region",
"name": "Geographic Region"
},
{
"key": "geo.city",
"name": "City"
},
{
"key": "custom.tier",
"name": "Customer Tier"
},
{
"key": "custom.feature_flag",
"name": "Feature Flag"
}
]
```
--------------------------------------------------------------------------------
/packages/mcp-server/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@sentry/mcp-server",
"version": "0.24.0",
"type": "module",
"packageManager": "[email protected]",
"engines": {
"node": ">=20"
},
"publishConfig": {
"access": "public"
},
"license": "FSL-1.1-ALv2",
"author": "Sentry",
"homepage": "https://github.com/getsentry/sentry-mcp",
"keywords": [
"sentry",
"mcp",
"model-context-protocol"
],
"bugs": {
"url": "https://github.com/getsentry/sentry-mcp/issues"
},
"repository": {
"type": "git",
"url": "[email protected]:getsentry/sentry-mcp.git"
},
"bin": {
"sentry-mcp": "./dist/index.js"
},
"files": [
"./dist/*"
],
"exports": {
".": {
"types": "./dist/index.ts",
"default": "./dist/index.js"
},
"./transports/stdio": {
"types": "./dist/transports/stdio.ts",
"default": "./dist/transports/stdio.js"
}
},
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"start": "tsx src/index.ts",
"test": "vitest run",
"test:watch": "vitest",
"tsc": "tsc --noEmit"
},
"dependencies": {
"@modelcontextprotocol/sdk": "catalog:",
"@sentry/node": "catalog:",
"@sentry/core": "catalog:",
"dotenv": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@sentry/mcp-core": "workspace:*",
"@sentry/mcp-server-mocks": "workspace:*",
"@sentry/mcp-server-tsconfig": "workspace:*",
"@types/node": "catalog:",
"tsdown": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/app.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import app from "./app";
describe("app", () => {
describe("GET /robots.txt", () => {
it("should return correct robots.txt content", async () => {
const res = await app.request("/robots.txt", {
headers: {
"CF-Connecting-IP": "192.0.2.1",
},
});
expect(res.status).toBe(200);
const text = await res.text();
expect(text).toBe(
["User-agent: *", "Allow: /$", "Disallow: /"].join("\n"),
);
});
});
describe("GET /llms.txt", () => {
it("should return correct llms.txt content", async () => {
const res = await app.request("/llms.txt", {
headers: {
"CF-Connecting-IP": "192.0.2.1",
},
});
expect(res.status).toBe(200);
const text = await res.text();
expect(text).toContain("# sentry-mcp");
expect(text).toContain("Model Context Protocol");
});
});
describe("GET /sse", () => {
it("should return deprecation message with 410 status", async () => {
const res = await app.request("/sse", {
headers: {
"CF-Connecting-IP": "192.0.2.1",
},
});
expect(res.status).toBe(410);
const json = await res.json();
expect(json).toEqual({
error: "SSE transport has been removed",
message:
"The SSE transport endpoint is no longer supported. Please use the HTTP transport at /mcp instead.",
migrationGuide: "https://mcp.sentry.dev",
});
});
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/server.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "server",
"description": "These attributes may be used to describe the server in a connection-based network interaction where there is one side that initiates the connection (the client is the side that initiates the connection). This covers all TCP network interactions since TCP is connection-based and one side initiates the connection (an exception is made for peer-to-peer communication over TCP where the \"user-facing\" surface of the protocol / API doesn't expose a clear notion of client and server). This also covers UDP network interactions where one side initiates the interaction, e.g. QUIC (HTTP/3) and DNS.\n",
"attributes": {
"server.address": {
"description": "Server domain name if available without reverse DNS lookup; otherwise, IP address or Unix domain socket name.",
"type": "string",
"note": "When observed from the client side, and when communicating through an intermediary, `server.address` SHOULD represent the server address behind any intermediaries, for example proxies, if it's available.\n",
"stability": "stable",
"examples": ["example.com", "10.1.2.80", "/tmp/my.sock"]
},
"server.port": {
"description": "Server port number.",
"type": "number",
"note": "When observed from the client side, and when communicating through an intermediary, `server.port` SHOULD represent the server port behind any intermediaries, for example proxies, if it's available.\n",
"stability": "stable",
"examples": ["80", "8080", "443"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/client.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "client",
"description": "These attributes may be used to describe the client in a connection-based network interaction where there is one side that initiates the connection (the client is the side that initiates the connection). This covers all TCP network interactions since TCP is connection-based and one side initiates the connection (an exception is made for peer-to-peer communication over TCP where the \"user-facing\" surface of the protocol / API doesn't expose a clear notion of client and server). This also covers UDP network interactions where one side initiates the interaction, e.g. QUIC (HTTP/3) and DNS.\n",
"attributes": {
"client.address": {
"description": "Client address - domain name if available without reverse DNS lookup; otherwise, IP address or Unix domain socket name.",
"type": "string",
"note": "When observed from the server side, and when communicating through an intermediary, `client.address` SHOULD represent the client address behind any intermediaries, for example proxies, if it's available.\n",
"stability": "stable",
"examples": ["client.example.com", "10.1.2.80", "/tmp/my.sock"]
},
"client.port": {
"description": "Client port number.",
"type": "number",
"note": "When observed from the server side, and when communicating through an intermediary, `client.port` SHOULD represent the client port behind any intermediaries, for example proxies, if it's available.\n",
"stability": "stable",
"examples": ["65123"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/slash-command-actions.tsx:
--------------------------------------------------------------------------------
```typescript
/**
* Component for rendering clickable slash command buttons
*/
import { Button } from "./button";
import { Terminal } from "lucide-react";
interface SlashCommandActionsProps {
onCommandSelect: (command: string) => void;
}
const SLASH_COMMANDS = [
{ command: "help", description: "Show help message" },
{ command: "tools", description: "List available MCP tools" },
{ command: "resources", description: "List available MCP resources" },
{ command: "prompts", description: "List available MCP prompts" },
{ command: "clear", description: "Clear all chat messages" },
{ command: "logout", description: "Log out of the current session" },
];
export function SlashCommandActions({
onCommandSelect,
}: SlashCommandActionsProps) {
return (
<div className="mt-4 space-y-3">
<h4 className="text-sm font-medium text-slate-300 mb-2">
Try these commands:
</h4>
<div className="space-y-2">
{SLASH_COMMANDS.map((cmd) => (
<div key={cmd.command} className="flex items-center gap-3">
<Button
onClick={() => onCommandSelect(cmd.command)}
size="sm"
variant="outline"
className="flex items-center gap-2 text-xs bg-blue-900/50 border-blue-700/50 hover:bg-blue-800/50 hover:border-blue-600/50 text-blue-300 font-mono"
>
<Terminal className="h-3 w-3" />/{cmd.command}
</Button>
<span className="text-xs text-slate-400">{cmd.description}</span>
</div>
))}
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/chat/auth-form.tsx:
--------------------------------------------------------------------------------
```typescript
import { Button } from "../ui/button";
import { AlertCircle } from "lucide-react";
import type { AuthFormProps } from "./types";
export function AuthForm({ authError, onOAuthLogin }: AuthFormProps) {
return (
<div className="sm:p-8 p-4 flex flex-col items-center">
<div className="max-w-md w-full space-y-6">
{/* Chat illustration - hidden on short screens */}
<div className=" hidden [@media(min-height:500px)]:block">
<img
src="/flow-transparent.png"
alt="Flow"
width={1536}
height={1024}
className="w-full mb-6 bg-violet-300 rounded-xl select-none"
draggable={false}
/>
</div>
<div className="text-center space-y-2">
<h1 className="text-2xl font-bold text-white">Live MCP Demo</h1>
<p className="">
Connect your Sentry account to test the Model Context Protocol with
real data from your projects.
</p>
</div>
<div className="space-y-4">
{authError && (
<div className="p-3 bg-red-900/20 border border-red-500/30 rounded flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-400" />
<div className="text-red-400 text-sm">{authError}</div>
</div>
)}
<Button
onClick={onOAuthLogin}
variant="default"
className="w-full cursor-pointer rounded-xl"
>
Connect with Sentry
</Button>
</div>
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/os.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "os",
"description": "The operating system (OS) on which the process represented by this resource is running.\n",
"attributes": {
"os.type": {
"description": "The operating system type.\n",
"type": "string",
"stability": "development",
"examples": [
"windows",
"linux",
"darwin",
"freebsd",
"netbsd",
"openbsd",
"dragonflybsd",
"hpux",
"aix",
"solaris",
"z_os",
"zos"
]
},
"os.description": {
"description": "Human readable (not intended to be parsed) OS version information, like e.g. reported by `ver` or `lsb_release -a` commands.\n",
"type": "string",
"stability": "development",
"examples": [
"Microsoft Windows [Version 10.0.18363.778]",
"Ubuntu 18.04.1 LTS"
]
},
"os.name": {
"description": "Human readable operating system name.",
"type": "string",
"stability": "development",
"examples": ["iOS", "Android", "Ubuntu"]
},
"os.version": {
"description": "The version string of the operating system as defined in [Version Attributes](/docs/resource/README.md#version-attributes).\n",
"type": "string",
"stability": "development",
"examples": ["14.2.1", "18.04.1"]
},
"os.build_id": {
"description": "Unique identifier for a particular build or compilation of the operating system.",
"type": "string",
"stability": "development",
"examples": ["TQ3C.230805.001.B2", "20E247", "22621"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/project.json:
--------------------------------------------------------------------------------
```json
{
"team": {
"id": "4509106733776896",
"slug": "the-goats",
"name": "the-goats"
},
"teams": [
{
"id": "4509106733776896",
"slug": "the-goats",
"name": "the-goats"
}
],
"id": "4509109104082945",
"name": "cloudflare-mcp",
"slug": "cloudflare-mcp",
"isBookmarked": false,
"isMember": true,
"access": [
"event:admin",
"alerts:read",
"project:write",
"org:integrations",
"alerts:write",
"member:read",
"team:write",
"project:read",
"event:read",
"event:write",
"project:admin",
"org:read",
"team:admin",
"project:releases",
"team:read"
],
"hasAccess": true,
"dateCreated": "2025-04-06T14:13:37.825970Z",
"environments": [],
"eventProcessing": {
"symbolicationDegraded": false
},
"features": [
"discard-groups",
"alert-filters",
"similarity-embeddings",
"similarity-indexing",
"similarity-view"
],
"firstEvent": null,
"firstTransactionEvent": false,
"hasSessions": false,
"hasProfiles": false,
"hasReplays": false,
"hasFeedbacks": false,
"hasNewFeedbacks": false,
"hasMonitors": false,
"hasMinifiedStackTrace": false,
"hasInsightsHttp": false,
"hasInsightsDb": false,
"hasInsightsAssets": false,
"hasInsightsAppStart": false,
"hasInsightsScreenLoad": false,
"hasInsightsVitals": false,
"hasInsightsCaches": false,
"hasInsightsQueues": false,
"hasInsightsLlmMonitoring": false,
"platform": "node",
"platforms": [],
"latestRelease": null,
"hasUserReports": false,
"hasFlags": false,
"latestDeploys": null
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/utils/chat-error-handler.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Simplified chat error handling utilities
* Only handles two concerns: auth errors vs other errors
*/
/**
* Check if an error is authentication-related (401)
*/
export function isAuthError(error: Error): boolean {
const message = error.message.toLowerCase();
// Check for 401 status code or auth-related keywords
return (
message.includes("401") ||
message.includes("auth_expired") ||
message.includes("invalid_token") ||
message.includes("session has expired") ||
message.includes("unauthorized")
);
}
/**
* Extract a user-friendly error message from the error
*/
export function getErrorMessage(error: Error): string {
try {
// Try to parse JSON error response
const jsonMatch = error.message.match(/\{.*\}/);
if (jsonMatch) {
const data = JSON.parse(jsonMatch[0]);
if (data.error) {
return data.error;
}
}
} catch {
// Ignore JSON parse errors
}
// Check for specific error types
if (isAuthError(error)) {
return "Your session has expired. Please log in again.";
}
if (
error.message.includes("429") ||
error.message.toLowerCase().includes("rate_limit")
) {
return "You've sent too many messages. Please wait a moment before trying again.";
}
if (
error.message.includes("403") ||
error.message.toLowerCase().includes("permission")
) {
return "You don't have permission to access this organization.";
}
if (error.message.includes("500")) {
return "Something went wrong on our end. Please try again.";
}
// Default message
return "An error occurred. Please try again.";
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/list-teams.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("list-teams", {
data: async () => {
return [
{
input: `What teams do I have access to in Sentry for '${FIXTURES.organizationSlug}'`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "find_teams",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
regionUrl: "https://us.sentry.io",
},
},
],
},
{
input: `Do I have access to the team '${FIXTURES.teamSlug}' for '${FIXTURES.organizationSlug}'`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "find_teams",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
regionUrl: "https://us.sentry.io",
},
},
],
},
{
input: `Do I have access to the team 'an-imaginary-team' for '${FIXTURES.organizationSlug}'`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "find_teams",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
regionUrl: "https://us.sentry.io",
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
import whoami from "./whoami";
import findOrganizations from "./find-organizations";
import findTeams from "./find-teams";
import findProjects from "./find-projects";
import findReleases from "./find-releases";
import getIssueDetails from "./get-issue-details";
import getTraceDetails from "./get-trace-details";
import getEventAttachment from "./get-event-attachment";
import updateIssue from "./update-issue";
import searchEvents from "./search-events";
import createTeam from "./create-team";
import createProject from "./create-project";
import updateProject from "./update-project";
import createDsn from "./create-dsn";
import findDsns from "./find-dsns";
import analyzeIssueWithSeer from "./analyze-issue-with-seer";
import searchDocs from "./search-docs";
import getDoc from "./get-doc";
import searchIssues from "./search-issues";
import useSentry from "./use-sentry";
// Default export: object mapping tool names to tools
export default {
whoami,
find_organizations: findOrganizations,
find_teams: findTeams,
find_projects: findProjects,
find_releases: findReleases,
get_issue_details: getIssueDetails,
get_trace_details: getTraceDetails,
get_event_attachment: getEventAttachment,
update_issue: updateIssue,
search_events: searchEvents,
create_team: createTeam,
create_project: createProject,
update_project: updateProject,
create_dsn: createDsn,
find_dsns: findDsns,
analyze_issue_with_seer: analyzeIssueWithSeer,
search_docs: searchDocs,
get_doc: getDoc,
search_issues: searchIssues,
use_sentry: useSentry,
} as const;
// Type export
export type ToolName = keyof typeof import("./index").default;
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/types.ts:
--------------------------------------------------------------------------------
```typescript
import type { OAuthHelpers } from "@cloudflare/workers-oauth-provider";
import type {
RateLimit,
WorkerVersionMetadata,
} from "@cloudflare/workers-types";
/**
* Props passed through OAuth and available via ExecutionContext.props
*
* These props are set in the OAuth callback and become available
* to the MCP handler through ExecutionContext.props (set by OAuth provider).
*/
export type WorkerProps = {
// OAuth standard fields
id: string;
// Sentry-specific fields
accessToken: string;
refreshToken?: string;
accessTokenExpiresAt?: number; // Timestamp when the upstream access token expires
clientId: string;
scope: string;
/**
* @deprecated grantedScopes is deprecated and will be removed on Jan 1, 2026.
* Use grantedSkills instead. Skills are the primary authorization method.
* This field exists only for backward compatibility during the transition period.
*/
grantedScopes?: string[];
/** Primary authorization method - array of skill strings */
grantedSkills?: string[];
// Note: constraints are NOT included - they're extracted per-request from URL
// Note: sentryHost and mcpUrl come from env, not OAuth props
};
export interface Env {
NODE_ENV: string;
ASSETS: Fetcher;
OAUTH_KV: KVNamespace;
COOKIE_SECRET: string;
SENTRY_CLIENT_ID: string;
SENTRY_CLIENT_SECRET: string;
SENTRY_ENVIRONMENT?: string;
SENTRY_DSN?: string;
SENTRY_HOST?: string;
OPENAI_API_KEY: string;
MCP_URL?: string;
OAUTH_PROVIDER: OAuthHelpers;
AI: Ai;
CF_VERSION_METADATA: WorkerVersionMetadata;
CHAT_RATE_LIMITER: RateLimit;
SEARCH_RATE_LIMITER: RateLimit;
MCP_RATE_LIMITER: RateLimit;
AUTORAG_INDEX_NAME?: string;
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/hardware.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "hardware",
"description": "Attributes for hardware.\n",
"attributes": {
"hw.id": {
"description": "An identifier for the hardware component, unique within the monitored host\n",
"type": "string",
"stability": "development",
"examples": ["win32battery_battery_testsysa33_1"]
},
"hw.name": {
"description": "An easily-recognizable name for the hardware component\n",
"type": "string",
"stability": "development",
"examples": ["eth0"]
},
"hw.parent": {
"description": "Unique identifier of the parent component (typically the `hw.id` attribute of the enclosure, or disk controller)\n",
"type": "string",
"stability": "development",
"examples": ["dellStorage_perc_0"]
},
"hw.type": {
"description": "Type of the component\n",
"type": "string",
"note": "Describes the category of the hardware component for which `hw.state` is being reported. For example, `hw.type=temperature` along with `hw.state=degraded` would indicate that the temperature of the hardware component has been reported as `degraded`.\n",
"stability": "development",
"examples": [
"battery",
"cpu",
"disk_controller",
"enclosure",
"fan",
"gpu",
"logical_disk",
"memory",
"network",
"physical_disk",
"power_supply",
"tape_drive",
"temperature",
"voltage"
]
},
"hw.state": {
"description": "The current state of the component\n",
"type": "string",
"stability": "development",
"examples": ["ok", "degraded", "failed"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-organizations.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import findOrganizations from "./find-organizations.js";
describe("find_organizations", () => {
it("serializes", async () => {
const result = await findOrganizations.handler(
{ query: null },
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toMatchInlineSnapshot(`
"# Organizations
## **sentry-mcp-evals**
**Web URL:** https://sentry.io/sentry-mcp-evals
**Region URL:** https://us.sentry.io
# Using this information
- The organization's name is the identifier for the organization, and is used in many tools for \`organizationSlug\`.
- If a tool supports passing in the \`regionUrl\`, you MUST pass in the correct value shown above for each organization.
- For Sentry's Cloud Service (sentry.io), always use the regionUrl to ensure requests go to the correct region.
"
`);
});
it("handles empty regionUrl parameter", async () => {
const result = await findOrganizations.handler(
{ query: null },
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toContain("Organizations");
});
it("handles undefined regionUrl parameter", async () => {
const result = await findOrganizations.handler(
{ query: null },
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toContain("Organizations");
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/utils/client-ip.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { getClientIp } from "./client-ip";
describe("getClientIp", () => {
it("returns CF-Connecting-IP when available", () => {
const request = new Request("https://example.com", {
headers: {
"CF-Connecting-IP": "192.0.2.1",
"X-Real-IP": "192.0.2.2",
"X-Forwarded-For": "192.0.2.3, 192.0.2.4",
},
});
expect(getClientIp(request)).toBe("192.0.2.1");
});
it("returns X-Real-IP when CF-Connecting-IP not available", () => {
const request = new Request("https://example.com", {
headers: {
"X-Real-IP": "192.0.2.2",
"X-Forwarded-For": "192.0.2.3, 192.0.2.4",
},
});
expect(getClientIp(request)).toBe("192.0.2.2");
});
it("returns first IP from X-Forwarded-For when others not available", () => {
const request = new Request("https://example.com", {
headers: {
"X-Forwarded-For": "192.0.2.3, 192.0.2.4, 192.0.2.5",
},
});
expect(getClientIp(request)).toBe("192.0.2.3");
});
it("trims whitespace from X-Forwarded-For IP", () => {
const request = new Request("https://example.com", {
headers: {
"X-Forwarded-For": " 192.0.2.3 , 192.0.2.4",
},
});
expect(getClientIp(request)).toBe("192.0.2.3");
});
it("returns null when no IP headers present", () => {
const request = new Request("https://example.com");
expect(getClientIp(request)).toBe(null);
});
it("returns null when X-Forwarded-For is empty", () => {
const request = new Request("https://example.com", {
headers: {
"X-Forwarded-For": "",
},
});
expect(getClientIp(request)).toBe(null);
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/db.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "db",
"description": "Database operations attributes",
"attributes": {
"db.system.name": {
"description": "Identifies the database management system product",
"type": "string",
"examples": [
"postgresql",
"mysql",
"microsoft.sql_server",
"mongodb",
"redis",
"cassandra"
]
},
"db.collection.name": {
"description": "Name of a collection (table, container) within the database",
"type": "string"
},
"db.namespace": {
"description": "Fully qualified database name within server address and port",
"type": "string"
},
"db.operation.name": {
"description": "Name of the operation or command being executed",
"type": "string",
"examples": [
"SELECT",
"INSERT",
"UPDATE",
"DELETE",
"CREATE",
"DROP",
"find",
"insert",
"update",
"delete"
]
},
"db.response.status_code": {
"description": "Status code returned by the database",
"type": "string"
},
"db.operation.batch.size": {
"description": "Number of queries in a batch operation",
"type": "number"
},
"db.query.summary": {
"description": "Low-cardinality summary of a database query",
"type": "string"
},
"db.query.text": {
"description": "The actual database query being executed",
"type": "string"
},
"db.stored_procedure.name": {
"description": "Name of a stored procedure within the database",
"type": "string"
},
"db.query.parameter.<key>": {
"description": "Database query parameter values",
"type": "string"
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/utils/auth-errors.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Shared utilities for detecting and handling authentication errors
*/
export interface AuthErrorInfo {
isAuthError: boolean;
isExpired: boolean;
isForbidden: boolean;
statusCode?: number;
}
/**
* Analyze an error to determine if it's authentication-related
*/
export function analyzeAuthError(error: unknown): AuthErrorInfo {
const result: AuthErrorInfo = {
isAuthError: false,
isExpired: false,
isForbidden: false,
};
if (!(error instanceof Error)) {
return result;
}
const errorMessage = error.message.toLowerCase();
// Check for 401 Unauthorized errors
if (
errorMessage.includes("401") ||
errorMessage.includes("unauthorized") ||
errorMessage.includes("authentication") ||
errorMessage.includes("invalid token") ||
errorMessage.includes("access token")
) {
result.isAuthError = true;
result.isExpired = true;
result.statusCode = 401;
}
// Check for 403 Forbidden errors
if (errorMessage.includes("403") || errorMessage.includes("forbidden")) {
result.isAuthError = true;
result.isForbidden = true;
result.statusCode = 403;
}
return result;
}
/**
* Get appropriate error response based on auth error type
*/
export function getAuthErrorResponse(authInfo: AuthErrorInfo) {
if (authInfo.isExpired) {
return {
error: "Authentication with Sentry has expired. Please log in again.",
name: "AUTH_EXPIRED" as const,
};
}
if (authInfo.isForbidden) {
return {
error: "You don't have permission to access this Sentry organization.",
name: "INSUFFICIENT_PERMISSIONS" as const,
};
}
return {
error: "Authentication error occurred",
name: "SENTRY_AUTH_INVALID" as const,
};
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/slash-command-text.tsx:
--------------------------------------------------------------------------------
```typescript
/**
* Component that renders text with clickable slash commands
*/
import { Fragment } from "react";
interface SlashCommandTextProps {
children: string;
onSlashCommand: (command: string) => void;
}
const SLASH_COMMAND_REGEX = /\/([a-zA-Z]+)/g;
export function SlashCommandText({
children,
onSlashCommand,
}: SlashCommandTextProps) {
const parts = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
// Reset regex state before using
SLASH_COMMAND_REGEX.lastIndex = 0;
// Find all slash commands in the text
match = SLASH_COMMAND_REGEX.exec(children);
while (match !== null) {
const fullMatch = match[0]; // e.g., "/help"
const command = match[1]; // e.g., "help"
const startIndex = match.index;
// Add text before the command
if (startIndex > lastIndex) {
parts.push(
<Fragment key={`text-${lastIndex}`}>
{children.slice(lastIndex, startIndex)}
</Fragment>,
);
}
// Add clickable command
parts.push(
<button
key={`command-${startIndex}`}
onClick={() => onSlashCommand(command)}
className="inline-flex items-center gap-1 px-1 py-0.5 text-xs bg-blue-900/50 border border-blue-700/50 rounded text-blue-300 hover:bg-blue-800/50 hover:border-blue-600/50 transition-colors font-mono cursor-pointer"
type="button"
>
{fullMatch}
</button>,
);
lastIndex = startIndex + fullMatch.length;
// Continue searching
match = SLASH_COMMAND_REGEX.exec(children);
}
// Add remaining text
if (lastIndex < children.length) {
parts.push(
<Fragment key={`text-${lastIndex}`}>
{children.slice(lastIndex)}
</Fragment>,
);
}
return <>{parts}</>;
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/cassandra.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "cassandra",
"description": "This section defines attributes for Cassandra.\n",
"attributes": {
"cassandra.coordinator.dc": {
"description": "The data center of the coordinating node for a query.\n",
"type": "string",
"stability": "development",
"examples": ["us-west-2"]
},
"cassandra.coordinator.id": {
"description": "The ID of the coordinating node for a query.\n",
"type": "string",
"stability": "development",
"examples": ["be13faa2-8574-4d71-926d-27f16cf8a7af"]
},
"cassandra.consistency.level": {
"description": "The consistency level of the query. Based on consistency values from [CQL](https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/dml/dmlConfigConsistency.html).\n",
"type": "string",
"stability": "development",
"examples": [
"all",
"each_quorum",
"quorum",
"local_quorum",
"one",
"two",
"three",
"local_one",
"any",
"serial",
"local_serial"
]
},
"cassandra.query.idempotent": {
"description": "Whether or not the query is idempotent.\n",
"type": "boolean",
"stability": "development"
},
"cassandra.page.size": {
"description": "The fetch size used for paging, i.e. how many rows will be returned at once.\n",
"type": "number",
"stability": "development",
"examples": ["5000"]
},
"cassandra.speculative_execution.count": {
"description": "The number of times a query was speculatively executed. Not set or `0` if the query was not executed speculatively.\n",
"type": "number",
"stability": "development",
"examples": ["0", "2"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/json-schema-params.tsx:
--------------------------------------------------------------------------------
```typescript
type JsonSchema =
| {
properties?: Record<string, { description?: string } | undefined>;
required?: string[];
}
| null
| undefined;
interface JsonSchemaParamsProps {
schema: unknown;
title?: string;
}
/**
* Renders a standardized Parameters box from a JSON Schema-like object.
* - Expects an object with a `properties` map; falls back to a flat key->description map.
* - Returns null when there are no parameters to display.
*/
export default function JsonSchemaParams({
schema,
title = "Parameters",
}: JsonSchemaParamsProps) {
let entries: Array<[string, { description?: string } | undefined]> = [];
const obj = (schema as JsonSchema) ?? undefined;
if (obj && typeof obj === "object" && "properties" in obj) {
const props = obj.properties;
if (props && Object.keys(props).length > 0) {
entries = Object.entries(props);
}
} else if (schema && typeof schema === "object") {
const flat = schema as Record<string, { description?: string } | undefined>;
const keys = Object.keys(flat);
if (keys.length > 0) {
entries = Object.entries(flat);
}
}
if (entries.length === 0) return null;
return (
<section className="rounded-md border border-slate-700/60 bg-black/30 p-3">
<div className="text-xs uppercase tracking-wide text-slate-300/80 mb-1">
{title}
</div>
<dl className="space-y-0">
{entries.map(([key, property]) => (
<div key={key} className="p-2 bg-black/20">
<dt className="text-sm font-medium text-violet-300">{key}</dt>
<dd className="text-sm text-slate-300 mt-0.5">
{property?.description || ""}
</dd>
</div>
))}
</dl>
</section>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/typewriter.tsx:
--------------------------------------------------------------------------------
```typescript
import { useState, useEffect, useRef } from "react";
interface TypewriterProps {
text: string;
speed?: number;
children?: (displayedText: string) => React.ReactNode;
className?: string;
onComplete?: () => void;
}
export function Typewriter({
text,
speed = 30,
children,
className = "",
onComplete,
}: TypewriterProps) {
const [displayedText, setDisplayedText] = useState("");
const [isComplete, setIsComplete] = useState(false);
const indexRef = useRef(0);
const previousTextRef = useRef("");
useEffect(() => {
// Reset if text has changed (new content streaming in)
if (text !== previousTextRef.current) {
const previousText = previousTextRef.current;
// Check if new text is an extension of the previous text
if (text.startsWith(previousText) && text.length > previousText.length) {
// Text got longer, continue from where we left off
indexRef.current = Math.max(displayedText.length, previousText.length);
} else {
// Text completely changed, restart
indexRef.current = 0;
setDisplayedText("");
setIsComplete(false);
}
previousTextRef.current = text;
}
if (indexRef.current >= text.length) {
if (!isComplete) {
setIsComplete(true);
onComplete?.();
}
return;
}
const timer = setTimeout(() => {
setDisplayedText(text.slice(0, indexRef.current + 1));
indexRef.current++;
}, speed);
return () => clearTimeout(timer);
}, [text, speed, displayedText.length, isComplete, onComplete]);
return (
<span className={className}>
{children ? children(displayedText) : displayedText}
{!isComplete && <span className="animate-pulse">|</span>}
</span>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/csp-issue.json:
--------------------------------------------------------------------------------
```json
{
"id": "4256774711",
"shareId": null,
"shortId": "BLOG-CSP-4XC",
"title": "Blocked 'image' from 'blob:'",
"culprit": "img-src",
"permalink": "https://sentry.sentry.io/issues/4256774711/",
"logger": "csp",
"level": "error",
"status": "unresolved",
"statusDetails": {},
"substatus": "ongoing",
"isPublic": false,
"platform": "other",
"project": {
"id": "1297621",
"name": "blog-csp",
"slug": "blog-csp",
"platform": "other"
},
"type": "csp",
"metadata": {
"message": "Blocked 'image' from 'blob:'",
"uri": "blob:",
"directive": "img-src",
"initial_priority": 75,
"title": null
},
"numComments": 0,
"assignedTo": null,
"isBookmarked": false,
"isSubscribed": false,
"subscriptionDetails": null,
"hasSeen": true,
"annotations": [],
"issueType": "error",
"issueCategory": "error",
"priority": "high",
"priorityLockedAt": null,
"isUnhandled": false,
"count": "7019",
"userCount": 580,
"firstSeen": "2023-06-17T03:33:19.825291Z",
"lastSeen": "2025-11-19T19:24:12Z",
"firstRelease": null,
"lastRelease": null,
"tags": [
{ "key": "blocked-uri", "name": "Blocked-Uri", "totalValues": 776 },
{ "key": "browser", "name": "Browser", "totalValues": 776 },
{ "key": "browser.name", "name": "Browser.Name", "totalValues": 776 },
{ "key": "effective-directive", "name": "Effective-Directive", "totalValues": 776 },
{ "key": "level", "name": "Level", "totalValues": 776 },
{ "key": "logger", "name": "Logger", "totalValues": 776 },
{ "key": "url", "name": "URL", "totalValues": 776 }
],
"activity": [],
"seenBy": [],
"pluginActions": [],
"pluginIssues": [],
"pluginContexts": [],
"userReportCount": 0,
"stats": {},
"participants": []
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/test-setup.ts:
--------------------------------------------------------------------------------
```typescript
import { config } from "dotenv";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { startMockServer } from "@sentry/mcp-server-mocks";
import type { ServerContext } from "./types.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, "../../../");
// Load environment variables from multiple possible locations
// IMPORTANT: Do NOT use override:true as it would overwrite shell/CI environment variables
// Load local package .env first (for package-specific overrides)
config({ path: path.resolve(__dirname, "../.env") });
// Load root .env second (for shared defaults - won't override local or shell vars)
config({ path: path.join(rootDir, ".env") });
startMockServer({ ignoreOpenAI: true });
/**
* Creates a ServerContext for testing with default values and optional overrides.
*
* @param overrides - Partial ServerContext to override default values
* @returns Complete ServerContext for testing
*
* @example
* ```typescript
* // Default context
* const context = getServerContext();
*
* // With constraint overrides
* const context = getServerContext({
* constraints: { organizationSlug: "my-org" }
* });
*
* // With user override
* const context = getServerContext({
* userId: "custom-user-id"
* });
* ```
*/
export function getServerContext(
overrides: Partial<ServerContext> = {},
): ServerContext {
const defaultContext: ServerContext = {
accessToken: "access-token",
userId: "1",
constraints: {
organizationSlug: null,
projectSlug: null,
},
};
return {
...defaultContext,
...overrides,
// Ensure constraints are properly merged
constraints: {
...defaultContext.constraints,
...overrides.constraints,
},
};
}
```
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
```yaml
name: Test
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
# pnpm/action-setup@v4
- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
name: Install pnpm
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run build
run: pnpm build
- name: Run type checking
run: pnpm -w run tsc
- name: Run linter
run: pnpm lint
- name: Run tests
run: pnpm test:ci
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
flags: unittests
name: codecov-unittests
fail_ci_if_error: false
- name: Upload results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Publish Test Report
uses: mikepenz/action-junit-report@cf701569b05ccdd861a76b8607a66d76f6fd4857
if: ${{ !cancelled() }}
with:
report_paths: "**/*.junit.xml"
comment: false
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-meta.json:
--------------------------------------------------------------------------------
```json
{
"logs": 0,
"errors": 0,
"performance_issues": 0,
"span_count": 112.0,
"transaction_child_count_map": [
{
"transaction.event_id": "0daf40dc453a429c8c57e4c215c4e82c",
"count()": 10.0
},
{
"transaction.event_id": "845c0f1eb7544741a9d08cf28e144f42",
"count()": 1.0
},
{
"transaction.event_id": "b49a5b53cba046a2bab9323d8f00de96",
"count()": 7.0
},
{
"transaction.event_id": "c0db3d88529744d393f091b692c024ca",
"count()": 11.0
},
{
"transaction.event_id": "ee6e7f39107847f980e06119bf116d38",
"count()": 7.0
},
{
"transaction.event_id": "efa09ae9091e400e8865fe48aaae80d6",
"count()": 12.0
},
{
"transaction.event_id": "f398bbc635c64e2091d94679dade2957",
"count()": 3.0
},
{
"transaction.event_id": "f531c48c3eaa43be9587dd880b8ec4bb",
"count()": 6.0
},
{
"transaction.event_id": "f779775e1c6a4f09b62d68818a25d7b5",
"count()": 55.0
}
],
"span_count_map": {
"cache.get": 41.0,
"middleware.django": 24.0,
"db": 11.0,
"function": 6.0,
"db.redis": 4.0,
"feature.flagpole.batch_has": 4.0,
"processor": 2.0,
"execute": 2.0,
"fetch_organization_projects": 2.0,
"other": 1.0,
"db.clickhouse": 1.0,
"validator": 1.0,
"serialize": 1.0,
"http.client": 1.0,
"base.paginate.on_results": 1.0,
"ratelimit.__call__": 1.0,
"base.dispatch.request": 1.0,
"discover.endpoint": 1.0,
"serialize.iterate": 1.0,
"base.dispatch.setup": 1.0,
"serialize.get_attrs": 1.0,
"build_plan.storage_query_plan_builder": 1.0,
"serialize.get_attrs.project.options": 1.0,
"check_object_permissions_on_organization": 1.0,
"allocation_policy.get_quota_allowance": 1.0
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/mcp.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "mcp",
"description": "Model Context Protocol attributes for MCP tool calls and sessions",
"attributes": {
"mcp.tool.name": {
"description": "Tool name (e.g., search_issues, search_events)",
"type": "string",
"examples": [
"search_issues",
"search_events",
"get_issue_details",
"update_issue"
]
},
"mcp.session.id": {
"description": "MCP session identifier",
"type": "string"
},
"mcp.transport": {
"description": "MCP transport protocol used",
"type": "string",
"examples": ["stdio", "http", "websocket"]
},
"mcp.request.id": {
"description": "MCP request identifier",
"type": "string"
},
"mcp.response.status": {
"description": "MCP response status",
"type": "string",
"examples": ["success", "error"]
},
"mcp.client.name": {
"description": "Name of the MCP client application",
"type": "string",
"examples": [
"Cursor",
"Claude Code",
"VSCode MCP Extension",
"sentry-mcp-stdio"
]
},
"mcp.client.version": {
"description": "Version of the MCP client application",
"type": "string",
"examples": ["0.16.0", "1.0.0", "2.3.1"]
},
"mcp.server.name": {
"description": "Name of the MCP server application",
"type": "string",
"examples": ["Sentry MCP Server", "GitHub MCP Server", "Slack MCP Server"]
},
"mcp.server.version": {
"description": "Version of the MCP server application",
"type": "string",
"examples": ["0.1.0", "1.2.3", "2.0.0"]
},
"mcp.protocol.version": {
"description": "MCP protocol version being used",
"type": "string",
"examples": ["2024-11-05", "1.0", "2.0"]
}
},
"custom": true
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/cloudevents.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "cloudevents",
"description": "This document defines attributes for CloudEvents.\n",
"attributes": {
"cloudevents.event_id": {
"description": "The [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md#id) uniquely identifies the event.\n",
"type": "string",
"stability": "development",
"examples": ["123e4567-e89b-12d3-a456-426614174000", "0001"]
},
"cloudevents.event_source": {
"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",
"type": "string",
"stability": "development",
"examples": [
"https://github.com/cloudevents",
"/cloudevents/spec/pull/123",
"my-service"
]
},
"cloudevents.event_spec_version": {
"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",
"type": "string",
"stability": "development",
"examples": ["1.0"]
},
"cloudevents.event_type": {
"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",
"type": "string",
"stability": "development",
"examples": [
"com.github.pull_request.opened",
"com.example.object.deleted.v2"
]
},
"cloudevents.event_subject": {
"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",
"type": "string",
"stability": "development",
"examples": ["mynewfile.jpg"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/aspnetcore.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "aspnetcore",
"description": "ASP.NET Core attributes",
"attributes": {
"aspnetcore.rate_limiting.policy": {
"description": "Rate limiting policy name.",
"type": "string",
"stability": "stable",
"examples": ["fixed", "sliding", "token"]
},
"aspnetcore.rate_limiting.result": {
"description": "Rate-limiting result, shows whether the lease was acquired or contains a rejection reason",
"type": "string",
"stability": "stable",
"examples": [
"acquired",
"endpoint_limiter",
"global_limiter",
"request_canceled"
]
},
"aspnetcore.routing.is_fallback": {
"description": "A value that indicates whether the matched route is a fallback route.",
"type": "boolean",
"stability": "stable",
"examples": ["true"]
},
"aspnetcore.diagnostics.handler.type": {
"description": "Full type name of the [`IExceptionHandler`](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.diagnostics.iexceptionhandler) implementation that handled the exception.",
"type": "string",
"stability": "stable",
"examples": ["Contoso.MyHandler"]
},
"aspnetcore.request.is_unhandled": {
"description": "Flag indicating if request was handled by the application pipeline.",
"type": "boolean",
"stability": "stable",
"examples": ["true"]
},
"aspnetcore.routing.match_status": {
"description": "Match result - success or failure",
"type": "string",
"stability": "stable",
"examples": ["success", "failure"]
},
"aspnetcore.diagnostics.exception.result": {
"description": "ASP.NET Core exception middleware handling result",
"type": "string",
"stability": "stable",
"examples": ["handled", "unhandled", "skipped", "aborted"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/whoami.ts:
--------------------------------------------------------------------------------
```typescript
import { defineTool } from "../internal/tool-helpers/define";
import { apiServiceFromContext } from "../internal/tool-helpers/api";
import type { ServerContext } from "../types";
import { ALL_SKILLS } from "../skills";
export default defineTool({
name: "whoami",
description: [
"Identify the authenticated user in Sentry.",
"",
"Use this tool when you need to:",
"- Get the user's name and email address.",
].join("\n"),
inputSchema: {},
skills: ALL_SKILLS, // Foundational tool - available to all skills
requiredScopes: [], // No specific scopes required - uses authentication token
annotations: {
readOnlyHint: true,
openWorldHint: true,
},
async handler(params, context: ServerContext) {
// User data endpoints (like /auth/) should never use regionUrl
// as they must always query the main API server, not region-specific servers
const apiService = apiServiceFromContext(context);
// API client will throw ApiClientError/ApiServerError which the MCP server wrapper handles
const user = await apiService.getAuthenticatedUser();
let output = `You are authenticated as ${user.name} (${user.email}).\n\nYour Sentry User ID is ${user.id}.`;
// Add constraints information
const constraints = context.constraints;
if (
constraints.organizationSlug ||
constraints.projectSlug ||
constraints.regionUrl
) {
output += "\n\n## Session Constraints\n\n";
if (constraints.organizationSlug) {
output += `- **Organization**: ${constraints.organizationSlug}\n`;
}
if (constraints.projectSlug) {
output += `- **Project**: ${constraints.projectSlug}\n`;
}
if (constraints.regionUrl) {
output += `- **Region URL**: ${constraints.regionUrl}\n`;
}
output += "\nThese constraints limit the scope of this MCP session.";
}
return output;
},
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/seer-noisefilter.tsx:
--------------------------------------------------------------------------------
```typescript
export default function SeerNoiseFilter() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="0"
height="0"
aria-hidden="true"
>
<defs>
<filter
id="nnnoise-darken-fine"
filterUnits="objectBoundingBox"
primitiveUnits="objectBoundingBox"
x="-50%"
y="-50%"
width="200%"
height="200%"
colorInterpolationFilters="linearRGB"
>
{/* <!-- 1) Fine monochrome noise --> */}
<feTurbulence
type="fractalNoise"
baseFrequency="0.69"
numOctaves="1"
seed="9"
result="noise"
/>
<feColorMatrix in="noise" type="saturate" values="0" result="g" />
{/* <!-- 2) Shape the noise -> mostly near 0 with occasional spikes (speckles) --> */}
{/* <!-- gamma < 1 = more speckles; > 1 = fewer --> */}
<feComponentTransfer in="g" result="mask">
<feFuncR type="gamma" amplitude="1" exponent="0.65" offset="0" />
<feFuncG type="gamma" amplitude="1" exponent="0.65" offset="0" />
<feFuncB type="gamma" amplitude="1" exponent="0.65" offset="0" />
</feComponentTransfer>
{/* <!-- 3) Keep noise only where the element is opaque (transparent areas stay clean) --> */}
<feComposite
in="mask"
in2="SourceAlpha"
operator="in"
result="maskedNoise"
/>
{/* <!-- 4) Darken-only: out = SourceGraphic * (1 - strength * maskedNoise) --> */}
{/* <!-- arithmetic: k1=-strength, k2=1, k3=0, k4=0 --> */}
<feComposite
in="SourceGraphic"
in2="maskedNoise"
operator="arithmetic"
k1="-1"
k2="1"
k3="0"
k4="0"
/>
</filter>
</defs>
</svg>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-evals/src/evals/update-issue.eval.ts:
--------------------------------------------------------------------------------
```typescript
import { describeEval } from "vitest-evals";
import { FIXTURES, NoOpTaskRunner, ToolPredictionScorer } from "./utils";
describeEval("update-issue", {
data: async () => {
return [
// Core use case: Resolve an issue
{
input: `Resolve the issue ${FIXTURES.issueId} in organization ${FIXTURES.organizationSlug}. Output only the new status as a single word.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "update_issue",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
issueId: FIXTURES.issueId,
status: "resolved",
regionUrl: "https://us.sentry.io",
},
},
],
},
// Core use case: Assign an issue
{
input: `Assign the issue ${FIXTURES.issueId} in organization ${FIXTURES.organizationSlug} to 'john.doe'. Output only the assigned username.`,
expectedTools: [
{
name: "find_organizations",
arguments: {},
},
{
name: "update_issue",
arguments: {
organizationSlug: FIXTURES.organizationSlug,
issueId: FIXTURES.issueId,
assignedTo: "john.doe",
regionUrl: "https://us.sentry.io",
},
},
],
},
// Core use case: Using issue URL (alternative input method)
{
input: `Resolve the issue at ${FIXTURES.issueUrl}. Output only the new status as a single word.`,
expectedTools: [
{
name: "update_issue",
arguments: {
issueUrl: FIXTURES.issueUrl,
status: "resolved",
},
},
],
},
];
},
task: NoOpTaskRunner(),
scorers: [ToolPredictionScorer()],
threshold: 0.6,
timeout: 30000,
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/index.html:
--------------------------------------------------------------------------------
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sentry MCP</title>
<link href="./src/client/index.css" rel="stylesheet" />
<link rel="icon" href="/favicon.ico" />
<!-- Primary Meta Tags -->
<meta name="title" content="Sentry MCP" />
<meta name="description" content="A Model Context Protocol implementation for interacting with Sentry." />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://mcp.sentry.dev/" />
<meta property="og:title" content="A model context protocol implementation for interacting with Sentry." />
<meta property="og:description"
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." />
<meta property="og:image" content="https://mcp.sentry.dev/flow.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://mcp.sentry.dev/" />
<meta property="twitter:title" content="A model context protocol implementation for interacting with Sentry." />
<meta property="twitter:description"
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." />
<meta property="twitter:image" content="https://mcp.sentry.dev/flow.jpg" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/client/main.tsx"></script>
</body>
</html>
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts:
--------------------------------------------------------------------------------
```typescript
import { UserInputError } from "../../errors";
import { SENTRY_ALLOWED_REGION_DOMAINS } from "../../constants";
/**
* Validates that a regionUrl is valid.
* Prevents SSRF attacks by only allowing the base host itself or domains from an allowlist.
*
* Rules:
* 1. By default, only the base host itself is allowed as regionUrl
* 2. For other domains, they must be in SENTRY_ALLOWED_REGION_DOMAINS
* 3. Protocol MUST be HTTPS for security
*
* @param regionUrl - The region URL to validate
* @param baseHost - The base host to validate against
* @returns The validated host if valid
* @throws {UserInputError} If the regionUrl is invalid or not allowed
*/
export function validateRegionUrl(regionUrl: string, baseHost: string): string {
let parsedUrl: URL;
try {
parsedUrl = new URL(regionUrl);
} catch {
throw new UserInputError(
`Invalid regionUrl provided: ${regionUrl}. Must be a valid URL.`,
);
}
// Validate protocol - MUST be HTTPS for security
if (parsedUrl.protocol !== "https:") {
throw new UserInputError(
`Invalid regionUrl provided: ${regionUrl}. Must use HTTPS protocol for security.`,
);
}
// Validate that the host is not just the protocol name
if (parsedUrl.host === "https" || parsedUrl.host === "http") {
throw new UserInputError(
`Invalid regionUrl provided: ${regionUrl}. The host cannot be just a protocol name.`,
);
}
const regionHost = parsedUrl.host.toLowerCase();
const baseLower = baseHost.toLowerCase();
// First, allow if it's the same as the base host
if (regionHost === baseLower) {
return regionHost;
}
// Otherwise, check against the allowlist
if (!SENTRY_ALLOWED_REGION_DOMAINS.has(regionHost)) {
throw new UserInputError(
`Invalid regionUrl: ${regionUrl}. The domain '${regionHost}' is not allowed. Allowed domains are: ${Array.from(SENTRY_ALLOWED_REGION_DOMAINS).join(", ")}`,
);
}
return regionHost;
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-mcp-metadata.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Custom hook to fetch and manage MCP metadata
*
* Provides immediate access to prompts and tools without waiting for chat stream
*/
import { useState, useEffect, useCallback } from "react";
export interface McpMetadata {
type: "mcp-metadata";
prompts: Array<{
name: string;
description: string;
parameters: Record<
string,
{
type: string;
required: boolean;
description?: string;
}
>;
}>;
tools: string[];
resources?: Array<{
name: string;
description: string;
}>;
timestamp: string;
}
interface UseMcpMetadataResult {
metadata: McpMetadata | null;
isLoading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
export function useMcpMetadata(enabled = true): UseMcpMetadataResult {
const [metadata, setMetadata] = useState<McpMetadata | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchMetadata = useCallback(async () => {
if (!enabled) {
return;
}
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/metadata", {
credentials: "include", // Include cookies
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
const data = await response.json();
setMetadata(data);
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : "Failed to fetch metadata";
setError(errorMessage);
console.error("Failed to fetch MCP metadata:", err);
} finally {
setIsLoading(false);
}
}, [enabled]);
// Fetch metadata when auth token changes or component mounts
useEffect(() => {
fetchMetadata();
}, [fetchMetadata]);
return {
metadata,
isLoading,
error,
refetch: fetchMetadata,
};
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/whoami.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import whoami from "./whoami.js";
import {
createTestContext,
createTestContextWithConstraints,
} from "../test-utils/context.js";
describe("whoami", () => {
it("serializes without constraints", async () => {
const result = await whoami.handler(
{},
createTestContext({
constraints: {},
accessToken: "access-token",
userId: "123456",
}),
);
expect(result).toMatchInlineSnapshot(
`
"You are authenticated as Test User ([email protected]).
Your Sentry User ID is 123456."
`,
);
});
it("serializes with constraints", async () => {
const result = await whoami.handler(
{},
createTestContextWithConstraints(
{
organizationSlug: "sentry",
projectSlug: "mcp-server",
regionUrl: "https://us.sentry.io",
},
{
accessToken: "access-token",
userId: "123456",
},
),
);
expect(result).toMatchInlineSnapshot(
`
"You are authenticated as Test User ([email protected]).
Your Sentry User ID is 123456.
## Session Constraints
- **Organization**: sentry
- **Project**: mcp-server
- **Region URL**: https://us.sentry.io
These constraints limit the scope of this MCP session."
`,
);
});
it("serializes with partial constraints", async () => {
const result = await whoami.handler(
{},
createTestContextWithConstraints(
{
organizationSlug: "sentry",
},
{
accessToken: "access-token",
userId: "123456",
},
),
);
expect(result).toMatchInlineSnapshot(
`
"You are authenticated as Test User ([email protected]).
Your Sentry User ID is 123456.
## Session Constraints
- **Organization**: sentry
These constraints limit the scope of this MCP session."
`,
);
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/animation/browser-ui/seer.tsx:
--------------------------------------------------------------------------------
```typescript
import { useId } from "react";
import RootCause from "./RootCause";
import SeerNoiseFilter from "./seer-noisefilter";
import SeerClipMask from "./seer-clipmask";
export default function Seer({ step }: { step: number }) {
const id = useId().replace(/:/g, "");
return (
<div
className={`${
step === 2
? "scale-100 opacity-100 duration-300"
: "scale-90 opacity-0 pointer-events-none"
} 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`}
>
<div
className={`inset-0 absolute bg-background rounded-tl-xl rounded-br-3xl border-t border-l border-white/20 ${
step === 2
? "duration-300 opacity-80 translate-x-0"
: "duration-0 opacity-0 motion-safe:translate-x-1/2"
}`}
/>
<SeerNoiseFilter />
<SeerClipMask id={id} />
{/* ⚠️ Seer */}
<div
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"
style={{
clipPath: `url(#${id})`,
}}
>
<div className="bg-pink-300 [mask-image:linear-gradient(to_top,red,transparent)] absolute inset-0 [filter:url(#nnnoise-darken-fine)]" />
{/* eye mask */}
<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%)]">
<div className="bg-amber-100 [mask-image:radial-gradient(ellipse_at_bottom,red_50%,transparent_50%)]">
{/* 👁️ Eye of the Seer */}
<div
className={`mx-auto h-8 w-8 translate-y-1/2 rounded-full bg-blue-700 ${
step === 2
? "translate-x-6 delay-1200 duration-1500"
: "-translate-x-6"
}`}
/>
</div>
</div>
</div>
<RootCause step={step} />
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/code-snippet.tsx:
--------------------------------------------------------------------------------
```typescript
import { Copy, Check } from "lucide-react";
import { useState, useEffect, useRef } from "react";
import { Button } from "./button";
export default function CodeSnippet({
snippet,
noMargin,
}: {
snippet: string;
noMargin?: boolean;
}) {
const [copied, setCopied] = useState(false);
const timeoutRef = useRef<number | null>(null);
// Clean up timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(snippet);
// Only show success if the operation actually succeeded
setCopied(true);
// Clear any existing timeout before setting a new one
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setCopied(false);
timeoutRef.current = null;
}, 2000);
} catch (error) {
// Handle clipboard write failure silently or you could show an error state
console.error("Failed to copy to clipboard:", error);
}
};
return (
<div className={`relative text-white max-w-full ${!noMargin && "mb-6"}`}>
<div className="absolute top-2.5 right-2.5 flex items-center justify-end">
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-neutral-500 cursor-pointer duration-300 hover:bg-violet-300/25 rounded-md"
onClick={handleCopy}
>
{copied ? (
<Check className="h-4 w-4 text-green-500" />
) : (
<Copy className="h-4 w-4 text-violet-300/50" />
)}
<span className="sr-only">Copy Snippet</span>
</Button>
</div>
<pre
className="p-4 border border-violet-300/25 pr-12 overflow-x-auto text-slate-200 text-sm bg-background rounded-xl"
style={{ margin: 0 }}
>
{snippet}
</pre>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/update-project.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import updateProject from "./update-project.js";
describe("update_project", () => {
it("updates name and platform", async () => {
const result = await updateProject.handler(
{
organizationSlug: "sentry-mcp-evals",
projectSlug: "cloudflare-mcp",
name: "New Project Name",
slug: null,
platform: "python",
teamSlug: null,
regionUrl: null,
},
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toMatchInlineSnapshot(`
"# Updated Project in **sentry-mcp-evals**
**ID**: 4509109104082945
**Slug**: cloudflare-mcp
**Name**: New Project Name
**Platform**: python
## Updates Applied
- Updated name to "New Project Name"
- Updated platform to "python"
# Using this information
- The project is now accessible at slug: \`cloudflare-mcp\`
"
`);
});
it("assigns project to new team", async () => {
const result = await updateProject.handler(
{
organizationSlug: "sentry-mcp-evals",
projectSlug: "cloudflare-mcp",
name: null,
slug: null,
platform: null,
teamSlug: "backend-team",
regionUrl: null,
},
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toMatchInlineSnapshot(`
"# Updated Project in **sentry-mcp-evals**
**ID**: 4509106749636608
**Slug**: cloudflare-mcp
**Name**: cloudflare-mcp
**Platform**: node
## Updates Applied
- Updated team assignment to "backend-team"
# Using this information
- The project is now accessible at slug: \`cloudflare-mcp\`
- The project is now assigned to the \`backend-team\` team
"
`);
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/error.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "error",
"description": "This document defines the shared attributes used to report an error.\n",
"attributes": {
"error.type": {
"description": "Describes a class of error the operation ended with.\n",
"type": "string",
"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",
"stability": "stable",
"examples": ["_OTHER"]
},
"error.message": {
"description": "A message providing more detail about an error in human-readable form.",
"type": "string",
"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",
"stability": "development",
"examples": [
"Unexpected input type: string",
"The user has exceeded their storage quota"
]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-streaming-simulation.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Hook for simulating streaming animation for local messages (like slash commands)
* This provides the same UX as AI-generated responses for locally generated content
*/
import { useState, useCallback, useRef, useEffect } from "react";
interface StreamingSimulationState {
isStreaming: boolean;
streamingMessageId: string | null;
}
export function useStreamingSimulation() {
const [state, setState] = useState<StreamingSimulationState>({
isStreaming: false,
streamingMessageId: null,
});
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Start streaming simulation for a specific message
const startStreaming = useCallback((messageId: string, duration = 1000) => {
setState({
isStreaming: true,
streamingMessageId: messageId,
});
// Clear any existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Stop streaming after the specified duration
timeoutRef.current = setTimeout(() => {
setState({
isStreaming: false,
streamingMessageId: null,
});
}, duration);
}, []);
// Stop streaming simulation immediately
const stopStreaming = useCallback(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
setState({
isStreaming: false,
streamingMessageId: null,
});
}, []);
// Check if a specific message is currently streaming
const isMessageStreaming = useCallback(
(messageId: string) => {
return state.isStreaming && state.streamingMessageId === messageId;
},
[state.isStreaming, state.streamingMessageId],
);
// Cleanup on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return {
isStreaming: state.isStreaming,
streamingMessageId: state.streamingMessageId,
startStreaming,
stopStreaming,
isMessageStreaming,
};
}
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/trace-items-attributes.json:
--------------------------------------------------------------------------------
```json
[
{
"key": "span.op",
"name": "Span Operation"
},
{
"key": "span.description",
"name": "Span Description"
},
{
"key": "span.status",
"name": "Span Status"
},
{
"key": "span.duration",
"name": "Span Duration"
},
{
"key": "transaction",
"name": "Transaction"
},
{
"key": "transaction.op",
"name": "Transaction Operation"
},
{
"key": "transaction.duration",
"name": "Transaction Duration"
},
{
"key": "transaction.status",
"name": "Transaction Status"
},
{
"key": "project",
"name": "Project"
},
{
"key": "environment",
"name": "Environment"
},
{
"key": "release",
"name": "Release"
},
{
"key": "user.id",
"name": "User ID"
},
{
"key": "user.email",
"name": "User Email"
},
{
"key": "user.username",
"name": "Username"
},
{
"key": "error.type",
"name": "Error Type"
},
{
"key": "error.value",
"name": "Error Value"
},
{
"key": "error.handled",
"name": "Error Handled"
},
{
"key": "message",
"name": "Message"
},
{
"key": "level",
"name": "Level"
},
{
"key": "platform",
"name": "Platform"
},
{
"key": "sdk.name",
"name": "SDK Name"
},
{
"key": "sdk.version",
"name": "SDK Version"
},
{
"key": "http.method",
"name": "HTTP Method"
},
{
"key": "http.status_code",
"name": "HTTP Status Code"
},
{
"key": "http.url",
"name": "HTTP URL"
},
{
"key": "browser.name",
"name": "Browser Name"
},
{
"key": "os.name",
"name": "OS Name"
},
{
"key": "device",
"name": "Device"
},
{
"key": "geo.country_code",
"name": "Country Code"
},
{
"key": "geo.region",
"name": "Geographic Region"
},
{
"key": "geo.city",
"name": "City"
},
{
"key": "custom.tier",
"name": "Customer Tier"
},
{
"key": "custom.feature_flag",
"name": "Feature Flag"
}
]
```
--------------------------------------------------------------------------------
/packages/mcp-test-client/src/logger.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Simple terminal output logger for the MCP client.
*
* In an ideal world, this would use a state manager to track the last active logger,
* and it'd accept streams to the log functions. It'd then handle automatically
* terminating a previous block, inserting a new block, and restarting the previous
* block when streams receive new data. This is just a simplified version as this is
* not a big concern in this project.
*/
import chalk from "chalk";
let responseStarted = false;
export const logError = (msg: string, detail?: any) =>
process.stdout.write(
`\n${chalk.red("●")} ${msg}${detail ? `\n ⎿ ${chalk.gray(detail instanceof Error ? detail.message : detail)}` : ""}\n`,
);
export const logSuccess = (msg: string, detail?: string) =>
process.stdout.write(
`\n${chalk.green("●")} ${msg}${detail ? `\n ⎿ ${chalk.gray(detail)}` : ""}\n`,
);
export const logInfo = (msg: string, detail?: string) =>
process.stdout.write(
`\n${chalk.blue("●")} ${msg}${detail ? `\n ⎿ ${chalk.gray(detail)}` : ""}\n`,
);
export const logUser = (msg: string) =>
process.stdout.write(`\n${chalk.gray(">")} ${chalk.gray(msg)}\n`);
export const logTool = (name: string, args?: any) => {
const params =
args && Object.keys(args).length > 0
? `(${Object.entries(args)
.map(
([k, v]) =>
`${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`,
)
.join(", ")})`
: "()";
process.stdout.write(`\n${chalk.green("●")} ${name}${params}\n`);
};
export const logToolResult = (msg: string) =>
process.stdout.write(` ⎿ ${chalk.white(msg)}\n`);
export const logStreamStart = () => {
if (!responseStarted) {
process.stdout.write(`\n${chalk.white("●")} `);
responseStarted = true;
}
};
export const logStreamWrite = (chunk: string) =>
process.stdout.write(chunk.replace(/\n/g, "\n "));
export const logStreamEnd = () => {
if (responseStarted) {
process.stdout.write("\n");
responseStarted = false;
}
};
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/create-team.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { setTag } from "@sentry/core";
import { defineTool } from "../internal/tool-helpers/define";
import { apiServiceFromContext } from "../internal/tool-helpers/api";
import type { ServerContext } from "../types";
import { ParamOrganizationSlug, ParamRegionUrl } from "../schema";
export default defineTool({
name: "create_team",
skills: ["project-management"], // Only available in project-management skill
requiredScopes: ["team:write"],
description: [
"Create a new team in Sentry.",
"",
"USE THIS TOOL WHEN USERS WANT TO:",
"- 'Create a new team'",
"- 'Set up a team called [X]'",
"- 'I need a team for my project'",
"",
"Be careful when using this tool!",
"",
"<examples>",
"### Create a new team",
"```",
"create_team(organizationSlug='my-organization', name='the-goats')",
"```",
"</examples>",
"",
"<hints>",
"- If any parameter is ambiguous, you should clarify with the user what they meant.",
"</hints>",
].join("\n"),
inputSchema: {
organizationSlug: ParamOrganizationSlug,
regionUrl: ParamRegionUrl.nullable().default(null),
name: z.string().trim().describe("The name of the team to create."),
},
annotations: {
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
async handler(params, context: ServerContext) {
const apiService = apiServiceFromContext(context, {
regionUrl: params.regionUrl ?? undefined,
});
const organizationSlug = params.organizationSlug;
setTag("organization.slug", organizationSlug);
const team = await apiService.createTeam({
organizationSlug,
name: params.name,
});
let output = `# New Team in **${organizationSlug}**\n\n`;
output += `**ID**: ${team.id}\n`;
output += `**Slug**: ${team.slug}\n`;
output += `**Name**: ${team.name}\n`;
output += "# Using this information\n\n";
output += `- You should always inform the user of the Team Slug value.\n`;
return output;
},
});
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/hooks/use-scroll-lock.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Hook to lock body scroll when a component is active
* Handles edge cases like iOS Safari and nested locks
*/
import { useEffect, useRef } from "react";
// Track active locks to handle nested components
let activeLocks = 0;
let originalStyles: {
overflow?: string;
position?: string;
top?: string;
width?: string;
} = {};
export function useScrollLock(enabled = true) {
const scrollPositionRef = useRef(0);
useEffect(() => {
if (!enabled) return;
// Save scroll position and lock scroll
const lockScroll = () => {
// First lock - save original styles
if (activeLocks === 0) {
scrollPositionRef.current = window.scrollY;
originalStyles = {
overflow: document.body.style.overflow,
position: document.body.style.position,
top: document.body.style.top,
width: document.body.style.width,
};
// Apply scroll lock styles
document.body.style.overflow = "hidden";
// iOS Safari fix - prevent rubber band scrolling
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
document.body.style.position = "fixed";
document.body.style.top = `-${scrollPositionRef.current}px`;
document.body.style.width = "100%";
}
}
activeLocks++;
};
// Restore scroll position and unlock
const unlockScroll = () => {
activeLocks--;
// Last lock removed - restore original styles
if (activeLocks === 0) {
document.body.style.overflow = originalStyles.overflow || "";
document.body.style.position = originalStyles.position || "";
document.body.style.top = originalStyles.top || "";
document.body.style.width = originalStyles.width || "";
// Restore scroll position for iOS
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
window.scrollTo(0, scrollPositionRef.current);
}
originalStyles = {};
}
};
lockScroll();
// Cleanup
return () => {
unlockScroll();
};
}, [enabled]);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/sliding-panel.tsx:
--------------------------------------------------------------------------------
```typescript
/**
* Reusable sliding panel component
* Handles responsive slide-out behavior
*/
import type { ReactNode } from "react";
import { useScrollLock } from "../../hooks/use-scroll-lock";
interface SlidingPanelProps {
isOpen: boolean;
onClose?: () => void;
children: ReactNode;
className?: string;
}
export function SlidingPanel({
isOpen,
onClose,
children,
className = "",
}: SlidingPanelProps) {
// Lock body scroll when panel is open on mobile
useScrollLock(isOpen && window.innerWidth < 768);
return (
<div className="fixed inset-0 bg-transparent max-w-none max-h-none w-full h-full z-50 pointer-events-none">
{/* Backdrop */}
<div
className={`fixed xl:hidden inset-0 bg-black/50 backdrop-blur-sm transition-opacity ${
isOpen
? "opacity-100 pointer-events-auto duration-200"
: "opacity-0 select-none pointer-events-none duration-300"
}`}
onClick={isOpen ? onClose : undefined}
onKeyDown={
isOpen ? (e) => e.key === "Escape" && onClose?.() : undefined
}
role={isOpen ? "button" : undefined}
tabIndex={isOpen ? 0 : -1}
aria-label={isOpen ? "Close panel" : undefined}
/>
{/* Panel */}
<div
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 ${
isOpen
? "translate-x-0 pointer-events-auto"
: "translate-x-full pointer-events-none"
} ${className}`}
>
{children}
</div>
<div
className={`fixed hidden xl:flex inset-y-0 right-0 w-full max-w-[50vw] bg-background-2 flex-col ease-out ${
isOpen
? "translate-x-0 scale-100 opacity-100 pointer-events-auto motion-safe:duration-300 delay-150 transition-[opacity,filter,scale]"
: "translate-x-full motion-safe:scale-90 opacity-0 pointer-events-none duration-0"
} ${className}`}
>
{children}
</div>
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/telemetry.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "telemetry",
"description": "This document defines attributes for telemetry SDK.\n",
"attributes": {
"telemetry.sdk.name": {
"description": "The name of the telemetry SDK as defined above.\n",
"type": "string",
"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",
"stability": "stable",
"examples": ["opentelemetry"]
},
"telemetry.sdk.language": {
"description": "The language of the telemetry SDK.\n",
"type": "string",
"stability": "stable",
"examples": [
"cpp",
"dotnet",
"erlang",
"go",
"java",
"nodejs",
"php",
"python",
"ruby",
"rust",
"swift",
"webjs"
]
},
"telemetry.sdk.version": {
"description": "The version string of the telemetry SDK.\n",
"type": "string",
"stability": "stable",
"examples": ["1.2.3"]
},
"telemetry.distro.name": {
"description": "The name of the auto instrumentation agent or distribution, if used.\n",
"type": "string",
"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",
"stability": "development",
"examples": ["parts-unlimited-java"]
},
"telemetry.distro.version": {
"description": "The version string of the auto instrumentation agent or distribution, if used.\n",
"type": "string",
"stability": "development",
"examples": ["1.2.3"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/permissions.parseScopes.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import { parseScopes, expandScopes, type Scope } from "./permissions";
describe("parseScopes", () => {
it("parses comma-separated string with trimming and de-dup", () => {
const { valid, invalid } = parseScopes(
"event:write, foo, org:admin, , event:write",
);
const v = new Set<Scope>(valid);
expect(v.has("event:write")).toBe(true);
expect(v.has("org:admin")).toBe(true);
expect(invalid).toEqual(["foo"]);
});
it("parses arrays and filters non-strings", () => {
const { valid, invalid } = parseScopes([
"member:read",
"x",
123 as unknown,
" team:write ",
"",
]);
const v = new Set<Scope>(valid);
expect(v.has("member:read")).toBe(true);
expect(v.has("team:write")).toBe(true);
expect(invalid).toEqual(["x"]);
});
it("handles empty or undefined inputs", () => {
expect(parseScopes("")).toEqual({ valid: new Set<Scope>(), invalid: [] });
expect(parseScopes(undefined)).toEqual({
valid: new Set<Scope>(),
invalid: [],
});
expect(parseScopes([])).toEqual({ valid: new Set<Scope>(), invalid: [] });
});
});
// Consolidated strict-like parseScopes cases
describe("parseScopes (strict-like cases)", () => {
it("returns invalid tokens for unknown scopes", () => {
const { valid, invalid } = parseScopes("foo,bar,org:admin");
expect(invalid).toEqual(["foo", "bar"]);
expect([...valid]).toContain("org:admin");
});
it("returns only valid set when all are valid", () => {
const { valid, invalid } = parseScopes("event:admin,org:read");
expect(invalid).toEqual([]);
const out = new Set<Scope>(valid);
expect(out.has("event:admin")).toBe(true);
expect(out.has("org:read")).toBe(true);
});
});
// Related behavior validation for expandScopes
describe("expandScopes", () => {
it("includes implied lower scopes", () => {
const expanded = expandScopes(new Set<Scope>(["event:write"]));
expect(expanded.has("event:read")).toBe(true);
expect(expanded.has("event:write")).toBe(true);
});
});
```
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
```json
{
"$schema": "https://turbo.build/schema.json",
"daemon": false,
"ui": "stream",
"tasks": {
"setup": {
"dependsOn": ["build"],
"cache": false
},
"generate-definitions": {
"dependsOn": [],
"inputs": [
"src/tools/**/*.ts",
"src/skills.ts",
"scripts/generate-definitions.ts",
"!src/**/*.test.ts"
],
"outputs": ["src/toolDefinitions.json", "src/skillDefinitions.json"],
"cache": true
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["^build"],
"outputs": [],
"cache": true
},
"lint": {
"dependsOn": ["^build"],
"inputs": ["**/*.{ts,tsx,js,jsx}", "biome.json", "package.json"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"inputs": [
"src/**/*.ts",
"**/*.test.ts",
"**/*.spec.ts",
"vitest.config.ts",
"package.json"
],
"outputs": ["coverage/**", "*.junit.xml"],
"cache": true
},
"tsc": {
"dependsOn": ["^build"],
"inputs": ["src/**/*.{ts,tsx}", "tsconfig*.json"],
"outputs": [],
"cache": true
},
"eval": {
"dependsOn": ["^build"],
"outputs": [],
"cache": true
},
"build": {
"dependsOn": ["^build"],
"inputs": [
"src/**/*.{ts,tsx,js,jsx,json}",
"package.json",
"tsconfig*.json",
"tsdown.config.ts",
"vite.config.ts",
"wrangler.jsonc",
"!**/*.test.{ts,tsx}",
"!**/*.spec.{ts,tsx}"
],
"outputs": [".next/**", "!.next/cache/**", "dist/**", "*.tsbuildinfo"],
"cache": true
},
"after-build": {
"dependsOn": ["build"],
"cache": false
}
},
"globalPassThroughEnv": [
"NODE_ENV",
"CI",
"OPENAI_API_KEY",
"COOKIE_SECRET",
"SENTRY_CLIENT_ID",
"SENTRY_CLIENT_SECRET",
"SENTRY_AUTH_TOKEN",
"SENTRY_ACCESS_TOKEN",
"SENTRY_SPOTLIGHT",
"VITE_SENTRY_DSN",
"VITE_SENTRY_ENVIRONMENT"
]
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/button.tsx:
--------------------------------------------------------------------------------
```typescript
import type * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const buttonVariants = cva(
"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",
{
variants: {
variant: {
default: "bg-violet-300 text-black shadow-xs hover:bg-violet-300/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20",
outline:
"bg-slate-800/50 border border-slate-600/50 shadow-xs hover:bg-slate-700/50 hover:text-white ",
secondary:
"bg-background-3 shadow-xs hover:bg-violet-300 hover:text-black",
ghost: "hover:text-white hover:bg-slate-700/50 ",
link: "text-primary hover:underline hover:text-violet-300 cursor-pointer",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
xs: "h-7 gap-1.5 px-2 has-[>svg]:px-1.5",
lg: "h-10 px-6 has-[>svg]:px-4",
icon: "size-9",
},
active: {
true: "text-violet-300 underline",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
function Button({
className,
variant,
size,
active = false,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
active?: boolean;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className, active }))}
{...props}
/>
);
}
export { Button, buttonVariants };
```
--------------------------------------------------------------------------------
/packages/mcp-server-mocks/src/fixtures/generic-event.json:
--------------------------------------------------------------------------------
```json
{
"id": "a6251c18f0194b8e8158518b8ee99545",
"groupID": "6898891101",
"eventID": "a6251c18f0194b8e8158518b8ee99545",
"projectID": "4509062593708032",
"size": 547,
"entries": [],
"dist": null,
"message": "",
"title": "Endpoint Regression",
"location": null,
"user": null,
"contexts": {},
"sdk": null,
"context": {},
"packages": {},
"type": "generic",
"metadata": {
"title": "Endpoint Regression"
},
"tags": [
{
"key": "level",
"value": "info"
},
{
"key": "transaction",
"value": "POST /oauth/token"
}
],
"platform": "python",
"dateReceived": "2025-11-18T06:01:22.186680Z",
"dateCreated": "2025-11-18T06:01:20Z",
"errors": [],
"occurrence": {
"id": "ae3754a99b294006b8d13ad59bb84d0f",
"projectId": 4509062593708032,
"eventId": "a6251c18f0194b8e8158518b8ee99545",
"fingerprint": ["ddf744fc1a47831ed53d9a489160fa7a"],
"issueTitle": "Endpoint Regression",
"subtitle": "Increased from 909.77ms to 1711.36ms (P95)",
"resourceId": null,
"evidenceData": {
"absolutePercentageChange": 1.8810815660491678,
"aggregateRange1": 909.7721153846148,
"aggregateRange2": 1711.3555555555554,
"breakpoint": 1763416800,
"change": "regression",
"transaction": "POST /oauth/token"
},
"evidenceDisplay": [
{
"name": "Regression",
"value": "POST /oauth/token duration increased from 909.77ms to 1711.36ms (P95)",
"important": true
},
{
"name": "Transaction",
"value": "POST /oauth/token",
"important": true
}
],
"type": 1018,
"detectionTime": 1763445680.827214,
"level": "info",
"culprit": "POST /oauth/token",
"priority": 50,
"assignee": null
},
"crashFile": null,
"culprit": "POST /oauth/token",
"fingerprints": ["d41d8cd98f00b204e9800998ecf8427e"],
"groupingConfig": {
"id": "newstyle:2023-01-11",
"enhancements": "test"
},
"release": null,
"userReport": null,
"sdkUpdates": [],
"resolvedWith": [],
"nextEventID": null,
"previousEventID": "65d7c166833945efad0a4d38a4fd3665"
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-releases.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it, expect } from "vitest";
import findReleases from "./find-releases.js";
describe("find_releases", () => {
it("works without project", async () => {
const result = await findReleases.handler(
{
organizationSlug: "sentry-mcp-evals",
projectSlug: null,
regionUrl: null,
query: null,
},
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toMatchInlineSnapshot(`
"# Releases in **sentry-mcp-evals**
## 8ce89484-0fec-4913-a2cd-e8e2d41dee36
**Created**: 2025-04-13T19:54:21.764Z
**First Event**: 2025-04-13T19:54:21.000Z
**Last Event**: 2025-04-13T20:28:23.000Z
**New Issues**: 0
**Projects**: cloudflare-mcp
# Using this information
- You can reference the Release version in commit messages or documentation.
- You can search for issues in a specific release using the \`find_errors()\` tool with the query \`release:8ce89484-0fec-4913-a2cd-e8e2d41dee36\`.
"
`);
});
it("works with project", async () => {
const result = await findReleases.handler(
{
organizationSlug: "sentry-mcp-evals",
projectSlug: "cloudflare-mcp",
regionUrl: null,
query: null,
},
{
constraints: {
organizationSlug: null,
},
accessToken: "access-token",
userId: "1",
},
);
expect(result).toMatchInlineSnapshot(`
"# Releases in **sentry-mcp-evals/cloudflare-mcp**
## 8ce89484-0fec-4913-a2cd-e8e2d41dee36
**Created**: 2025-04-13T19:54:21.764Z
**First Event**: 2025-04-13T19:54:21.000Z
**Last Event**: 2025-04-13T20:28:23.000Z
**New Issues**: 0
**Projects**: cloudflare-mcp
# Using this information
- You can reference the Release version in commit messages or documentation.
- You can search for issues in a specific release using the \`find_errors()\` tool with the query \`release:8ce89484-0fec-4913-a2cd-e8e2d41dee36\`.
"
`);
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/browser.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "browser",
"description": "The web browser attributes\n",
"attributes": {
"browser.brands": {
"description": "Array of brand name and version separated by a space",
"type": "string",
"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",
"stability": "development",
"examples": ["[\" Not A;Brand 99\",\"Chromium 99\",\"Chrome 99\"]"]
},
"browser.platform": {
"description": "The platform on which the browser is running",
"type": "string",
"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",
"stability": "development",
"examples": ["Windows", "macOS", "Android"]
},
"browser.mobile": {
"description": "A boolean that is true if the browser is running on a mobile device",
"type": "boolean",
"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",
"stability": "development"
},
"browser.language": {
"description": "Preferred language of the user using the browser",
"type": "string",
"note": "This value is intended to be taken from the Navigator API `navigator.language`.\n",
"stability": "development",
"examples": ["en", "en-US", "fr", "fr-FR"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/errors.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, expect, it } from "vitest";
import { UserInputError, ConfigurationError } from "./errors";
describe("UserInputError", () => {
it("should create a UserInputError with the correct message and name", () => {
const message = "Invalid input provided";
const error = new UserInputError(message);
expect(error.message).toBe(message);
expect(error.name).toBe("UserInputError");
expect(error instanceof Error).toBe(true);
expect(error instanceof UserInputError).toBe(true);
});
it("should be distinguishable from regular Error", () => {
const userInputError = new UserInputError("User input error");
const regularError = new Error("Regular error");
expect(userInputError instanceof UserInputError).toBe(true);
expect(regularError instanceof UserInputError).toBe(false);
});
it("should support error cause", () => {
const cause = new Error("Original error");
const error = new UserInputError("User input error", { cause });
expect(error.cause).toBe(cause);
});
});
describe("ConfigurationError", () => {
it("should create a ConfigurationError with the correct message and name", () => {
const message = "Invalid configuration";
const error = new ConfigurationError(message);
expect(error.message).toBe(message);
expect(error.name).toBe("ConfigurationError");
expect(error instanceof Error).toBe(true);
expect(error instanceof ConfigurationError).toBe(true);
});
it("should be distinguishable from regular Error and UserInputError", () => {
const configError = new ConfigurationError("Config error");
const userInputError = new UserInputError("User input error");
const regularError = new Error("Regular error");
expect(configError instanceof ConfigurationError).toBe(true);
expect(userInputError instanceof ConfigurationError).toBe(false);
expect(regularError instanceof ConfigurationError).toBe(false);
});
it("should support error cause", () => {
const cause = new Error("DNS resolution failed");
const error = new ConfigurationError("Unable to connect to server", {
cause,
});
expect(error.cause).toBe(cause);
});
});
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/fetch-utils.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Fetch with timeout using AbortController
* @param url - The URL to fetch
* @param options - Standard fetch options
* @param timeoutMs - Timeout in milliseconds (default: 30000)
* @returns Promise<Response>
* @throws Error if request times out
*/
export async function fetchWithTimeout(
url: string | URL,
options: RequestInit = {},
timeoutMs = 30000,
): Promise<Response> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
return response;
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
throw new Error(`Request timeout after ${timeoutMs}ms`);
}
throw error;
} finally {
clearTimeout(timeoutId);
}
}
/**
* Retry a function with exponential backoff
* @param fn - The async function to retry
* @param options - Retry options
* @param options.maxRetries - Maximum number of retries (default: 3)
* @param options.initialDelay - Initial delay in milliseconds (default: 1000)
* @param options.shouldRetry - Predicate to determine if error should be retried (default: always retry)
* @returns Promise with the function result
* @throws The last error if all retries are exhausted
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
{
maxRetries = 3,
initialDelay = 1000,
shouldRetry = (error: unknown) => true,
}: {
maxRetries?: number;
initialDelay?: number;
shouldRetry?: (error: unknown) => boolean;
} = {},
): Promise<T> {
let lastError: unknown;
let delay = initialDelay;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Don't retry if we've exhausted attempts or if the error is non-retryable
if (attempt === maxRetries || !shouldRetry(error)) {
throw error;
}
// Wait before retrying with exponential backoff
await new Promise((resolve) => setTimeout(resolve, delay));
delay = Math.min(delay * 2, 30000); // Cap at 30 seconds
}
}
throw lastError;
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/chat/tool-invocation.tsx:
--------------------------------------------------------------------------------
```typescript
import { useState } from "react";
import { Bolt, ChevronDown, ChevronRight } from "lucide-react";
import type { ToolMessage, ToolInvocationProps } from "./types";
import { isTextMessage } from "./types";
function getTokenCount(content: ToolMessage[]): number {
return content.reduce((acc, message) => {
if (isTextMessage(message)) {
return acc + message.text.length;
}
return acc;
}, 0);
}
export function ToolInvocation({
tool,
messageId,
index,
}: ToolInvocationProps) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className="border border-slate-900 rounded overflow-hidden">
<button
type="button"
onClick={() => setIsExpanded(!isExpanded)}
className="w-full p-3 text-left cursor-pointer hover:bg-slate-900/50 transition-colors"
>
<div className="flex items-center gap-2 text-violet-400">
{isExpanded ? (
<ChevronDown className="h-3 w-3" />
) : (
<ChevronRight className="h-3 w-3" />
)}
<Bolt className="h-3 w-3" />
<span className="font-mono">{tool.toolName}</span>
{tool.state === "result" && (
<span className="text-xs text-slate-500 ml-auto">
{`~${getTokenCount(tool.result?.content ?? []).toLocaleString()}
tokens`}
</span>
)}
</div>
</button>
{isExpanded && tool.state === "result" && tool.result && (
<div className="px-3 pb-3 border-t border-slate-600/30 text-slate-300">
<div className="mt-2">
<ToolContent content={tool.result.content} />
</div>
</div>
)}
</div>
);
}
export function ToolContent({ content }: { content: ToolMessage[] }) {
return (
<div className="space-y-3">
{content.map((message: ToolMessage, index: number) => (
<div key={`message-${message.type}-${index}`} className="space-y-2">
<pre className="text-slate-400 text-sm whitespace-pre-wrap overflow-x-auto">
{isTextMessage(message)
? message.text
: JSON.stringify(message, null, 2)}
</pre>
</div>
))}
</div>
);
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@sentry/mcp-cloudflare",
"version": "0.24.0",
"private": true,
"type": "module",
"license": "FSL-1.1-ALv2",
"files": [
"./dist/*"
],
"exports": {
".": {
"types": "./dist/index.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc -b && vite build",
"dev": "vite",
"deploy": "pnpm exec wrangler deploy",
"cf:versions:upload": "npx wrangler versions upload",
"preview": "vite preview",
"cf-typegen": "wrangler types",
"test": "vitest run",
"test:ci": "vitest run --coverage --reporter=default --reporter=junit --outputFile=tests.junit.xml",
"test:watch": "vitest",
"tsc": "tsc --noEmit"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.13.15",
"@cloudflare/workers-types": "catalog:",
"@sentry/mcp-core": "workspace:*",
"@sentry/mcp-server-mocks": "workspace:*",
"@sentry/mcp-server-tsconfig": "workspace:*",
"@sentry/vite-plugin": "catalog:",
"@tailwindcss/typography": "catalog:",
"@tailwindcss/vite": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-scroll-to-bottom": "^4.2.5",
"@vitejs/plugin-react": "catalog:",
"tailwindcss": "catalog:",
"urlpattern-polyfill": "^10.1.0",
"vite": "catalog:",
"vitest": "catalog:",
"wrangler": "^4.45.0"
},
"dependencies": {
"@ai-sdk/openai": "catalog:",
"@ai-sdk/react": "catalog:",
"@cloudflare/workers-oauth-provider": "catalog:",
"@modelcontextprotocol/sdk": "catalog:",
"@radix-ui/react-accordion": "catalog:",
"@radix-ui/react-slot": "catalog:",
"@sentry/cloudflare": "catalog:",
"@sentry/react": "catalog:",
"agents": "catalog:",
"ai": "catalog:",
"asciinema-player": "^3.10.0",
"better-sqlite3": "catalog:",
"class-variance-authority": "catalog:",
"clsx": "catalog:",
"hono": "catalog:",
"lucide-react": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-markdown": "catalog:",
"react-scroll-to-bottom": "^4.2.0",
"remark-gfm": "catalog:",
"tailwind-merge": "catalog:",
"tw-animate-css": "catalog:",
"workers-mcp": "catalog:",
"zod": "catalog:"
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
```typescript
import type * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";
import { cn } from "../../lib/utils";
function Accordion({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
return (
<AccordionPrimitive.Root
data-slot="accordion"
className="space-y-1"
{...props}
/>
);
}
function AccordionItem({
className,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
return (
<AccordionPrimitive.Item
data-slot="accordion-item"
className={cn("px-4 bg-background-2 rounded-xl", className)}
{...props}
/>
);
}
function AccordionTrigger({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
return (
<AccordionPrimitive.Header className="flex m-0">
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"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 ",
className,
)}
{...props}
>
{children}
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 motion-safe:transition-transform motion-safe:duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
);
}
function AccordionContent({
className,
children,
...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
return (
<AccordionPrimitive.Content
data-slot="accordion-content"
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"
{...props}
>
<div className={cn("pt-0 pb-4", className)}>{children}</div>
</AccordionPrimitive.Content>
);
}
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/search-issues/agent.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import type { SentryApiService } from "../../api-client";
import { ConfigurationError } from "../../errors";
import { callEmbeddedAgent } from "../../internal/agents/callEmbeddedAgent";
import { createDatasetFieldsTool } from "../../internal/agents/tools/dataset-fields";
import { createWhoamiTool } from "../../internal/agents/tools/whoami";
import { systemPrompt } from "./config";
// OpenAI structured outputs (used by GPT-5) require all properties to be in the 'required' array.
// Avoid .optional()/.default() so the generated JSON Schema keeps every field required.
// Tracking: https://github.com/getsentry/sentry-mcp/issues/623
export const searchIssuesAgentOutputSchema = z.object({
query: z.string().describe("The Sentry issue search query"),
sort: z
.enum(["date", "freq", "new", "user"])
.nullable()
.describe("How to sort the results"),
explanation: z
.string()
.describe("Brief explanation of how you translated this query."),
});
export interface SearchIssuesAgentOptions {
query: string;
organizationSlug: string;
apiService: SentryApiService;
projectId?: string;
}
/**
* Search issues agent - single entry point for translating natural language queries to Sentry issue search syntax
* This returns both the translated query result AND the tool calls made by the agent
*/
export async function searchIssuesAgent(
options: SearchIssuesAgentOptions,
): Promise<{
result: z.output<typeof searchIssuesAgentOutputSchema>;
toolCalls: any[];
}> {
if (!process.env.OPENAI_API_KEY) {
throw new ConfigurationError(
"OPENAI_API_KEY environment variable is required for semantic search",
);
}
// Create tools pre-bound with the provided API service and organization
return await callEmbeddedAgent<
z.output<typeof searchIssuesAgentOutputSchema>,
typeof searchIssuesAgentOutputSchema
>({
system: systemPrompt,
prompt: options.query,
tools: {
issueFields: createDatasetFieldsTool({
apiService: options.apiService,
organizationSlug: options.organizationSlug,
dataset: "search_issues",
projectId: options.projectId,
}),
whoami: createWhoamiTool({ apiService: options.apiService }),
},
schema: searchIssuesAgentOutputSchema,
});
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/agents/tools/data/geo.json:
--------------------------------------------------------------------------------
```json
{
"namespace": "geo",
"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",
"attributes": {
"geo.locality.name": {
"description": "Locality name. Represents the name of a city, town, village, or similar populated place.\n",
"type": "string",
"stability": "development",
"examples": ["Montreal", "Berlin"]
},
"geo.continent.code": {
"description": "Two-letter code representing continent’s name.\n",
"type": "string",
"stability": "development",
"examples": ["AF", "AN", "AS", "EU", "NA", "OC", "SA"]
},
"geo.country.iso_code": {
"description": "Two-letter ISO Country Code ([ISO 3166-1 alpha2](https://wikipedia.org/wiki/ISO_3166-1#Codes)).\n",
"type": "string",
"stability": "development",
"examples": ["CA"]
},
"geo.location.lon": {
"description": "Longitude of the geo location in [WGS84](https://wikipedia.org/wiki/World_Geodetic_System#WGS84).\n",
"type": "number",
"stability": "development",
"examples": ["-73.61483"]
},
"geo.location.lat": {
"description": "Latitude of the geo location in [WGS84](https://wikipedia.org/wiki/World_Geodetic_System#WGS84).\n",
"type": "number",
"stability": "development",
"examples": ["45.505918"]
},
"geo.postal_code": {
"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",
"type": "string",
"stability": "development",
"examples": ["94040"]
},
"geo.region.iso_code": {
"description": "Region ISO code ([ISO 3166-2](https://wikipedia.org/wiki/ISO_3166-2)).\n",
"type": "string",
"stability": "development",
"examples": ["CA-QC"]
}
}
}
```
--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-dsns.ts:
--------------------------------------------------------------------------------
```typescript
import { setTag } from "@sentry/core";
import { defineTool } from "../internal/tool-helpers/define";
import { apiServiceFromContext } from "../internal/tool-helpers/api";
import type { ServerContext } from "../types";
import {
ParamOrganizationSlug,
ParamRegionUrl,
ParamProjectSlug,
} from "../schema";
export default defineTool({
name: "find_dsns",
skills: ["project-management"], // DSN management is part of project setup
requiredScopes: ["project:read"],
description: [
"List all Sentry DSNs for a specific project.",
"",
"Use this tool when you need to:",
"- Retrieve a SENTRY_DSN for a specific project",
"",
"<hints>",
"- If the user passes a parameter in the form of name/otherName, its likely in the format of <organizationSlug>/<projectSlug>.",
"- 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.",
"</hints>",
].join("\n"),
inputSchema: {
organizationSlug: ParamOrganizationSlug,
regionUrl: ParamRegionUrl.nullable().default(null),
projectSlug: ParamProjectSlug,
},
annotations: {
readOnlyHint: true,
openWorldHint: true,
},
async handler(params, context: ServerContext) {
const apiService = apiServiceFromContext(context, {
regionUrl: params.regionUrl ?? undefined,
});
const organizationSlug = params.organizationSlug;
setTag("organization.slug", organizationSlug);
setTag("project.slug", params.projectSlug);
const clientKeys = await apiService.listClientKeys({
organizationSlug,
projectSlug: params.projectSlug,
});
let output = `# DSNs in **${organizationSlug}/${params.projectSlug}**\n\n`;
if (clientKeys.length === 0) {
output +=
"No DSNs were found.\n\nYou can create new one using the `create_dsn` tool.";
return output;
}
for (const clientKey of clientKeys) {
output += `## ${clientKey.name}\n`;
output += `**ID**: ${clientKey.id}\n`;
output += `**DSN**: ${clientKey.dsn.public}\n\n`;
}
output += "# Using this information\n\n";
output +=
"- The `SENTRY_DSN` value is a URL that you can use to initialize Sentry's SDKs.\n";
return output;
},
});
```