ai 7.0.130 → 7.0.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +96 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -8
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/02-foundations/04-tools.mdx +1 -1
- package/docs/03-agents/06-memory.mdx +1 -1
- package/docs/03-ai-sdk-core/17-mcp-events.mdx +169 -0
- package/docs/03-ai-sdk-core/42-batch.mdx +2 -2
- package/docs/03-ai-sdk-core/45-provider-management.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +13 -0
- package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +41 -2
- package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +10 -1
- package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +10 -1
- package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +46 -1
- package/docs/07-reference/01-ai-sdk-core/24-mcp-events.mdx +797 -0
- package/docs/07-reference/01-ai-sdk-core/index.mdx +6 -0
- package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +13 -0
- package/package.json +6 -6
- package/src/agent/create-agent-ui-stream-response.ts +6 -10
- package/src/agent/create-agent-ui-stream.ts +22 -4
- package/src/agent/pipe-agent-ui-stream-to-response.ts +6 -10
- package/src/generate-text/stream-text-result.ts +11 -0
- package/src/generate-text/stream-text.ts +9 -0
- package/src/ui-message-stream/to-ui-message-stream.ts +10 -2
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: MCP Events
|
|
3
|
+
description: Reference for experimental MCP event methods, the webhook handler, and subscription and storage types.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MCP Events
|
|
7
|
+
|
|
8
|
+
This page covers the experimental methods on `client.experimental_events`, the webhook
|
|
9
|
+
handler, and their public types. Create the client with
|
|
10
|
+
[`createMCPClient`](/docs/reference/ai-sdk-core/create-mcp-client). See the
|
|
11
|
+
[MCP Events guide](/docs/ai-sdk-core/mcp-events) for the application flow.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
17
|
+
import { eventStore } from './event-store';
|
|
18
|
+
|
|
19
|
+
const client = await createMCPClient({
|
|
20
|
+
transport: { type: 'http', url: 'https://service.example/mcp' },
|
|
21
|
+
experimental_events: { store: eventStore },
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`experimental_events` accepts [`Experimental_MCPEventsConfig`](/docs/reference/ai-sdk-core/mcp-events#client-configuration).
|
|
26
|
+
In direct mode, the store is required for subscribe, refresh, and unsubscribe.
|
|
27
|
+
Alternatively, configure a managed adapter as described below. Discovery works
|
|
28
|
+
without configuring either mode. Mount the
|
|
29
|
+
[webhook handler](/docs/reference/ai-sdk-core/mcp-events#webhook-handler) before subscribing.
|
|
30
|
+
|
|
31
|
+
The methods below belong to `client.experimental_events`; they are not standalone imports.
|
|
32
|
+
|
|
33
|
+
## Listing Events
|
|
34
|
+
|
|
35
|
+
### `client.experimental_events.list()`
|
|
36
|
+
|
|
37
|
+
Sends `events/list` and returns one catalog page.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const { events, nextCursor } = await client.experimental_events.list();
|
|
41
|
+
console.log(events.map(event => event.name));
|
|
42
|
+
|
|
43
|
+
if (nextCursor != null) {
|
|
44
|
+
const nextPage = await client.experimental_events.list({
|
|
45
|
+
params: { cursor: nextCursor },
|
|
46
|
+
});
|
|
47
|
+
console.log(nextPage.events);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Parameters
|
|
52
|
+
|
|
53
|
+
The optional argument has these properties:
|
|
54
|
+
|
|
55
|
+
<PropertiesTable
|
|
56
|
+
content={[
|
|
57
|
+
{
|
|
58
|
+
name: 'params',
|
|
59
|
+
type: '{ cursor?: string }',
|
|
60
|
+
description:
|
|
61
|
+
'Pagination parameters. Omit to read the first page. Pass a previous nextCursor to read the next page.',
|
|
62
|
+
isOptional: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'options',
|
|
66
|
+
type: '{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }',
|
|
67
|
+
description:
|
|
68
|
+
'Request cancellation and timeout settings. See Request Options below.',
|
|
69
|
+
isOptional: true,
|
|
70
|
+
},
|
|
71
|
+
]}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
### Returns
|
|
75
|
+
|
|
76
|
+
`Promise<Experimental_ListEventsResult>`:
|
|
77
|
+
|
|
78
|
+
<PropertiesTable
|
|
79
|
+
content={[
|
|
80
|
+
{
|
|
81
|
+
name: 'events',
|
|
82
|
+
type: 'Experimental_MCPEventDefinition[]',
|
|
83
|
+
description:
|
|
84
|
+
'Event definitions with name, optional description, delivery modes, inputSchema, and payloadSchema. See the event definition type reference.',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'nextCursor',
|
|
88
|
+
type: 'string | null',
|
|
89
|
+
description:
|
|
90
|
+
'Cursor for the next catalog page. Missing or null means there is no next page.',
|
|
91
|
+
isOptional: true,
|
|
92
|
+
},
|
|
93
|
+
]}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
See [`Experimental_MCPEventDefinition`](/docs/reference/ai-sdk-core/mcp-events#event-definition)
|
|
97
|
+
for the definition fields. Pagination is explicit; this method does not fetch all pages.
|
|
98
|
+
|
|
99
|
+
## Managed Subscriptions
|
|
100
|
+
|
|
101
|
+
Configure `experimental_events: { adapter }` to delegate subscription lifecycle
|
|
102
|
+
operations to a backend. The adapter implements `Experimental_MCPEventsAdapter`
|
|
103
|
+
from `@ai-sdk/mcp`; it is bound to the authorized account and destination by your
|
|
104
|
+
application or integration package.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { createMCPClient } from '@ai-sdk/mcp';
|
|
108
|
+
import { authenticatedTransport, eventsAdapter } from '@/lib/mcp';
|
|
109
|
+
|
|
110
|
+
const client = await createMCPClient({
|
|
111
|
+
transport: authenticatedTransport,
|
|
112
|
+
experimental_events: { adapter: eventsAdapter },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Authenticated MCP discovery; this does not list saved subscriptions.
|
|
117
|
+
const catalog = await client.experimental_events.list();
|
|
118
|
+
|
|
119
|
+
const watch = await client.experimental_events.subscribe({
|
|
120
|
+
name: 'comment.created',
|
|
121
|
+
arguments: { document_id: 'doc_123' },
|
|
122
|
+
context: { bindingId: 'saved_intent_123' },
|
|
123
|
+
expiresAt: null,
|
|
124
|
+
idempotencyKey: 'saved_intent_123',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const state = await client.experimental_events.getSubscription({
|
|
128
|
+
id: watch.id,
|
|
129
|
+
});
|
|
130
|
+
const page = await client.experimental_events.listSubscriptions({
|
|
131
|
+
status: 'active',
|
|
132
|
+
limit: 20,
|
|
133
|
+
});
|
|
134
|
+
const stopped = await client.experimental_events.unsubscribe({
|
|
135
|
+
id: watch.id,
|
|
136
|
+
});
|
|
137
|
+
} finally {
|
|
138
|
+
await client.close();
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`authenticatedTransport` and `eventsAdapter` are supplied by your application.
|
|
143
|
+
Use the same authorized account for discovery and subscription management.
|
|
144
|
+
Never derive account identity, destination or authorization from model arguments.
|
|
145
|
+
|
|
146
|
+
### Adapter Contract
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import type { JSONObject } from '@ai-sdk/provider';
|
|
150
|
+
|
|
151
|
+
type ManagedSubscription = {
|
|
152
|
+
id: string;
|
|
153
|
+
name: string;
|
|
154
|
+
arguments: JSONObject;
|
|
155
|
+
status:
|
|
156
|
+
| 'pending'
|
|
157
|
+
| 'active'
|
|
158
|
+
| 'needs_auth'
|
|
159
|
+
| 'stopped'
|
|
160
|
+
| 'expired'
|
|
161
|
+
| 'failed';
|
|
162
|
+
expiresAt: string | null;
|
|
163
|
+
cleanupStatus?: 'pending' | 'complete' | 'exhausted';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
type ManagedSubscribeInput = {
|
|
167
|
+
name: string;
|
|
168
|
+
arguments: JSONObject;
|
|
169
|
+
context?: JSONObject;
|
|
170
|
+
idempotencyKey: string;
|
|
171
|
+
options?: {
|
|
172
|
+
signal?: AbortSignal;
|
|
173
|
+
timeout?: number;
|
|
174
|
+
maxTotalTimeout?: number;
|
|
175
|
+
};
|
|
176
|
+
} & (
|
|
177
|
+
| { ttlMs: number; expiresAt?: never }
|
|
178
|
+
| { expiresAt: string | null; ttlMs?: never }
|
|
179
|
+
);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
These types are exported as `Experimental_ManagedSubscription` and
|
|
183
|
+
`Experimental_ManagedSubscribeInput`. Supply exactly one monitoring lifetime:
|
|
184
|
+
a positive `ttlMs`, an ISO deadline in `expiresAt`, or `expiresAt: null` to
|
|
185
|
+
monitor until stopped. The backend validates these values and enforces its limits.
|
|
186
|
+
|
|
187
|
+
`Experimental_MCPEventsAdapter` has four operations:
|
|
188
|
+
|
|
189
|
+
| Operation | Input | Result |
|
|
190
|
+
| ------------------- | ------------------------------------------------- | ---------------------------------- |
|
|
191
|
+
| `subscribe` | `Experimental_ManagedSubscribeInput` | `Experimental_ManagedSubscription` |
|
|
192
|
+
| `getSubscription` | `{ id, options? }` | `Experimental_ManagedSubscription` |
|
|
193
|
+
| `listSubscriptions` | Optional `{ cursor?, limit?, status?, options? }` | `{ subscriptions, nextCursor? }` |
|
|
194
|
+
| `unsubscribe` | `{ id, options? }` | `Experimental_ManagedSubscription` |
|
|
195
|
+
|
|
196
|
+
All operations return promises. The client passes inputs, cancellation/timeout
|
|
197
|
+
options, results and errors through without retries or remapping. The adapter
|
|
198
|
+
must implement request-option handling and retain actionable backend errors,
|
|
199
|
+
such as consent required, quota exceeded or temporary unavailability.
|
|
200
|
+
|
|
201
|
+
Managed IDs identify backend records. `expiresAt` is the application's monitoring
|
|
202
|
+
deadline, not the upstream MCP `refreshBefore` grant. A pending result means
|
|
203
|
+
durable acceptance by the backend; upstream setup may still be running.
|
|
204
|
+
Stopping can return `cleanupStatus: 'pending'` while remote cleanup continues.
|
|
205
|
+
|
|
206
|
+
The backend owns callback URLs, secrets, upstream subscription calls, verification,
|
|
207
|
+
renewal and cleanup. Managed `subscribe` does not implicitly discover the catalog,
|
|
208
|
+
generate a secret, write a local store or call upstream `events/subscribe`.
|
|
209
|
+
`getSubscription` and `listSubscriptions` are adapter methods, not MCP wire methods.
|
|
210
|
+
The managed facade has no `refresh()`; the backend renews finite upstream leases
|
|
211
|
+
even when the application watch has no deadline.
|
|
212
|
+
|
|
213
|
+
Persist an application intent and reuse its idempotency key after an uncertain
|
|
214
|
+
create result, within the backend's documented recovery window. A timeout or
|
|
215
|
+
aborted request does not prove that a remote subscription was cancelled.
|
|
216
|
+
Closing the MCP client does not unsubscribe managed watches.
|
|
217
|
+
|
|
218
|
+
Managed delivery is separate from this client. Use the backend's delivery verifier
|
|
219
|
+
and receiver contract; `experimental_createMCPEventWebhook` serves direct MCP
|
|
220
|
+
webhooks. Applications still own authorized routing, durable receipt acceptance
|
|
221
|
+
and idempotent work dispatch. The adapter does not invoke event callbacks.
|
|
222
|
+
|
|
223
|
+
### Configuration and Types
|
|
224
|
+
|
|
225
|
+
Choose either `experimental_events: { adapter }` or `experimental_events: { store }`.
|
|
226
|
+
Mixing them is rejected in TypeScript and at runtime. `validateArguments` is a
|
|
227
|
+
direct-mode option; managed validation belongs to the backend. The unreleased
|
|
228
|
+
`events` configuration spelling is replaced by `experimental_events`.
|
|
229
|
+
|
|
230
|
+
`createMCPClient` infers `Experimental_ManagedMCPClient` with
|
|
231
|
+
`Experimental_ManagedMCPEvents` when an adapter is configured. Both modes use
|
|
232
|
+
`MCPClientConfig`, whose `experimental_events` property accepts the
|
|
233
|
+
`Experimental_MCPEventsConfig` union. Direct clients keep `MCPClient` and
|
|
234
|
+
`Experimental_MCPEvents`. The input and result types of each mode remain distinct.
|
|
235
|
+
|
|
236
|
+
Use `satisfies MCPClientConfig` when saving a configuration to retain inference of
|
|
237
|
+
the selected mode. A configuration whose mode is only known at runtime returns a
|
|
238
|
+
union of the two client types; it does not promise direct-only methods such as
|
|
239
|
+
`refresh()`. Omit `experimental_events` entirely for catalog discovery without a
|
|
240
|
+
store or adapter.
|
|
241
|
+
|
|
242
|
+
The remaining subscribe, refresh and unsubscribe sections describe direct mode.
|
|
243
|
+
|
|
244
|
+
## Subscribing to Events
|
|
245
|
+
|
|
246
|
+
### `client.experimental_events.subscribe(options)`
|
|
247
|
+
|
|
248
|
+
Discovers the named event, checks webhook support, saves a pending subscription,
|
|
249
|
+
and sends `events/subscribe`.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const subscription = await client.experimental_events.subscribe({
|
|
253
|
+
name: 'comment.created',
|
|
254
|
+
arguments: { document_id: 'doc_123' },
|
|
255
|
+
delivery: {
|
|
256
|
+
mode: 'webhook',
|
|
257
|
+
url: 'https://app.example/api/mcp-events',
|
|
258
|
+
},
|
|
259
|
+
ttlMs: 3_600_000,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
console.log(subscription.id, subscription.refreshBefore);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Parameters
|
|
266
|
+
|
|
267
|
+
`options` has type [`Experimental_SubscribeEventOptions`](/docs/reference/ai-sdk-core/mcp-events#subscription-options):
|
|
268
|
+
|
|
269
|
+
<PropertiesTable
|
|
270
|
+
content={[
|
|
271
|
+
{
|
|
272
|
+
name: 'name',
|
|
273
|
+
type: 'string',
|
|
274
|
+
description: 'Name of the event to subscribe to.',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'arguments',
|
|
278
|
+
type: 'JSONObject',
|
|
279
|
+
description:
|
|
280
|
+
'Event filters. Defaults to {}. The SDK does not automatically validate them against inputSchema; experimental_events.validateArguments can provide application validation.',
|
|
281
|
+
isOptional: true,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'delivery',
|
|
285
|
+
type: "{ mode: 'webhook'; url: string; secret?: string; allowInsecureLocalhost?: boolean }",
|
|
286
|
+
description: 'Webhook delivery configuration.',
|
|
287
|
+
properties: [
|
|
288
|
+
{
|
|
289
|
+
type: 'Webhook delivery',
|
|
290
|
+
parameters: [
|
|
291
|
+
{
|
|
292
|
+
name: 'mode',
|
|
293
|
+
type: "'webhook'",
|
|
294
|
+
description: 'The only supported delivery mode.',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'url',
|
|
298
|
+
type: 'string',
|
|
299
|
+
description:
|
|
300
|
+
'Reachable HTTPS callback URL without credentials or a fragment. The SDK adds a mcp_event_subscription query parameter; preserve it when forwarding requests.',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'secret',
|
|
304
|
+
type: 'string',
|
|
305
|
+
description:
|
|
306
|
+
'Standard Webhooks secret in whsec_ format containing 24\u201364 base64-encoded random bytes. When omitted, the SDK generates a 32-byte secret. Saved privately under delivery.secret before events/subscribe is sent.',
|
|
307
|
+
isOptional: true,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'allowInsecureLocalhost',
|
|
311
|
+
type: 'boolean',
|
|
312
|
+
description:
|
|
313
|
+
'Defaults to false. Allows HTTP only for literal 127.0.0.1 or [::1] callback addresses during local development. This option is not sent to the server.',
|
|
314
|
+
isOptional: true,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'cursor',
|
|
322
|
+
type: 'string | null',
|
|
323
|
+
description: 'Replay position. Defaults to null.',
|
|
324
|
+
isOptional: true,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: 'ttlMs',
|
|
328
|
+
type: 'number | null',
|
|
329
|
+
description:
|
|
330
|
+
'Requested lifetime in milliseconds, as a positive safe integer. Omit for the server default; null requests no expiration. The server chooses the granted deadline.',
|
|
331
|
+
isOptional: true,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: 'maxAgeMs',
|
|
335
|
+
type: 'number',
|
|
336
|
+
description:
|
|
337
|
+
'Optional replay-age bound in milliseconds, as a nonnegative safe integer.',
|
|
338
|
+
isOptional: true,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'options',
|
|
342
|
+
type: '{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }',
|
|
343
|
+
description:
|
|
344
|
+
'Cancellation and timeout settings for MCP requests. Catalog discovery can make multiple requests.',
|
|
345
|
+
isOptional: true,
|
|
346
|
+
},
|
|
347
|
+
]}
|
|
348
|
+
/>
|
|
349
|
+
|
|
350
|
+
Pending records are retained when subscription creation fails, because a timed-out
|
|
351
|
+
request might have registered remotely. Obtain the pending record's `key` from
|
|
352
|
+
your application's store and recover with `refresh({ key })`, or
|
|
353
|
+
cancel with `unsubscribe({ key })`. Both reuse the saved callback
|
|
354
|
+
identity even when no server ID was saved. Calling `subscribe` again
|
|
355
|
+
creates a new callback identity.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
// pending is the record retained by your application's store after a failed subscribe.
|
|
359
|
+
const recovered = await client.experimental_events.refresh({
|
|
360
|
+
key: pending.key,
|
|
361
|
+
});
|
|
362
|
+
// Alternatively, cancel the uncertain remote subscription:
|
|
363
|
+
await client.experimental_events.unsubscribe({ key: pending.key });
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Returns
|
|
367
|
+
|
|
368
|
+
`Promise<Experimental_SubscribeEventResult>`:
|
|
369
|
+
|
|
370
|
+
<PropertiesTable
|
|
371
|
+
content={[
|
|
372
|
+
{
|
|
373
|
+
name: 'id',
|
|
374
|
+
type: 'string',
|
|
375
|
+
description:
|
|
376
|
+
'Server-derived subscription ID. Use it with refresh and unsubscribe.',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: 'refreshBefore',
|
|
380
|
+
type: 'string | null',
|
|
381
|
+
description:
|
|
382
|
+
'Server-granted renewal deadline as an ISO timestamp. Null means no renewal deadline was granted.',
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
name: 'cursor',
|
|
386
|
+
type: 'string | null',
|
|
387
|
+
description:
|
|
388
|
+
'Server-provided replay cursor. If the server omits it, the SDK returns null. Null means no replay cursor is available.',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'truncated',
|
|
392
|
+
type: 'boolean',
|
|
393
|
+
description:
|
|
394
|
+
'Whether the server reports that the requested replay history was truncated.',
|
|
395
|
+
},
|
|
396
|
+
]}
|
|
397
|
+
/>
|
|
398
|
+
|
|
399
|
+
The result excludes the signing secret. The store is updated with the server ID,
|
|
400
|
+
deadline, cursor, and active status before the method resolves.
|
|
401
|
+
|
|
402
|
+
## Refreshing a Subscription
|
|
403
|
+
|
|
404
|
+
### `client.experimental_events.refresh(options)`
|
|
405
|
+
|
|
406
|
+
Loads a saved subscription by ID or storage key and sends `events/subscribe` with
|
|
407
|
+
its original name, arguments, callback URL, secret, requested lifetime, and latest
|
|
408
|
+
saved cursor.
|
|
409
|
+
|
|
410
|
+
The subscription is marked pending before renewal. Event deliveries return `503`
|
|
411
|
+
until the response saves the new expiration and restores active status. If renewal
|
|
412
|
+
fails, the record stays pending because the server may have renewed it already;
|
|
413
|
+
you can retry refresh or unsubscribe using the saved ID or key.
|
|
414
|
+
|
|
415
|
+
Verified termination messages for a subscription with a saved ID are handled even
|
|
416
|
+
while renewal is pending. For example, if access was revoked, `onTerminated` can
|
|
417
|
+
still run and the saved subscription is removed after successful handling.
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
const renewed = await client.experimental_events.refresh({
|
|
421
|
+
id: subscription.id,
|
|
422
|
+
});
|
|
423
|
+
console.log(renewed.refreshBefore);
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Parameters
|
|
427
|
+
|
|
428
|
+
Provide exactly one of `id` or `key`.
|
|
429
|
+
|
|
430
|
+
<PropertiesTable
|
|
431
|
+
content={[
|
|
432
|
+
{
|
|
433
|
+
name: 'id',
|
|
434
|
+
type: 'string',
|
|
435
|
+
description: 'ID of a saved subscription in the configured store.',
|
|
436
|
+
isOptional: true,
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'key',
|
|
440
|
+
type: 'string',
|
|
441
|
+
description:
|
|
442
|
+
'Storage key of a saved subscription. Use this to recover a pending subscription whose server ID is unknown.',
|
|
443
|
+
isOptional: true,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: 'options',
|
|
447
|
+
type: '{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }',
|
|
448
|
+
description: 'Request cancellation and timeout settings.',
|
|
449
|
+
isOptional: true,
|
|
450
|
+
},
|
|
451
|
+
]}
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
### Returns
|
|
455
|
+
|
|
456
|
+
`Promise<Experimental_SubscribeEventResult>`, with the same fields as
|
|
457
|
+
[subscribe](#subscribing-to-events). The new deadline and cursor are saved locally.
|
|
458
|
+
|
|
459
|
+
Your application schedules renewal before `refreshBefore`. The SDK does not run
|
|
460
|
+
a background renewal timer. Refresh reuses the saved filters without calling
|
|
461
|
+
`validateArguments` again.
|
|
462
|
+
|
|
463
|
+
## Unsubscribing from Events
|
|
464
|
+
|
|
465
|
+
### `client.experimental_events.unsubscribe(options)`
|
|
466
|
+
|
|
467
|
+
Resolves the ID or storage key to the saved name, arguments, and callback URL and
|
|
468
|
+
sends `events/unsubscribe`. It deletes the local record only after the server
|
|
469
|
+
succeeds.
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
await client.experimental_events.unsubscribe({ id: subscription.id });
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Parameters
|
|
476
|
+
|
|
477
|
+
Provide exactly one of `id` or `key`.
|
|
478
|
+
|
|
479
|
+
<PropertiesTable
|
|
480
|
+
content={[
|
|
481
|
+
{
|
|
482
|
+
name: 'id',
|
|
483
|
+
type: 'string',
|
|
484
|
+
description: 'ID of a saved subscription in the configured store.',
|
|
485
|
+
isOptional: true,
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: 'key',
|
|
489
|
+
type: 'string',
|
|
490
|
+
description:
|
|
491
|
+
'Storage key of a saved subscription. Use this to cancel a pending subscription whose server ID is unknown.',
|
|
492
|
+
isOptional: true,
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'options',
|
|
496
|
+
type: '{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }',
|
|
497
|
+
description: 'Request cancellation and timeout settings.',
|
|
498
|
+
isOptional: true,
|
|
499
|
+
},
|
|
500
|
+
]}
|
|
501
|
+
/>
|
|
502
|
+
|
|
503
|
+
### Returns
|
|
504
|
+
|
|
505
|
+
`Promise<void>`. If the server request fails, the local record is retained.
|
|
506
|
+
Calling `client.close()` only closes the MCP connection; it does not unsubscribe.
|
|
507
|
+
|
|
508
|
+
## Request Options
|
|
509
|
+
|
|
510
|
+
All four methods accept an `options` property with the following fields.
|
|
511
|
+
The listing method takes `{ params, options }`; the other methods include
|
|
512
|
+
`options` alongside their own parameters.
|
|
513
|
+
|
|
514
|
+
<PropertiesTable
|
|
515
|
+
content={[
|
|
516
|
+
{
|
|
517
|
+
name: 'signal',
|
|
518
|
+
type: 'AbortSignal',
|
|
519
|
+
description: 'Abort the MCP request.',
|
|
520
|
+
isOptional: true,
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'timeout',
|
|
524
|
+
type: 'number',
|
|
525
|
+
description: 'Request timeout in milliseconds.',
|
|
526
|
+
isOptional: true,
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: 'maxTotalTimeout',
|
|
530
|
+
type: 'number',
|
|
531
|
+
description:
|
|
532
|
+
'Maximum request duration in milliseconds. If timeout is also supplied, the smaller limit applies.',
|
|
533
|
+
isOptional: true,
|
|
534
|
+
},
|
|
535
|
+
]}
|
|
536
|
+
/>
|
|
537
|
+
|
|
538
|
+
## Errors
|
|
539
|
+
|
|
540
|
+
The client rejects requests to servers without the `events` capability.
|
|
541
|
+
Subscription lifecycle methods require `experimental_events.store`; refresh and unsubscribe
|
|
542
|
+
also require a known subscription ID or storage key. Subscribe rejects unknown
|
|
543
|
+
event names, unsupported delivery modes, invalid callback URLs, signing secrets,
|
|
544
|
+
or lifetime settings. These checks use `MCPClientError`, except URL parsing may
|
|
545
|
+
throw `TypeError`.
|
|
546
|
+
|
|
547
|
+
MCP request errors, store failures, and application validation errors propagate
|
|
548
|
+
to the caller. Event requests are not retried by the client's tool-call retry
|
|
549
|
+
setting. See [MCP client error handling](/docs/reference/ai-sdk-core/create-mcp-client#error-handling).
|
|
550
|
+
|
|
551
|
+
## Webhook Handler
|
|
552
|
+
|
|
553
|
+
### `experimental_createMCPEventWebhook()`
|
|
554
|
+
|
|
555
|
+
Creates a Web Request/Response handler for experimental MCP webhook events.
|
|
556
|
+
It verifies callback challenges and event signatures using subscriptions saved by
|
|
557
|
+
[`client.experimental_events.subscribe`](/docs/reference/ai-sdk-core/mcp-events#subscribing-to-events).
|
|
558
|
+
|
|
559
|
+
### Import
|
|
560
|
+
|
|
561
|
+
<Snippet
|
|
562
|
+
text={`import { experimental_createMCPEventWebhook } from "@ai-sdk/mcp"`}
|
|
563
|
+
prompt={false}
|
|
564
|
+
/>
|
|
565
|
+
|
|
566
|
+
### Example
|
|
567
|
+
|
|
568
|
+
```ts filename="app/api/mcp-events/route.ts"
|
|
569
|
+
import { experimental_createMCPEventWebhook } from '@ai-sdk/mcp';
|
|
570
|
+
import { eventStore } from '@/lib/event-store';
|
|
571
|
+
|
|
572
|
+
export const POST = experimental_createMCPEventWebhook({
|
|
573
|
+
store: eventStore,
|
|
574
|
+
async onEvent({ subscription, event }) {
|
|
575
|
+
console.log(subscription.id, event.name, event.data);
|
|
576
|
+
},
|
|
577
|
+
async onGap({ subscription, gap }) {
|
|
578
|
+
// Some events cannot be replayed; reconcile state or notify the agent.
|
|
579
|
+
console.log('Replay gap:', subscription.id, gap.cursor, gap.truncated);
|
|
580
|
+
},
|
|
581
|
+
async onTerminated({ subscription, termination }) {
|
|
582
|
+
// The server ended the subscription; notify the agent of the reason.
|
|
583
|
+
console.log('Subscription terminated:', subscription.id, termination.error);
|
|
584
|
+
},
|
|
585
|
+
onError(error) {
|
|
586
|
+
console.error(error);
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Mount this route before subscribing. The handler and subscribing client must
|
|
592
|
+
access the same private subscription store.
|
|
593
|
+
|
|
594
|
+
### Parameters
|
|
595
|
+
|
|
596
|
+
The argument has type
|
|
597
|
+
[`Experimental_MCPEventWebhookOptions`](/docs/reference/ai-sdk-core/mcp-events#webhook-options).
|
|
598
|
+
|
|
599
|
+
<PropertiesTable
|
|
600
|
+
content={[
|
|
601
|
+
{
|
|
602
|
+
name: 'store',
|
|
603
|
+
type: 'Experimental_MCPEventStore',
|
|
604
|
+
description:
|
|
605
|
+
'Shared subscription storage. Used to look up the routing key and signing secret, and save the cursor after successful handling.',
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
name: 'onEvent',
|
|
609
|
+
type: '({ subscription, event }) => Promise<void>',
|
|
610
|
+
description:
|
|
611
|
+
'Required application handler for verified events. Resolve after successful processing or durable acceptance. Throw to return 503 and allow retry.',
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: 'onGap',
|
|
615
|
+
type: '({ subscription, messageId, gap }) => Promise<void>',
|
|
616
|
+
description:
|
|
617
|
+
'Optional handler when some events cannot be replayed. Reconcile state or notify the agent, then resolve after processing or durable acceptance. The helper saves the fresh cursor and truncated: true; the subscription stays active. Throw to return 503 without changing saved state.',
|
|
618
|
+
isOptional: true,
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: 'onTerminated',
|
|
622
|
+
type: '({ subscription, messageId, termination }) => Promise<void>',
|
|
623
|
+
description:
|
|
624
|
+
'Optional handler when the server ends the subscription. Inspect termination.error for the reason and notify the agent, then resolve after processing or durable acceptance. The helper removes the saved subscription. Throw to return 503 without changing saved state.',
|
|
625
|
+
isOptional: true,
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
name: 'validatePayload',
|
|
629
|
+
type: '({ definition, data }) => void | PromiseLike<void>',
|
|
630
|
+
description:
|
|
631
|
+
'Optional application validation after signature and envelope checks, before onEvent. Receives the saved event definition and event.data. Throw to return 400 without calling onEvent or advancing the cursor.',
|
|
632
|
+
isOptional: true,
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: 'onError',
|
|
636
|
+
type: '(error: unknown) => void',
|
|
637
|
+
description:
|
|
638
|
+
'Reports processing, storage, and payload-validation callback errors. Should not throw. Invalid signatures and malformed requests return their HTTP status without invoking this callback.',
|
|
639
|
+
isOptional: true,
|
|
640
|
+
},
|
|
641
|
+
]}
|
|
642
|
+
/>
|
|
643
|
+
|
|
644
|
+
#### Callback Arguments
|
|
645
|
+
|
|
646
|
+
`onEvent` receives:
|
|
647
|
+
|
|
648
|
+
<PropertiesTable
|
|
649
|
+
content={[
|
|
650
|
+
{
|
|
651
|
+
name: 'subscription',
|
|
652
|
+
type: 'Experimental_MCPEventSubscriptionInfo',
|
|
653
|
+
description:
|
|
654
|
+
'Subscription id, name, arguments, delivery mode and URL, and refreshBefore. Does not include delivery.secret.',
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: 'event',
|
|
658
|
+
type: 'Experimental_MCPEvent',
|
|
659
|
+
description:
|
|
660
|
+
'The eventId, name, timestamp, data object, and optional nullable cursor.',
|
|
661
|
+
},
|
|
662
|
+
]}
|
|
663
|
+
/>
|
|
664
|
+
|
|
665
|
+
See [subscription metadata](/docs/reference/ai-sdk-core/mcp-events#subscription-metadata)
|
|
666
|
+
and [event fields](/docs/reference/ai-sdk-core/mcp-events#event) for their full types.
|
|
667
|
+
|
|
668
|
+
`onGap` and `onTerminated` receive the same safe `subscription` metadata and a
|
|
669
|
+
`messageId` from the signed `webhook-id` header. Each callback receives only its
|
|
670
|
+
matching control payload, so no `type` check is needed. Deduplicate retries by
|
|
671
|
+
subscription ID and `messageId`.
|
|
672
|
+
|
|
673
|
+
- `onGap` receives `gap: { type: 'gap', cursor, truncated: true }`. It means some
|
|
674
|
+
events are no longer available for replay. Reconcile application state or
|
|
675
|
+
notify the agent. The helper adds `truncated: true` and saves it with the fresh
|
|
676
|
+
cursor after the callback resolves. The subscription remains active and event
|
|
677
|
+
delivery continues.
|
|
678
|
+
- `onTerminated` receives
|
|
679
|
+
`termination: { type: 'terminated', error: { code, message, data? } }`. It means
|
|
680
|
+
the server has ended the subscription. Inspect the error for the reason and
|
|
681
|
+
notify the agent. After the callback resolves, the helper deletes the saved
|
|
682
|
+
subscription record.
|
|
683
|
+
|
|
684
|
+
Both return `204` after successful handling. State updates also occur when
|
|
685
|
+
the corresponding callback is omitted. Callback or storage failures return `503`
|
|
686
|
+
for retry. Events and gaps return `503` until activation or renewal is persisted.
|
|
687
|
+
Termination is also accepted during pending renewal when the subscription ID is
|
|
688
|
+
already saved; subscriptions without a saved ID still return `503`.
|
|
689
|
+
Controls are processed even if the saved expiration has passed. They are never
|
|
690
|
+
sent to `onEvent` or `validatePayload`. The helper does not run or notify an agent
|
|
691
|
+
automatically.
|
|
692
|
+
|
|
693
|
+
### Returns
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
(request: Request) => Promise<Response>;
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
The handler accepts `POST` requests with `Content-Type: application/json`.
|
|
700
|
+
Pass the original request body bytes and preserve the `mcp_event_subscription`
|
|
701
|
+
query parameter added by the subscribing client. Do not parse and reserialize
|
|
702
|
+
the body before passing it to this handler.
|
|
703
|
+
|
|
704
|
+
The server must include `webhook-id`, `webhook-timestamp`, `webhook-signature`,
|
|
705
|
+
and `X-MCP-Subscription-Id`. The helper verifies Standard Webhooks v1 HMAC-SHA256
|
|
706
|
+
signatures and accepts signing timestamps within five minutes of the current time.
|
|
707
|
+
An event's `eventId` must match `webhook-id`, and its name must match the subscription.
|
|
708
|
+
|
|
709
|
+
## Event Types
|
|
710
|
+
|
|
711
|
+
The following types are exported from `@ai-sdk/mcp` and are experimental.
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
import type {
|
|
715
|
+
Experimental_MCPEvents,
|
|
716
|
+
Experimental_MCPEventsConfig,
|
|
717
|
+
Experimental_MCPEventDefinition,
|
|
718
|
+
Experimental_ListEventsResult,
|
|
719
|
+
Experimental_SubscribeEventOptions,
|
|
720
|
+
Experimental_SubscribeEventResult,
|
|
721
|
+
Experimental_MCPEvent,
|
|
722
|
+
Experimental_MCPEventControl,
|
|
723
|
+
Experimental_MCPEventSubscription,
|
|
724
|
+
Experimental_MCPEventSubscriptionInfo,
|
|
725
|
+
Experimental_MCPEventStore,
|
|
726
|
+
Experimental_MCPEventWebhookOptions,
|
|
727
|
+
} from '@ai-sdk/mcp';
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
The shapes below use `JSONObject` from `@ai-sdk/provider` for JSON-compatible
|
|
731
|
+
filter objects. Request settings are written inline because `RequestOptions`
|
|
732
|
+
is not exported from `@ai-sdk/mcp`.
|
|
733
|
+
|
|
734
|
+
### Webhook Controls
|
|
735
|
+
|
|
736
|
+
`Experimental_MCPEventControl` is the union of the lifecycle payloads. `onGap`
|
|
737
|
+
receives its `gap` variant; `onTerminated` receives its `terminated` variant as
|
|
738
|
+
`termination`:
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
type Experimental_MCPEventControl =
|
|
742
|
+
| { type: 'gap'; cursor: string; truncated: true }
|
|
743
|
+
| {
|
|
744
|
+
type: 'terminated';
|
|
745
|
+
error: { code: number; message: string; data?: unknown };
|
|
746
|
+
};
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Unknown extension fields are preserved. Verification challenges are echoed
|
|
750
|
+
automatically and do not invoke `onEvent`, `onGap`, or `onTerminated`.
|
|
751
|
+
|
|
752
|
+
### Client Methods
|
|
753
|
+
|
|
754
|
+
`Experimental_MCPEvents` describes the `client.experimental_events` property:
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
interface Experimental_MCPEvents {
|
|
758
|
+
list(options?: {
|
|
759
|
+
params?: { cursor?: string };
|
|
760
|
+
options?: {
|
|
761
|
+
signal?: AbortSignal;
|
|
762
|
+
timeout?: number;
|
|
763
|
+
maxTotalTimeout?: number;
|
|
764
|
+
};
|
|
765
|
+
}): Promise<Experimental_ListEventsResult>;
|
|
766
|
+
|
|
767
|
+
subscribe(
|
|
768
|
+
options: Experimental_SubscribeEventOptions,
|
|
769
|
+
): Promise<Experimental_SubscribeEventResult>;
|
|
770
|
+
|
|
771
|
+
refresh(
|
|
772
|
+
options: ({ id: string; key?: never } | { key: string; id?: never }) & {
|
|
773
|
+
options?: {
|
|
774
|
+
signal?: AbortSignal;
|
|
775
|
+
timeout?: number;
|
|
776
|
+
maxTotalTimeout?: number;
|
|
777
|
+
};
|
|
778
|
+
},
|
|
779
|
+
): Promise<Experimental_SubscribeEventResult>;
|
|
780
|
+
|
|
781
|
+
unsubscribe(
|
|
782
|
+
options: ({ id: string; key?: never } | { key: string; id?: never }) & {
|
|
783
|
+
options?: {
|
|
784
|
+
signal?: AbortSignal;
|
|
785
|
+
timeout?: number;
|
|
786
|
+
maxTotalTimeout?: number;
|
|
787
|
+
};
|
|
788
|
+
},
|
|
789
|
+
): Promise<void>;
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
## Related Guides
|
|
794
|
+
|
|
795
|
+
- [MCP Events guide](/docs/ai-sdk-core/mcp-events)
|
|
796
|
+
- [MCP client configuration](/docs/reference/ai-sdk-core/create-mcp-client)
|
|
797
|
+
- [Runnable example](https://github.com/vercel/ai/tree/main/examples/mcp/src/events)
|