Skip to content
Docs

MCP Events Beta

MCP events let your agents listen for changes from compatible Model Context Protocol (MCP) servers and act when something happens. Build agents that summarize new messages, review newly created issues, or respond to document feedback without polling for updates.

Connect supports webhook delivery and callback verification from the draft MCP Events specification. It creates and renews subscriptions, verifies the server's webhooks, and forwards events to your application using authenticated delivery. Your application verifies each delivery and decides how the agent should respond.

If you're building with eve, see MCP events in eve to configure event callbacks for your MCP connections.

Agents can subscribe to events to respond to new comments, messages, issues, and emails.

Use caseWhat the agent doesEventArguments
Apply document feedbackReads new review comments on a document and drafts the requested edits.comment.createddocument_id
Summarize new messagesPosts a digest of new messages in a channel.message.createdchannel_id
Triage new issuesLabels, prioritizes, and assigns issues as they're filed.issue.createdproject_id
Turn bug reports into pull requestsWatches a feedback channel for bug reports and opens draft pull requests with fixes and tests.message.createdchannel_id
Follow up on inbound emailDrafts a reply or creates a task when a new email arrives in a shared inbox.email.receivedmailbox_id

Event names and arguments vary by server. Call client.experimental_events.list() to see what your server supports.

The rest of this page builds the document feedback use case with a connector named oauth/documents and the comment.created event.

  1. Your application creates a subscription with an event name, arguments, and destination path.
  2. Connect registers a webhook with the MCP server and handles its verification challenge.
  3. The MCP server sends events to Connect. Connect verifies them and forwards them to your destination.
  4. Your receiver verifies Connect's delivery and durably accepts the event before acknowledging it.

Connect persists subscriptions, renews provider leases, and manages expiry and cleanup. Your application owns the receiver, subscription-to-user bindings, deduplication, and event processing. Unlike connector triggers, MCP subscriptions select a particular event and arguments for an authorized account.

MCP event subscriptions currently require Production or a Custom Environment. For a Custom Environment, deploy it and assign a domain. Use a verified domain that serves the deployment without redirecting requests. The SDK authenticates the calling project with its Edgenivo OpenID Connect (OIDC) token.

Install Connect SDK 2.5.2 or later and AI SDK MCP client 2.0.76 or later:

pnpm add @vercel/connect @ai-sdk/mcp

Use connectAuthProvider and experimental_connectEventsAdapter from the same SDK entrypoint. Pass the same resolved token parameters to both helpers:

HelperPurpose
connectAuthProviderObtains credentials for direct MCP requests, including event catalog discovery.
experimental_connectEventsAdapterDelegates subscription management to Connect and receives the transport URL automatically.

The following server-side function assumes a connector named oauth/documents whose server offers comment.created. Replace the connector and URL with your configuration. Derive userId from your authenticated session and authorize access to documentId before calling it. Persist a unique requestId for the creation intent and reuse it when retrying the same request:

lib/watch-document.ts
import { createMCPClient } from '@ai-sdk/mcp';
import {
  connectAuthProvider,
  experimental_connectEventsAdapter,
  type ConnectTokenParams,
} from '@vercel/connect/ai-sdk';
 
export async function watchDocument({
  userId,
  documentId,
  requestId,
}: {
  userId: string;
  documentId: string;
  requestId: string;
}) {
  const connector = 'oauth/documents';
  const tokenParams: ConnectTokenParams = {
    subject: { type: 'user', id: userId },
  };
 
  const client = await createMCPClient({
    transport: {
      type: 'http',
      url: 'https://documents.example/mcp',
      authProvider: connectAuthProvider(connector, tokenParams),
    },
    experimental_events: {
      adapter: experimental_connectEventsAdapter({
        connector,
        tokenParams,
        destination: { path: '/hooks' },
      }),
    },
  });
 
  try {
    return await client.experimental_events.subscribe({
      name: 'comment.created',
      arguments: { document_id: documentId },
      idempotencyKey: requestId,
      ttlMs: null,
    });
  } finally {
    await client.close();
  }
}
lib/watch-document.js
import { createMCPClient } from '@ai-sdk/mcp';
import {
  connectAuthProvider,
  experimental_connectEventsAdapter,
} from '@vercel/connect/ai-sdk';
 
export async function watchDocument({ userId, documentId, requestId }) {
  const connector = 'oauth/documents';
  const tokenParams = {
    subject: { type: 'user', id: userId },
  };
 
  const client = await createMCPClient({
    transport: {
      type: 'http',
      url: 'https://documents.example/mcp',
      authProvider: connectAuthProvider(connector, tokenParams),
    },
    experimental_events: {
      adapter: experimental_connectEventsAdapter({
        connector,
        tokenParams,
        destination: { path: '/hooks' },
      }),
    },
  });
 
  try {
    return await client.experimental_events.subscribe({
      name: 'comment.created',
      arguments: { document_id: documentId },
      idempotencyKey: requestId,
      ttlMs: null,
    });
  } finally {
    await client.close();
  }
}

To discover which events your server offers, call client.experimental_events.list() separately and choose an event and arguments from the authenticated catalog. Each definition includes inputSchema, payloadSchema, and supported delivery modes. Follow nextCursor when the catalog has more pages.

You don't implement an adapter or provide an AI SDK event store. The adapter takes the MCP URL from transport.url; an explicit mcpUrl option overrides it. Connect must authorize that endpoint before using provider credentials. Add scopes or other credential constraints to the shared tokenParams when your provider requires them.

Creation can return pending while upstream registration finishes. Save the returned Connect subscription ID with the authorized user and application intent. Closing the MCP client does not stop the subscription.

Deploy the receiver before creating subscriptions. For the /hooks destination, create a Next.js App Router handler at app/hooks/route.ts or app/hooks/route.js. Use experimental_verifyMcpEvent to authenticate the Connect-to-application hop:

app/hooks/route.ts
import {
  experimental_verifyMcpEvent,
  Experimental_McpEventVerificationError,
} from '@vercel/connect/mcp';
import { acceptEventDelivery } from '@/lib/event-inbox';
 
export async function POST(request: Request) {
  let delivery;
  try {
    delivery = await experimental_verifyMcpEvent(request, {
      path: '/hooks',
    });
  } catch (error) {
    if (error instanceof Experimental_McpEventVerificationError) {
      return new Response(null, {
        status: error.code === 'invalid_delivery' ? 401 : 503,
      });
    }
    throw error;
  }
 
  try {
    await acceptEventDelivery(delivery);
    return new Response(null, { status: 204 });
  } catch {
    return new Response(null, { status: 503 });
  }
}
app/hooks/route.js
import {
  experimental_verifyMcpEvent,
  Experimental_McpEventVerificationError,
} from '@vercel/connect/mcp';
import { acceptEventDelivery } from '@/lib/event-inbox';
 
export async function POST(request) {
  let delivery;
  try {
    delivery = await experimental_verifyMcpEvent(request, {
      path: '/hooks',
    });
  } catch (error) {
    if (error instanceof Experimental_McpEventVerificationError) {
      return new Response(null, {
        status: error.code === 'invalid_delivery' ? 401 : 503,
      });
    }
    throw error;
  }
 
  try {
    await acceptEventDelivery(delivery);
    return new Response(null, { status: 204 });
  } catch {
    return new Response(null, { status: 503 });
  }
}

acceptEventDelivery is an application-owned durable inbox operation, not a Connect SDK export. Implement it before using this handler. It must:

  • Store the verified receipt and deduplicate it by deliveryId.
  • Resolve the subscription's saved, authorized application binding before starting work.
  • Commit receipt acceptance and queued work atomically, then allow the handler to return 204.

A delivery can arrive before your creation request saves the returned subscription ID. Retain it durably for later binding resolution or return a retryable error. Don't infer its recipient from the payload or treat context as authorization. If acceptance fails, return 503; don't acknowledge work that you only started in memory.

The verifier consumes the request body and validates the delivery envelope. It does not deduplicate events, validate your provider-specific payload schema, or run an agent. Validate event.data before processing it. Do not use AI SDK's experimental_createMCPEventWebhook here: that helper verifies direct MCP webhooks, not Connect's forwarded envelopes.

Set path to the registered public destination path, including any rewrite prefix. Keep identity settings in trusted server configuration, not request headers or payloads:

OptionValue
pathRequired public route, such as /hooks.
projectIdDefaults to VERCEL_PROJECT_ID.
environmentDefaults to VERCEL_TARGET_ENV, then VERCEL_ENV.
customEnvironmentIdRequired for a Custom Environment. Use its stable env_... ID.
ownerId, issuer, audienceOptional additional restrictions. ownerId defaults to VERCEL_ORG_ID when available.

Make the system environment variables available to your deployment, or pass the expected values explicitly. Missing receiver identity fails verification. See system environment variables and OIDC for deployment configuration.

Each envelope includes deliveryId, subscriptionId, source.connectorId, and saved application context. It contains one of these payloads:

PayloadWhat your application should do
eventValidate event.data and process the business event.
control.type: 'gap'Reconcile state with the source because some events cannot be replayed.
control.type: 'terminated'Record the provider's reason and stop starting new work for the terminated subscription.

Persist lifecycle changes before dispatching application callbacks. Make worker side effects idempotent because deliveries can repeat or arrive out of order.

Use the returned Connect subscription ID, not an upstream provider ID. The direct SDK calls below avoid opening an MCP connection to inspect or stop a subscription. Authorize the requested subscription against your application's saved binding first:

lib/manage-watch.ts
import {
  experimental_getSubscription,
  experimental_unsubscribe,
} from '@vercel/connect/mcp';
 
export async function getWatch(userId: string, subscriptionId: string) {
  return experimental_getSubscription('oauth/documents', {
    id: subscriptionId,
    expectedSubject: { type: 'user', id: userId },
  });
}
 
export async function stopWatch(userId: string, subscriptionId: string) {
  return experimental_unsubscribe('oauth/documents', {
    id: subscriptionId,
    expectedSubject: { type: 'user', id: userId },
  });
}
lib/manage-watch.js
import {
  experimental_getSubscription,
  experimental_unsubscribe,
} from '@vercel/connect/mcp';
 
export async function getWatch(userId, subscriptionId) {
  return experimental_getSubscription('oauth/documents', {
    id: subscriptionId,
    expectedSubject: { type: 'user', id: userId },
  });
}
 
export async function stopWatch(userId, subscriptionId) {
  return experimental_unsubscribe('oauth/documents', {
    id: subscriptionId,
    expectedSubject: { type: 'user', id: userId },
  });
}

expectedSubject checks that the subscription belongs to the intended account. The SDK also authenticates the calling project and environment. A subscription ID alone does not authorize access.

Use experimental_listSubscriptions from @vercel/connect/mcp with the same tokenParams to list account-scoped subscriptions. The direct SDK returns pagination.next; follow it even if a filtered page is empty. With the AI SDK adapter, client.experimental_events.listSubscriptions() returns nextCursor instead. These lists have account, project, and environment scope, not conversation scope.

StatusMeaning
pendingConnect has saved the subscription, but registration is not yet complete.
activeThe subscription is active.
needs_authThe subscription needs renewed account authorization.
stoppedLocal delivery has stopped. Check cleanupStatus for provider cleanup.
expiredThe application's monitoring deadline has passed.
failedThe subscription failed or the provider terminated it.

A stop operation can return stopped with cleanupStatus: 'pending' while remote cleanup remains unfinished. Retry unsubscribe to finish cleanup. The operation can also report cleanupStatus: 'complete' or 'exhausted'.

Open your connector's Settings and choose when to show MCP Events:

  • Auto (default): Show event subscriptions after the first successful event delivery.
  • Always show: Show event subscriptions before the first delivery.
  • Always hide: Keep event subscriptions hidden, even after future deliveries.

Select Save changes to apply your choice across all projects using this connector. Changing visibility doesn't start or stop subscriptions. Returning to Auto uses the connector's existing delivery history.

When creating a subscription in the dashboard, choose a lifetime from 10 minutes through one year, or Until stopped to monitor until you stop the subscription.

Set ttlMs when subscribing. Use an integer from 120000 to 31536000000 milliseconds (two minutes to 365 days), or ttlMs: null to monitor until explicitly stopped. For example, ttlMs: 600000 monitors for 10 minutes. Connect renews the upstream provider lease within that application lifetime. You don't schedule AI SDK refresh() calls when using the Connect adapter.

Subscription responses include the resolved expiresAt deadline, or null for an indefinite watch. expiresAt is not a creation input in Connect SDK 2.5.2 and later. Replace earlier expiresAt: null inputs with ttlMs: null.

Connect currently forwards deliveries inline. It does not retain a durable payload outbox or provide scheduled redelivery, delivery history, or manual replay. The provider's retry and replay window limits recovery when your receiver is unavailable. Use your durable inbox to accept work quickly, then process it asynchronously.

MCP events are priced as triggers. Each delivery that Connect forwards to your destination counts as one trigger. To stop being billed, stop the subscription.

  • AI SDK and MCP: Configure authenticated MCP requests and handle user consent.
  • SDK Reference: Review event API options, return values, and verification errors.
  • Triggers: Receive provider webhooks configured at the connector level.
Last updated October 14, 2026

Was this helpful?

supported.