Skip to main content
Glama

Sentry MCP

Official
by getsentry
mcpagent-architecture.md10.7 kB
# McpAgent Architecture Documentation ## Overview McpAgent is a base class from the Cloudflare agents library that provides the foundation for building MCP (Model Context Protocol) servers as Durable Objects. It handles the protocol transport, state management, and lifecycle of MCP server instances. Our Sentry MCP implementation extends this base class to provide authenticated, constraint-scoped access to Sentry's API through MCP tools and resources. ## Key Components ### 1. Sentry MCP Agent Implementation ```typescript class SentryMCPBase extends McpAgent< Env, { constraints?: Constraints }, WorkerProps & { organizationSlug?: string; projectSlug?: string; } > { // MCP server created in constructor for performance server = new McpServer({ name: "Sentry MCP", version: LIB_VERSION, }); // Lifecycle methods async init(): Promise<void>; async fetch(request: Request): Promise<Response>; // State management (simplified) state: { constraints?: Constraints }; setState(state): void; } ``` ### 2. Transport Methods and Constraint Handling McpAgent supports two transport protocols: - **`serve()`**: Streamable HTTP transport (recommended) - **`serveSSE()`**: Server-Sent Events transport (legacy) Our implementation adds **constraint verification** before routing to handlers: ```typescript // Usage in index.ts export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const url = new URL(request.url); // SSE endpoint if (url.pathname === "/sse" || url.pathname === "/sse/message") { return SentryMCP.serveSSE("/sse").fetch(request, env, ctx); } // Pattern match for constraint extraction const pattern = new URLPattern({ pathname: "/mcp/:org?/:project?" }); const result = pattern.exec(url); if (result) { const { groups } = result.pathname; // Extract constraints from URL const organizationSlug = groups?.org ?? ""; const projectSlug = groups?.project ?? ""; // Verify access using OAuth token const verification = await verifyConstraintsAccess( { organizationSlug, projectSlug }, { accessToken: ctx.props?.accessToken, sentryHost: env.SENTRY_HOST || "sentry.io", } ); if (!verification.ok) { return new Response(verification.message, { status: verification.status, }); } // Mutate props with verified constraints ctx.props.constraints = verification.constraints; return SentryMCP.serve(pattern.pathname).fetch(request, env, ctx); } return new Response("Not found", { status: 404 }); }, }; ``` ## Lifecycle ### 1. Durable Object Creation ``` Request arrives → URL pattern matching → Constraint verification → OAuth Provider validates token → Extracts props from token → Mutates props with constraints → Creates DO with sessionId → DO instance created → init() called ``` **Key points:** - DO ID (sessionId) is generated from connection context by agents library - Different constraint contexts get separate DO instances - Props are mutated with verified constraints before DO creation - DO persists in memory between requests (~30 seconds idle timeout) ### 2. Request Flow ``` First Request (DO Creation): 1. URL pattern matching extracts org/project from path 2. verifyConstraintsAccess() validates org/project exist and user has access 3. OAuth provider validates token 4. Props mutated with verified constraints 5. DO instance created with unique sessionId for this constraint context 6. init() called - configures MCP server with constraints 7. fetch() called for the request (guaranteed after init() completes) 8. Returns response Subsequent Requests (Same Constraint Context): 1. Routes to existing DO instance (same sessionId) 2. fetch() called - init() is NOT called again 3. Returns response After Hibernation: 1. DO wakes from hibernation 2. init() called to restore state and reconfigure MCP server 3. fetch() called for the request (guaranteed after init() completes) 4. Returns response ``` **Important Lifecycle Guarantee**: McpAgent ensures `init()` always completes before `fetch()` is called. This is guaranteed both on DO creation and when waking from hibernation. **Constraint Immutability**: Once props are mutated with constraints and the DO is created, the constraint configuration remains immutable throughout the DO's lifetime. ### 3. Method Responsibilities **`init()`**: - Called when DO is created or wakes from hibernation - Configures the MCP server with user authentication and constraints - Restores constraint state from props - NOT called for every request **`fetch()`**: - Called for EVERY request - Handles the actual MCP protocol communication - Can access `this.props` including mutated constraints - All MCP tools automatically scoped to the constraint context - Returns the MCP response ## Props System Props are the bridge between OAuth, constraint verification, and the Durable Object: ```typescript // Base props from OAuth type WorkerProps = ServerContext & { id: string; // User ID from OAuth token name: string; // User name scope: string; // OAuth scopes accessToken: string; // Sentry API token }; // Extended props with constraints type ExtendedProps = WorkerProps & { organizationSlug?: string; // Extracted from URL projectSlug?: string; // Extracted from URL constraints?: Constraints; // Verified constraints }; ``` **How props work:** 1. URL pattern matching extracts org/project from request path 2. `verifyConstraintsAccess()` validates constraints using OAuth token 3. OAuth provider decrypts the OAuth token and extracts base props 4. Props are mutated with verified constraints before DO creation 5. DO can access complete props via `this.props` **Important:** Props are mutated once during request routing, then remain immutable throughout the DO's lifetime. ## Constraint Verification System The constraint verification system validates org/project access before DO creation: ```typescript export async function verifyConstraintsAccess( { organizationSlug, projectSlug }: Constraints, { accessToken, sentryHost }: { accessToken: string; sentryHost?: string } ): Promise< | { ok: true; constraints: Constraints } | { ok: false; status: number; message: string; eventId?: string } > { // Verify organization exists and user has access const org = await api.getOrganization(organizationSlug); const regionUrl = org.links?.regionUrl || null; // Verify project access if specified if (projectSlug) { await api.getProject( { organizationSlug, projectSlugOrId: projectSlug }, regionUrl ? { host: new URL(regionUrl).host } : undefined ); } return { ok: true, constraints: { organizationSlug, projectSlug, regionUrl }, }; } ``` **Benefits:** - Early validation prevents unauthorized access - Regional URL detection for proper API routing - Consistent error handling with proper HTTP status codes - Integration with Sentry error tracking ## State Management Our implementation uses simplified state management focused on constraints: ```typescript // State structure type State = { constraints?: Constraints; }; // In init() if (!this.state) { this.setState({ constraints: this.props.constraints, }); } // Access state const constraints = this.state.constraints || {}; ``` State is persisted automatically and restored after hibernation. The constraints from props are preserved in state for consistency. ## Storage Durable Objects have access to persistent storage: ```typescript // Store data await this.ctx.storage.put("key", value); // Retrieve data const value = await this.ctx.storage.get("key"); // Delete data await this.ctx.storage.delete("key"); ``` Storage persists across DO hibernation and restarts. ## Hibernation After ~30 seconds of inactivity: 1. DO goes to sleep (removed from memory) 2. State is persisted to storage 3. On next request, DO wakes up 4. `init()` is called to restore state 5. Normal request processing resumes ## Integration with OAuth Provider The OAuth provider and McpAgent work together: ``` OAuth Provider McpAgent ------------- --------- 1. Receives request 2. Validates OAuth token 3. Extracts props from token 4. Generates DO ID from props 5. Gets/creates DO instance → Constructor called (if new) 6. Passes props to DO → Props available as this.props 7. Calls handler.fetch() → init() called (if new/hibernated) → fetch() called ← Returns response 8. Returns response to client ``` ## Limitations and Constraints 1. **Static handler creation**: `serve()` and `serveSSE()` are static methods, can't access instance data 2. **Props are immutable**: Set once at DO creation, can't be updated 3. **URL rewriting**: Original paths lost during transport, must use headers 4. **One DO per user**: DO ID based on userId, all user requests go to same instance 5. **No request context in init()**: Can't access request data during initialization 6. **Constraint immutability**: Constraints are verified and set once at DO creation, remaining immutable throughout the DO's lifetime. Different constraint contexts create separate DO instances via unique sessionIds. ## Best Practices 1. **Verify constraints early**: Use `verifyConstraintsAccess()` before DO creation 2. **Use storage sparingly**: Storage operations are expensive 3. **Cache in memory**: Use instance variables for frequently accessed data 4. **Prepare for hibernation**: Constraints are preserved in state automatically 5. **Trust lifecycle guarantees**: McpAgent ensures init() completes before fetch() 6. **Leverage immutable constraints**: Once set, constraints don't change - design accordingly ## Implementation Reference The actual implementation can be found in: - Main class: @packages/mcp-cloudflare/src/server/lib/mcp-agent.ts - Constraint utilities: @packages/mcp-cloudflare/src/server/lib/constraint-utils.ts - Type definitions: @packages/mcp-cloudflare/src/server/types.ts ## Related Documentation - OAuth Architecture: @docs/cloudflare/oauth-architecture.md — How OAuth provider integrates - MCP Transport (stdio) Implementation: @packages/mcp-server/src/transports/stdio.ts — Core server transport - Constraint DO Analysis: @docs/cloudflare/constraint-do-analysis.md — Alternative architectures considered

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