Skip to main content
Glama

Sentry MCP

Official
by getsentry
mcp-agent.ts6.37 kB
import * as Sentry from "@sentry/cloudflare"; import { McpAgent } from "agents/mcp"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { configureServer } from "@sentry/mcp-server/server"; import { expandScopes, parseScopes } from "@sentry/mcp-server/permissions"; import { logWarn } from "@sentry/mcp-server/telem/logging"; import type { Env, WorkerProps } from "../types"; import type { Constraints } from "@sentry/mcp-server/types"; import { LIB_VERSION } from "@sentry/mcp-server/version"; import getSentryConfig from "../sentry.config"; import { verifyConstraintsAccess } from "./constraint-utils"; import type { ExecutionContext } from "@cloudflare/workers-types"; /** * Sentry MCP Agent - A Durable Object that provides Model Context Protocol access to Sentry. * * This class extends the Cloudflare agents library McpAgent to provide authenticated, * constraint-scoped access to Sentry's API through MCP tools and resources. * * ARCHITECTURE: * * Each MCP client connection creates a unique Durable Object instance via sessionId. * The agents library generates sessionIds based on the connection context, ensuring * that different constraint contexts (e.g., /mcp/org1/proj1 vs /mcp/org2/proj2) * get separate DO instances with immutable configurations. * * LIFECYCLE: * * 1. Connection: MCP client connects to URL like /mcp/sentry/my-project * 2. Authentication: OAuth flow provides user credentials and permissions * 3. DO Creation: Agents library creates DO with unique sessionId for this context * 4. Initialization: init() configures MCP server with user auth and URL constraints * 5. Request Handling: fetch() processes MCP protocol messages (tools, resources, prompts) * 6. Hibernation: DO persists state and hibernates after inactivity * 7. Recovery: init() restores state when DO wakes from hibernation * * CONSTRAINT SCOPING: * * URL paths like /mcp/sentry/my-project are parsed to extract organization and project * constraints. These constraints scope all Sentry API calls to the specific org/project * context, ensuring users can only access data they're authorized for within that scope. * * Static serve() methods extract constraints from URLs and mutate props before DO instantiation, * ensuring immutable constraint configuration throughout the DO's lifetime. * * AUTHENTICATION: * * User authentication flows through OAuth, with credentials stored in encrypted props * that persist across DO hibernation cycles. The DO uses these credentials to authenticate * all Sentry API requests on behalf of the MCP client. */ /** * Sentry MCP Agent - A Durable Object that provides Model Context Protocol access to Sentry. * * Following the pattern from the gist: this class extends McpAgent and mutates props * directly on first request to include URL-extracted constraints. Props are then immutable * throughout the DO's lifetime. */ class SentryMCPBase extends McpAgent< Env, { constraints?: Constraints; }, WorkerProps & { organizationSlug?: string; projectSlug?: string; } > { // Create server once in constructor, as per Cloudflare MCP Agent API docs server = new McpServer({ name: "Sentry MCP", version: LIB_VERSION, }); // biome-ignore lint/complexity/noUselessConstructor: Need the constructor to match the durable object types. constructor(state: DurableObjectState, env: Env) { super(state, env); } /** * Initialize Durable Object state and MCP server configuration * * Called when the DO is first created or wakes from hibernation. * Configures the MCP server with user authentication and constraint scoping. * * Since props (including constraints) are set on first request and then immutable, * the server configuration remains constant throughout the DO's lifetime. */ async init() { if (!this.state?.constraints) { this.setState({ constraints: this.props.constraints, }); } await configureServer({ server: this.server, context: { userId: this.props.id, mcpUrl: process.env.MCP_URL, accessToken: this.props.accessToken, grantedScopes: this.props.grantedScopes ? (() => { const { valid, invalid } = parseScopes(this.props.grantedScopes); if (invalid.length > 0) { logWarn(`Ignoring invalid scopes from OAuth provider`, { loggerScope: ["cloudflare", "mcp-agent"], extra: { invalidScopes: invalid, }, }); } return expandScopes(valid); })() : undefined, constraints: this.state.constraints || {}, }, onToolComplete: () => { this.ctx.waitUntil(Sentry.flush(2000)); }, }); } } export const SentryMCP = Sentry.instrumentDurableObjectWithSentry( getSentryConfig.partial({ initialScope: { tags: { durable_object: true, "mcp.server_version": LIB_VERSION, }, }, }), SentryMCPBase, ); export default { async fetch(request: Request, env: unknown, ctx: ExecutionContext) { const url = new URL(request.url); if (url.pathname === "/sse" || url.pathname === "/sse/message") { return SentryMCP.serveSSE("/sse").fetch(request, env, ctx); } const pattern = new URLPattern({ pathname: "/mcp/:org?/:project?" }); const result = pattern.exec(url); if (result) { const { groups } = result.pathname; const organizationSlug = groups?.org ?? ""; const projectSlug = groups?.project ?? ""; // Verify access to org/project using OAuth token const verification = await verifyConstraintsAccess( { organizationSlug, projectSlug }, { // @ts-ignore props is provided by OAuth provider → agents library accessToken: ctx.props?.accessToken, sentryHost: (env as any)?.SENTRY_HOST || "sentry.io", }, ); if (!verification.ok) { return new Response(verification.message, { status: verification.status ?? 500, }); } ctx.props.constraints = verification.constraints; return SentryMCP.serve(pattern.pathname).fetch(request, env, ctx); } return new Response("Not found", { status: 404 }); }, };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/getsentry/sentry-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server