Skip to content

Commit 3a95eef

Browse files
docs: MCP connection state improvements and discovery lifecycle
Updates documentation for PR cloudflare/agents#672 ## Changes - Updated addMcpServer() return type to discriminated union - Added MCPConnectionState enum documentation - Documented new "connected" state in connection lifecycle - Added connection state transition diagrams - Documented new discovery behavior with timeout and cancellation - Created comprehensive changelog with migration guide ## Breaking Changes - MCPClientConnection.init() no longer auto-discovers capabilities - addMcpServer() return type changed to discriminated union - New "connected" state added to connection lifecycle 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a6a5baf commit 3a95eef

File tree

2 files changed

+237
-20
lines changed

2 files changed

+237
-20
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
title: MCP connection state improvements and standardized tool discovery
3+
description: The Agents SDK now includes a formalized MCPConnectionState enum, improved discovery lifecycle with timeouts and cancellation, and better error handling for MCP server connections.
4+
products:
5+
- agents
6+
- workers
7+
date: 2025-11-26
8+
---
9+
10+
We have improved the MCP (Model Context Protocol) client connection lifecycle in the [Agents SDK](https://github.com/cloudflare/agents) with better state management, explicit discovery control, and enhanced reliability.
11+
12+
## New Features
13+
14+
### MCPConnectionState Enum
15+
16+
A new `MCPConnectionState` enum formalizes the connection lifecycle with six distinct states:
17+
18+
- `AUTHENTICATING` — Waiting for OAuth authorization
19+
- `CONNECTING` — Establishing transport connection
20+
- `CONNECTED` — Transport connected, ready for discovery
21+
- `DISCOVERING` — Fetching server capabilities
22+
- `READY` — Fully connected with all tools available
23+
- `FAILED` — Connection or discovery error
24+
25+
```ts
26+
import { MCPConnectionState } from "agents";
27+
28+
if (result.state === MCPConnectionState.READY) {
29+
// Server is connected and tools are available
30+
}
31+
```
32+
33+
### Enhanced Discovery Control
34+
35+
Discovery is now a separate, explicit phase with full lifecycle management:
36+
37+
- **Configurable timeout** — Default 15-second timeout prevents hanging connections
38+
- **Cancellation support** — New discoveries automatically cancel previous in-flight requests
39+
- **Better error handling** — Discovery failures throw errors immediately instead of continuing with empty tool arrays
40+
- **New `discover()` method** — On `MCPClientConnection` for manual discovery control
41+
- **New `discoverIfConnected()` method** — On `MCPClientManager` for simpler per-server discovery
42+
43+
### Improved `addMcpServer()` Return Type
44+
45+
The return type is now a discriminated union that makes OAuth vs. ready states explicit:
46+
47+
```ts
48+
const result = await this.addMcpServer("Weather", "https://weather.example.com/mcp");
49+
50+
if (result.state === "authenticating") {
51+
// Redirect user to result.authUrl
52+
} else {
53+
// result.state === "ready", tools are available
54+
}
55+
```
56+
57+
### Better `createConnection()` Return Value
58+
59+
The `createConnection()` method now returns the connection object for immediate use, eliminating the need for separate lookup calls.
60+
61+
## Bug Fixes
62+
63+
- **Fixed discovery hanging on repeated requests** — New discoveries now cancel previous in-flight ones via AbortController
64+
- **Fixed Durable Object crash-looping**`restoreConnectionsFromStorage()` now starts connections in background to avoid blocking initialization and causing timeouts
65+
- **Fixed OAuth callback race condition** — When `auth_url` exists in storage during restoration, state is set to `AUTHENTICATING` directly instead of calling `connectToServer()` which was overwriting the state
66+
67+
## Migration Guide
68+
69+
### Breaking Change: `MCPClientConnection.init()` No Longer Auto-Discovers
70+
71+
Previously, `init()` would automatically trigger discovery. Now connection and discovery are separate phases.
72+
73+
**For most use cases, no changes are needed**`addMcpServer()` handles both connection and discovery automatically.
74+
75+
If you are using lower-level APIs like `MCPClientConnection` directly, you must now call `discover()` explicitly after connection:
76+
77+
```ts
78+
// Before
79+
await connection.init();
80+
// init() would automatically discover
81+
82+
// After
83+
await connection.init();
84+
await connection.discover();
85+
// Or use the MCPClientManager method
86+
await clientManager.discoverIfConnected(serverId);
87+
```
88+
89+
### Updated Return Type for `addMcpServer()`
90+
91+
The return type changed from:
92+
93+
```ts
94+
{ id: string; authUrl: string | undefined }
95+
```
96+
97+
To a discriminated union:
98+
99+
```ts
100+
| { id: string; state: "authenticating"; authUrl: string }
101+
| { id: string; state: "ready"; authUrl?: undefined }
102+
```
103+
104+
Update your code to check the `state` field:
105+
106+
```ts
107+
// Before
108+
const { id, authUrl } = await this.addMcpServer(...);
109+
if (authUrl) {
110+
// Handle OAuth
111+
}
112+
113+
// After
114+
const result = await this.addMcpServer(...);
115+
if (result.state === "authenticating") {
116+
// Handle OAuth with result.authUrl
117+
}
118+
```
119+
120+
### New Connection State: `connected`
121+
122+
A new `connected` state represents a connected transport with no tools loaded yet. This state occurs between `CONNECTING` and `DISCOVERING`, allowing you to distinguish between:
123+
124+
- **`connected`** — Transport is connected, but capabilities are not yet discovered
125+
- **`ready`** — Transport is connected and all capabilities have been discovered
126+
127+
Update any code that checks connection states to account for this new intermediate state.
128+
129+
## Learn More
130+
131+
- [MCP Client API reference](/agents/model-context-protocol/mcp-client-api/)
132+
- [Connect to MCP servers guide](/agents/guides/connect-mcp-client/)
133+
- [OAuth handling for MCP clients](/agents/guides/oauth-mcp-client/)

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

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Connections persist in the Agent's [SQL storage](/agents/api-reference/store-and
5050

5151
### `addMcpServer()`
5252

53-
Add a connection to an MCP server and make its tools available to your Agent.
53+
Add a connection to an MCP server and make its tools available to your Agent. This method establishes the connection and automatically discovers server capabilities (tools, resources, prompts).
5454

5555
```ts
5656
async addMcpServer(
@@ -65,7 +65,18 @@ async addMcpServer(
6565
type?: "sse" | "streamable-http" | "auto";
6666
};
6767
}
68-
): Promise<{ id: string; authUrl: string | undefined }>
68+
): Promise<
69+
| {
70+
id: string;
71+
state: "authenticating";
72+
authUrl: string;
73+
}
74+
| {
75+
id: string;
76+
state: "ready";
77+
authUrl?: undefined;
78+
}
79+
>
6980
```
7081

7182
#### Parameters
@@ -82,10 +93,22 @@ async addMcpServer(
8293

8394
#### Returns
8495

85-
A Promise that resolves to an object containing:
96+
A Promise that resolves to a discriminated union based on connection state:
8697

87-
- **`id`** (string) — Unique identifier for this server connection
88-
- **`authUrl`** (string | undefined) — OAuth authorization URL if authentication is required, otherwise `undefined`
98+
- If **OAuth authentication is required**:
99+
- **`id`** (string) — Unique identifier for this server connection
100+
- **`state`** (`"authenticating"`) — Indicates OAuth flow is needed
101+
- **`authUrl`** (string) — OAuth authorization URL for user authentication
102+
103+
- If **connection and discovery succeeded**:
104+
- **`id`** (string) — Unique identifier for this server connection
105+
- **`state`** (`"ready"`) — Server is connected and tools are available
106+
- **`authUrl`** (undefined) — Not present for non-OAuth connections
107+
108+
#### Throws
109+
110+
- Throws an error if connection fails
111+
- Throws an error if discovery fails (with 15-second timeout)
89112

90113
#### Example
91114

@@ -94,26 +117,32 @@ A Promise that resolves to an object containing:
94117
```ts title="src/index.ts"
95118
export class MyAgent extends Agent<Env, never> {
96119
async onRequest(request: Request): Promise<Response> {
97-
const { id, authUrl } = await this.addMcpServer(
98-
"Weather API",
99-
"https://weather-mcp.example.com/mcp",
100-
);
101-
102-
if (authUrl) {
103-
// User needs to complete OAuth flow
104-
return new Response(JSON.stringify({ serverId: id, authUrl }), {
105-
headers: { "Content-Type": "application/json" },
106-
});
107-
}
120+
try {
121+
const result = await this.addMcpServer(
122+
"Weather API",
123+
"https://weather-mcp.example.com/mcp",
124+
);
108125

109-
return new Response("Connected", { status: 200 });
126+
if (result.state === "authenticating") {
127+
// User needs to complete OAuth flow
128+
return new Response(JSON.stringify({ serverId: result.id, authUrl: result.authUrl }), {
129+
headers: { "Content-Type": "application/json" },
130+
});
131+
}
132+
133+
// Server is ready - tools are available
134+
return new Response(`Connected: ${result.id}`, { status: 200 });
135+
} catch (error) {
136+
// Connection or discovery failed
137+
return new Response(`Failed to connect: ${error.message}`, { status: 500 });
138+
}
110139
}
111140
}
112141
```
113142

114143
</TypeScriptExample>
115144

116-
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.
145+
If the MCP server requires OAuth authentication, the result will include `state: "authenticating"` with an `authUrl` for user authentication. Otherwise, the server transitions to `"ready"` state with all tools discovered and available. Connections persist across requests and the Agent will automatically reconnect if the connection is lost.
117146

118147
:::note[Default JSON Schema Validation]
119148

@@ -193,8 +222,9 @@ An `MCPServersState` object containing:
193222
state:
194223
| "authenticating"
195224
| "connecting"
196-
| "ready"
225+
| "connected"
197226
| "discovering"
227+
| "ready"
198228
| "failed";
199229
capabilities: ServerCapabilities | null;
200230
instructions: string | null;
@@ -228,7 +258,16 @@ export class MyAgent extends Agent<Env, never> {
228258

229259
</TypeScriptExample>
230260

231-
The `state` field can be: `authenticating`, `connecting`, `ready`, `discovering`, or `failed`. Use this method to monitor connection status, list available tools, or build UI for connected servers.
261+
The `state` field represents the connection lifecycle:
262+
263+
- **`authenticating`** — Waiting for OAuth authorization
264+
- **`connecting`** — Establishing transport connection
265+
- **`connected`** — Transport connected, but capabilities not yet discovered
266+
- **`discovering`** — Discovering server capabilities (tools, resources, prompts)
267+
- **`ready`** — Fully connected with all capabilities discovered
268+
- **`failed`** — Connection or discovery failed
269+
270+
Use this method to monitor connection status, list available tools, or build UI for connected servers.
232271

233272
## OAuth Configuration
234273

@@ -251,6 +290,51 @@ export class MyAgent extends Agent<Env, never> {
251290

252291
You can also provide a `customHandler` function for full control over the callback response. Refer to the [OAuth handling guide](/agents/guides/oauth-mcp-client) for details.
253292

293+
## Connection State Lifecycle
294+
295+
The Agents SDK exports an `MCPConnectionState` enum that defines the connection lifecycle states:
296+
297+
<TypeScriptExample>
298+
299+
```ts title="src/index.ts"
300+
import { MCPConnectionState } from "agents";
301+
302+
// Access state constants
303+
console.log(MCPConnectionState.AUTHENTICATING); // "authenticating"
304+
console.log(MCPConnectionState.CONNECTING); // "connecting"
305+
console.log(MCPConnectionState.CONNECTED); // "connected"
306+
console.log(MCPConnectionState.DISCOVERING); // "discovering"
307+
console.log(MCPConnectionState.READY); // "ready"
308+
console.log(MCPConnectionState.FAILED); // "failed"
309+
```
310+
311+
</TypeScriptExample>
312+
313+
### State Transitions
314+
315+
Connections follow this lifecycle:
316+
317+
**Non-OAuth flow:**
318+
```
319+
CONNECTING → CONNECTED → DISCOVERING → READY
320+
```
321+
322+
**OAuth flow:**
323+
```
324+
AUTHENTICATING → (user completes OAuth) → CONNECTING → CONNECTED → DISCOVERING → READY
325+
```
326+
327+
Any state can transition to `FAILED` if an error occurs.
328+
329+
### Key Changes in Connection Behavior
330+
331+
Previously, `init()` would automatically trigger discovery. Now:
332+
333+
1. **Connection and discovery are separate phases** — After a successful connection (state `CONNECTED`), you must explicitly call discovery
334+
2. **`addMcpServer()` handles both automatically** — For most use cases, use `addMcpServer()` which connects and discovers in one call
335+
3. **Discovery has a 15-second timeout** — If a server does not respond within 15 seconds, discovery fails and the connection returns to `CONNECTED` state
336+
4. **Discovery can be cancelled** — Repeated discovery requests automatically cancel previous in-flight discoveries
337+
254338
## Error Handling
255339

256340
Use error detection utilities to handle connection errors:

0 commit comments

Comments
 (0)