本页还没有译文
本页 31 段还没译成한국어,下面显示的是英文原文。MCP Events
还没翻译译自英文(ai-sdk.dev)本页 31 段都还没译
MCP events let a server notify your application when something changes, such as a new document comment. Your MCP client manages the subscription; your webhook endpoint receives the events.
Note
These APIs are experimental and implement the webhook portion of the MCP Events design draft. Polling and push-stream delivery are not supported.
Choose who manages subscriptions
Use experimental_events: { adapter } when a backend manages subscriptions,
webhook delivery and renewal. Supply an adapter whose
createAdapter({ transport }) receives the MCP transport type and URL
(or just the type for custom transports) and returns bound
managed watch operations. No AI SDK event store is required. A backend SDK helper can
return this adapter directly, so callers do not need an inline createAdapter
wrapper. See managed subscriptions.
Use experimental_events: { store } when your application directly registers
MCP webhooks, persists their secrets and schedules renewal. The following flow
describes this direct mode. Event catalog discovery works in either mode, and
without either configuration.
How MCP events work
- The client sends
events/subscribewith the event name, filters, webhook URL, and signing secret. OptionalttlMsandcursorparameters set the requested lifetime and replay position. - The server can send a signed challenge to verify the webhook before accepting
the subscription. After verification, it returns the subscription ID,
refreshBefore,cursor, andtruncated. - As events occur, the server sends signed POST requests to the webhook. The webhook verifies each signature, handles the event, and acknowledges delivery.
- The client renews the subscription before
refreshBeforeby sendingevents/subscribewith the same identity, or stops it withevents/unsubscribe. Subscriptions with a finite lifetime expire if they are not renewed.
Your application decides what to do with each event. Deliveries may be duplicated or reordered, so use the subscription ID and event ID to deduplicate processing.
Using MCP Events
Create an MCP client with createMCPClient and manage subscriptions through
client.experimental_events. The following methods are experimental:
| Method | Description |
|---|---|
list() | List event names, schemas, and delivery modes with events/list. |
subscribe(options) | Save the signing secret, send events/subscribe, and return the subscription result. |
refresh({ id }) | Renew an existing subscription with events/subscribe. |
unsubscribe({ id }) | Send events/unsubscribe and remove the saved subscription after success. |
Refresh reuses the saved callback URL, arguments, signing secret, and latest
cursor. Your application schedules renewal before refreshBefore.
To receive events, use
experimental_createMCPEventWebhook({ store, onEvent })
as your webhook handler. It handles signed verification challenges, verifies
incoming event signatures, and passes each event directly to your onEvent
callback.
Connect the webhook and client
Both use an application-provided eventStore implementing
Experimental_MCPEventStore. This is an AI SDK storage adapter, not an MCP
protocol primitive. It privately persists subscription identities, callback URLs,
signing secrets, and cursors so both sides can access them. See the
file-store example
for the five methods: get, getById, set, update, and delete. Use shared
durable storage scoped to one MCP server and authenticated principal;
update must atomically merge the supplied fields.
First, expose the webhook route. It must be reachable before subscribing.
import { experimental_createMCPEventWebhook } from '@ai-sdk/mcp';
import { eventStore } from '@/lib/event-store';
export const POST = experimental_createMCPEventWebhook({
store: eventStore,
async onEvent({ subscription, event }) {
console.log(subscription.id, event.name, event.data);
},
async onGap({ subscription, gap }) {
// Some events cannot be replayed; reconcile state or notify the agent.
console.log('Replay gap:', subscription.id, gap.cursor, gap.truncated);
},
async onTerminated({ subscription, termination }) {
// The server ended the subscription; notify the agent of the reason.
console.log('Subscription terminated:', subscription.id, termination.error);
},
});The helper verifies signatures and echoes challenges automatically. It invokes
onEvent without exposing the secret, then returns 204 after successful
processing. Processing failures return 503 so the server can retry. In your
app, resolve onEvent only after processing or durably accepting the event.
Identify duplicate agent runs. If an agent finishes inside onEvent but a
later operation throws (for example, a database write), the handler returns 503.
Redelivery can then run the agent again for the same event, producing another
invoke_agent trace. These spans do not automatically include the MCP server or
event identity.
With an OpenTelemetry tracer provider configured,
use this pattern inside onEvent to attach that identity to agent spans. Here,
model is your configured language model, and subscription and event are the
callback arguments:
import { OpenTelemetry } from '@ai-sdk/otel';
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({
model,
runtimeContext: {
mcpServer: 'comments-server', // Identify the server this handler receives from.
eventName: event.name,
subscriptionId: subscription.id,
eventId: event.eventId,
},
telemetry: {
includeRuntimeContext: {
mcpServer: true,
eventName: true,
subscriptionId: true,
eventId: true,
},
integrations: new OpenTelemetry({
enrichSpan: ({ runtimeContext }) => ({
'app.mcp.server': runtimeContext?.mcpServer as string | undefined,
'app.mcp.event_name': runtimeContext?.eventName as string | undefined,
'app.mcp.subscription_id': runtimeContext?.subscriptionId as
| string
| undefined,
'app.mcp.event_id': runtimeContext?.eventId as string | undefined,
}),
}),
},
});
await agent.generate({ prompt: JSON.stringify(event.data) });This makes duplicate runs identifiable; your application still needs to deduplicate
processing. See custom OpenTelemetry span attributes
for more about enrichSpan.
Two optional callbacks handle signed subscription lifecycle messages:
onGap({ subscription, messageId, gap }): some events are no longer available for replay. Reconcile application state or notify the agent. After the callback succeeds, the helper savesgap.cursorandtruncated: true; the subscription stays active and event delivery continues.onTerminated({ subscription, messageId, termination }): the server has ended the subscription. Inspecttermination.errorfor the reason and notify the agent. After the callback succeeds, the helper removes the saved subscription.
State updates also occur when the corresponding callback is omitted. Callback or
storage failures return 503 for retry. Resolve callbacks only after processing
or durable acceptance, and deduplicate by subscription ID and messageId (the
signed webhook-id). The helper does not run or notify an agent automatically.
Then subscribe using the same store.
import { createMCPClient } from '@ai-sdk/mcp';
import { eventStore } from '@/lib/event-store';
const client = await createMCPClient({
transport: { type: 'http', url: 'https://service.example/mcp' },
experimental_events: { store: eventStore },
});
try {
const { events } = await client.experimental_events.list();
console.log(events.map(event => event.name));
const subscription = await client.experimental_events.subscribe({
name: 'comment.created',
arguments: { document_id: 'doc_123' },
delivery: {
mode: 'webhook',
url: 'https://my-app.example/api/mcp-events',
},
ttlMs: 60 * 60 * 1000,
});
// Save the ID and schedule renewal before the server's granted deadline.
console.log(subscription.id, subscription.refreshBefore);
} finally {
await client.close(); // Webhook delivery continues until unsubscribe or expiry.
}After catalog discovery and before sending events/subscribe, the SDK generates and saves
delivery.secret. It appends a mcp_event_subscription query parameter to the
callback URL so the webhook can find that secret even while verification is
pending. Preserve this parameter and pass the raw request body to the handler.
Runnable example
For a step-by-step walkthrough of receiving events and choosing a subscription backend, see the MCP events recipe.
The local example
uses @modelcontextprotocol/server to emit a comment and the AI SDK to subscribe,
verify, and receive it. It needs no model API key. Local HTTP callbacks require
delivery.allowInsecureLocalhost: true; production callbacks use HTTPS.