#
tokens: 49359/50000 31/493 files (page 5/16)
lines: off (toggle) GitHub
raw markdown copy
This is page 5 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

--------------------------------------------------------------------------------
/docs/cloudflare/architecture.md:
--------------------------------------------------------------------------------

```markdown
# Cloudflare Chat Agent Architecture

Technical architecture for the web-based chat interface hosted on Cloudflare Workers.

## Overview

The Cloudflare chat agent provides a web interface for interacting with Sentry through an AI assistant. It's built as a full-stack application using:

- **Frontend**: React with Tailwind CSS
- **Backend**: Cloudflare Workers with Hono framework
- **AI**: OpenAI GPT-4 via Vercel AI SDK
- **MCP Integration**: HTTP transport to core MCP server

## Package Structure

```
packages/mcp-cloudflare/
├── src/
│   ├── client/           # React frontend
│   │   ├── components/   # UI components
│   │   ├── contexts/     # React contexts
│   │   ├── hooks/        # Custom React hooks
│   │   └── utils/        # Client utilities
│   └── server/           # Cloudflare Workers backend
│       ├── lib/          # Server libraries
│       ├── routes/       # API routes
│       ├── types/        # TypeScript types
│       └── utils/        # Server utilities
├── public/               # Static assets
└── wrangler.toml         # Cloudflare configuration
```

## Key Components

### 1. OAuth Authentication

Handles Sentry OAuth flow for user authentication:

```typescript
// server/routes/auth.ts
export default new Hono()
  .get("/login", handleOAuthLogin)
  .get("/callback", handleOAuthCallback)
  .post("/logout", handleLogout);
```

**Features:**
- OAuth 2.0 flow with Sentry
- Token storage in Cloudflare KV
- Automatic token refresh
- Per-organization access control

### 2. Chat Interface

React-based chat UI with real-time streaming:

```typescript
// client/components/chat/chat.tsx
export function Chat() {
  const { messages, handleSubmit } = useChat({
    api: "/api/chat",
    headers: { Authorization: `Bearer ${authToken}` }
  });
}
```

**Features:**
- Message streaming with Vercel AI SDK
- Tool call visualization
- Slash commands (/help, /prompts, /clear)
- Prompt parameter dialogs
- Markdown rendering with syntax highlighting

### 3. MCP Integration

Connects to the core MCP server via HTTP transport:

```typescript
// server/routes/chat.ts
const mcpClient = await experimental_createMCPClient({
  name: "sentry",
  transport: {
    type: "sse",
    url: sseUrl,
    headers: { Authorization: `Bearer ${accessToken}` }
  }
});
```

**Features:**
- Server-sent events (SSE) for MCP communication
- Automatic tool discovery
- Prompt metadata endpoint
- Error handling with fallbacks

### 4. AI Assistant

GPT-4 integration with Sentry-specific system prompt:

```typescript
const result = streamText({
  model: openai("gpt-4o"),
  messages: processedMessages,
  tools: mcpTools,
  system: "You are an AI assistant for testing Sentry MCP..."
});
```

**Features:**
- Streaming responses
- Tool execution
- Prompt template processing
- Context-aware assistance

## Data Flow

1. **User Authentication**:
   ```
   User → OAuth Login → Sentry → OAuth Callback → KV Storage
   ```

2. **Chat Message Flow**:
   ```
   User Input → Chat API → Process Prompts → AI Model → Stream Response
                         ↓
                    MCP Server ← Tool Calls
   ```

3. **MCP Communication**:
   ```
   Chat Server → SSE Transport → MCP Server → Sentry API
   ```

## Deployment Architecture

### Cloudflare Resources

- **Workers**: Serverless compute for API routes
- **Pages**: Static asset hosting for React app
- **KV Namespace**: OAuth token storage
- **AI Binding**: Access to Cloudflare AI models (AutoRAG for docs search)
- **R2**: File storage (future)

### Environment Variables

Required for deployment:

```toml
[vars]
COOKIE_SECRET = "..."      # For session encryption
OPENAI_API_KEY = "..."     # For GPT-4 access
SENTRY_CLIENT_ID = "..."   # OAuth app ID
SENTRY_CLIENT_SECRET = "..." # OAuth app secret
```

### API Routes

- `/api/auth/*` - Authentication endpoints
- `/api/chat` - Main chat endpoint
- `/api/metadata` - MCP metadata endpoint
- `/sse` - Server-sent events for MCP

## Security Considerations

1. **Authentication**: OAuth tokens stored encrypted in KV
2. **Authorization**: Per-organization access control
3. **Rate Limiting**: Cloudflare rate limiter integration
4. **CORS**: Restricted to same-origin requests
5. **CSP**: Content Security Policy headers

## Performance Optimizations

1. **Edge Computing**: Runs at Cloudflare edge locations
2. **Caching**: Metadata endpoint with cache headers
3. **Streaming**: Server-sent events for real-time updates
4. **Bundle Splitting**: Optimized React build

## Monitoring

- Sentry integration for error tracking
- Cloudflare Analytics for usage metrics
- Custom telemetry for MCP operations

## Related Documentation

- See "OAuth Architecture" in @docs/cloudflare/oauth-architecture.md
- See "Chat Interface" in @docs/cloudflare/architecture.md
- See "Deployment" in @docs/cloudflare/deployment.md
- See "Architecture" in @docs/architecture.md

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/chat/types.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Type definitions for Chat components
 */

import type React from "react";
import type { Message } from "ai/react";

// Re-export AI SDK types for convenience
export type { Message } from "ai/react";

// Extended message type that includes our custom metadata
export interface ExtendedMessage extends Message {
  data?: {
    type?: string;
    prompts?: any[];
    toolsDetailed?: Array<{ name: string; description: string }>;
    hasSlashCommands?: boolean;
    error?: string;
    // Prompt execution data
    promptName?: string;
    parameters?: Record<string, any>;
    wasExecuted?: boolean;
    simulateStreaming?: boolean;
    [key: string]: any;
  };
}

// Error handling types (simplified)
// We only keep this for potential server response parsing
export interface ChatErrorData {
  error?: string;
  name?: string;
  eventId?: string;
  statusCode?: number;
  message?: string;
}

// Authentication types
export interface AuthState {
  isLoading: boolean;
  isAuthenticated: boolean;
  authToken: string;
  isAuthenticating: boolean;
  authError: string;
}

export interface AuthActions {
  handleOAuthLogin: () => void;
  handleLogout: () => void;
  clearAuthState: () => void;
}

export type AuthContextType = AuthState & AuthActions;

// OAuth message types
export interface OAuthSuccessMessage {
  type: "SENTRY_AUTH_SUCCESS";
  data: Record<string, never>;
}

export interface OAuthErrorMessage {
  type: "SENTRY_AUTH_ERROR";
  error?: string;
}

export type OAuthMessage = OAuthSuccessMessage | OAuthErrorMessage;

// Tool invocation types
export interface ToolInvocationContent {
  type: "text";
  text: string;
}

export interface ToolInvocationUnknownContent {
  type: string;
  [key: string]: unknown;
}

export type ToolMessage = ToolInvocationContent | ToolInvocationUnknownContent;

// Define our own ToolInvocation interface since AI SDK's is not properly exported
export interface ChatToolInvocation {
  toolCallId: string;
  toolName: string;
  args: Record<string, unknown>;
  state: "partial-call" | "call" | "result";
  result?: {
    content: ToolMessage[];
  };
}

// Message processing types
export interface ProcessedMessagePart {
  part: NonNullable<Message["parts"]>[number];
  messageId: string;
  messageRole: string;
  partIndex: number;
  isStreaming: boolean;
}

// Component prop types
export interface ChatProps {
  isOpen: boolean;
  isMobile?: boolean;
  onClose: () => void;
  onLogout: () => void;
}

export interface ChatUIProps {
  messages: Message[];
  input: string;
  error?: Error | null;
  isChatLoading: boolean;
  isOpen?: boolean;
  showControls?: boolean;
  onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
  onStop?: () => void;
  onRetry?: () => void;
  onClose?: () => void;
  onLogout?: () => void;
  onSlashCommand?: (command: string) => void;
  onSendPrompt?: (prompt: string) => void;
}

export interface ChatMessagesProps {
  messages: Message[];
  isChatLoading: boolean;
  isLocalStreaming?: boolean;
  isMessageStreaming?: (messageId: string) => boolean;
  error?: Error | null;
  onRetry?: () => void;
  onSlashCommand?: (command: string) => void;
}

export interface ChatInputProps {
  input: string;
  isLoading: boolean;
  isOpen: boolean;
  onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
  onStop: () => void;
}

export interface AuthFormProps {
  authError: string;
  onOAuthLogin: () => void;
}

export interface PanelBackdropProps {
  isOpen: boolean;
  onClose: () => void;
}

export interface MessagePartProps {
  part: NonNullable<Message["parts"]>[number];
  messageId: string;
  messageRole: string;
  partIndex: number;
  isStreaming?: boolean;
  messageData?: any;
  onSlashCommand?: (command: string) => void;
}

export interface TextPartProps {
  text: string;
  role: string;
  messageId: string;
  isStreaming?: boolean;
  messageData?: any;
  onSlashCommand?: (command: string) => void;
}

export interface ToolPartProps {
  toolInvocation: ChatToolInvocation;
  messageId: string;
  partIndex: number;
}

export interface ToolInvocationProps {
  tool: ChatToolInvocation;
  messageId: string;
  index: number;
}

// Type guards
export function isTextMessage(
  message: ToolMessage,
): message is ToolInvocationContent {
  return message.type === "text";
}

export function isOAuthSuccessMessage(
  message: unknown,
): message is OAuthSuccessMessage {
  return (
    typeof message === "object" &&
    message !== null &&
    "type" in message &&
    message.type === "SENTRY_AUTH_SUCCESS"
  );
}

export function isOAuthErrorMessage(
  message: unknown,
): message is OAuthErrorMessage {
  return (
    typeof message === "object" &&
    message !== null &&
    "type" in message &&
    message.type === "SENTRY_AUTH_ERROR"
  );
}

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/use-sentry/handler.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { experimental_createMCPClient } from "ai";
import { defineTool } from "../../internal/tool-helpers/define";
import type { ServerContext } from "../../types";
import { useSentryAgent } from "./agent";
import { buildServer } from "../../server";
import tools from "../index";
import type { ToolCall } from "../../internal/agents/callEmbeddedAgent";

/**
 * Format tool calls into a readable trace
 */
function formatToolCallTrace(toolCalls: ToolCall[]): string {
  let trace = "";

  for (let i = 0; i < toolCalls.length; i++) {
    const call = toolCalls[i];
    trace += `### ${i + 1}. ${call.toolName}\n\n`;

    // Type assertion is safe: AI SDK guarantees args is always a JSON-serializable object
    const args = call.args as Record<string, unknown>;

    // Format arguments
    if (Object.keys(args).length === 0) {
      trace += "_No arguments_\n\n";
    } else {
      trace += "**Arguments:**\n```json\n";
      trace += JSON.stringify(args, null, 2);
      trace += "\n```\n\n";
    }
  }

  return trace;
}

export default defineTool({
  name: "use_sentry",
  skills: [], // Only available in agent mode - bypasses authorization
  requiredScopes: [], // No specific scopes - uses authentication token
  description: [
    "Natural language interface to Sentry via an embedded AI agent.",
    "",
    "Use this tool when you need to:",
    "- Perform complex multi-step operations",
    "- Explore and analyze Sentry data with natural language",
    "- Chain multiple operations automatically",
    "",
    "Capabilities depend on granted skills:",
    "- inspect: Search errors/events, analyze traces, explore issues and projects",
    "- seer: Get AI-powered debugging insights and root cause analysis",
    "- docs: Search and retrieve Sentry documentation",
    "- triage: Resolve, assign, comment on, and update issues",
    "- project-management: Create/modify teams, projects, and configure DSNs",
    "",
    "<examples>",
    "use_sentry(request='find unresolved errors from yesterday')",
    "use_sentry(request='analyze the top 3 performance issues')",
    "use_sentry(request='create a backend team and assign them to API project')",
    "</examples>",
    "",
    "<hints>",
    "- If user asks to 'use Sentry' for something, they always mean to call this tool",
    "- Pass the user's request verbatim - do not interpret or rephrase",
    "- The agent can chain multiple tool calls automatically",
    "- Use trace=true parameter to see which tools were called",
    "- For simple single-tool operations, consider calling tools directly instead",
    "</hints>",
  ].join("\n"),
  inputSchema: {
    request: z
      .string()
      .trim()
      .min(1)
      .describe(
        "The user's raw input. Do not interpret the prompt in any way. Do not add any additional information to the prompt.",
      ),
    trace: z
      .boolean()
      .nullable()
      .default(null)
      .describe(
        "Enable tracing to see all tool calls made by the agent. Useful for debugging.",
      ),
  },
  annotations: {
    readOnlyHint: true, // Will be adjusted based on actual implementation
    openWorldHint: true,
  },
  async handler(params, context: ServerContext) {
    // Create linked pair of in-memory transports for client-server communication
    const [clientTransport, serverTransport] =
      InMemoryTransport.createLinkedPair();

    // Exclude use_sentry from tools to prevent recursion
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { use_sentry, ...toolsForAgent } = tools;

    // Build internal MCP server with the provided context
    // Context is captured in tool handler closures during buildServer()
    const server = buildServer({
      context,
      tools: toolsForAgent,
    });

    // Connect server to its transport
    await server.server.connect(serverTransport);

    // Create MCP client with the other end of the transport
    const mcpClient = await experimental_createMCPClient({
      name: "mcp.sentry.dev (use-sentry)",
      transport: clientTransport,
    });

    try {
      // Get tools from MCP server (returns Vercel AI SDK compatible tools)
      const mcpTools = await mcpClient.tools();

      // Call the embedded agent with MCP tools and the user's request
      const agentResult = await useSentryAgent({
        request: params.request,
        tools: mcpTools,
      });

      let output = agentResult.result.result;

      // If tracing is enabled, append the tool call trace
      if (params.trace && agentResult.toolCalls.length > 0) {
        output += "\n\n---\n\n## Tool Call Trace\n\n";
        output += formatToolCallTrace(agentResult.toolCalls);
      }

      return output;
    } finally {
      // Clean up connections
      await mcpClient.close();
      await server.server.close();
    }
  },
});

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/lib/mcp-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import "urlpattern-polyfill";
import type { Env } from "../types";
import type { ExecutionContext } from "@cloudflare/workers-types";
import handler from "./mcp-handler.js";

// Mock Sentry to avoid actual telemetry
vi.mock("@sentry/cloudflare", () => ({
  flush: vi.fn(() => Promise.resolve(true)),
}));

// Mock the MCP handler creation - we're testing the wrapper logic, not the MCP protocol
vi.mock("agents/mcp", () => ({
  createMcpHandler: vi.fn(() => {
    return vi.fn(() => {
      return Promise.resolve(new Response("OK", { status: 200 }));
    });
  }),
}));

describe("mcp-handler", () => {
  let env: Env;
  let ctx: ExecutionContext & { props?: Record<string, unknown> };

  beforeEach(() => {
    vi.clearAllMocks();

    env = {
      SENTRY_HOST: "sentry.io",
      COOKIE_SECRET: "test-secret",
    } as Env;

    // ExecutionContext with OAuth props (set by OAuth provider)
    ctx = {
      waitUntil: vi.fn(),
      passThroughOnException: vi.fn(),
      props: {
        id: "test-user-123",
        clientId: "test-client",
        accessToken: "test-token",
        grantedSkills: ["inspect", "docs"],
      },
    };
  });

  it("successfully handles request with org constraint", async () => {
    const request = new Request(
      "https://test.mcp.sentry.io/mcp/sentry-mcp-evals",
    );

    const response = await handler.fetch!(request as any, env, ctx);

    // Verify successful response
    expect(response.status).toBe(200);
  });

  it("returns 404 for invalid organization", async () => {
    const request = new Request(
      "https://test.mcp.sentry.io/mcp/nonexistent-org",
    );

    const response = await handler.fetch!(request as any, env, ctx);

    expect(response.status).toBe(404);
    expect(await response.text()).toContain("not found");
  });

  it("returns 404 for invalid project", async () => {
    const request = new Request(
      "https://test.mcp.sentry.io/mcp/sentry-mcp-evals/nonexistent-project",
    );

    const response = await handler.fetch!(request as any, env, ctx);

    expect(response.status).toBe(404);
    expect(await response.text()).toContain("not found");
  });

  it("returns error when authentication context is missing", async () => {
    const ctxWithoutAuth = {
      waitUntil: vi.fn(),
      passThroughOnException: vi.fn(),
      props: undefined,
    };

    const request = new Request("https://test.mcp.sentry.io/mcp");

    await expect(
      handler.fetch!(request as any, env, ctxWithoutAuth as any),
    ).rejects.toThrow("No authentication context");
  });

  it("successfully handles request with org and project constraints", async () => {
    const request = new Request(
      "https://test.mcp.sentry.io/mcp/sentry-mcp-evals/cloudflare-mcp",
    );

    const response = await handler.fetch!(request as any, env, ctx);

    // Verify successful response
    expect(response.status).toBe(200);
  });

  it("successfully handles request without constraints", async () => {
    const request = new Request("https://test.mcp.sentry.io/mcp");

    const response = await handler.fetch!(request as any, env, ctx);

    // Verify successful response
    expect(response.status).toBe(200);
  });

  it("returns 401 and revokes grant for legacy tokens without grantedSkills", async () => {
    const legacyCtx = {
      waitUntil: vi.fn(),
      passThroughOnException: vi.fn(),
      props: {
        id: "test-user-123",
        clientId: "test-client",
        accessToken: "test-token",
        // Legacy token: has grantedScopes but no grantedSkills
        grantedScopes: ["org:read", "project:read"],
      },
    };

    // Mock the OAuth provider for grant revocation
    const mockRevokeGrant = vi.fn();
    const mockListUserGrants = vi.fn().mockResolvedValue({
      items: [{ id: "grant-123", clientId: "test-client" }],
    });
    const envWithOAuth = {
      ...env,
      OAUTH_PROVIDER: {
        listUserGrants: mockListUserGrants,
        revokeGrant: mockRevokeGrant,
      },
    } as unknown as Env;

    const request = new Request("https://test.mcp.sentry.io/mcp");

    const response = await handler.fetch!(
      request as any,
      envWithOAuth,
      legacyCtx as any,
    );

    // Verify 401 response with re-auth message and WWW-Authenticate header
    expect(response.status).toBe(401);
    expect(await response.text()).toContain("re-authorize");
    expect(response.headers.get("WWW-Authenticate")).toContain("invalid_token");

    // Verify waitUntil was called for background grant revocation
    expect(legacyCtx.waitUntil).toHaveBeenCalled();

    // Wait for the background task to complete
    const waitUntilPromise = legacyCtx.waitUntil.mock.calls[0][0];
    await waitUntilPromise;

    // Verify grant was looked up and revoked
    expect(mockListUserGrants).toHaveBeenCalledWith("test-user-123");
    expect(mockRevokeGrant).toHaveBeenCalledWith("grant-123", "test-user-123");
  });
});

```

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

```json
[
  {
    "children": [
      {
        "children": [],
        "errors": [],
        "occurrences": [],
        "event_id": "aa8e7f3384ef4ff5850ba966b29ed10d",
        "transaction_id": "aa8e7f3384ef4ff5850ba966b29ed10d",
        "project_id": 4509109107622913,
        "project_slug": "mcp-server",
        "profile_id": "",
        "profiler_id": "",
        "parent_span_id": "a4d1aae7216b47ff",
        "start_timestamp": 1713805458.405616,
        "end_timestamp": 1713805463.608875,
        "measurements": {},
        "description": "POST https://api.openai.com/v1/chat/completions",
        "duration": 1708,
        "trace": "b4d1aae7216b47ff8117cf4e09ce9d0b",
        "span_id": "ad0f7c48fb294de3",
        "organization": null,
        "op": "http.client",
        "hash": "4ed30c7c-4fae-4c79-b2f1-be95c24e7b04",
        "exclusive_time": 1708,
        "status": null,
        "is_segment": true,
        "sdk_name": "sentry.javascript.bun",
        "same_process_as_parent": true,
        "tags": {
          "http.method": "POST",
          "http.status_code": "200",
          "server_name": "mcp-server"
        },
        "timestamp": 1713805463.608875,
        "data": {}
      }
    ],
    "errors": [],
    "occurrences": [],
    "event_id": "aa8e7f3384ef4ff5850ba966b29ed10d",
    "transaction_id": "aa8e7f3384ef4ff5850ba966b29ed10d",
    "project_id": 4509109107622913,
    "project_slug": "mcp-server",
    "profile_id": "",
    "profiler_id": "",
    "parent_span_id": null,
    "start_timestamp": 1713805458.405616,
    "end_timestamp": 1713805463.608875,
    "measurements": {},
    "description": "tools/call search_events",
    "duration": 5203,
    "trace": "b4d1aae7216b47ff8117cf4e09ce9d0b",
    "span_id": "aa8e7f3384ef4ff5",
    "organization": null,
    "op": "function",
    "hash": "4ed30c7c-4fae-4c79-b2f1-be95c24e7b04",
    "exclusive_time": 3495,
    "status": null,
    "is_segment": true,
    "sdk_name": "sentry.javascript.bun",
    "same_process_as_parent": true,
    "tags": {
      "ai.input_messages": "1",
      "ai.model_id": "gpt-4o-2024-08-06",
      "ai.pipeline.name": "search_events",
      "ai.response.finish_reason": "stop",
      "ai.streaming": "false",
      "ai.total_tokens.used": "435",
      "server_name": "mcp-server"
    },
    "timestamp": 1713805463.608875,
    "data": {}
  },
  {
    "id": 6507376925,
    "issue_id": 6507376925,
    "project_id": 4509109107622913,
    "project_slug": "mcp-server",
    "title": "Error: Standalone issue not associated with spans",
    "culprit": "standalone-error.js:42",
    "type": "error",
    "timestamp": 1713805460.123456
  },
  {
    "children": [
      {
        "children": [],
        "errors": [],
        "occurrences": [],
        "event_id": "b4abfe5ed7984c2b",
        "transaction_id": "b4abfe5ed7984c2b",
        "project_id": 4509109107622913,
        "project_slug": "mcp-server",
        "profile_id": "",
        "profiler_id": "",
        "parent_span_id": "b4abfe5ed7984c2b",
        "start_timestamp": 1713805461.126859,
        "end_timestamp": 1713805462.534782,
        "measurements": {},
        "description": "/api/0/organizations/{organization_id_or_slug}/events/",
        "duration": 1408,
        "trace": "b4d1aae7216b47ff8117cf4e09ce9d0b",
        "span_id": "99a97a1d42c3489a",
        "organization": null,
        "op": "http.server",
        "hash": "another-hash-here",
        "exclusive_time": 1408,
        "status": "ok",
        "is_segment": true,
        "sdk_name": "sentry.python",
        "same_process_as_parent": false,
        "tags": {
          "http.method": "GET",
          "http.status_code": "200"
        },
        "timestamp": 1713805462.534782,
        "data": {}
      }
    ],
    "errors": [],
    "occurrences": [],
    "event_id": "b4abfe5ed7984c2b",
    "transaction_id": "b4abfe5ed7984c2b",
    "project_id": 4509109107622913,
    "project_slug": "mcp-server",
    "profile_id": "",
    "profiler_id": "",
    "parent_span_id": "aa8e7f3384ef4ff5",
    "start_timestamp": 1713805461.126859,
    "end_timestamp": 1713805462.608782,
    "measurements": {},
    "description": "GET https://us.sentry.io/api/0/organizations/example-org/events/",
    "duration": 1482,
    "trace": "b4d1aae7216b47ff8117cf4e09ce9d0b",
    "span_id": "b4abfe5ed7984c2b",
    "organization": null,
    "op": "http.client",
    "hash": "yet-another-hash",
    "exclusive_time": 74,
    "status": "ok",
    "is_segment": true,
    "sdk_name": "sentry.javascript.bun",
    "same_process_as_parent": true,
    "tags": {
      "http.method": "GET",
      "http.status_code": "200",
      "server_name": "mcp-server"
    },
    "timestamp": 1713805462.608782,
    "data": {}
  },
  {
    "id": 6507376926,
    "issue_id": 6507376926,
    "project_id": 4509109107622913,
    "project_slug": "mcp-server",
    "title": "TypeError: Cannot read property 'span' of undefined",
    "culprit": "trace-processor.js:156",
    "type": "error",
    "timestamp": 1713805462.234567
  }
]

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/utils/rate-limiter.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach } from "vitest";
import { checkRateLimit } from "./rate-limiter";
import type { RateLimit } from "@cloudflare/workers-types";

describe("checkRateLimit", () => {
  let mockRateLimiter: RateLimit;

  beforeEach(() => {
    // Reset mocks before each test
    vi.clearAllMocks();
  });

  it("allows request when rate limiter binding is not available", async () => {
    const result = await checkRateLimit("192.168.1.1", undefined, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    expect(result.allowed).toBe(true);
    expect(result.errorMessage).toBeUndefined();
  });

  it("allows request when rate limit not exceeded", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: true }),
    } as unknown as RateLimit;

    const result = await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    expect(result.allowed).toBe(true);
    expect(result.errorMessage).toBeUndefined();
    expect(mockRateLimiter.limit).toHaveBeenCalledWith({
      key: expect.stringMatching(/^test:/),
    });
  });

  it("denies request when rate limit exceeded", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: false }),
    } as unknown as RateLimit;

    const result = await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limit exceeded. Please wait.",
    });

    expect(result.allowed).toBe(false);
    expect(result.errorMessage).toBe("Rate limit exceeded. Please wait.");
  });

  it("uses hashed identifier for privacy", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: true }),
    } as unknown as RateLimit;

    await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    // Verify the key is hashed and not the raw IP
    expect(mockRateLimiter.limit).toHaveBeenCalledWith({
      key: expect.not.stringContaining("192.168.1.1"),
    });

    // Verify the key has the correct format (prefix:hash)
    const callArg = (mockRateLimiter.limit as any).mock.calls[0][0];
    expect(callArg.key).toMatch(/^test:[0-9a-f]{16}$/);
  });

  it("uses consistent hash for same identifier", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: true }),
    } as unknown as RateLimit;

    await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    const firstKey = (mockRateLimiter.limit as any).mock.calls[0][0].key;

    // Clear and call again with same IP
    vi.clearAllMocks();

    await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    const secondKey = (mockRateLimiter.limit as any).mock.calls[0][0].key;

    // Same identifier should produce same hash
    expect(firstKey).toBe(secondKey);
  });

  it("uses different keys for different identifiers", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: true }),
    } as unknown as RateLimit;

    await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    const firstKey = (mockRateLimiter.limit as any).mock.calls[0][0].key;

    // Clear and call with different IP
    vi.clearAllMocks();

    await checkRateLimit("192.168.1.2", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    const secondKey = (mockRateLimiter.limit as any).mock.calls[0][0].key;

    // Different identifiers should produce different hashes
    expect(firstKey).not.toBe(secondKey);
  });

  it("includes key prefix in rate limit key", async () => {
    mockRateLimiter = {
      limit: vi.fn().mockResolvedValue({ success: true }),
    } as unknown as RateLimit;

    await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "mcp:ip",
      errorMessage: "Rate limited",
    });

    expect(mockRateLimiter.limit).toHaveBeenCalledWith({
      key: expect.stringMatching(/^mcp:ip:/),
    });
  });

  it("allows request when rate limiter throws error", async () => {
    const consoleErrorSpy = vi
      .spyOn(console, "error")
      .mockImplementation(() => {});

    mockRateLimiter = {
      limit: vi.fn().mockRejectedValue(new Error("Rate limiter service error")),
    } as unknown as RateLimit;

    const result = await checkRateLimit("192.168.1.1", mockRateLimiter, {
      keyPrefix: "test",
      errorMessage: "Rate limited",
    });

    // Should allow request to proceed even if rate limiter fails
    expect(result.allowed).toBe(true);
    expect(result.errorMessage).toBeUndefined();

    // Should log the error
    expect(consoleErrorSpy).toHaveBeenCalledWith(
      "Rate limiter error:",
      expect.any(Error),
    );

    consoleErrorSpy.mockRestore();
  });
});

```

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

```json
{
  "namespace": "cicd",
  "description": "This group describes attributes specific to pipelines within a Continuous Integration and Continuous Deployment (CI/CD) system. A [pipeline](https://wikipedia.org/wiki/Pipeline_(computing)) in this case is a series of steps that are performed in order to deliver a new version of software. This aligns with the [Britannica](https://www.britannica.com/dictionary/pipeline) definition of a pipeline where a **pipeline** is the system for developing and producing something. In the context of CI/CD, a pipeline produces or delivers software.\n",
  "attributes": {
    "cicd.pipeline.name": {
      "description": "The human readable name of the pipeline within a CI/CD system.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "Build and Test",
        "Lint",
        "Deploy Go Project",
        "deploy_to_environment"
      ]
    },
    "cicd.pipeline.run.id": {
      "description": "The unique identifier of a pipeline run within a CI/CD system.\n",
      "type": "string",
      "stability": "development",
      "examples": ["120912"]
    },
    "cicd.pipeline.run.url.full": {
      "description": "The [URL](https://wikipedia.org/wiki/URL) of the pipeline run, providing the complete address in order to locate and identify the pipeline run.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "https://github.com/open-telemetry/semantic-conventions/actions/runs/9753949763?pr=1075"
      ]
    },
    "cicd.pipeline.run.state": {
      "description": "The pipeline run goes through these states during its lifecycle.\n",
      "type": "string",
      "stability": "development",
      "examples": ["pending", "executing", "finalizing"]
    },
    "cicd.pipeline.task.name": {
      "description": "The human readable name of a task within a pipeline. Task here most closely aligns with a [computing process](https://wikipedia.org/wiki/Pipeline_(computing)) in a pipeline. Other terms for tasks include commands, steps, and procedures.\n",
      "type": "string",
      "stability": "development",
      "examples": ["Run GoLang Linter", "Go Build", "go-test", "deploy_binary"]
    },
    "cicd.pipeline.task.run.id": {
      "description": "The unique identifier of a task run within a pipeline.\n",
      "type": "string",
      "stability": "development",
      "examples": ["12097"]
    },
    "cicd.pipeline.task.run.url.full": {
      "description": "The [URL](https://wikipedia.org/wiki/URL) of the pipeline task run, providing the complete address in order to locate and identify the pipeline task run.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "https://github.com/open-telemetry/semantic-conventions/actions/runs/9753949763/job/26920038674?pr=1075"
      ]
    },
    "cicd.pipeline.task.run.result": {
      "description": "The result of a task run.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "success",
        "failure",
        "error",
        "timeout",
        "cancellation",
        "skip"
      ]
    },
    "cicd.pipeline.task.type": {
      "description": "The type of the task within a pipeline.\n",
      "type": "string",
      "stability": "development",
      "examples": ["build", "test", "deploy"]
    },
    "cicd.pipeline.result": {
      "description": "The result of a pipeline run.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "success",
        "failure",
        "error",
        "timeout",
        "cancellation",
        "skip"
      ]
    },
    "cicd.pipeline.action.name": {
      "description": "The kind of action a pipeline run is performing.\n",
      "type": "string",
      "stability": "development",
      "examples": ["BUILD", "RUN", "SYNC"]
    },
    "cicd.worker.id": {
      "description": "The unique identifier of a worker within a CICD system.",
      "type": "string",
      "stability": "development",
      "examples": ["abc123", "10.0.1.2", "controller"]
    },
    "cicd.worker.name": {
      "description": "The name of a worker within a CICD system.",
      "type": "string",
      "stability": "development",
      "examples": ["agent-abc", "controller", "Ubuntu LTS"]
    },
    "cicd.worker.url.full": {
      "description": "The [URL](https://wikipedia.org/wiki/URL) of the worker, providing the complete address in order to locate and identify the worker.",
      "type": "string",
      "stability": "development",
      "examples": ["https://cicd.example.org/worker/abc123"]
    },
    "cicd.worker.state": {
      "description": "The state of a CICD worker / agent.\n",
      "type": "string",
      "stability": "development",
      "examples": ["available", "busy", "offline"]
    },
    "cicd.system.component": {
      "description": "The name of a component of the CICD system.",
      "type": "string",
      "stability": "development",
      "examples": ["controller", "scheduler", "agent"]
    }
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/docs/toc.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";
import { useEffect, useRef, useState } from "react";

type TocSection = { name: string; id: string; active: boolean };

export default function TableOfContents() {
  const [sections, setSections] = useState<TocSection[]>([
    { name: "Getting Started", id: "getting-started", active: true },
    // { name: "Integration Guides", id: "integration-guides", active: false },
    { name: "Available Skills", id: "skills", active: false },
    // { name: "Available Prompts", id: "prompts", active: false },
    // { name: "Available Resources", id: "resources", active: false },
    { name: "More Information", id: "more-information", active: false },
  ]);

  // live set of elements currently intersecting
  const inViewRef = useRef<Set<HTMLElement>>(new Set());
  const rafRef = useRef<number | null>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);
  const currentActiveIdRef = useRef<string | null>(null);

  useEffect(() => {
    const biasPx = 6; // tiny bias so a section "counts" as soon as it peeks in
    const computeAndSetActive = () => {
      if (inViewRef.current.size === 0) return;

      let bottomMost: HTMLElement | null = null;
      let maxTop = Number.NEGATIVE_INFINITY;
      const vh = window.innerHeight;

      // biome-ignore lint/complexity/noForEach: <explanation: vibes>
      inViewRef.current.forEach((el) => {
        const r = el.getBoundingClientRect();
        // truly visible in viewport (with a small bias)
        const visible = r.bottom > biasPx && r.top < vh - biasPx;
        if (!visible) return;

        // pick the one closest to the bottom of the viewport
        if (r.top > maxTop) {
          maxTop = r.top;
          bottomMost = el as HTMLElement;
        }
      });

      if (!bottomMost) return;

      const id = (bottomMost as HTMLElement).id;
      if (id === currentActiveIdRef.current) return; // skip redundant state updates
      currentActiveIdRef.current = id;

      setSections((prev) =>
        prev.map((s) =>
          s.id === id ? { ...s, active: true } : { ...s, active: false },
        ),
      );
    };

    const cb: IntersectionObserverCallback = (entries) => {
      for (const entry of entries) {
        const el = entry.target as HTMLElement;
        if (entry.isIntersecting) {
          inViewRef.current.add(el);
        } else {
          inViewRef.current.delete(el);
        }
      }
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      rafRef.current = requestAnimationFrame(computeAndSetActive);
    };

    observerRef.current = new IntersectionObserver(cb, {
      root: null,
      rootMargin: "-120px 0px 0px 0px",
      threshold: 0,
    });

    const els = Array.from(
      document.querySelectorAll<HTMLElement>("section[id]"),
    );
    // biome-ignore lint/complexity/noForEach: <explanation: vibes>
    els.forEach((el) => observerRef.current!.observe(el));

    // initial calculation on load/refresh
    requestAnimationFrame(() => {
      const vh = window.innerHeight;
      // biome-ignore lint/complexity/noForEach: <explanation: vibes>
      els.forEach((el) => {
        const r = el.getBoundingClientRect();
        const visible = r.bottom > biasPx && r.top < vh - biasPx;
        if (visible) inViewRef.current.add(el);
      });
      computeAndSetActive();
    });

    return () => {
      if (observerRef.current) {
        // biome-ignore lint/complexity/noForEach: <explanation: vibes>
        els.forEach((el) => observerRef.current!.unobserve(el));
        observerRef.current.disconnect();
      }
      observerRef.current = null;
      if (rafRef.current) cancelAnimationFrame(rafRef.current);
      inViewRef.current.clear();
    };
  }, []); // run once

  return (
    <div className="group pointer-events-none sticky top-20 px-12 text-white/60">
      <div className="pointer-events-auto flex flex-col py-2">
        <b className="-ml-5 mb-2 font-mono text-xs text-white">
          [table of contents]
        </b>
        {sections.map((section) => (
          <a
            key={section.id}
            href={`#${section.id}`}
            onClick={(e) => {
              e.preventDefault();
              document
                .getElementById(section.id)
                ?.scrollIntoView({ behavior: "smooth", block: "start" });
              // preserve current query string, only change the hash
              const url = new URL(window.location.href);
              url.hash = section.id;
              window.history.pushState(
                window.history.state,
                "",
                url.toString(),
              );
            }}
            className={`-ml-[calc(1rem+1px)] border-l py-1.5 pl-3 duration-75 max-xl:lg:opacity-20 max-xl:lg:group-hover:opacity-100 ${
              section.active
                ? "border-violet-300 text-violet-300"
                : "border-neutral-400/30 hover:border-white hover:text-white"
            }`}
          >
            {section.name}
          </a>
        ))}
      </div>
    </div>
  );
}

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/find-releases.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,
  ParamProjectSlugOrAll,
} from "../schema";

export default defineTool({
  name: "find_releases",
  skills: ["inspect"], // Only available in inspect skill
  requiredScopes: ["project:read"],
  description: [
    "Find releases in Sentry.",
    "",
    "Use this tool when you need to:",
    "- Find recent releases in a Sentry organization",
    "- Find the most recent version released of a specific project",
    "- Determine when a release was deployed to an environment",
    "",
    "<examples>",
    "### Find the most recent releases in the 'my-organization' organization",
    "",
    "```",
    "find_releases(organizationSlug='my-organization')",
    "```",
    "",
    "### Find releases matching '2ce6a27' in the 'my-organization' organization",
    "",
    "```",
    "find_releases(organizationSlug='my-organization', query='2ce6a27')",
    "```",
    "</examples>",
    "",
    "<hints>",
    "- If the user passes a parameter in the form of name/otherName, its likely in the format of <organizationSlug>/<projectSlug>.",
    "</hints>",
  ].join("\n"),
  inputSchema: {
    organizationSlug: ParamOrganizationSlug,
    regionUrl: ParamRegionUrl.nullable().default(null),
    projectSlug: ParamProjectSlugOrAll.nullable().default(null),
    query: z
      .string()
      .trim()
      .describe("Search for versions which contain the provided string.")
      .nullable()
      .default(null),
  },
  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);

    const releases = await apiService.listReleases({
      organizationSlug,
      projectSlug: params.projectSlug ?? undefined,
      query: params.query ?? undefined,
    });
    let output = `# Releases in **${organizationSlug}${params.projectSlug ? `/${params.projectSlug}` : ""}**\n\n`;
    if (releases.length === 0) {
      output += "No releases found.\n";
      return output;
    }
    output += releases
      .map((release) => {
        const releaseInfo = [
          `## ${release.shortVersion}`,
          "",
          `**Created**: ${new Date(release.dateCreated).toISOString()}`,
        ];
        if (release.dateReleased) {
          releaseInfo.push(
            `**Released**: ${new Date(release.dateReleased).toISOString()}`,
          );
        }
        if (release.firstEvent) {
          releaseInfo.push(
            `**First Event**: ${new Date(release.firstEvent).toISOString()}`,
          );
        }
        if (release.lastEvent) {
          releaseInfo.push(
            `**Last Event**: ${new Date(release.lastEvent).toISOString()}`,
          );
        }
        if (release.newGroups !== undefined) {
          releaseInfo.push(`**New Issues**: ${release.newGroups}`);
        }
        if (release.projects && release.projects.length > 0) {
          releaseInfo.push(
            `**Projects**: ${release.projects.map((p) => p.name).join(", ")}`,
          );
        }
        if (release.lastCommit) {
          releaseInfo.push("", `### Last Commit`, "");
          releaseInfo.push(`**Commit ID**: ${release.lastCommit.id}`);
          releaseInfo.push(`**Commit Message**: ${release.lastCommit.message}`);
          releaseInfo.push(
            `**Commit Author**: ${release.lastCommit.author.name}`,
          );
          releaseInfo.push(
            `**Commit Date**: ${new Date(release.lastCommit.dateCreated).toISOString()}`,
          );
        }
        if (release.lastDeploy) {
          releaseInfo.push("", `### Last Deploy`, "");
          releaseInfo.push(`**Deploy ID**: ${release.lastDeploy.id}`);
          releaseInfo.push(
            `**Environment**: ${release.lastDeploy.environment}`,
          );
          if (release.lastDeploy.dateStarted) {
            releaseInfo.push(
              `**Deploy Started**: ${new Date(release.lastDeploy.dateStarted).toISOString()}`,
            );
          }
          if (release.lastDeploy.dateFinished) {
            releaseInfo.push(
              `**Deploy Finished**: ${new Date(release.lastDeploy.dateFinished).toISOString()}`,
            );
          }
        }
        return releaseInfo.join("\n");
      })
      .join("\n\n");
    output += "\n\n";
    output += "# Using this information\n\n";
    output += `- You can reference the Release version in commit messages or documentation.\n`;
    output += `- You can search for issues in a specific release using the \`find_errors()\` tool with the query \`release:${releases.length ? releases[0]!.shortVersion : "VERSION"}\`.\n`;
    return output;
  },
});

```

--------------------------------------------------------------------------------
/packages/mcp-test-client/src/auth/config.ts:
--------------------------------------------------------------------------------

```typescript
import { promises as fs } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";

export interface OAuthClientConfig {
  clientId: string;
  mcpHost: string;
  registeredAt: string;
  accessToken?: string;
  tokenExpiresAt?: string;
}

export interface ClientConfigFile {
  oauthClients: Record<string, OAuthClientConfig>;
}

export class ConfigManager {
  private configDir: string;
  private configFile: string;

  constructor() {
    this.configDir = join(homedir(), ".config", "sentry-mcp");
    this.configFile = join(this.configDir, "config.json");
  }

  /**
   * Ensure config directory exists
   */
  private async ensureConfigDir(): Promise<void> {
    try {
      await fs.mkdir(this.configDir, { recursive: true });
    } catch (error) {
      // Directory might already exist, ignore EEXIST errors
      if ((error as any).code !== "EEXIST") {
        throw error;
      }
    }
  }

  /**
   * Load config file
   */
  private async loadConfig(): Promise<ClientConfigFile> {
    try {
      const content = await fs.readFile(this.configFile, "utf-8");
      return JSON.parse(content);
    } catch (error) {
      // Config file doesn't exist or is invalid, return empty config
      return { oauthClients: {} };
    }
  }

  /**
   * Save config file
   */
  private async saveConfig(config: ClientConfigFile): Promise<void> {
    await this.ensureConfigDir();
    await fs.writeFile(
      this.configFile,
      JSON.stringify(config, null, 2),
      "utf-8",
    );
  }

  /**
   * Get OAuth client ID for a specific MCP host
   */
  async getOAuthClientId(mcpHost: string): Promise<string | null> {
    const config = await this.loadConfig();
    const clientConfig = config.oauthClients[mcpHost];
    return clientConfig?.clientId || null;
  }

  /**
   * Store OAuth client ID for a specific MCP host
   */
  async setOAuthClientId(mcpHost: string, clientId: string): Promise<void> {
    const config = await this.loadConfig();

    // Preserve existing access token if present
    const existing = config.oauthClients[mcpHost];
    config.oauthClients[mcpHost] = {
      clientId,
      mcpHost,
      registeredAt: new Date().toISOString(),
      accessToken: existing?.accessToken,
      tokenExpiresAt: existing?.tokenExpiresAt,
    };

    await this.saveConfig(config);
  }

  /**
   * Remove OAuth client configuration for a specific MCP host
   */
  async removeOAuthClientId(mcpHost: string): Promise<void> {
    const config = await this.loadConfig();
    delete config.oauthClients[mcpHost];
    await this.saveConfig(config);
  }

  /**
   * Get cached access token for a specific MCP host
   */
  async getAccessToken(mcpHost: string): Promise<string | null> {
    const config = await this.loadConfig();
    const clientConfig = config.oauthClients[mcpHost];

    if (!clientConfig?.accessToken) {
      return null;
    }

    // Check if token is expired
    if (clientConfig.tokenExpiresAt) {
      const expiresAt = new Date(clientConfig.tokenExpiresAt);
      const now = new Date();
      // Add 5 minute buffer before expiration
      const bufferTime = 5 * 60 * 1000;

      if (now.getTime() + bufferTime >= expiresAt.getTime()) {
        // Token is expired or will expire soon
        await this.removeAccessToken(mcpHost);
        return null;
      }
    }

    return clientConfig.accessToken;
  }

  /**
   * Store access token for a specific MCP host
   */
  async setAccessToken(
    mcpHost: string,
    accessToken: string,
    expiresIn?: number,
  ): Promise<void> {
    const config = await this.loadConfig();

    const existing = config.oauthClients[mcpHost];
    if (!existing) {
      throw new Error(`No OAuth client configuration found for ${mcpHost}`);
    }

    let tokenExpiresAt: string | undefined;
    if (expiresIn) {
      // expiresIn is in seconds, convert to milliseconds
      const expiresAtMs = Date.now() + expiresIn * 1000;
      tokenExpiresAt = new Date(expiresAtMs).toISOString();
    }

    config.oauthClients[mcpHost] = {
      ...existing,
      accessToken,
      tokenExpiresAt,
    };

    await this.saveConfig(config);
  }

  /**
   * Remove cached access token for a specific MCP host
   */
  async removeAccessToken(mcpHost: string): Promise<void> {
    const config = await this.loadConfig();
    const existing = config.oauthClients[mcpHost];

    if (existing) {
      config.oauthClients[mcpHost] = {
        ...existing,
        accessToken: undefined,
        tokenExpiresAt: undefined,
      };
      await this.saveConfig(config);
    }
  }

  /**
   * Clear all cached tokens (useful for logout)
   */
  async clearAllTokens(): Promise<void> {
    const config = await this.loadConfig();

    for (const [host, clientConfig] of Object.entries(config.oauthClients)) {
      config.oauthClients[host] = {
        ...clientConfig,
        accessToken: undefined,
        tokenExpiresAt: undefined,
      };
    }

    await this.saveConfig(config);
  }

  /**
   * List all registered OAuth clients
   */
  async listOAuthClients(): Promise<OAuthClientConfig[]> {
    const config = await this.loadConfig();
    return Object.values(config.oauthClients);
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/use-sentry/handler.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect, vi, beforeEach, type Mock } from "vitest";
import useSentry from "./handler";
import type { ServerContext } from "../../types";
import type { Skill } from "../../skills";

// Mock the embedded agent
vi.mock("./agent", () => ({
  useSentryAgent: vi.fn(),
}));

// Import the mocked module to get access to the mock function
import { useSentryAgent } from "./agent";
const mockUseSentryAgent = useSentryAgent as Mock;

// Use all skills for testing to ensure all tools are available
const ALL_SKILLS: Skill[] = [
  "inspect",
  "docs",
  "seer",
  "triage",
  "project-management",
];

const mockContext: ServerContext = {
  accessToken: "test-token",
  sentryHost: "sentry.io",
  userId: "1",
  clientId: "test-client",
  constraints: {},
  grantedSkills: new Set(ALL_SKILLS),
};

describe("use_sentry handler", () => {
  beforeEach(() => {
    mockUseSentryAgent.mockClear();
  });

  it("calls embedded agent with request and wrapped tools", async () => {
    mockUseSentryAgent.mockResolvedValue({
      result: {
        result: "Agent executed tools successfully",
      },
      toolCalls: [{ toolName: "whoami", args: {} }],
    });

    const result = await useSentry.handler(
      { request: "Show me unresolved issues", trace: null },
      mockContext,
    );

    // Verify agent was called
    expect(mockUseSentryAgent).toHaveBeenCalledWith({
      request: "Show me unresolved issues",
      tools: expect.objectContaining({
        whoami: expect.any(Object),
        find_organizations: expect.any(Object),
        search_issues: expect.any(Object),
      }),
    });

    // Verify all 19 tools were provided (20 total - use_sentry itself)
    const toolsArg = mockUseSentryAgent.mock.calls[0][0].tools;
    expect(Object.keys(toolsArg)).toHaveLength(19);

    // Verify result is returned
    expect(result).toBe("Agent executed tools successfully");
  });

  it("provides wrapped tools with ServerContext", async () => {
    mockUseSentryAgent.mockResolvedValue({
      result: {
        result: "Success",
      },
      toolCalls: [],
    });

    await useSentry.handler(
      { request: "test request", trace: null },
      mockContext,
    );

    // Verify tools were provided to agent
    const toolsArg = mockUseSentryAgent.mock.calls[0][0].tools;
    expect(toolsArg).toBeDefined();

    // Verify key tools are present
    expect(toolsArg.whoami).toBeDefined();
    expect(toolsArg.find_organizations).toBeDefined();
    expect(toolsArg.search_events).toBeDefined();
    expect(toolsArg.search_issues).toBeDefined();
    expect(toolsArg.get_issue_details).toBeDefined();
  });

  it("excludes use_sentry from available tools to prevent recursion", async () => {
    mockUseSentryAgent.mockResolvedValue({
      result: {
        result: "Success",
      },
      toolCalls: [],
    });

    await useSentry.handler({ request: "test", trace: null }, mockContext);

    const toolsArg = mockUseSentryAgent.mock.calls[0][0].tools;
    const toolNames = Object.keys(toolsArg);

    // Verify use_sentry is NOT in the list
    expect(toolNames).not.toContain("use_sentry");

    // Verify we have exactly 19 tools (20 total - 1 use_sentry)
    expect(toolNames).toHaveLength(19);
  });

  it("filters find_organizations when organizationSlug constraint is set", async () => {
    const orgConstrainedContext: ServerContext = {
      ...mockContext,
      constraints: {
        organizationSlug: "constrained-org",
      },
    };

    mockUseSentryAgent.mockResolvedValue({
      result: {
        result: "Success with org constraint",
      },
      toolCalls: [],
    });

    await useSentry.handler(
      { request: "test with org constraint", trace: null },
      orgConstrainedContext,
    );

    const toolsArg = mockUseSentryAgent.mock.calls[0][0].tools;
    expect(toolsArg).toBeDefined();

    // With only org constraint, find_organizations is filtered (19 - 1 = 18)
    expect(Object.keys(toolsArg)).toHaveLength(18);

    // Verify find_organizations is filtered but find_projects remains
    expect(toolsArg.find_organizations).toBeUndefined();
    expect(toolsArg.find_projects).toBeDefined();
  });

  it("filters both find tools when org and project constraints are set", async () => {
    const fullyConstrainedContext: ServerContext = {
      ...mockContext,
      constraints: {
        organizationSlug: "constrained-org",
        projectSlug: "constrained-project",
      },
    };

    mockUseSentryAgent.mockResolvedValue({
      result: {
        result: "Success with both constraints",
      },
      toolCalls: [],
    });

    await useSentry.handler(
      { request: "test with both constraints", trace: null },
      fullyConstrainedContext,
    );

    const toolsArg = mockUseSentryAgent.mock.calls[0][0].tools;
    expect(toolsArg).toBeDefined();

    // When both org and project constraints are present,
    // find_organizations and find_projects are filtered out (19 - 2 = 17)
    expect(Object.keys(toolsArg)).toHaveLength(17);

    // Verify both find tools are filtered
    expect(toolsArg.find_organizations).toBeUndefined();
    expect(toolsArg.find_projects).toBeUndefined();
  });
});

```

--------------------------------------------------------------------------------
/docs/llms/document-scopes.md:
--------------------------------------------------------------------------------

```markdown
# Document Scopes

Defines the specific purpose and content for each documentation file.

## Reference Style (MANDATORY)

- Use @path for all local file references, repo-root relative (e.g., `@packages/mcp-server/src/server.ts`).
- Refer to sections by name: `See "Error Handling" in @docs/common-patterns.md`.
- Keep Markdown links only for external sites.

## Core Documents

### architecture.md
**Purpose**: Explain system design and package interactions

**Must Include**:
- Package responsibilities and boundaries
- Data flow between components
- Key architectural decisions and trade-offs
- How MCP concepts map to implementation

**Must Exclude**:
- Installation instructions
- Implementation details (link to code instead)
- General MCP protocol explanation

### common-patterns.md
**Purpose**: Reusable patterns used throughout the codebase

**Must Include**:
- Error handling patterns (UserInputError, ApiError)
- Zod schema patterns and conventions
- TypeScript type helpers
- Response formatting patterns
- Parameter validation patterns

**Must Exclude**:
- Tool/prompt/resource-specific patterns
- External library documentation
- One-off patterns used in single places

### quality-checks.md
**Purpose**: Required checks and commands for code quality

**Must Include**:
- Essential commands that must pass
- When to run each check
- What each check validates
- Common failure fixes

**Must Exclude**:
- Tool installation instructions
- Detailed explanations of what linting is
- CI/CD configuration

## Feature Implementation Guides

### adding-tools.md
**Purpose**: How to add new MCP tools

**Must Include**:
- Tool definition structure
- Handler implementation pattern
- Required tests (unit + eval)
- LLM-friendly descriptions
- References to existing tools

**Must Exclude**:
- What MCP tools are conceptually
- Duplicate testing patterns (link to testing.md)
- Full code examples (reference real implementations)

## Technical Guides

### testing.md
**Purpose**: Testing strategies and patterns

**Must Include**:
- Unit test patterns with snapshots
- Evaluation test setup
- Mock patterns with MSW
- When to update snapshots
- Test file organization

**Must Exclude**:
- Vitest documentation
- General testing philosophy
- Duplicate mock examples

### api-patterns.md
**Purpose**: Sentry API client usage and mocking

**Must Include**:
- apiServiceFromContext pattern
- Schema definitions with Zod
- Mock handler patterns
- Multi-region support
- Error handling

**Must Exclude**:
- HTTP basics
- Zod library documentation
- Duplicate error patterns (link to common-patterns.md)

## Operations Guides

### releases/cloudflare.md
**Purpose**: Cloudflare Workers release process

**Must Include**:
- Wrangler configuration
- Environment variables
- MCP handler setup
- OAuth provider configuration
- Deployment commands (manual and automated)
- Version uploads and gradual rollouts
- Monitoring and troubleshooting

**Must Exclude**:
- Cloudflare Workers concepts
- General deployment best practices
- npm package release process (see stdio.md)

### releases/stdio.md
**Purpose**: npm package release process

**Must Include**:
- Version management
- npm publishing workflow
- User installation instructions (Claude Desktop, Cursor)
- Environment variable configuration
- Testing releases locally
- Beta releases

**Must Exclude**:
- Cloudflare deployment (see cloudflare.md)
- General npm documentation
- IDE-specific setup details

### monitoring.md
**Purpose**: Observability and instrumentation

**Must Include**:
- Sentry integration patterns
- Telemetry setup
- Error tracking
- Performance monitoring
- Tag conventions

**Must Exclude**:
- What observability is
- Sentry product documentation
- General monitoring concepts

### security.md
**Purpose**: Authentication and security patterns

**Must Include**:
- OAuth implementation
- Token management
- Multi-tenant security
- CORS configuration
- Security headers

**Must Exclude**:
- OAuth protocol explanation
- General security best practices
- Duplicate deployment content

## Meta Documents

### README.md
**Purpose**: Documentation index and navigation

**Must Include**:
- Document listing with one-line descriptions
- Quick reference for common tasks
- Links to style guide and scopes

**Must Exclude**:
- Detailed explanations
- Duplicate content from other docs
- Installation instructions

### AGENTS.md
**Purpose**: Agent entry point (Claude Code, Cursor, etc.)

**Must Include**:
- Brief project description
- Documentation directory reference
- Critical quality checks
- Agent-specific notes (tools, transports, auth defaults)

**Must Exclude**:
- Detailed architecture (link to architecture.md)
- Development setup (link to relevant docs)
- Integration instructions (keep minimal)

## Optimization Strategy

Focus on clarity and usefulness, not arbitrary line counts.

Key improvements needed:
- **security.md**: Too much OAuth theory → focus on implementation
- **api-patterns.md**: Redundant examples → consolidate patterns
- **releases/cloudflare.md**: Focus on MCP-specific config, not generic Cloudflare docs
- **monitoring.md**: Verbose explanations → code examples

The goal: Each document should be focused enough to be useful in a single context window while remaining comprehensive for its topic.

```

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

```json
{
  "namespace": "network",
  "description": "These attributes may be used for any network related operation.\n",
  "attributes": {
    "network.carrier.icc": {
      "description": "The ISO 3166-1 alpha-2 2-character country code associated with the mobile carrier network.",
      "type": "string",
      "stability": "development",
      "examples": ["DE"]
    },
    "network.carrier.mcc": {
      "description": "The mobile carrier country code.",
      "type": "string",
      "stability": "development",
      "examples": ["310"]
    },
    "network.carrier.mnc": {
      "description": "The mobile carrier network code.",
      "type": "string",
      "stability": "development",
      "examples": ["001"]
    },
    "network.carrier.name": {
      "description": "The name of the mobile carrier.",
      "type": "string",
      "stability": "development",
      "examples": ["sprint"]
    },
    "network.connection.subtype": {
      "description": "This describes more details regarding the connection.type. It may be the type of cell technology connection, but it could be used for describing details about a wifi connection.",
      "type": "string",
      "stability": "development",
      "examples": [
        "gprs",
        "edge",
        "umts",
        "cdma",
        "evdo_0",
        "evdo_a",
        "cdma2000_1xrtt",
        "hsdpa",
        "hsupa",
        "hspa",
        "iden",
        "evdo_b",
        "lte",
        "ehrpd",
        "hspap",
        "gsm",
        "td_scdma",
        "iwlan",
        "nr",
        "nrnsa",
        "lte_ca"
      ]
    },
    "network.connection.type": {
      "description": "The internet connection type.",
      "type": "string",
      "stability": "development",
      "examples": ["wifi", "wired", "cell", "unavailable", "unknown"]
    },
    "network.local.address": {
      "description": "Local address of the network connection - IP address or Unix domain socket name.",
      "type": "string",
      "stability": "stable",
      "examples": ["10.1.2.80", "/tmp/my.sock"]
    },
    "network.local.port": {
      "description": "Local port number of the network connection.",
      "type": "number",
      "stability": "stable",
      "examples": ["65123"]
    },
    "network.peer.address": {
      "description": "Peer address of the network connection - IP address or Unix domain socket name.",
      "type": "string",
      "stability": "stable",
      "examples": ["10.1.2.80", "/tmp/my.sock"]
    },
    "network.peer.port": {
      "description": "Peer port number of the network connection.",
      "type": "number",
      "stability": "stable",
      "examples": ["65123"]
    },
    "network.protocol.name": {
      "description": "[OSI application layer](https://wikipedia.org/wiki/Application_layer) or non-OSI equivalent.",
      "type": "string",
      "note": "The value SHOULD be normalized to lowercase.",
      "stability": "stable",
      "examples": ["amqp", "http", "mqtt"]
    },
    "network.protocol.version": {
      "description": "The actual version of the protocol used for network communication.",
      "type": "string",
      "note": "If protocol version is subject to negotiation (for example using [ALPN](https://www.rfc-editor.org/rfc/rfc7301.html)), this attribute SHOULD be set to the negotiated version. If the actual protocol version is not known, this attribute SHOULD NOT be set.\n",
      "stability": "stable",
      "examples": ["1.1", "2"]
    },
    "network.transport": {
      "description": "[OSI transport layer](https://wikipedia.org/wiki/Transport_layer) or [inter-process communication method](https://wikipedia.org/wiki/Inter-process_communication).\n",
      "type": "string",
      "note": "The value SHOULD be normalized to lowercase.\n\nConsider always setting the transport when setting a port number, since\na port number is ambiguous without knowing the transport. For example\ndifferent processes could be listening on TCP port 12345 and UDP port 12345.\n",
      "stability": "stable",
      "examples": ["tcp", "udp", "pipe", "unix", "quic"]
    },
    "network.type": {
      "description": "[OSI network layer](https://wikipedia.org/wiki/Network_layer) or non-OSI equivalent.",
      "type": "string",
      "note": "The value SHOULD be normalized to lowercase.",
      "stability": "stable",
      "examples": ["ipv4", "ipv6"]
    },
    "network.io.direction": {
      "description": "The network IO operation direction.",
      "type": "string",
      "stability": "development",
      "examples": ["transmit", "receive"]
    },
    "network.interface.name": {
      "description": "The network interface name.",
      "type": "string",
      "stability": "development",
      "examples": ["lo", "eth0"]
    },
    "network.connection.state": {
      "description": "The state of network connection",
      "type": "string",
      "note": "Connection states are defined as part of the [rfc9293](https://datatracker.ietf.org/doc/html/rfc9293#section-3.3.2)",
      "stability": "development",
      "examples": [
        "closed",
        "close_wait",
        "closing",
        "established",
        "fin_wait_1",
        "fin_wait_2",
        "last_ack",
        "listen",
        "syn_received",
        "syn_sent",
        "time_wait"
      ]
    }
  }
}

```

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

```json
{
  "namespace": "cloud",
  "description": "A cloud environment (e.g. GCP, Azure, AWS).\n",
  "attributes": {
    "cloud.provider": {
      "description": "Name of the cloud provider.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "alibaba_cloud",
        "aws",
        "azure",
        "gcp",
        "heroku",
        "ibm_cloud",
        "oracle_cloud",
        "tencent_cloud"
      ]
    },
    "cloud.account.id": {
      "description": "The cloud account ID the resource is assigned to.\n",
      "type": "string",
      "stability": "development",
      "examples": ["111111111111", "opentelemetry"]
    },
    "cloud.region": {
      "description": "The geographical region within a cloud provider. When associated with a resource, this attribute specifies the region where the resource operates. When calling services or APIs deployed on a cloud, this attribute identifies the region where the called destination is deployed.\n",
      "type": "string",
      "note": "Refer to your provider's docs to see the available regions, for example [Alibaba Cloud regions](https://www.alibabacloud.com/help/doc-detail/40654.htm), [AWS regions](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/), [Azure regions](https://azure.microsoft.com/global-infrastructure/geographies/), [Google Cloud regions](https://cloud.google.com/about/locations), or [Tencent Cloud regions](https://www.tencentcloud.com/document/product/213/6091).\n",
      "stability": "development",
      "examples": ["us-central1", "us-east-1"]
    },
    "cloud.resource_id": {
      "description": "Cloud provider-specific native identifier of the monitored cloud resource (e.g. an [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) on AWS, a [fully qualified resource ID](https://learn.microsoft.com/rest/api/resources/resources/get-by-id) on Azure, a [full resource name](https://google.aip.dev/122#full-resource-names) on GCP)\n",
      "type": "string",
      "note": "On some cloud providers, it may not be possible to determine the full ID at startup,\nso it may be necessary to set `cloud.resource_id` as a span attribute instead.\n\nThe exact value to use for `cloud.resource_id` depends on the cloud provider.\nThe following well-known definitions MUST be used if you set this attribute and they apply:\n\n- **AWS Lambda:** The function [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html).\n  Take care not to use the \"invoked ARN\" directly but replace any\n  [alias suffix](https://docs.aws.amazon.com/lambda/latest/dg/configuration-aliases.html)\n  with the resolved function version, as the same runtime instance may be invocable with\n  multiple different aliases.\n- **GCP:** The [URI of the resource](https://cloud.google.com/iam/docs/full-resource-names)\n- **Azure:** The [Fully Qualified Resource ID](https://learn.microsoft.com/rest/api/resources/resources/get-by-id) of the invoked function,\n  *not* the function app, having the form\n  `/subscriptions/<SUBSCRIPTION_GUID>/resourceGroups/<RG>/providers/Microsoft.Web/sites/<FUNCAPP>/functions/<FUNC>`.\n  This means that a span attribute MUST be used, as an Azure function app can host multiple functions that would usually share\n  a TracerProvider.\n",
      "stability": "development",
      "examples": [
        "arn:aws:lambda:REGION:ACCOUNT_ID:function:my-function",
        "//run.googleapis.com/projects/PROJECT_ID/locations/LOCATION_ID/services/SERVICE_ID",
        "/subscriptions/<SUBSCRIPTION_GUID>/resourceGroups/<RG>/providers/Microsoft.Web/sites/<FUNCAPP>/functions/<FUNC>"
      ]
    },
    "cloud.availability_zone": {
      "description": "Cloud regions often have multiple, isolated locations known as zones to increase availability. Availability zone represents the zone where the resource is running.\n",
      "type": "string",
      "note": "Availability zones are called \"zones\" on Alibaba Cloud and Google Cloud.\n",
      "stability": "development",
      "examples": ["us-east-1c"]
    },
    "cloud.platform": {
      "description": "The cloud platform in use.\n",
      "type": "string",
      "note": "The prefix of the service SHOULD match the one specified in `cloud.provider`.\n",
      "stability": "development",
      "examples": [
        "alibaba_cloud_ecs",
        "alibaba_cloud_fc",
        "alibaba_cloud_openshift",
        "aws_ec2",
        "aws_ecs",
        "aws_eks",
        "aws_lambda",
        "aws_elastic_beanstalk",
        "aws_app_runner",
        "aws_openshift",
        "azure.vm",
        "azure.container_apps",
        "azure.container_instances",
        "azure.aks",
        "azure.functions",
        "azure.app_service",
        "azure.openshift",
        "azure_vm",
        "azure_container_apps",
        "azure_container_instances",
        "azure_aks",
        "azure_functions",
        "azure_app_service",
        "azure_openshift",
        "gcp_bare_metal_solution",
        "gcp_compute_engine",
        "gcp_cloud_run",
        "gcp_kubernetes_engine",
        "gcp_cloud_functions",
        "gcp_app_engine",
        "gcp_openshift",
        "ibm_cloud_openshift",
        "oracle_cloud_compute",
        "oracle_cloud_oke",
        "tencent_cloud_cvm",
        "tencent_cloud_eks",
        "tencent_cloud_scf"
      ]
    }
  }
}

```

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

```json
{
  "namespace": "container",
  "description": "A container instance.\n",
  "attributes": {
    "container.name": {
      "description": "Container name used by container runtime.\n",
      "type": "string",
      "stability": "development",
      "examples": ["opentelemetry-autoconf"]
    },
    "container.id": {
      "description": "Container ID. Usually a UUID, as for example used to [identify Docker containers](https://docs.docker.com/engine/containers/run/#container-identification). The UUID might be abbreviated.\n",
      "type": "string",
      "stability": "development",
      "examples": ["a3bf90e006b2"]
    },
    "container.runtime": {
      "description": "The container runtime managing this container.\n",
      "type": "string",
      "stability": "development",
      "examples": ["docker", "containerd", "rkt"]
    },
    "container.image.name": {
      "description": "Name of the image the container was built on.\n",
      "type": "string",
      "stability": "development",
      "examples": ["gcr.io/opentelemetry/operator"]
    },
    "container.image.tags": {
      "description": "Container image tags. An example can be found in [Docker Image Inspect](https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageInspect). Should be only the `<tag>` section of the full name for example from `registry.example.com/my-org/my-image:<tag>`.\n",
      "type": "string",
      "stability": "development",
      "examples": ["[\"v1.27.1\",\"3.5.7-0\"]"]
    },
    "container.image.id": {
      "description": "Runtime specific image identifier. Usually a hash algorithm followed by a UUID.\n",
      "type": "string",
      "note": "Docker defines a sha256 of the image id; `container.image.id` corresponds to the `Image` field from the Docker container inspect [API](https://docs.docker.com/engine/api/v1.43/#tag/Container/operation/ContainerInspect) endpoint.\nK8s defines a link to the container registry repository with digest `\"imageID\": \"registry.azurecr.io /namespace/service/dockerfile@sha256:bdeabd40c3a8a492eaf9e8e44d0ebbb84bac7ee25ac0cf8a7159d25f62555625\"`.\nThe ID is assigned by the container runtime and can vary in different environments. Consider using `oci.manifest.digest` if it is important to identify the same image in different environments/runtimes.\n",
      "stability": "development",
      "examples": [
        "sha256:19c92d0a00d1b66d897bceaa7319bee0dd38a10a851c60bcec9474aa3f01e50f"
      ]
    },
    "container.image.repo_digests": {
      "description": "Repo digests of the container image as provided by the container runtime.\n",
      "type": "string",
      "note": "[Docker](https://docs.docker.com/engine/api/v1.43/#tag/Image/operation/ImageInspect) and [CRI](https://github.com/kubernetes/cri-api/blob/c75ef5b473bbe2d0a4fc92f82235efd665ea8e9f/pkg/apis/runtime/v1/api.proto#L1237-L1238) report those under the `RepoDigests` field.\n",
      "stability": "development",
      "examples": [
        "[\"example@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb\",\"internal.registry.example.com:5000/example@sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578\"]"
      ]
    },
    "container.command": {
      "description": "The command used to run the container (i.e. the command name).\n",
      "type": "string",
      "note": "If using embedded credentials or sensitive data, it is recommended to remove them to prevent potential leakage.\n",
      "stability": "development",
      "examples": ["otelcontribcol"]
    },
    "container.command_line": {
      "description": "The full command run by the container as a single string representing the full command.\n",
      "type": "string",
      "stability": "development",
      "examples": ["otelcontribcol --config config.yaml"]
    },
    "container.command_args": {
      "description": "All the command arguments (including the command/executable itself) run by the container.\n",
      "type": "string",
      "stability": "development",
      "examples": ["[\"otelcontribcol\",\"--config\",\"config.yaml\"]"]
    },
    "container.label": {
      "description": "Container labels, `<key>` being the label name, the value being the label value.\n",
      "type": "string",
      "note": "For example, a docker container label `app` with value `nginx` SHOULD be recorded as the `container.label.app` attribute with value `\"nginx\"`.\n",
      "stability": "development",
      "examples": ["nginx"]
    },
    "container.csi.plugin.name": {
      "description": "The name of the CSI ([Container Storage Interface](https://github.com/container-storage-interface/spec)) plugin used by the volume.\n",
      "type": "string",
      "note": "This can sometimes be referred to as a \"driver\" in CSI implementations. This should represent the `name` field of the GetPluginInfo RPC.\n",
      "stability": "development",
      "examples": ["pd.csi.storage.gke.io"]
    },
    "container.csi.volume.id": {
      "description": "The unique volume ID returned by the CSI ([Container Storage Interface](https://github.com/container-storage-interface/spec)) plugin.\n",
      "type": "string",
      "note": "This can sometimes be referred to as a \"volume handle\" in CSI implementations. This should represent the `Volume.volume_id` field in CSI spec.\n",
      "stability": "development",
      "examples": [
        "projects/my-gcp-project/zones/my-gcp-zone/disks/my-gcp-disk"
      ]
    }
  }
}

```

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

```typescript
import { describe, it, expect } from "vitest";
import updateIssue from "./update-issue.js";

describe("update_issue", () => {
  it("updates issue status", async () => {
    const result = await updateIssue.handler(
      {
        organizationSlug: "sentry-mcp-evals",
        issueId: "CLOUDFLARE-MCP-41",
        status: "resolved",
        assignedTo: undefined,
        issueUrl: undefined,
        regionUrl: null,
      },
      {
        constraints: {
          organizationSlug: undefined,
        },
        accessToken: "access-token",
        userId: "1",
      },
    );
    expect(result).toMatchInlineSnapshot(`
      "# Issue CLOUDFLARE-MCP-41 Updated in **sentry-mcp-evals**

      **Issue**: Error: Tool list_organizations is already registered
      **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41

      ## Changes Made

      **Status**: unresolved → **resolved**

      ## Current Status

      **Status**: resolved
      **Assigned To**: Jane Developer

      # Using this information

      - The issue has been successfully updated in Sentry
      - You can view the issue details using: \`get_issue_details(organizationSlug="sentry-mcp-evals", issueId="CLOUDFLARE-MCP-41")\`
      - The issue is now marked as resolved and will no longer generate alerts
      "
    `);
  });

  it("updates issue assignment", async () => {
    const result = await updateIssue.handler(
      {
        organizationSlug: "sentry-mcp-evals",
        issueId: "CLOUDFLARE-MCP-41",
        status: undefined,
        assignedTo: "john.doe",
        issueUrl: undefined,
        regionUrl: null,
      },
      {
        constraints: {
          organizationSlug: undefined,
        },
        accessToken: "access-token",
        userId: "1",
      },
    );
    expect(result).toMatchInlineSnapshot(`
      "# Issue CLOUDFLARE-MCP-41 Updated in **sentry-mcp-evals**

      **Issue**: Error: Tool list_organizations is already registered
      **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41

      ## Changes Made

      **Assigned To**: Jane Developer → **john.doe**

      ## Current Status

      **Status**: unresolved
      **Assigned To**: john.doe

      # Using this information

      - The issue has been successfully updated in Sentry
      - You can view the issue details using: \`get_issue_details(organizationSlug="sentry-mcp-evals", issueId="CLOUDFLARE-MCP-41")\`
      "
    `);
  });

  it("updates both status and assignment", async () => {
    const result = await updateIssue.handler(
      {
        organizationSlug: "sentry-mcp-evals",
        issueId: "CLOUDFLARE-MCP-41",
        status: "resolved",
        assignedTo: "me",
        issueUrl: undefined,
        regionUrl: null,
      },
      {
        constraints: {
          organizationSlug: undefined,
        },
        accessToken: "access-token",
        userId: "1",
      },
    );
    expect(result).toMatchInlineSnapshot(`
      "# Issue CLOUDFLARE-MCP-41 Updated in **sentry-mcp-evals**

      **Issue**: Error: Tool list_organizations is already registered
      **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41

      ## Changes Made

      **Status**: unresolved → **resolved**
      **Assigned To**: Jane Developer → **You**

      ## Current Status

      **Status**: resolved
      **Assigned To**: me

      # Using this information

      - The issue has been successfully updated in Sentry
      - You can view the issue details using: \`get_issue_details(organizationSlug="sentry-mcp-evals", issueId="CLOUDFLARE-MCP-41")\`
      - The issue is now marked as resolved and will no longer generate alerts
      "
    `);
  });

  it("validates required parameters", async () => {
    await expect(
      updateIssue.handler(
        {
          organizationSlug: undefined,
          issueId: undefined,
          status: undefined,
          assignedTo: undefined,
          issueUrl: undefined,
          regionUrl: null,
        },
        {
          constraints: {
            organizationSlug: undefined,
          },
          accessToken: "access-token",
          userId: "1",
        },
      ),
    ).rejects.toThrow("Either `issueId` or `issueUrl` must be provided");
  });

  it("validates organization slug when using issueId", async () => {
    await expect(
      updateIssue.handler(
        {
          organizationSlug: undefined,
          issueId: "CLOUDFLARE-MCP-41",
          status: "resolved",
          assignedTo: undefined,
          issueUrl: undefined,
          regionUrl: null,
        },
        {
          constraints: {
            organizationSlug: undefined,
          },
          accessToken: "access-token",
          userId: "1",
        },
      ),
    ).rejects.toThrow(
      "`organizationSlug` is required when providing `issueId`",
    );
  });

  it("validates update parameters", async () => {
    await expect(
      updateIssue.handler(
        {
          organizationSlug: "sentry-mcp-evals",
          issueId: "CLOUDFLARE-MCP-41",
          status: undefined,
          assignedTo: undefined,
          issueUrl: undefined,
          regionUrl: null,
        },
        {
          constraints: {
            organizationSlug: undefined,
          },
          accessToken: "access-token",
          userId: "1",
        },
      ),
    ).rejects.toThrow(
      "At least one of `status` or `assignedTo` must be provided to update the issue",
    );
  });
});

```

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

```json
{
  "namespace": "file",
  "description": "Describes file attributes.",
  "attributes": {
    "file.accessed": {
      "description": "Time when the file was last accessed, in ISO 8601 format.\n",
      "type": "string",
      "note": "This attribute might not be supported by some file systems — NFS, FAT32, in embedded OS, etc.\n",
      "stability": "development",
      "examples": ["2021-01-01T12:00:00Z"]
    },
    "file.attributes": {
      "description": "Array of file attributes.\n",
      "type": "string",
      "note": "Attributes names depend on the OS or file system. Here’s a non-exhaustive list of values expected for this attribute: `archive`, `compressed`, `directory`, `encrypted`, `execute`, `hidden`, `immutable`, `journaled`, `read`, `readonly`, `symbolic link`, `system`, `temporary`, `write`.\n",
      "stability": "development",
      "examples": ["[\"readonly\",\"hidden\"]"]
    },
    "file.created": {
      "description": "Time when the file was created, in ISO 8601 format.\n",
      "type": "string",
      "note": "This attribute might not be supported by some file systems — NFS, FAT32, in embedded OS, etc.\n",
      "stability": "development",
      "examples": ["2021-01-01T12:00:00Z"]
    },
    "file.changed": {
      "description": "Time when the file attributes or metadata was last changed, in ISO 8601 format.\n",
      "type": "string",
      "note": "`file.changed` captures the time when any of the file's properties or attributes (including the content) are changed, while `file.modified` captures the timestamp when the file content is modified.\n",
      "stability": "development",
      "examples": ["2021-01-01T12:00:00Z"]
    },
    "file.directory": {
      "description": "Directory where the file is located. It should include the drive letter, when appropriate.\n",
      "type": "string",
      "stability": "development",
      "examples": ["/home/user", "C:\\Program Files\\MyApp"]
    },
    "file.extension": {
      "description": "File extension, excluding the leading dot.\n",
      "type": "string",
      "note": "When the file name has multiple extensions (example.tar.gz), only the last one should be captured (\"gz\", not \"tar.gz\").\n",
      "stability": "development",
      "examples": ["png", "gz"]
    },
    "file.fork_name": {
      "description": "Name of the fork. A fork is additional data associated with a filesystem object.\n",
      "type": "string",
      "note": "On Linux, a resource fork is used to store additional data with a filesystem object. A file always has at least one fork for the data portion, and additional forks may exist.\nOn NTFS, this is analogous to an Alternate Data Stream (ADS), and the default data stream for a file is just called $DATA. Zone.Identifier is commonly used by Windows to track contents downloaded from the Internet. An ADS is typically of the form: C:\\path\\to\\filename.extension:some_fork_name, and some_fork_name is the value that should populate `fork_name`. `filename.extension` should populate `file.name`, and `extension` should populate `file.extension`. The full path, `file.path`, will include the fork name.\n",
      "stability": "development",
      "examples": ["Zone.Identifer"]
    },
    "file.group.id": {
      "description": "Primary Group ID (GID) of the file.\n",
      "type": "string",
      "stability": "development",
      "examples": ["1000"]
    },
    "file.group.name": {
      "description": "Primary group name of the file.\n",
      "type": "string",
      "stability": "development",
      "examples": ["users"]
    },
    "file.inode": {
      "description": "Inode representing the file in the filesystem.\n",
      "type": "string",
      "stability": "development",
      "examples": ["256383"]
    },
    "file.mode": {
      "description": "Mode of the file in octal representation.\n",
      "type": "string",
      "stability": "development",
      "examples": ["0640"]
    },
    "file.modified": {
      "description": "Time when the file content was last modified, in ISO 8601 format.\n",
      "type": "string",
      "stability": "development",
      "examples": ["2021-01-01T12:00:00Z"]
    },
    "file.name": {
      "description": "Name of the file including the extension, without the directory.\n",
      "type": "string",
      "stability": "development",
      "examples": ["example.png"]
    },
    "file.owner.id": {
      "description": "The user ID (UID) or security identifier (SID) of the file owner.\n",
      "type": "string",
      "stability": "development",
      "examples": ["1000"]
    },
    "file.owner.name": {
      "description": "Username of the file owner.\n",
      "type": "string",
      "stability": "development",
      "examples": ["root"]
    },
    "file.path": {
      "description": "Full path to the file, including the file name. It should include the drive letter, when appropriate.\n",
      "type": "string",
      "stability": "development",
      "examples": [
        "/home/alice/example.png",
        "C:\\Program Files\\MyApp\\myapp.exe"
      ]
    },
    "file.size": {
      "description": "File size in bytes.\n",
      "type": "number",
      "stability": "development"
    },
    "file.symbolic_link.target_path": {
      "description": "Path to the target of a symbolic link.\n",
      "type": "string",
      "note": "This attribute is only applicable to symbolic links.\n",
      "stability": "development",
      "examples": ["/usr/bin/python3"]
    }
  }
}

```

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

```json
{
  "namespace": "cloudfoundry",
  "description": "CloudFoundry resource attributes.\n",
  "attributes": {
    "cloudfoundry.system.id": {
      "description": "A guid or another name describing the event source.\n",
      "type": "string",
      "note": "CloudFoundry defines the `source_id` in the [Loggregator v2 envelope](https://github.com/cloudfoundry/loggregator-api#v2-envelope).\nIt is used for logs and metrics emitted by CloudFoundry. It is\nsupposed to contain the component name, e.g. \"gorouter\", for\nCloudFoundry components.\n\nWhen system components are instrumented, values from the\n[Bosh spec](https://bosh.io/docs/jobs/#properties-spec)\nshould be used. The `system.id` should be set to\n`spec.deployment/spec.name`.\n",
      "stability": "development",
      "examples": ["cf/gorouter"]
    },
    "cloudfoundry.system.instance.id": {
      "description": "A guid describing the concrete instance of the event source.\n",
      "type": "string",
      "note": "CloudFoundry defines the `instance_id` in the [Loggregator v2 envelope](https://github.com/cloudfoundry/loggregator-api#v2-envelope).\nIt is used for logs and metrics emitted by CloudFoundry. It is\nsupposed to contain the vm id for CloudFoundry components.\n\nWhen system components are instrumented, values from the\n[Bosh spec](https://bosh.io/docs/jobs/#properties-spec)\nshould be used. The `system.instance.id` should be set to `spec.id`.\n",
      "stability": "development",
      "examples": ["218fc5a9-a5f1-4b54-aa05-46717d0ab26d"]
    },
    "cloudfoundry.app.name": {
      "description": "The name of the application.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.application_name`. This is the same value\nas reported by `cf apps`.\n",
      "stability": "development",
      "examples": ["my-app-name"]
    },
    "cloudfoundry.app.id": {
      "description": "The guid of the application.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.application_id`. This is the same value as\nreported by `cf app <app-name> --guid`.\n",
      "stability": "development",
      "examples": ["218fc5a9-a5f1-4b54-aa05-46717d0ab26d"]
    },
    "cloudfoundry.app.instance.id": {
      "description": "The index of the application instance. 0 when just one instance is active.\n",
      "type": "string",
      "note": "CloudFoundry defines the `instance_id` in the [Loggregator v2 envelope](https://github.com/cloudfoundry/loggregator-api#v2-envelope).\nIt is used for logs and metrics emitted by CloudFoundry. It is\nsupposed to contain the application instance index for applications\ndeployed on the runtime.\n\nApplication instrumentation should use the value from environment\nvariable `CF_INSTANCE_INDEX`.\n",
      "stability": "development",
      "examples": ["0", "1"]
    },
    "cloudfoundry.space.name": {
      "description": "The name of the CloudFoundry space the application is running in.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.space_name`. This is the same value as\nreported by `cf spaces`.\n",
      "stability": "development",
      "examples": ["my-space-name"]
    },
    "cloudfoundry.space.id": {
      "description": "The guid of the CloudFoundry space the application is running in.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.space_id`. This is the same value as\nreported by `cf space <space-name> --guid`.\n",
      "stability": "development",
      "examples": ["218fc5a9-a5f1-4b54-aa05-46717d0ab26d"]
    },
    "cloudfoundry.org.name": {
      "description": "The name of the CloudFoundry organization the app is running in.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.org_name`. This is the same value as\nreported by `cf orgs`.\n",
      "stability": "development",
      "examples": ["my-org-name"]
    },
    "cloudfoundry.org.id": {
      "description": "The guid of the CloudFoundry org the application is running in.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.org_id`. This is the same value as\nreported by `cf org <org-name> --guid`.\n",
      "stability": "development",
      "examples": ["218fc5a9-a5f1-4b54-aa05-46717d0ab26d"]
    },
    "cloudfoundry.process.id": {
      "description": "The UID identifying the process.\n",
      "type": "string",
      "note": "Application instrumentation should use the value from environment\nvariable `VCAP_APPLICATION.process_id`. It is supposed to be equal to\n`VCAP_APPLICATION.app_id` for applications deployed to the runtime.\nFor system components, this could be the actual PID.\n",
      "stability": "development",
      "examples": ["218fc5a9-a5f1-4b54-aa05-46717d0ab26d"]
    },
    "cloudfoundry.process.type": {
      "description": "The type of process.\n",
      "type": "string",
      "note": "CloudFoundry applications can consist of multiple jobs. Usually the\nmain process will be of type `web`. There can be additional background\ntasks or side-cars with different process types.\n",
      "stability": "development",
      "examples": ["web"]
    }
  }
}

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/usecases/search-visual.tsx:
--------------------------------------------------------------------------------

```typescript
"use client";
import { Search, SearchCheck, SearchCode, SearchX } from "lucide-react";
import { useEffect, useRef, useState } from "react";

export default function ErrorListWithCursorFollower() {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const followerRef = useRef<HTMLDivElement | null>(null);
  const rafRef = useRef<number | null>(null);
  const mousePosRef = useRef({ x: 0, y: 0, visible: false });
  const [hoverIdx, setHoverIdx] = useState<number | null>(null);

  useEffect(() => {
    const el = containerRef.current;
    const follower = followerRef.current;
    if (!el || !follower) return;

    const update = () => {
      const { x, y, visible } = mousePosRef.current;
      follower.style.opacity = visible ? "1" : "0";
      follower.style.transform = `translate(${x}px, ${y}px)`;
      rafRef.current = requestAnimationFrame(update);
    };
    rafRef.current = requestAnimationFrame(update);
    return () => {
      if (rafRef.current !== null) {
        cancelAnimationFrame(rafRef.current);
        rafRef.current = null;
      }
    };
  }, []);

  const handleMove: React.MouseEventHandler<HTMLDivElement> = (e) => {
    const el = containerRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const pad = 12;
    let x = e.clientX - rect.left;
    let y = e.clientY - rect.top;
    x = Math.max(pad, Math.min(rect.width - pad, x));
    y = Math.max(pad, Math.min(rect.height - pad, y));
    mousePosRef.current = { x, y, visible: true };
  };

  const handleEnter = () => {
    mousePosRef.current.visible = true;
  };
  const handleLeave = () => {
    mousePosRef.current.visible = false;
    setHoverIdx(null);
  };

  const onEnterRow = (idx: number) => () => setHoverIdx(idx);
  const onLeaveRow = () => setHoverIdx(null);

  const iconBase =
    "absolute inset-0 size-8 stroke-[1px] transition-opacity duration-200 text-white/80";

  return (
    <div
      ref={containerRef}
      onMouseEnter={handleEnter}
      onMouseMove={handleMove}
      onMouseLeave={handleLeave}
      className="relative mb-auto [mask-image:linear-gradient(to_bottom,rgba(0,0,0,1)_60%,rgba(0,0,0,0)_100%)] flex flex-col cursor-none [*]:cursor-none select-none"
    >
      {/* follower (on top) */}
      <div
        ref={followerRef}
        className="pointer-events-none absolute top-0 left-0 z-50 size-16 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white/5 backdrop-blur-sm transition-opacity duration-150 will-change-transform flex items-center justify-center [mask-image:radial-gradient(circle,rgba(0,0,0,1)_40%,rgba(0,0,0,0)_70%)]"
        style={{ opacity: 0 }}
      >
        {/* icon stack wrapper gets remounted on switch to retrigger pop */}
        <div key={hoverIdx ?? -1} className="relative size-8 pop">
          <Search
            className={`${iconBase} ${
              hoverIdx === null || hoverIdx === 0 ? "opacity-100" : "opacity-0"
            }`}
            aria-hidden={!(hoverIdx === null || hoverIdx === 0)}
          />
          <SearchX
            className={`${iconBase} !text-[#fd918f] ${
              hoverIdx === 1 ? "opacity-100" : "opacity-0"
            }`}
            aria-hidden={hoverIdx !== 1}
          />
          <SearchCode
            className={`${iconBase} !text-violet-300 ${
              hoverIdx === 2 ? "opacity-100" : "opacity-0"
            }`}
            aria-hidden={hoverIdx !== 2}
          />
          <SearchCheck
            className={`${iconBase} !text-lime-300 ${
              hoverIdx === 3 ? "opacity-100" : "opacity-0"
            }`}
            aria-hidden={hoverIdx !== 3}
          />
        </div>
      </div>

      {/* rows */}
      <div
        onMouseEnter={onEnterRow(0)}
        onMouseLeave={onLeaveRow}
        className="border-y-[0.5px] border-x h-12 w-full border-white/10 bg-background-2 duration-300 hover:bg-background-3 delay-150 translate-y-4 group-hover:translate-y-0 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] p-4 pb-10 hover:delay-0 hover:duration-0 items-center rounded-t-xl"
      >
        Error&nbsp;<span className="opacity-50">call(components/Checkout)</span>
      </div>
      <div
        onMouseEnter={onEnterRow(1)}
        onMouseLeave={onLeaveRow}
        className="border-y-[0.5px] border-x h-12 w-full border-white/10 bg-background-2 duration-300 hover:bg-background-3 delay-100 translate-y-4 group-hover:translate-y-0 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] p-4 pb-10 hover:delay-0 hover:duration-0 items-center"
      >
        SyntaxError&nbsp;<span className="opacity-50">json([native code])</span>
      </div>
      <div
        onMouseEnter={onEnterRow(2)}
        onMouseLeave={onLeaveRow}
        className="border-y-[0.5px] border-x h-12 w-full border-white/10 bg-background-2 duration-300 hover:bg-background-3 delay-50 translate-y-4 group-hover:translate-y-0 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] p-4 pb-10 hover:delay-0 hover:duration-0 items-center"
      >
        TypeError&nbsp;
        <span className="opacity-50">notAFunctionError(utils/errors)</span>
      </div>
      <div
        onMouseEnter={onEnterRow(3)}
        onMouseLeave={onLeaveRow}
        className="border-y-[0.5px] border-x h-12 w-full border-white/10 bg-background-2 duration-300 hover:bg-background-3 delay-0 translate-y-4 group-hover:translate-y-0 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] p-4 pb-10 hover:delay-0 hover:duration-0 items-center"
      >
        ReferenceError&nbsp;
        <span className="opacity-50">referenceError(utils/errors)</span>
      </div>
    </div>
  );
}

```

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

```typescript
import { useCallback, useMemo } from "react";
import type { Message } from "ai";

const CHAT_STORAGE_KEY = "sentry_chat_messages";
const TIMESTAMP_STORAGE_KEY = "sentry_chat_timestamp";
const MAX_STORED_MESSAGES = 100; // Limit storage size
const CACHE_EXPIRY_MS = 60 * 60 * 1000; // 1 hour in milliseconds

export function usePersistedChat(isAuthenticated: boolean) {
  // Check if cache is expired
  const isCacheExpired = useCallback(() => {
    try {
      const timestampStr = localStorage.getItem(TIMESTAMP_STORAGE_KEY);
      if (!timestampStr) return true;

      const timestamp = Number.parseInt(timestampStr, 10);
      const now = Date.now();
      return now - timestamp > CACHE_EXPIRY_MS;
    } catch {
      return true;
    }
  }, []);

  // Update timestamp to extend cache expiry
  const updateTimestamp = useCallback(() => {
    try {
      localStorage.setItem(TIMESTAMP_STORAGE_KEY, Date.now().toString());
    } catch (error) {
      console.error("Failed to update chat timestamp:", error);
    }
  }, []);

  // Validate a message to ensure it won't cause conversion errors
  const isValidMessage = useCallback((msg: Message): boolean => {
    // Check if message has parts (newer structure)
    if (msg.parts && Array.isArray(msg.parts)) {
      // Check each part for validity
      return msg.parts.every((part) => {
        // Text parts are always valid
        if (part.type === "text") {
          return true;
        }

        // Tool invocation parts must be complete (have result) if state is "call" or "result"
        if (part.type === "tool-invocation") {
          const invocation = part as any;
          // If it's in "call" or "result" state, it must have a result
          if (invocation.state === "call" || invocation.state === "result") {
            const content = invocation.result?.content;
            // Ensure content exists and is not an empty array
            return (
              content && (Array.isArray(content) ? content.length > 0 : true)
            );
          }
          // partial-call state is okay without result
          return true;
        }

        // Other part types are assumed valid
        return true;
      });
    }

    // Check if message has content (legacy structure)
    if (msg.content && typeof msg.content === "string") {
      return msg.content.trim() !== "";
    }

    return false;
  }, []);

  // Load initial messages from localStorage
  const initialMessages = useMemo(() => {
    if (!isAuthenticated) return [];

    // Check if cache is expired
    if (isCacheExpired()) {
      // Clear expired data
      localStorage.removeItem(CHAT_STORAGE_KEY);
      localStorage.removeItem(TIMESTAMP_STORAGE_KEY);
      return [];
    }

    try {
      const stored = localStorage.getItem(CHAT_STORAGE_KEY);
      if (stored) {
        const parsed = JSON.parse(stored) as Message[];
        // Validate the data structure
        if (Array.isArray(parsed) && parsed.length > 0) {
          // Filter out any invalid or incomplete messages
          const validMessages = parsed.filter(isValidMessage);
          if (validMessages.length > 0) {
            // Update timestamp since we're loading existing messages
            updateTimestamp();
            return validMessages;
          }
        }
      }
    } catch (error) {
      console.error("Failed to load chat history:", error);
      // Clear corrupted data
      localStorage.removeItem(CHAT_STORAGE_KEY);
      localStorage.removeItem(TIMESTAMP_STORAGE_KEY);
    }

    return [];
  }, [isAuthenticated, isCacheExpired, updateTimestamp, isValidMessage]);

  // Function to save messages
  const saveMessages = useCallback(
    (messages: Message[]) => {
      if (!isAuthenticated || messages.length === 0) return;

      try {
        // Filter out invalid messages before storing
        const validMessages = messages.filter(isValidMessage);

        // Only store the most recent valid messages to avoid storage limits
        const messagesToStore = validMessages.slice(-MAX_STORED_MESSAGES);

        // Don't save if there are no valid messages
        if (messagesToStore.length === 0) {
          localStorage.removeItem(CHAT_STORAGE_KEY);
          localStorage.removeItem(TIMESTAMP_STORAGE_KEY);
          return;
        }

        localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(messagesToStore));
        // Update timestamp when saving messages (extends expiry)
        updateTimestamp();
      } catch (error) {
        console.error("Failed to save chat history:", error);
        // If we hit storage quota, try to clear old messages
        if (
          error instanceof DOMException &&
          error.name === "QuotaExceededError"
        ) {
          try {
            const validMessages = messages.filter(isValidMessage);
            const recentMessages = validMessages.slice(-50); // Keep only last 50
            localStorage.setItem(
              CHAT_STORAGE_KEY,
              JSON.stringify(recentMessages),
            );
            updateTimestamp();
          } catch {
            // If still failing, clear the storage
            localStorage.removeItem(CHAT_STORAGE_KEY);
            localStorage.removeItem(TIMESTAMP_STORAGE_KEY);
          }
        }
      }
    },
    [isAuthenticated, updateTimestamp, isValidMessage],
  );

  // Clear persisted messages
  const clearPersistedMessages = useCallback(() => {
    localStorage.removeItem(CHAT_STORAGE_KEY);
    localStorage.removeItem(TIMESTAMP_STORAGE_KEY);
  }, []);

  return {
    initialMessages,
    saveMessages,
    clearPersistedMessages,
  };
}

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/client/components/usecases/instrument.tsx:
--------------------------------------------------------------------------------

```typescript
import { ChartSpline, Hammer, Shield } from "lucide-react";
import { SentryIcon } from "../ui/icons/sentry";

export default function Instrument() {
  return (
    <div
      id="connector"
      className="group relative p-4 sm:p-8 lg:border-r max-lg:border-b border-dashed border-white/10 overflow-hidden justify-end flex flex-col"
    >
      <div className="absolute inset-0 pointer-events-none bg-grid [--size:1rem] [mask-image:linear-gradient(to_bottom,red,transparent,red)] group-hover:opacity-50 opacity-30 duration-300 -z-20" />
      <div className="px-12 p-6 mb-auto">
        <div className="flex w-full">
          <div className="rounded-full size-12 my-auto border border-white/20 group-hover:border-white/20 duration-300 border-dashed bg-background-2 group-hover:bg-white relative">
            <div className="absolute top-1/2 left-1/2 -translate-1/2 size-24 scale-50 group-hover:scale-100 duration-500 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] bg-pink-300/20 rounded-full opacity-0 group-hover:opacity-100 -z-10 border border-select/40" />
            <div className="absolute top-1/2 left-1/2 -translate-1/2 size-36 scale-50 group-hover:scale-100 duration-500 group-hover:delay-300 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] bg-purple-400/20 rounded-full opacity-0 group-hover:opacity-100 -z-10 border border-dashed border-purple-400/40" />
          </div>

          <div className="relative flex-1">
            <svg
              viewBox="0 0 100 100"
              preserveAspectRatio="none"
              className="absolute inset-0 h-full w-full block"
              aria-hidden="true"
            >
              {[
                {
                  endY: 25,
                  color: "text-white/30 group-hover:text-white",
                },
                {
                  endY: 50,
                  color: "text-fuchsia-200/40 group-hover:text-select/80",
                },
                {
                  endY: 75,
                  color: "text-purple-200/50 group-hover:text-purple-400/80",
                },
              ].map(({ endY, color }, i) => {
                // All start from one point (0,50) and diverge rightward with wavy curves.
                const d = `
      M 0 50
      C 15 ${45 + (i - 1) * 8},
        25 ${55 - (i - 1) * 10},
        40 ${45 + (i - 1) * 12}
      S 70 ${55 - (i - 1) * 14},
        100 ${endY}
    `;

                return (
                  <path
                    // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
                    key={i}
                    d={d}
                    fill="none"
                    stroke="currentColor"
                    strokeWidth={1}
                    strokeLinecap="round"
                    strokeDasharray="8 8"
                    strokeDashoffset={0}
                    vectorEffect="non-scaling-stroke"
                    className={color}
                  >
                    {/* Right-to-left motion (increase offset) */}
                    <animate
                      attributeName="stroke-dashoffset"
                      from="0"
                      to="160"
                      dur="1.2s"
                      begin="connector.mouseover"
                      end="connector.mouseout"
                      repeatCount="indefinite"
                    />
                  </path>
                );
              })}
            </svg>
          </div>

          <div className="flex flex-col gap-6 justify-center">
            <div className="size-12 rounded-xl bg-white relative">
              <div className="inset-0 size-full absolute grid place-items-center border border-white/20 border-dashed bg-background-2 rounded-xl duration-300 group-hover:border-white/80 group-hover:-translate-y-1 group-hover:ease-[cubic-bezier(0.175,0.885,0.32,1.275)]">
                <SentryIcon className="h-8 w-8 duration-150 group-hover:opacity-100 opacity-50" />
              </div>
            </div>
            <div className="size-12 rounded-xl bg-select relative">
              <div className="inset-0 size-full absolute border border-white/20 border-dashed bg-background-2 rounded-xl duration-300 group-hover:-translate-y-1 group-hover:ease-[cubic-bezier(0.175,0.885,0.32,1.275)] grid place-items-center group-hover:delay-200 group-hover:border-select/80">
                <ChartSpline className="h-8 w-8 duration-150 group-hover:delay-200 group-hover:opacity-100 group-hover:text-select/80 opacity-50" />
              </div>
            </div>
            <div className="size-12 rounded-xl relative bg-purple-400">
              <div className="inset-0 size-full absolute border border-white/20 border-dashed bg-background-2 rounded-xl group-hover:-translate-y-1 group-hover:ease-[cubic-bezier(0.175,0.885,0.32,1.275)] grid place-items-center group-hover:delay-400 duration-300 group-hover:border-purple-400/80">
                <Shield className="h-8 w-8 duration-150 group-hover:delay-400 group-hover:opacity-100 group-hover:text-purple-400/80 opacity-50" />
              </div>
            </div>
          </div>
        </div>
      </div>

      <div className="flex">
        <div className="flex flex-col">
          <h3 className="md:text-xl font-bold">Instrument Your App</h3>
          <p className="text-balance text-white/70">
            Teach your app to overshare. Traces, metrics, errors—finally all
            gossiping in one place.
          </p>
        </div>
        <Hammer className="size-16 ml-auto text-white/20 group-hover:text-white/40 stroke-[0.5px] duration-300 mt-auto" />
      </div>
    </div>
  );
}

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/internal/issue-helpers.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, expect, it } from "vitest";
import {
  extractIssueId,
  parseIssueId,
  parseIssueParams,
} from "./issue-helpers";

describe("extractIssueId", () => {
  it("should extract issue ID from a full Sentry URL", () => {
    expect(
      extractIssueId("https://sentry.sentry.io/issues/1234"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "1234",
        "organizationSlug": "sentry",
      }
    `);
  });

  it("should extract issue ID from a Sentry URL with organization in path", () => {
    expect(
      extractIssueId("https://sentry.io/sentry/issues/123"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "123",
        "organizationSlug": "sentry",
      }
    `);
  });

  it("should extract issue ID and org slug from URL with organizations path", () => {
    expect(
      extractIssueId("https://sentry.io/organizations/my-org/issues/123"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "123",
        "organizationSlug": "my-org",
      }
    `);
  });

  it("should extract issue ID and org slug from subdomain URL", () => {
    expect(extractIssueId("https://my-team.sentry.io/issues/123")).toEqual({
      issueId: "123",
      organizationSlug: "my-team",
    });
  });

  it("should extract issue ID and org slug from self-hosted Sentry with subdomain", () => {
    expect(
      extractIssueId("https://sentry.mycompany.com/issues/123"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "123",
        "organizationSlug": "sentry",
      }
    `);
  });

  it("should extract issue ID and org slug from self-hosted Sentry with organization path", () => {
    expect(
      extractIssueId("https://mycompany.com/my-team/issues/123"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "123",
        "organizationSlug": "my-team",
      }
    `);
  });

  it("should throw error for empty input", () => {
    expect(() => extractIssueId("")).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Invalid Sentry issue URL. URL must be a non-empty string.]`,
    );
  });

  it("should throw error for invalid URL path", () => {
    expect(() =>
      extractIssueId("https://sentry.sentry.io/projects/123"),
    ).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Invalid Sentry issue URL. Path must contain '/issues/{issue_id}']`,
    );
  });

  it("should throw error for non-numeric issue ID in URL", () => {
    expect(
      extractIssueId("https://sentry.sentry.io/issues/abc"),
    ).toMatchInlineSnapshot(`
      {
        "issueId": "abc",
        "organizationSlug": "sentry",
      }
    `);
  });

  it("should throw error for non-numeric standalone ID", () => {
    expect(() => extractIssueId("abc")).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Invalid Sentry issue URL. Must start with http:// or https://]`,
    );
  });
});

describe("parseIssueId", () => {
  describe("cleaning", () => {
    it("should remove trailing punctuation", () => {
      expect(parseIssueId("CLOUDFLARE-MCP-41.!")).toBe("CLOUDFLARE-MCP-41");
    });

    it("should remove special characters except dash and underscore", () => {
      expect(parseIssueId("ID_123-456!@#")).toBe("ID_123-456");
    });
  });

  describe("format validation", () => {
    it("should accept pure numeric issue IDs", () => {
      expect(parseIssueId("12345")).toBe("12345");
    });

    it("should accept project-based IDs starting with letters", () => {
      expect(parseIssueId("PROJECT-123")).toBe("PROJECT-123");
      expect(parseIssueId("MCP-SERVER-E9E")).toBe("MCP-SERVER-E9E");
    });

    it("should accept project-based IDs starting with numbers", () => {
      expect(parseIssueId("3R-3")).toBe("3R-3");
      expect(parseIssueId("3R-AUTOMATION-SYSTEM-3")).toBe(
        "3R-AUTOMATION-SYSTEM-3",
      );
    });

    it("should throw error for invalid formats", () => {
      // Starting with hyphen
      expect(() => parseIssueId("-123")).toThrowError(
        /Invalid issue ID format/,
      );

      // Ending with hyphen
      expect(() => parseIssueId("PROJECT-")).toThrowError(
        /Invalid issue ID format/,
      );

      // Empty string after cleaning
      expect(() => parseIssueId("!!!")).toThrowError(
        /empty after removing special characters/,
      );
    });
  });
});

describe("parseIssueParams", () => {
  it("should parse from issueUrl", () => {
    expect(
      parseIssueParams({
        issueUrl: "https://sentry.io/sentry/issues/123",
      }),
    ).toEqual({ organizationSlug: "sentry", issueId: "123" });
  });

  it("should parse from issueId and organizationSlug", () => {
    expect(
      parseIssueParams({
        issueId: "CLOUDFLARE-MCP-41.!",
        organizationSlug: "sentry-mcp-evals",
      }),
    ).toEqual({
      organizationSlug: "sentry-mcp-evals",
      issueId: "CLOUDFLARE-MCP-41",
    });
  });

  it("should throw if neither issueId nor issueUrl is provided", () => {
    expect(() =>
      parseIssueParams({ organizationSlug: "foo" }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Either issueId or issueUrl must be provided]`,
    );
  });

  it("should throw if organizationSlug is missing and no issueUrl", () => {
    expect(() =>
      parseIssueParams({ issueId: "123" }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Organization slug is required]`,
    );
  });

  it("should throw if issueUrl is invalid", () => {
    expect(() =>
      parseIssueParams({ issueUrl: "not-a-url" }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[UserInputError: Invalid Sentry issue URL. Must start with http:// or https://]`,
    );
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * MCP Handler using createMcpHandler from Cloudflare agents library.
 *
 * Stateless request handling approach:
 * - Uses createMcpHandler to wrap the MCP server
 * - Extracts auth props directly from ExecutionContext (set by OAuth provider)
 * - Context captured in tool handler closures during buildServer()
 * - No session state required - each request is independent
 */

import { createMcpHandler } from "agents/mcp";
import { buildServer } from "@sentry/mcp-core/server";
import { parseSkills } from "@sentry/mcp-core/skills";
import { logWarn } from "@sentry/mcp-core/telem/logging";
import type { ServerContext } from "@sentry/mcp-core/types";
import type { Env } from "../types";
import { verifyConstraintsAccess } from "./constraint-utils";
import type { ExportedHandler } from "@cloudflare/workers-types";

/**
 * ExecutionContext with OAuth props injected by the OAuth provider.
 */
type OAuthExecutionContext = ExecutionContext & {
  props?: Record<string, unknown>;
};

/**
 * Main request handler that:
 * 1. Extracts auth props from ExecutionContext
 * 2. Parses org/project constraints from URL
 * 3. Verifies user has access to the constraints
 * 4. Builds complete ServerContext
 * 5. Creates and configures MCP server per-request (context captured in closures)
 * 6. Runs MCP handler
 */
const mcpHandler: ExportedHandler<Env> = {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
    const url = new URL(request.url);

    // Parse constraints from URL pattern /mcp/:org?/:project?
    const pattern = new URLPattern({ pathname: "/mcp/:org?/:project?" });
    const result = pattern.exec(url);

    if (!result) {
      return new Response("Not found", { status: 404 });
    }

    const { groups } = result.pathname;
    const organizationSlug = groups?.org || null;
    const projectSlug = groups?.project || null;

    // Check for agent mode query parameter
    const isAgentMode = url.searchParams.get("agent") === "1";

    // Extract OAuth props from ExecutionContext (set by OAuth provider)
    const oauthCtx = ctx as OAuthExecutionContext;

    if (!oauthCtx.props) {
      throw new Error("No authentication context available");
    }

    const sentryHost = env.SENTRY_HOST || "sentry.io";

    // Verify user has access to the requested org/project
    const verification = await verifyConstraintsAccess(
      { organizationSlug, projectSlug },
      {
        accessToken: oauthCtx.props.accessToken as string,
        sentryHost,
      },
    );

    if (!verification.ok) {
      return new Response(verification.message, {
        status: verification.status ?? 500,
      });
    }

    // Parse and validate granted skills (primary authorization method)
    // Legacy tokens without grantedSkills are no longer supported
    if (!oauthCtx.props.grantedSkills) {
      const userId = oauthCtx.props.id as string;
      const clientId = oauthCtx.props.clientId as string;

      logWarn("Legacy token without grantedSkills detected - revoking grant", {
        loggerScope: ["cloudflare", "mcp-handler"],
        extra: { clientId, userId },
      });

      // Revoke the grant in the background (don't block the response)
      ctx.waitUntil(
        (async () => {
          try {
            // Find the grant for this user/client combination
            const grants = await env.OAUTH_PROVIDER.listUserGrants(userId);
            const grant = grants.items.find((g) => g.clientId === clientId);

            if (grant) {
              await env.OAUTH_PROVIDER.revokeGrant(grant.id, userId);
            }
          } catch (err) {
            logWarn("Failed to revoke legacy grant", {
              loggerScope: ["cloudflare", "mcp-handler"],
              extra: { error: String(err), clientId, userId },
            });
          }
        })(),
      );

      return new Response(
        "Your authorization has expired. Please re-authorize to continue using Sentry MCP.",
        {
          status: 401,
          headers: {
            "WWW-Authenticate":
              'Bearer realm="Sentry MCP", error="invalid_token", error_description="Token requires re-authorization"',
          },
        },
      );
    }

    const { valid: validSkills, invalid: invalidSkills } = parseSkills(
      oauthCtx.props.grantedSkills as string[],
    );

    if (invalidSkills.length > 0) {
      logWarn("Ignoring invalid skills from OAuth provider", {
        loggerScope: ["cloudflare", "mcp-handler"],
        extra: {
          invalidSkills,
        },
      });
    }

    // Validate that at least one valid skill was granted
    if (validSkills.size === 0) {
      return new Response(
        "Authorization failed: No valid skills were granted. Please re-authorize and select at least one permission.",
        { status: 400 },
      );
    }

    // Build complete ServerContext from OAuth props + verified constraints
    const serverContext: ServerContext = {
      userId: oauthCtx.props.id as string | undefined,
      clientId: oauthCtx.props.clientId as string,
      accessToken: oauthCtx.props.accessToken as string,
      grantedSkills: validSkills,
      constraints: verification.constraints,
      sentryHost,
      mcpUrl: env.MCP_URL,
    };

    // Create and configure MCP server with tools filtered by context
    // Context is captured in tool handler closures during buildServer()
    const server = buildServer({
      context: serverContext,
      agentMode: isAgentMode,
    });

    // Run MCP handler - context already captured in closures
    return createMcpHandler(server, {
      route: url.pathname,
    })(request, env, ctx);
  },
};

export default mcpHandler;

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/search-events/utils.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import type { SentryApiService } from "../../api-client";
import { agentTool } from "../../internal/agents/tools/utils";

// Type for flexible event data that can contain any fields
export type FlexibleEventData = Record<string, unknown>;

// Helper to safely get a string value from event data
export function getStringValue(
  event: FlexibleEventData,
  key: string,
  defaultValue = "",
): string {
  const value = event[key];
  return typeof value === "string" ? value : defaultValue;
}

// Helper to safely get a number value from event data
export function getNumberValue(
  event: FlexibleEventData,
  key: string,
): number | undefined {
  const value = event[key];
  return typeof value === "number" ? value : undefined;
}

// Helper to check if fields contain aggregate functions
export function isAggregateQuery(fields: string[]): boolean {
  return fields.some((field) => field.includes("(") && field.includes(")"));
}

// Helper function to fetch custom attributes for a dataset
export async function fetchCustomAttributes(
  apiService: SentryApiService,
  organizationSlug: string,
  dataset: "errors" | "logs" | "spans",
  projectId?: string,
  timeParams?: { statsPeriod?: string; start?: string; end?: string },
): Promise<{
  attributes: Record<string, string>;
  fieldTypes: Record<string, "string" | "number">;
}> {
  const customAttributes: Record<string, string> = {};
  const fieldTypes: Record<string, "string" | "number"> = {};

  if (dataset === "errors") {
    // TODO: For errors dataset, we currently need to use the old listTags API
    // This will be updated in the future to use the new trace-items attributes API
    const tagsResponse = await apiService.listTags({
      organizationSlug,
      dataset: "events",
      project: projectId,
      statsPeriod: "14d",
      useCache: true,
      useFlagsBackend: true,
    });

    for (const tag of tagsResponse) {
      if (tag.key && !tag.key.startsWith("sentry:")) {
        customAttributes[tag.key] = tag.name || tag.key;
      }
    }
  } else {
    // For logs and spans datasets, use the trace-items attributes endpoint
    const itemType = dataset === "logs" ? "logs" : "spans";
    const attributesResponse = await apiService.listTraceItemAttributes({
      organizationSlug,
      itemType,
      project: projectId,
      statsPeriod: "14d",
    });

    for (const attr of attributesResponse) {
      if (attr.key && !attr.key.startsWith("sentry:")) {
        customAttributes[attr.key] = attr.name || attr.key;
        // Track field type from the attribute response with validation
        if (attr.type && (attr.type === "string" || attr.type === "number")) {
          fieldTypes[attr.key] = attr.type;
        }
      }
    }
  }

  return { attributes: customAttributes, fieldTypes };
}

/**
 * Create a tool for the agent to query available attributes by dataset
 * The tool is pre-bound with the API service and organization configured for the appropriate region
 */
export function createDatasetAttributesTool(options: {
  apiService: SentryApiService;
  organizationSlug: string;
  projectId?: string;
}) {
  const { apiService, organizationSlug, projectId } = options;
  return agentTool({
    description:
      "Query available attributes and fields for a specific Sentry dataset to understand what data is available",
    parameters: z.object({
      dataset: z
        .enum(["spans", "errors", "logs"])
        .describe("The dataset to query attributes for"),
    }),
    execute: async ({ dataset }) => {
      const {
        BASE_COMMON_FIELDS,
        DATASET_FIELDS,
        RECOMMENDED_FIELDS,
        NUMERIC_FIELDS,
      } = await import("./config");

      // Get custom attributes for this dataset
      // IMPORTANT: Let ALL errors bubble up to wrapAgentToolExecute
      // UserInputError will be converted to error string for the AI agent
      // Other errors will bubble up to be captured by Sentry
      const { attributes: customAttributes, fieldTypes } =
        await fetchCustomAttributes(
          apiService,
          organizationSlug,
          dataset,
          projectId,
        );

      // Combine all available fields
      const allFields = {
        ...BASE_COMMON_FIELDS,
        ...DATASET_FIELDS[dataset],
        ...customAttributes,
      };

      const recommendedFields = RECOMMENDED_FIELDS[dataset];

      // Combine field types from both static config and dynamic API
      const allFieldTypes = { ...fieldTypes };
      const staticNumericFields = NUMERIC_FIELDS[dataset] || new Set();
      for (const field of staticNumericFields) {
        allFieldTypes[field] = "number";
      }

      return `Dataset: ${dataset}

Available Fields (${Object.keys(allFields).length} total):
${Object.entries(allFields)
  .slice(0, 50) // Limit to first 50 to avoid overwhelming the agent
  .map(([key, desc]) => `- ${key}: ${desc}`)
  .join("\n")}
${Object.keys(allFields).length > 50 ? `\n... and ${Object.keys(allFields).length - 50} more fields` : ""}

Recommended Fields for ${dataset}:
${recommendedFields.basic.map((f) => `- ${f}`).join("\n")}

Field Types (CRITICAL for aggregate functions):
${Object.entries(allFieldTypes)
  .slice(0, 30) // Show more field types since this is critical for validation
  .map(([key, type]) => `- ${key}: ${type}`)
  .join("\n")}
${Object.keys(allFieldTypes).length > 30 ? `\n... and ${Object.keys(allFieldTypes).length - 30} more fields` : ""}

IMPORTANT: Only use numeric aggregate functions (avg, sum, min, max, percentiles) with numeric fields. Use count() or count_unique() for non-numeric fields.

Use this information to construct appropriate queries for the ${dataset} dataset.`;
    },
  });
}

```

--------------------------------------------------------------------------------
/docs/common-patterns.md:
--------------------------------------------------------------------------------

```markdown
# Common Patterns

Reusable patterns used throughout the Sentry MCP codebase. Reference these instead of duplicating.

## Error Handling

### UserInputError Pattern

For invalid user input that needs clear feedback:

```typescript
if (!params.organizationSlug) {
  throw new UserInputError(
    "Organization slug is required. Please provide an organizationSlug parameter. " +
    "You can find available organizations using the `find_organizations()` tool."
  );
}
```

See implementation: `packages/mcp-server/src/errors.ts`

### API Error Wrapping

When external API calls fail:

```typescript
try {
  const data = await apiService.issues.list(params);
  return data;
} catch (error) {
  throw new Error(`Failed to fetch issues: ${error.message}`);
}
```

### Error Message Transformation

Make error messages LLM-friendly:

```typescript
if (message.includes("You do not have the multi project stream feature enabled")) {
  return "You do not have access to query across multiple projects. Please select a project for your query.";
}
```

## Zod Schema Patterns

### Reusable Parameter Schemas

Define once, use everywhere:

```typescript
export const ParamOrganizationSlug = z
  .string()
  .trim()
  .describe("The organization's slug. You can find a list using the `find_organizations()` tool.");

export const ParamRegionUrl = z
  .string()
  .url()
  .optional()
  .describe("Sentry region URL. If not provided, uses default region.");
```

See: `packages/mcp-server/src/schema.ts`

### Flexible Schema Patterns

```typescript
// Support multiple ID formats
z.union([z.string(), z.number()])

// Optional with transforms
z.string().optional().transform(val => val?.trim())

// Partial objects with passthrough
IssueSchema.partial().passthrough()
```

### Type Derivation

```typescript
export type Organization = z.infer<typeof OrganizationSchema>;
export type ToolParams<T> = z.infer<typeof toolDefinitions[T].parameters>;
```

## Testing Patterns

For comprehensive testing guidance, see `testing.md` and `adding-tools.md#step-3-add-tests`.

### Unit Test Structure

```typescript
describe("tool_name", () => {
  it("returns formatted output", async () => {
    const result = await TOOL_HANDLERS.tool_name(mockContext, {
      organizationSlug: "test-org",
    });
    
    expect(result).toMatchInlineSnapshot(`
      "# Results in **test-org**
      
      Expected formatted output here"
    `);
  });
});
```

### Snapshot Updates

When tool output changes:

```bash
cd packages/mcp-server
pnpm vitest --run -u
```

### Mock Server Setup

```typescript
beforeAll(() => mswServer.listen());
afterEach(() => mswServer.resetHandlers());
afterAll(() => mswServer.close());
```

See: `packages/mcp-server/src/test-utils/setup.ts`

## API Patterns

For complete API usage patterns, see `api-patterns.md`.

### Service Creation

```typescript
const apiService = apiServiceFromContext(context, {
  regionUrl: params.regionUrl,
});
```

See: `packages/mcp-server/src/api-utils.ts:apiServiceFromContext`

### Multi-Region Support

```typescript
if (opts.regionUrl) {
  try {
    host = new URL(opts.regionUrl).host;
  } catch (error) {
    throw new UserInputError(
      `Invalid regionUrl provided: ${opts.regionUrl}. Must be a valid URL.`
    );
  }
}
```

## Response Formatting

### Markdown Structure

```typescript
let output = `# ${title}\n\n`;

// Handle empty results
if (data.length === 0) {
  output += "No results found.\n";
  return output;
}

// Add data sections
output += "## Section\n";
output += formatData(data);

// Add usage instructions
output += "\n\n# Using this information\n\n";
output += "- Next steps...\n";
```

### Multi-Content Resources

```typescript
return {
  contents: [
    {
      uri: url.toString(),
      mimeType: "application/json",
      text: JSON.stringify(data, null, 2)
    }
  ]
};
```

## Parameter Validation

### Required Parameters

```typescript
if (!params.requiredParam) {
  throw new UserInputError(
    "Required parameter is missing. Please provide requiredParam."
  );
}
```

### Multiple Options

```typescript
if (params.issueUrl) {
  // Extract from URL
} else if (params.organizationSlug && params.issueId) {
  // Use direct parameters
} else {
  throw new UserInputError(
    "Either issueUrl or both organizationSlug and issueId must be provided"
  );
}
```

## Mock Patterns

### Basic Handler

```typescript
{
  method: "get",
  path: "/api/0/organizations/:orgSlug/issues/",
  fetch: ({ params }) => {
    return HttpResponse.json(issueListFixture);
  },
}
```

### Request Validation

```typescript
fetch: ({ request, params }) => {
  const url = new URL(request.url);
  const sort = url.searchParams.get("sort");
  
  if (sort && !["date", "freq", "new"].includes(sort)) {
    return HttpResponse.json("Invalid sort parameter", { status: 400 });
  }
  
  return HttpResponse.json(data);
}
```

See: `packages/mcp-server-mocks/src/handlers/`

## Quality Checks

Required before any commit:

```bash
pnpm -w run lint:fix    # Fix linting issues
pnpm tsc --noEmit       # TypeScript type checking
pnpm test               # Run all tests
```

## TypeScript Helpers

### Generic Type Utilities

```typescript
// Extract Zod schema types from records
type ZodifyRecord<T extends Record<string, any>> = {
  [K in keyof T]: z.infer<T[K]>;
};

// Const assertions for literal types
export const TOOL_NAMES = ["tool1", "tool2"] as const;
export type ToolName = typeof TOOL_NAMES[number];
```

## References

- Error handling: `packages/mcp-server/src/errors.ts`
- Schema definitions: `packages/mcp-server/src/schema.ts`
- API utilities: `packages/mcp-server/src/api-utils.ts`
- Test setup: `packages/mcp-server/src/test-utils/`
- Mock handlers: `packages/mcp-server-mocks/src/handlers/`
```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/use-sentry/tool-wrapper.test.ts:
--------------------------------------------------------------------------------

```typescript
import { describe, it, expect } from "vitest";
import { z } from "zod";
import { wrapToolForAgent } from "./tool-wrapper";
import type { ServerContext } from "../../types";
import type { ToolConfig } from "../types";

// Create a simple mock tool for testing
const mockTool: ToolConfig<{
  organizationSlug: z.ZodOptional<z.ZodString>;
  projectSlug: z.ZodOptional<z.ZodString>;
  someParam: z.ZodString;
}> = {
  name: "mock_tool",
  description: "A mock tool for testing",
  inputSchema: {
    organizationSlug: z.string().optional(), // Optional to test constraint injection
    projectSlug: z.string().optional(),
    someParam: z.string(),
  },
  skills: [], // Required by ToolConfig
  requiredScopes: [],
  annotations: {
    readOnlyHint: true,
    openWorldHint: true,
  },
  handler: async (params, context) => {
    // Return the params so we can verify what was passed
    return JSON.stringify({
      params,
      contextOrg: context.constraints.organizationSlug,
      contextProject: context.constraints.projectSlug,
    });
  },
};

describe("wrapToolForAgent", () => {
  it("wraps a tool and calls it successfully", async () => {
    const context: ServerContext = {
      accessToken: "test-token",
      sentryHost: "sentry.io",
      userId: "1",
      clientId: "test-client",
      constraints: {},
    };

    const wrappedTool = wrapToolForAgent(mockTool, { context });

    // Call the wrapped tool
    // AI SDK tools expect a toolContext parameter (messages, abortSignal, etc.)
    const result = await wrappedTool.execute(
      {
        organizationSlug: "my-org",
        someParam: "test-value",
      },
      {} as any, // Empty tool context for testing
    );

    // Verify the tool was called with correct params
    expect(result.result).toBeDefined();
    const parsed = JSON.parse(result.result as string);
    expect(parsed.params.organizationSlug).toBe("my-org");
    expect(parsed.params.someParam).toBe("test-value");
  });

  it("injects organizationSlug constraint", async () => {
    const context: ServerContext = {
      accessToken: "test-token",
      sentryHost: "sentry.io",
      userId: "1",
      clientId: "test-client",
      constraints: {
        organizationSlug: "constrained-org",
      },
    };

    const wrappedTool = wrapToolForAgent(mockTool, { context });

    // Call without providing organizationSlug
    const result = await wrappedTool.execute(
      {
        someParam: "test-value",
      },
      {} as any,
    );

    // Verify the constraint was injected
    expect(result.result).toBeDefined();
    const parsed = JSON.parse(result.result as string);
    expect(parsed.params.organizationSlug).toBe("constrained-org");
    expect(parsed.contextOrg).toBe("constrained-org");
  });

  it("injects projectSlug constraint", async () => {
    const context: ServerContext = {
      accessToken: "test-token",
      sentryHost: "sentry.io",
      userId: "1",
      clientId: "test-client",
      constraints: {
        organizationSlug: "constrained-org",
        projectSlug: "constrained-project",
      },
    };

    const wrappedTool = wrapToolForAgent(mockTool, { context });

    // Call without providing projectSlug
    const result = await wrappedTool.execute(
      {
        someParam: "test-value",
      },
      {} as any,
    );

    // Verify both constraints were injected
    expect(result.result).toBeDefined();
    const parsed = JSON.parse(result.result as string);
    expect(parsed.params.organizationSlug).toBe("constrained-org");
    expect(parsed.params.projectSlug).toBe("constrained-project");
    expect(parsed.contextProject).toBe("constrained-project");
  });

  it("allows agent-provided params to override constraints", async () => {
    const context: ServerContext = {
      accessToken: "test-token",
      sentryHost: "sentry.io",
      userId: "1",
      clientId: "test-client",
      constraints: {
        organizationSlug: "constrained-org",
      },
    };

    const wrappedTool = wrapToolForAgent(mockTool, { context });

    // Provide organizationSlug explicitly (should NOT override since constraint injection doesn't override)
    const result = await wrappedTool.execute(
      {
        organizationSlug: "agent-provided-org",
        someParam: "test-value",
      },
      {} as any,
    );

    expect(result.result).toBeDefined();
    const parsed = JSON.parse(result.result as string);
    // The constraint injection only adds if not present, so agent's value should remain
    expect(parsed.params.organizationSlug).toBe("agent-provided-org");
  });

  it("handles tool errors via agentTool wrapper", async () => {
    const errorTool: ToolConfig<{ param: z.ZodString }> = {
      name: "error_tool",
      description: "A tool that throws an error",
      inputSchema: {
        param: z.string(),
      },
      skills: [], // Required by ToolConfig
      requiredScopes: [],
      annotations: {
        readOnlyHint: true,
        openWorldHint: true,
      },
      handler: async () => {
        throw new Error("Test error from tool");
      },
    };

    const context: ServerContext = {
      accessToken: "test-token",
      sentryHost: "sentry.io",
      userId: "1",
      clientId: "test-client",
      constraints: {},
    };

    const wrappedTool = wrapToolForAgent(errorTool, { context });

    // Call the tool that throws an error
    const result = await wrappedTool.execute({ param: "test" }, {} as any);

    // Verify the error was caught and returned in structured format
    // Generic errors are wrapped as "System Error" by agentTool for security
    expect(result.error).toBeDefined();
    expect(result.error).toContain("System Error");
    expect(result.error).toContain("Event ID:");
    expect(result.result).toBeUndefined();
  });
});

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/update-issue.ts:
--------------------------------------------------------------------------------

```typescript
import { setTag } from "@sentry/core";
import { defineTool } from "../internal/tool-helpers/define";
import { apiServiceFromContext } from "../internal/tool-helpers/api";
import { parseIssueParams } from "../internal/tool-helpers/issue";
import { formatAssignedTo } from "../internal/tool-helpers/formatting";
import { UserInputError } from "../errors";
import type { ServerContext } from "../types";
import {
  ParamOrganizationSlug,
  ParamRegionUrl,
  ParamIssueShortId,
  ParamIssueUrl,
  ParamIssueStatus,
  ParamAssignedTo,
} from "../schema";

export default defineTool({
  name: "update_issue",
  skills: ["triage"], // Only available in triage skill
  requiredScopes: ["event:write"],
  description: [
    "Update an issue's status or assignment in Sentry. This allows you to resolve, ignore, or reassign issues.",
    "",
    "Use this tool when you need to:",
    "- Resolve an issue that has been fixed",
    "- Assign an issue to a team member or team for investigation",
    "- Mark an issue as ignored to reduce noise",
    "- Reopen a resolved issue by setting status to 'unresolved'",
    "",
    "<examples>",
    "### Resolve an issue",
    "",
    "```",
    "update_issue(organizationSlug='my-organization', issueId='PROJECT-123', status='resolved')",
    "```",
    "",
    "### Assign an issue to a user (use whoami to get your user ID)",
    "",
    "```",
    "update_issue(organizationSlug='my-organization', issueId='PROJECT-123', assignedTo='user:123456')",
    "```",
    "",
    "### Assign an issue to a team",
    "",
    "```",
    "update_issue(organizationSlug='my-organization', issueId='PROJECT-123', assignedTo='team:789')",
    "```",
    "",
    "### Mark an issue as ignored",
    "",
    "```",
    "update_issue(organizationSlug='my-organization', issueId='PROJECT-123', status='ignored')",
    "```",
    "",
    "</examples>",
    "",
    "<hints>",
    "- If the user provides the `issueUrl`, you can ignore the other required parameters and extract them from the URL.",
    "- At least one of `status` or `assignedTo` must be provided to update the issue.",
    "- assignedTo format: Use 'user:ID' for users (e.g., 'user:123456') or 'team:ID' for teams (e.g., 'team:789')",
    "- To find your user ID, first use the whoami tool which returns your numeric user ID",
    "- Valid status values are: 'resolved', 'resolvedInNextRelease', 'unresolved', 'ignored'.",
    "</hints>",
  ].join("\n"),
  inputSchema: {
    organizationSlug: ParamOrganizationSlug.optional(),
    regionUrl: ParamRegionUrl.nullable().default(null),
    issueId: ParamIssueShortId.optional(),
    issueUrl: ParamIssueUrl.optional(),
    status: ParamIssueStatus.optional(),
    assignedTo: ParamAssignedTo.optional(),
  },
  annotations: {
    readOnlyHint: false,
    destructiveHint: true,
    idempotentHint: true,
    openWorldHint: true,
  },
  async handler(params, context: ServerContext) {
    const apiService = apiServiceFromContext(context, {
      regionUrl: params.regionUrl ?? undefined,
    });

    // Validate that we have the minimum required parameters
    if (!params.issueUrl && !params.issueId) {
      throw new UserInputError(
        "Either `issueId` or `issueUrl` must be provided",
      );
    }

    if (!params.issueUrl && !params.organizationSlug) {
      throw new UserInputError(
        "`organizationSlug` is required when providing `issueId`",
      );
    }

    // Validate that at least one update parameter is provided
    if (!params.status && !params.assignedTo) {
      throw new UserInputError(
        "At least one of `status` or `assignedTo` must be provided to update the issue",
      );
    }

    const { organizationSlug: orgSlug, issueId: parsedIssueId } =
      parseIssueParams({
        organizationSlug: params.organizationSlug,
        issueId: params.issueId,
        issueUrl: params.issueUrl,
      });

    setTag("organization.slug", orgSlug);

    // Get current issue details first
    const currentIssue = await apiService.getIssue({
      organizationSlug: orgSlug,
      issueId: parsedIssueId!,
    });

    // Update the issue
    const updatedIssue = await apiService.updateIssue({
      organizationSlug: orgSlug,
      issueId: parsedIssueId!,
      status: params.status,
      assignedTo: params.assignedTo,
    });

    let output = `# Issue ${updatedIssue.shortId} Updated in **${orgSlug}**\n\n`;
    output += `**Issue**: ${updatedIssue.title}\n`;
    output += `**URL**: ${apiService.getIssueUrl(orgSlug, updatedIssue.shortId)}\n\n`;

    // Show what changed
    output += "## Changes Made\n\n";

    if (params.status && currentIssue.status !== params.status) {
      output += `**Status**: ${currentIssue.status} → **${params.status}**\n`;
    }

    if (params.assignedTo) {
      const oldAssignee = formatAssignedTo(currentIssue.assignedTo ?? null);
      const newAssignee =
        params.assignedTo === "me" ? "You" : params.assignedTo;
      output += `**Assigned To**: ${oldAssignee} → **${newAssignee}**\n`;
    }

    output += "\n## Current Status\n\n";
    output += `**Status**: ${updatedIssue.status}\n`;
    const currentAssignee = formatAssignedTo(updatedIssue.assignedTo ?? null);
    output += `**Assigned To**: ${currentAssignee}\n`;

    output += "\n# Using this information\n\n";
    output += `- The issue has been successfully updated in Sentry\n`;
    output += `- You can view the issue details using: \`get_issue_details(organizationSlug="${orgSlug}", issueId="${updatedIssue.shortId}")\`\n`;

    if (params.status === "resolved") {
      output += `- The issue is now marked as resolved and will no longer generate alerts\n`;
    } else if (params.status === "ignored") {
      output += `- The issue is now ignored and will not generate alerts until it escalates\n`;
    }

    return output;
  },
});

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts:
--------------------------------------------------------------------------------

```typescript
import { Hono } from "hono";
import type { AuthRequest } from "@cloudflare/workers-oauth-provider";
import {
  renderApprovalDialog,
  parseRedirectApproval,
} from "../../lib/approval-dialog";
import type { Env } from "../../types";
import { SENTRY_AUTH_URL } from "../constants";
import { getUpstreamAuthorizeUrl } from "../helpers";
import { SCOPES } from "../../../constants";
import { signState, type OAuthState } from "../state";
import { logWarn } from "@sentry/mcp-core/telem/logging";

/**
 * Extended AuthRequest that includes skills
 */
interface AuthRequestWithSkills extends AuthRequest {
  skills?: unknown;
}

async function redirectToUpstream(
  env: Env,
  request: Request,
  oauthReqInfo: AuthRequest | AuthRequestWithSkills,
  headers: Record<string, string> = {},
  stateOverride?: string,
) {
  return new Response(null, {
    status: 302,
    headers: {
      ...headers,
      location: getUpstreamAuthorizeUrl({
        upstream_url: new URL(
          SENTRY_AUTH_URL,
          `https://${env.SENTRY_HOST || "sentry.io"}`,
        ).href,
        scope: Object.keys(SCOPES).join(" "),
        client_id: env.SENTRY_CLIENT_ID,
        redirect_uri: new URL("/oauth/callback", request.url).href,
        state: stateOverride ?? btoa(JSON.stringify(oauthReqInfo)),
      }),
    },
  });
}

// Export Hono app for /authorize endpoints
export default new Hono<{ Bindings: Env }>()
  /**
   * OAuth Authorization Endpoint (GET /oauth/authorize)
   *
   * This route initiates the OAuth flow when a user wants to log in.
   */
  .get("/", async (c) => {
    let oauthReqInfo: AuthRequest;
    try {
      oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw);
    } catch (err) {
      // Log invalid redirect URI errors without sending them to Sentry
      const errorMessage = err instanceof Error ? err.message : String(err);
      if (errorMessage.includes("Invalid redirect URI")) {
        logWarn(`OAuth authorization failed: ${errorMessage}`, {
          loggerScope: ["cloudflare", "oauth", "authorize"],
          extra: { error: errorMessage },
        });
        return c.text("Invalid redirect URI", 400);
      }
      // Re-throw other errors to be captured by Sentry
      throw err;
    }

    const { clientId } = oauthReqInfo;
    if (!clientId) {
      return c.text("Invalid request", 400);
    }

    // XXX(dcramer): we want to confirm permissions on each time
    // so you can always choose new ones
    // This shouldn't be highly visible to users, as clients should use refresh tokens
    // behind the scenes.
    //
    // because we share a clientId with the upstream provider, we need to ensure that the
    // downstream client has been approved by the end-user (e.g. for a new client)
    // https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/265
    // const isApproved = await clientIdAlreadyApproved(
    //   c.req.raw,
    //   clientId,
    //   c.env.COOKIE_SECRET,
    // );
    // if (isApproved) {
    //   return redirectToUpstream(c.env, c.req.raw, oauthReqInfo);
    // }

    return await renderApprovalDialog(c.req.raw, {
      client: await c.env.OAUTH_PROVIDER.lookupClient(clientId),
      server: {
        name: "Sentry MCP",
      },
      state: { oauthReqInfo }, // arbitrary data that flows through the form submission below
      cookieSecret: c.env.COOKIE_SECRET,
    });
  })

  /**
   * OAuth Authorization Endpoint (POST /oauth/authorize)
   *
   * This route handles the approval form submission and redirects to Sentry.
   */
  .post("/", async (c) => {
    // Validates form submission, extracts state, and generates Set-Cookie headers to skip approval dialog next time
    let result: Awaited<ReturnType<typeof parseRedirectApproval>>;
    try {
      result = await parseRedirectApproval(c.req.raw, c.env.COOKIE_SECRET);
    } catch (err) {
      logWarn("Failed to parse approval form", {
        loggerScope: ["cloudflare", "oauth", "authorize"],
        extra: { error: String(err) },
      });
      return c.text("Invalid request", 400);
    }

    const { state, headers, skills } = result;

    if (!state.oauthReqInfo) {
      return c.text("Invalid request", 400);
    }

    // Store the selected skills in the OAuth request info
    // This will be passed through to the callback via the state parameter
    const oauthReqWithSkills = {
      ...state.oauthReqInfo,
      skills,
    };

    // Validate redirectUri is registered for this client before proceeding
    try {
      const client = await c.env.OAUTH_PROVIDER.lookupClient(
        oauthReqWithSkills.clientId,
      );
      const uriIsAllowed =
        Array.isArray(client?.redirectUris) &&
        client.redirectUris.includes(oauthReqWithSkills.redirectUri);
      if (!uriIsAllowed) {
        logWarn("Redirect URI not registered for client", {
          loggerScope: ["cloudflare", "oauth", "authorize"],
          extra: {
            clientId: oauthReqWithSkills.clientId,
            redirectUri: oauthReqWithSkills.redirectUri,
          },
        });
        return c.text("Invalid redirect URI", 400);
      }
    } catch (lookupErr) {
      logWarn("Failed to validate client redirect URI", {
        loggerScope: ["cloudflare", "oauth", "authorize"],
        extra: { error: String(lookupErr) },
      });
      return c.text("Invalid request", 400);
    }

    // Build signed state for redirect to Sentry (10 minute validity)
    const now = Date.now();
    const payload: OAuthState = {
      req: oauthReqWithSkills as unknown as Record<string, unknown>,
      iat: now,
      exp: now + 10 * 60 * 1000,
    };
    const signedState = await signState(payload, c.env.COOKIE_SECRET);

    return redirectToUpstream(
      c.env,
      c.req.raw,
      oauthReqWithSkills,
      headers,
      signedState,
    );
  });

```

--------------------------------------------------------------------------------
/packages/mcp-core/src/tools/update-project.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 { logIssue } from "../telem/logging";
import { UserInputError } from "../errors";
import type { ServerContext } from "../types";
import type { Project } from "../api-client/index";
import {
  ParamOrganizationSlug,
  ParamRegionUrl,
  ParamProjectSlug,
  ParamPlatform,
  ParamTeamSlug,
} from "../schema";

export default defineTool({
  name: "update_project",
  skills: ["project-management"], // Only available in project-management skill
  requiredScopes: ["project:write"],
  description: [
    "Update project settings in Sentry, such as name, slug, platform, and team assignment.",
    "",
    "Be careful when using this tool!",
    "",
    "Use this tool when you need to:",
    "- Update a project's name or slug to fix onboarding mistakes",
    "- Change the platform assigned to a project",
    "- Update team assignment for a project",
    "",
    "<examples>",
    "### Update a project's name and slug",
    "",
    "```",
    "update_project(organizationSlug='my-organization', projectSlug='old-project', name='New Project Name', slug='new-project-slug')",
    "```",
    "",
    "### Assign a project to a different team",
    "",
    "```",
    "update_project(organizationSlug='my-organization', projectSlug='my-project', teamSlug='backend-team')",
    "```",
    "",
    "### Update platform",
    "",
    "```",
    "update_project(organizationSlug='my-organization', projectSlug='my-project', platform='python')",
    "```",
    "",
    "</examples>",
    "",
    "<hints>",
    "- If the user passes a parameter in the form of name/otherName, it's likely in the format of <organizationSlug>/<projectSlug>.",
    "- Team assignment is handled separately from other project settings",
    "- If any parameter is ambiguous, you should clarify with the user what they meant.",
    "- When updating the slug, the project will be accessible at the new slug after the update",
    "</hints>",
  ].join("\n"),
  inputSchema: {
    organizationSlug: ParamOrganizationSlug,
    regionUrl: ParamRegionUrl.nullable().default(null),
    projectSlug: ParamProjectSlug,
    name: z
      .string()
      .trim()
      .describe("The new name for the project")
      .nullable()
      .default(null),
    slug: z
      .string()
      .toLowerCase()
      .trim()
      .describe("The new slug for the project (must be unique)")
      .nullable()
      .default(null),
    platform: ParamPlatform.nullable().default(null),
    teamSlug: ParamTeamSlug.nullable()
      .default(null)
      .describe(
        "The team to assign this project to. Note: this will replace the current team assignment.",
      ),
  },
  annotations: {
    readOnlyHint: false,
    destructiveHint: true,
    idempotentHint: 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);

    // Handle team assignment separately if provided
    if (params.teamSlug) {
      setTag("team.slug", params.teamSlug);
      try {
        await apiService.addTeamToProject({
          organizationSlug,
          projectSlug: params.projectSlug,
          teamSlug: params.teamSlug,
        });
      } catch (err) {
        logIssue(err);
        throw new Error(
          `Failed to assign team ${params.teamSlug} to project ${params.projectSlug}: ${err instanceof Error ? err.message : "Unknown error"}`,
        );
      }
    }

    // Update project settings if any are provided
    const hasProjectUpdates = params.name || params.slug || params.platform;

    let project: Project | undefined;
    if (hasProjectUpdates) {
      try {
        project = await apiService.updateProject({
          organizationSlug,
          projectSlug: params.projectSlug,
          name: params.name,
          slug: params.slug,
          platform: params.platform,
        });
      } catch (err) {
        logIssue(err);
        throw new Error(
          `Failed to update project ${params.projectSlug}: ${err instanceof Error ? err.message : "Unknown error"}`,
        );
      }
    } else {
      // If only team assignment, fetch current project data for display
      const projects = await apiService.listProjects(organizationSlug);
      project = projects.find((p) => p.slug === params.projectSlug);
      if (!project) {
        throw new UserInputError(`Project ${params.projectSlug} not found`);
      }
    }

    let output = `# Updated Project in **${organizationSlug}**\n\n`;
    output += `**ID**: ${project.id}\n`;
    output += `**Slug**: ${project.slug}\n`;
    output += `**Name**: ${project.name}\n`;
    if (project.platform) {
      output += `**Platform**: ${project.platform}\n`;
    }

    // Display what was updated
    const updates: string[] = [];
    if (params.name) updates.push(`name to "${params.name}"`);
    if (params.slug) updates.push(`slug to "${params.slug}"`);
    if (params.platform) updates.push(`platform to "${params.platform}"`);
    if (params.teamSlug)
      updates.push(`team assignment to "${params.teamSlug}"`);

    if (updates.length > 0) {
      output += `\n## Updates Applied\n`;
      output += updates.map((update) => `- Updated ${update}`).join("\n");
      output += `\n`;
    }

    output += "\n# Using this information\n\n";
    output += `- The project is now accessible at slug: \`${project.slug}\`\n`;
    if (params.teamSlug) {
      output += `- The project is now assigned to the \`${params.teamSlug}\` team\n`;
    }
    return output;
  },
});

```

--------------------------------------------------------------------------------
/docs/llms/documentation-style-guide.md:
--------------------------------------------------------------------------------

```markdown
# Documentation Style Guide

This guide defines how to write effective documentation for LLMs working with the Sentry MCP codebase.

## Core Principles

### 1. Assume Intelligence
- LLMs understand programming concepts - don't explain basics
- Focus on project-specific patterns and conventions
- Skip obvious steps like "create a file" or "save your changes"

### 2. Optimize for Context Windows
- Keep documents focused on a single topic
- Use code examples instead of verbose explanations
- Every line should provide unique value
- Split large topics across multiple focused docs

### 3. Show, Don't Tell
- Include minimal, focused code examples
- Reference actual implementations: `See @packages/mcp-server/src/server.ts:45`
- Use real patterns from the codebase

## Document Structure

### Required Sections

```markdown
# [Feature/Pattern Name]

Brief one-line description of what this covers.

## When to Use

Bullet points describing specific scenarios.

## Implementation Pattern

```typescript
// Minimal example showing the pattern
const example = {
  // Only include what's unique to this project
};
```

## Key Conventions

Project-specific rules that must be followed.

## Common Patterns

Link to reusable patterns: See "Error Handling" in @docs/common-patterns.md

## References

- Implementation: `@packages/mcp-server/src/[file].ts`
- Tests: `@packages/mcp-server/src/[file].test.ts`
- Examples in codebase: [specific function/tool names]
```

## What to Include

### DO Include:
- **Project-specific patterns** - How THIS codebase does things
- **Architecture decisions** - Why things are structured this way
- **Required conventions** - Must-follow rules for consistency
- **Integration points** - How components interact
- **Validation requirements** - What checks must pass

### DON'T Include:
- **General programming concepts** - How to write TypeScript
- **Tool documentation** - How to use pnpm or Vitest
- **Verbose examples** - Keep code samples minimal
- **Redundant content** - Link to other docs instead
- **Step-by-step tutorials** - LLMs don't need hand-holding

## Code Examples

### Good Example:
```typescript
// Tool parameter pattern used throughout the codebase
export const ParamOrganizationSlug = z
  .string()
  .toLowerCase()
  .trim()
  .describe("The organization's slug. Find using `find_organizations()` tool.");
```

### Bad Example:
```typescript
// First, import the required libraries
import { z } from "zod";

// Define a schema for the organization slug parameter
// This schema will validate that the input is a string
// It will also convert to lowercase and trim whitespace
export const ParamOrganizationSlug = z
  .string() // Ensures the value is a string
  .toLowerCase() // Converts to lowercase
  .trim() // Removes whitespace
  .describe("The organization's slug..."); // Adds description
```

## Cross-References

### File References (MANDATORY):
- Use @path syntax for local files: `@docs/common-patterns.md`
- Always reference from repo root: `@packages/mcp-server/src/server.ts`
- Do NOT use Markdown links for local files (avoid markdown `[text](./...)` patterns)
- Prefer path-only mentions to help agents parse

### Section References:
- Refer to sections by name, not anchors: `See "Error Handling" in @docs/common-patterns.md`
- If multiple sections share a name, include a short hint: `("Zod Patterns" in @docs/common-patterns.md)`

### Code References:
- Use concrete paths and identifiers: `@packages/mcp-server/src/tools/search-events/index.ts:buildQuery`
- Optional line hints for humans: `server.ts:45-52` (agents may ignore)
- Prefer real implementations over fabricated examples

### External Links:
- Keep standard Markdown links for external sites
- Use concise link text; avoid link-only bullets

## Language and Tone

### Use Direct Language:
- ❌ "You might want to consider using..."
- ✅ "Use UserInputError for validation failures"

### Be Specific:
- ❌ "Handle errors appropriately"
- ✅ "Throw UserInputError with a message explaining how to fix it"

### Focus on Requirements:
- ❌ "It's a good practice to run tests"
- ✅ "Run `pnpm test` - all tests must pass"

## Document Length Guidelines

### Context Window Optimization:
- Each document should be consumable in a single context
- Length depends on complexity, not arbitrary limits
- Verbose explanations → concise code examples
- Complex topics → split into focused documents

### Examples:
- **Quality checks**: ~100 lines (simple commands)
- **Adding a tool**: ~300 lines (includes examples)
- **API patterns**: May be longer if examples are valuable
- **Architecture**: Split into overview + detailed sections

## Maintenance

### When Updating Docs:
1. Check for redundancy with other docs
2. Update cross-references if needed
3. Ensure examples still match codebase
4. Keep line count under 400

### Red Flags:
- Verbose prose explaining what code could show
- Repeated content → extract to common-patterns.md
- No code references → add implementation examples
- Generic programming advice → remove it
- Multiple concepts in one doc → split by topic

## Example: Refactoring a Verbose Section

### Before:
```markdown
## Setting Up Your Development Environment

First, make sure you have Node.js installed. You can download it from nodejs.org.
Next, install pnpm globally using npm install -g pnpm. Then clone the repository
using git clone. Navigate to the project directory and run pnpm install to install
all dependencies. Make sure to create your .env file with the required variables.
```

### After:
```markdown
## Environment Setup

Required: Node.js 20+, pnpm

```bash
pnpm install
cp .env.example .env  # Add your API keys
```

See "Development Setup" in @AGENTS.md for environment variables.
```

## Agent Readability Checklist

- Uses @path for all local file references
- Short, focused sections with concrete examples
- Minimal prose; prefers code and commands
- Clear preconditions and environment notes
- Error handling and validation rules are explicit

This style guide ensures documentation remains focused, valuable, and maintainable for LLM consumption.

```

--------------------------------------------------------------------------------
/packages/mcp-cloudflare/src/server/lib/html-utils.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Shared HTML utilities for consistent styling across server-rendered pages
 */

/**
 * Sanitizes HTML content to prevent XSS attacks
 */
export function sanitizeHtml(unsafe: string): string {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

/**
 * Common CSS styles used across all pages
 */
const SHARED_STYLES = `
  /* Modern, responsive styling with system fonts */
  :root {
    --primary-color: oklch(0.205 0 0);
    --highlight-color: oklch(0.811 0.111 293.571);
    --border-color: oklch(0.278 0.033 256.848);
    --error-color: #f44336;
    --success-color: #4caf50;
    --border-color: oklch(0.269 0 0);
    --text-color: oklch(0.872 0.01 258.338);
    --background-color: oklab(0 0 0 / 0.3);
  }
  
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 
                 Helvetica, Arial, sans-serif, "Apple Color Emoji", 
                 "Segoe UI Emoji", "Segoe UI Symbol";
    line-height: 1.6;
    color: var(--text-color);
    background: linear-gradient(oklch(0.13 0.028 261.692) 0%, oklch(0.21 0.034 264.665) 50%, oklch(0.13 0.028 261.692) 100%);
    min-height: 100vh;
    margin: 0;
    padding: 0;
  }
  
  .container {
    max-width: 600px;
    margin: 2rem auto;
    padding: 1rem;
  }
  
  .precard {
    padding: 2rem;
    text-align: center;
  }
  
  .card {
    background-color: var(--background-color);
    padding: 2rem;
    text-align: center;
  }
  
  .header {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 1.5rem;
  }
  
  .logo {
    width: 36px;
    height: 36px;
    margin-right: 1rem;
    color: var(--highlight-color);
  }
  
  .title {
    margin: 0;
    font-size: 26px;
    font-weight: 400;
    color: white;
  }
  
  .status-message {
    margin: 1.5rem 0;
    font-size: 1.5rem;
    font-weight: 400;
    color: white;
  }
  
  .description {
    margin: 1rem 0;
    color: var(--text-color);
    font-size: 1rem;
  }

  .spinner {
    width: 24px;
    height: 24px;
    border: 2px solid var(--border-color);
    border-top: 2px solid var(--highlight-color);
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin: 1rem auto;
  }
  
  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
  
  /* Responsive adjustments */
  @media (max-width: 640px) {
    .container {
      margin: 1rem auto;
      padding: 0.5rem;
    }
    
    .card {
      padding: 1.5rem;
    }
    
    .precard {
      padding: 1rem;
    }
  }
`;

/**
 * Sentry logo SVG
 */
const SENTRY_LOGO = `
  <svg class="logo" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-labelledby="icon-title">
    <title id="icon-title">Sentry Logo</title>
    <path d="M17.48 1.996c.45.26.823.633 1.082 1.083l13.043 22.622a2.962 2.962 0 0 1-2.562 4.44h-3.062c.043-.823.039-1.647 0-2.472h3.052a.488.488 0 0 0 .43-.734L16.418 4.315a.489.489 0 0 0-.845 0L12.582 9.51a23.16 23.16 0 0 1 7.703 8.362 23.19 23.19 0 0 1 2.8 11.024v1.234h-7.882v-1.236a15.284 15.284 0 0 0-6.571-12.543l-1.48 2.567a12.301 12.301 0 0 1 5.105 9.987v1.233h-9.3a2.954 2.954 0 0 1-2.56-1.48A2.963 2.963 0 0 1 .395 25.7l1.864-3.26a6.854 6.854 0 0 1 2.15 1.23l-1.883 3.266a.49.49 0 0 0 .43.734h6.758a9.985 9.985 0 0 0-4.83-7.272l-1.075-.618 3.927-6.835 1.075.615a17.728 17.728 0 0 1 6.164 5.956 17.752 17.752 0 0 1 2.653 8.154h2.959a20.714 20.714 0 0 0-3.05-9.627 20.686 20.686 0 0 0-7.236-7.036l-1.075-.618 4.215-7.309a2.958 2.958 0 0 1 4.038-1.083Z" fill="currentColor"></path>
  </svg>
`;

/**
 * Options for creating HTML pages
 */
export interface HtmlPageOptions {
  title: string;
  serverName?: string;
  statusMessage: string;
  description?: string;
  type?: "success" | "error" | "info";
  showSpinner?: boolean;
  additionalStyles?: string;
  bodyScript?: string;
}

/**
 * Creates a consistent HTML page with Sentry branding and styling
 */
export function createHtmlPage(options: HtmlPageOptions): string {
  const {
    title,
    serverName = "Sentry MCP",
    statusMessage,
    description,
    showSpinner = false,
    additionalStyles = "",
    bodyScript = "",
  } = options;

  const safeTitle = sanitizeHtml(title);
  const safeServerName = sanitizeHtml(serverName);
  const safeStatusMessage = sanitizeHtml(statusMessage);
  const safeDescription = description ? sanitizeHtml(description) : "";

  return `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${safeTitle}</title>
        <style>
          ${SHARED_STYLES}
          ${additionalStyles}
        </style>
      </head>
      <body>
        <div class="container">
          <div class="precard">
            <div class="header">
              ${SENTRY_LOGO}
              <h1 class="title"><strong>${safeServerName}</strong></h1>
            </div>
          </div>
          
          <div class="card">
            <h2 class="status-message">${safeStatusMessage}</h2>
            
            ${showSpinner ? '<div class="spinner"></div>' : ""}
            
            ${safeDescription ? `<p class="description">${safeDescription}</p>` : ""}
          </div>
        </div>
        
        ${bodyScript ? `<script>${bodyScript}</script>` : ""}
      </body>
    </html>
  `;
}

/**
 * Creates a success page for OAuth flows
 */
export function createSuccessPage(
  options: Partial<HtmlPageOptions> = {},
): string {
  return createHtmlPage({
    title: "Authentication Successful",
    statusMessage: "Authentication Successful",
    description: "Authentication completed successfully.",
    type: "success",
    ...options,
  });
}

/**
 * Creates an error page for OAuth flows
 */
export function createErrorPage(
  title: string,
  message: string,
  options: Partial<HtmlPageOptions> = {},
): string {
  return createHtmlPage({
    title: sanitizeHtml(title),
    statusMessage: sanitizeHtml(title),
    description: sanitizeHtml(message),
    type: "error",
    ...options,
  });
}

```
Page 5/16FirstPrevNextLast