Skip to content

Commit 4ed06ae

Browse files
committed
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
1 parent cf485d0 commit 4ed06ae

File tree

2 files changed

+206
-3
lines changed

2 files changed

+206
-3
lines changed

src/content/docs/agents/model-context-protocol/mcp-client-api.mdx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class MyAgent extends Agent<Env, never> {
4444

4545
</TypeScriptExample>
4646

47-
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.
47+
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.
4848

4949
## Agent MCP Client Methods
5050

@@ -279,7 +279,12 @@ export class MyAgent extends Agent<Env, never> {
279279

280280
</TypeScriptExample>
281281

282+
## Advanced Configuration
283+
284+
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.
285+
282286
## Next Steps
283287

284-
- [Connect your first MCP server](/agents/guides/connect-mcp-client) — Tutorial to get started
285-
- [Handle OAuth flows](/agents/guides/oauth-mcp-client) — Complete OAuth integration guide
288+
- [Connect your first MCP server](/agents/guides/connect-mcp-client/) — Tutorial to get started
289+
- [Handle OAuth flows](/agents/guides/oauth-mcp-client/) — Complete OAuth integration guide
290+
- [Storage Adapter](/agents/model-context-protocol/storage-adapter/) — Custom storage backends for MCP
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
---
2+
title: MCP Storage Adapter
3+
pcx_content_type: concept
4+
tags:
5+
- MCP
6+
sidebar:
7+
order: 8
8+
---
9+
10+
import { TypeScriptExample } from "~/components";
11+
12+
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.
13+
14+
## Overview
15+
16+
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:
17+
18+
- Use different storage backends (SQL, KV, external databases)
19+
- Test MCP functionality with mock storage
20+
- Implement custom persistence strategies
21+
22+
## Default Behavior
23+
24+
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:
25+
26+
<TypeScriptExample>
27+
28+
```ts title="src/index.ts"
29+
export class MyAgent extends Agent<Env, never> {
30+
async onRequest(request: Request): Promise<Response> {
31+
// Storage is handled automatically
32+
const { id } = await this.addMcpServer(
33+
"Weather API",
34+
"https://weather-mcp.example.com/mcp",
35+
);
36+
37+
return new Response(`Connected: ${id}`);
38+
}
39+
}
40+
```
41+
42+
</TypeScriptExample>
43+
44+
## Storage Adapter Interface
45+
46+
For advanced use cases, you can implement the `MCPStorageAdapter` interface:
47+
48+
```ts
49+
interface MCPStorageAdapter {
50+
create(): void | Promise<void>;
51+
destroy(): void | Promise<void>;
52+
saveServer(server: MCPServerRow): void | Promise<void>;
53+
removeServer(serverId: string): void | Promise<void>;
54+
listServers(): MCPServerRow[] | Promise<MCPServerRow[]>;
55+
getServerByCallbackUrl(
56+
callbackUrl: string,
57+
): MCPServerRow | null | Promise<MCPServerRow | null>;
58+
clearOAuthCredentials(serverId: string): void | Promise<void>;
59+
}
60+
```
61+
62+
### MCPServerRow Type
63+
64+
The storage adapter works with server configuration objects:
65+
66+
```ts
67+
type MCPServerRow = {
68+
id: string;
69+
name: string;
70+
server_url: string;
71+
client_id: string | null;
72+
auth_url: string | null;
73+
callback_url: string;
74+
server_options: string | null;
75+
};
76+
```
77+
78+
## Built-in Storage Adapter
79+
80+
The `AgentMCPStorageAdapter` class wraps Agent SQL storage and is used automatically by the Agent class:
81+
82+
<TypeScriptExample>
83+
84+
```ts title="src/index.ts"
85+
import { AgentMCPStorageAdapter } from "agents/mcp";
86+
87+
// This is done automatically inside the Agent class
88+
const storage = new AgentMCPStorageAdapter(this.sql.bind(this));
89+
```
90+
91+
</TypeScriptExample>
92+
93+
## Custom Storage Implementation
94+
95+
If you need to use `MCPClientManager` directly (outside of an Agent), you must provide a storage adapter:
96+
97+
<TypeScriptExample>
98+
99+
```ts title="src/index.ts"
100+
import { MCPClientManager, type MCPStorageAdapter, type MCPServerRow } from "agents/mcp";
101+
102+
class CustomStorageAdapter implements MCPStorageAdapter {
103+
private servers = new Map<string, MCPServerRow>();
104+
105+
create(): void {
106+
// Initialize storage
107+
}
108+
109+
destroy(): void {
110+
// Clean up storage
111+
this.servers.clear();
112+
}
113+
114+
saveServer(server: MCPServerRow): void {
115+
this.servers.set(server.id, server);
116+
}
117+
118+
removeServer(serverId: string): void {
119+
this.servers.delete(serverId);
120+
}
121+
122+
listServers(): MCPServerRow[] {
123+
return Array.from(this.servers.values());
124+
}
125+
126+
getServerByCallbackUrl(callbackUrl: string): MCPServerRow | null {
127+
for (const server of this.servers.values()) {
128+
if (server.callback_url === callbackUrl) {
129+
return server;
130+
}
131+
}
132+
return null;
133+
}
134+
135+
clearOAuthCredentials(serverId: string): void {
136+
const server = this.servers.get(serverId);
137+
if (server) {
138+
server.callback_url = "";
139+
server.auth_url = null;
140+
this.servers.set(serverId, server);
141+
}
142+
}
143+
}
144+
145+
// Use custom storage with MCPClientManager
146+
const manager = new MCPClientManager("my-client", "1.0.0", {
147+
storage: new CustomStorageAdapter(),
148+
});
149+
```
150+
151+
</TypeScriptExample>
152+
153+
## Storage Operations
154+
155+
### Server Lifecycle
156+
157+
The storage adapter manages server configuration throughout its lifecycle:
158+
159+
1. **Creation**: `saveServer()` is called when connecting to a new server via `addMcpServer()`
160+
2. **OAuth Flow**: `clearOAuthCredentials()` is called after successful authentication to prevent replay attacks
161+
3. **Restoration**: `listServers()` is called on Agent initialization to restore connections
162+
4. **Removal**: `removeServer()` is called when disconnecting via `removeMcpServer()`
163+
164+
### Security Considerations
165+
166+
The `clearOAuthCredentials()` method is called after successful OAuth authentication to clear both `callback_url` and `auth_url`. This prevents:
167+
168+
- Malicious second callback attempts from being processed
169+
- Unnecessary re-authentication prompts on reconnection
170+
- OAuth tokens from being exposed in storage
171+
172+
## Migration Notes
173+
174+
If you are using `MCPClientManager` directly (not through the Agent class), you must update your code to provide a storage adapter:
175+
176+
**Before:**
177+
178+
```ts
179+
const manager = new MCPClientManager("client-name", "1.0.0");
180+
```
181+
182+
**After:**
183+
184+
```ts
185+
import { MCPClientManager, AgentMCPStorageAdapter } from "agents/mcp";
186+
187+
const manager = new MCPClientManager("client-name", "1.0.0", {
188+
storage: new AgentMCPStorageAdapter(sqlFunction),
189+
});
190+
```
191+
192+
This change does not affect normal Agent usage - storage is handled automatically when using `this.addMcpServer()`.
193+
194+
## Next Steps
195+
196+
- [MCP Client API reference](/agents/model-context-protocol/mcp-client-api/) — Full API documentation
197+
- [OAuth handling guide](/agents/guides/oauth-mcp-client/) — Complete OAuth integration guide
198+
- [Connect your first MCP server](/agents/guides/connect-mcp-client/) — Tutorial to get started

0 commit comments

Comments
 (0)