From cf485d0a9b70c90cbe5c4cc88ac221d9a9cede82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 14 Nov 2025 16:38:12 +0000 Subject: [PATCH 1/2] docs: MCP client storage adapter refactor and security improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the MCP client manager refactoring that introduces a storage adapter interface and implements automatic OAuth credential cleanup for enhanced security. Changes: - Add new mcp-storage.mdx explaining storage adapter architecture - Document automatic OAuth credential cleanup after authentication - Add security notes about replay attack prevention - Document automatic connection restoration after hibernation - Update OAuth guide with security information Related PR: https://github.com/cloudflare/agents/pull/652 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../docs/agents/guides/oauth-mcp-client.mdx | 8 +- .../model-context-protocol/mcp-client-api.mdx | 9 +- .../model-context-protocol/mcp-storage.mdx | 179 ++++++++++++++++++ 3 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/content/docs/agents/model-context-protocol/mcp-storage.mdx diff --git a/src/content/docs/agents/guides/oauth-mcp-client.mdx b/src/content/docs/agents/guides/oauth-mcp-client.mdx index 914aa34caf8..c5ac6d14aaf 100644 --- a/src/content/docs/agents/guides/oauth-mcp-client.mdx +++ b/src/content/docs/agents/guides/oauth-mcp-client.mdx @@ -20,7 +20,13 @@ When your Agent connects to an OAuth-protected MCP server, here's what happens: 3. **Your application presents** the `authUrl` to your user 4. **Your user authenticates** on the provider's site (Slack, etc.) 5. **The provider redirects** back to your Agent's callback URL with an authorization code -6. **Your Agent completes** the connection automatically +6. **Your Agent completes** the connection automatically and clears OAuth credentials for security + +:::note[Security] + +After successful OAuth completion, the Agent automatically clears the `auth_url` and `callback_url` from storage. This security measure prevents replay attacks and ensures completed authentications cannot be hijacked by malicious actors. + +::: ## Connect and initiate OAuth diff --git a/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx b/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx index a43762dfe1d..01a174b9718 100644 --- a/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx @@ -44,7 +44,7 @@ export class MyAgent extends Agent { -Connections persist in the Agent's [SQL storage](/agents/api-reference/store-and-sync-state), and when an Agent connects to an MCP server, all tools from that server become available automatically. +Connections persist in the Agent's [SQL storage](/agents/api-reference/store-and-sync-state), and when an Agent connects to an MCP server, all tools from that server become available automatically. The Agent automatically restores MCP connections after hibernation without requiring manual reconnection. ## Agent MCP Client Methods @@ -115,10 +115,17 @@ export class MyAgent extends Agent { If the MCP server requires OAuth authentication, `authUrl` will be returned for user authentication. Connections persist across requests and the Agent will automatically reconnect if the connection is lost. +:::note[Security] + +After successful OAuth authentication, the Agent automatically clears sensitive OAuth credentials (`auth_url` and `callback_url`) from storage. This prevents replay attacks and ensures completed authentications cannot be hijacked. Learn more in [MCP storage architecture](/agents/model-context-protocol/mcp-storage/). + +::: + **Related:** - [OAuth handling guide](/agents/guides/oauth-mcp-client) - [Transport options](/agents/model-context-protocol/transport) +- [MCP storage architecture](/agents/model-context-protocol/mcp-storage/) - [removeMcpServer()](#removemcpserver) ### `removeMcpServer()` diff --git a/src/content/docs/agents/model-context-protocol/mcp-storage.mdx b/src/content/docs/agents/model-context-protocol/mcp-storage.mdx new file mode 100644 index 00000000000..c096b463ad4 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/mcp-storage.mdx @@ -0,0 +1,179 @@ +--- +title: MCP storage architecture +pcx_content_type: concept +tags: + - MCP +sidebar: + order: 9 +--- + +import { TypeScriptExample } from "~/components"; + +The MCP client manager uses a storage adapter interface to persist server configurations and OAuth credentials. This architecture allows for flexible storage implementations while maintaining security best practices. + +## Storage adapter interface + +The `MCPStorageAdapter` interface defines how MCP server configurations are persisted: + + + +```ts +interface MCPStorageAdapter { + // Create storage schema + create(): void | Promise; + + // Destroy storage schema + destroy(): void | Promise; + + // Save or update server configuration + saveServer(server: MCPServerRow): void | Promise; + + // Remove server from storage + removeServer(serverId: string): void | Promise; + + // List all servers + listServers(): MCPServerRow[] | Promise; + + // Get server by callback URL (for OAuth) + getServerByCallbackUrl(callbackUrl: string): MCPServerRow | null | Promise; + + // Clear OAuth credentials after successful auth + clearOAuthCredentials(serverId: string): void | Promise; +} +``` + + + +### Server configuration structure + +Each MCP server configuration includes: + +```ts +type MCPServerRow = { + id: string; // Unique server identifier + name: string; // Display name + server_url: string; // MCP server endpoint + client_id: string | null; // OAuth client ID (if applicable) + auth_url: string | null; // OAuth authorization URL (cleared after auth) + callback_url: string; // OAuth callback endpoint + server_options: string | null; // JSON-stringified connection options +}; +``` + +## Default SQL storage + +Agents use the `AgentMCPStorageAdapter` by default, which stores MCP configurations in the Agent's SQL database: + +- **Table**: `cf_agents_mcp_servers` +- **Persistence**: Configurations survive Agent hibernation +- **Security**: OAuth credentials automatically cleared after authentication + +The storage adapter is automatically initialized when you create an Agent: + + + +```ts +import { Agent } from "agents"; + +export class MyAgent extends Agent { + // Storage adapter is automatically configured + // No setup required for default SQL storage +} +``` + + + +## Security considerations + +### Automatic credential cleanup + +After successful OAuth authentication, the storage adapter automatically clears sensitive credentials: + +- **`callback_url`**: Cleared to prevent malicious second callbacks +- **`auth_url`**: Cleared to prevent re-authentication loops + +This ensures: +- OAuth tokens cannot be replayed +- Completed authentications cannot be hijacked +- Storage contains only necessary persistent data + +### Callback URL validation + +The MCP client manager validates OAuth callbacks by: + +1. Checking callback URLs against the database +2. Matching the exact callback URL prefix +3. Rejecting callbacks after credentials are cleared + +This prevents unauthorized callback requests from being processed. + +## Connection restoration + +When an Agent wakes from hibernation, the MCP client manager automatically: + +1. Reads server configurations from storage +2. Recreates OAuth-protected connections in `authenticating` state +3. Reconnects to non-OAuth servers +4. Preserves connection state across Agent lifecycles + +You don't need to manually restore connections - the Agent handles this automatically. + +## Advanced: Custom storage adapters + +For advanced use cases, you can implement a custom storage adapter. This is typically only needed for: + +- Non-SQL storage backends +- Custom encryption requirements +- Multi-tenant isolation patterns +- External configuration management + +:::note + +Most users should use the default SQL storage adapter. Custom storage adapters are an advanced feature. + +::: + +To implement a custom adapter: + + + +```ts +import { + Agent, + type MCPStorageAdapter, + type MCPServerRow +} from "agents"; + +class CustomStorageAdapter implements MCPStorageAdapter { + async create(): Promise { + // Initialize your storage + } + + async saveServer(server: MCPServerRow): Promise { + // Persist server configuration + } + + async listServers(): Promise { + // Retrieve all servers + } + + // Implement remaining methods... +} + +export class MyAgent extends Agent { + constructor(ctx: AgentContext, env: Env) { + super(ctx, env); + + // Custom storage adapter must be configured in constructor + // This is an advanced pattern - most users should use default storage + } +} +``` + + + +## Related + +- [McpClient — API reference](/agents/model-context-protocol/mcp-client-api/) — MCP client methods +- [Handle OAuth with MCP servers](/agents/guides/oauth-mcp-client/) — OAuth integration guide +- [Store and sync state](/agents/api-reference/store-and-sync-state/) — Agent SQL storage From 4ed06aec8bbbe10c31a823fde410d6364bd86192 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Fri, 14 Nov 2025 16:42:29 +0000 Subject: [PATCH 2/2] Document MCP storage adapter pattern - Add new storage-adapter.mdx page documenting the MCPStorageAdapter interface - Update mcp-client-api.mdx to reference storage adapter pattern - Document breaking change: MCPClientManager now requires storage option - Include migration guide for direct MCPClientManager usage - Document security improvements in OAuth credential handling Related to cloudflare/agents#652 --- .../model-context-protocol/mcp-client-api.mdx | 11 +- .../storage-adapter.mdx | 198 ++++++++++++++++++ 2 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/content/docs/agents/model-context-protocol/storage-adapter.mdx diff --git a/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx b/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx index 01a174b9718..90195e302a2 100644 --- a/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx +++ b/src/content/docs/agents/model-context-protocol/mcp-client-api.mdx @@ -44,7 +44,7 @@ export class MyAgent extends Agent { -Connections persist in the Agent's [SQL storage](/agents/api-reference/store-and-sync-state), and when an Agent connects to an MCP server, all tools from that server become available automatically. The Agent automatically restores MCP connections after hibernation without requiring manual reconnection. +Connections persist in the Agent's [SQL storage](/agents/api-reference/store-and-sync-state/) using a [storage adapter pattern](/agents/model-context-protocol/storage-adapter/), and when an Agent connects to an MCP server, all tools from that server become available automatically. The Agent automatically restores MCP connections after hibernation without requiring manual reconnection. ## Agent MCP Client Methods @@ -279,7 +279,12 @@ export class MyAgent extends Agent { +## Advanced Configuration + +For advanced use cases, you can implement custom storage backends for MCP server configurations. Refer to the [Storage Adapter documentation](/agents/model-context-protocol/storage-adapter/) to learn about the storage adapter pattern and how to implement custom storage solutions. + ## Next Steps -- [Connect your first MCP server](/agents/guides/connect-mcp-client) — Tutorial to get started -- [Handle OAuth flows](/agents/guides/oauth-mcp-client) — Complete OAuth integration guide +- [Connect your first MCP server](/agents/guides/connect-mcp-client/) — Tutorial to get started +- [Handle OAuth flows](/agents/guides/oauth-mcp-client/) — Complete OAuth integration guide +- [Storage Adapter](/agents/model-context-protocol/storage-adapter/) — Custom storage backends for MCP diff --git a/src/content/docs/agents/model-context-protocol/storage-adapter.mdx b/src/content/docs/agents/model-context-protocol/storage-adapter.mdx new file mode 100644 index 00000000000..c2bc36feb95 --- /dev/null +++ b/src/content/docs/agents/model-context-protocol/storage-adapter.mdx @@ -0,0 +1,198 @@ +--- +title: MCP Storage Adapter +pcx_content_type: concept +tags: + - MCP +sidebar: + order: 8 +--- + +import { TypeScriptExample } from "~/components"; + +The MCP storage adapter pattern provides a flexible way to persist MCP server configurations and connection state. By default, Agents use SQL storage, but you can implement custom storage backends for specialized use cases. + +## Overview + +MCP server configurations are stored persistently so that connections can be restored after Agent restarts or during OAuth flows. The storage adapter interface abstracts these storage operations, making it possible to: + +- Use different storage backends (SQL, KV, external databases) +- Test MCP functionality with mock storage +- Implement custom persistence strategies + +## Default Behavior + +When you use `this.addMcpServer()` in an Agent class, storage is handled automatically using the Agent's built-in SQL storage. No additional configuration is required: + + + +```ts title="src/index.ts" +export class MyAgent extends Agent { + async onRequest(request: Request): Promise { + // Storage is handled automatically + const { id } = await this.addMcpServer( + "Weather API", + "https://weather-mcp.example.com/mcp", + ); + + return new Response(`Connected: ${id}`); + } +} +``` + + + +## Storage Adapter Interface + +For advanced use cases, you can implement the `MCPStorageAdapter` interface: + +```ts +interface MCPStorageAdapter { + create(): void | Promise; + destroy(): void | Promise; + saveServer(server: MCPServerRow): void | Promise; + removeServer(serverId: string): void | Promise; + listServers(): MCPServerRow[] | Promise; + getServerByCallbackUrl( + callbackUrl: string, + ): MCPServerRow | null | Promise; + clearOAuthCredentials(serverId: string): void | Promise; +} +``` + +### MCPServerRow Type + +The storage adapter works with server configuration objects: + +```ts +type MCPServerRow = { + id: string; + name: string; + server_url: string; + client_id: string | null; + auth_url: string | null; + callback_url: string; + server_options: string | null; +}; +``` + +## Built-in Storage Adapter + +The `AgentMCPStorageAdapter` class wraps Agent SQL storage and is used automatically by the Agent class: + + + +```ts title="src/index.ts" +import { AgentMCPStorageAdapter } from "agents/mcp"; + +// This is done automatically inside the Agent class +const storage = new AgentMCPStorageAdapter(this.sql.bind(this)); +``` + + + +## Custom Storage Implementation + +If you need to use `MCPClientManager` directly (outside of an Agent), you must provide a storage adapter: + + + +```ts title="src/index.ts" +import { MCPClientManager, type MCPStorageAdapter, type MCPServerRow } from "agents/mcp"; + +class CustomStorageAdapter implements MCPStorageAdapter { + private servers = new Map(); + + create(): void { + // Initialize storage + } + + destroy(): void { + // Clean up storage + this.servers.clear(); + } + + saveServer(server: MCPServerRow): void { + this.servers.set(server.id, server); + } + + removeServer(serverId: string): void { + this.servers.delete(serverId); + } + + listServers(): MCPServerRow[] { + return Array.from(this.servers.values()); + } + + getServerByCallbackUrl(callbackUrl: string): MCPServerRow | null { + for (const server of this.servers.values()) { + if (server.callback_url === callbackUrl) { + return server; + } + } + return null; + } + + clearOAuthCredentials(serverId: string): void { + const server = this.servers.get(serverId); + if (server) { + server.callback_url = ""; + server.auth_url = null; + this.servers.set(serverId, server); + } + } +} + +// Use custom storage with MCPClientManager +const manager = new MCPClientManager("my-client", "1.0.0", { + storage: new CustomStorageAdapter(), +}); +``` + + + +## Storage Operations + +### Server Lifecycle + +The storage adapter manages server configuration throughout its lifecycle: + +1. **Creation**: `saveServer()` is called when connecting to a new server via `addMcpServer()` +2. **OAuth Flow**: `clearOAuthCredentials()` is called after successful authentication to prevent replay attacks +3. **Restoration**: `listServers()` is called on Agent initialization to restore connections +4. **Removal**: `removeServer()` is called when disconnecting via `removeMcpServer()` + +### Security Considerations + +The `clearOAuthCredentials()` method is called after successful OAuth authentication to clear both `callback_url` and `auth_url`. This prevents: + +- Malicious second callback attempts from being processed +- Unnecessary re-authentication prompts on reconnection +- OAuth tokens from being exposed in storage + +## Migration Notes + +If you are using `MCPClientManager` directly (not through the Agent class), you must update your code to provide a storage adapter: + +**Before:** + +```ts +const manager = new MCPClientManager("client-name", "1.0.0"); +``` + +**After:** + +```ts +import { MCPClientManager, AgentMCPStorageAdapter } from "agents/mcp"; + +const manager = new MCPClientManager("client-name", "1.0.0", { + storage: new AgentMCPStorageAdapter(sqlFunction), +}); +``` + +This change does not affect normal Agent usage - storage is handled automatically when using `this.addMcpServer()`. + +## Next Steps + +- [MCP Client API reference](/agents/model-context-protocol/mcp-client-api/) — Full API documentation +- [OAuth handling guide](/agents/guides/oauth-mcp-client/) — Complete OAuth integration guide +- [Connect your first MCP server](/agents/guides/connect-mcp-client/) — Tutorial to get started