# MCP Events let an agent react without being asked. Here's how we'd make one survive a restart

> MCP Events let a customer's agent subscribe to your platform and act the moment something changes. Where Temporal and Databricks belong behind it.

Published: 2026-10-15
Author: Preetham Reddy
Tags: Applied AI, Temporal, Workflows
Canonical: https://www.techfabric.com/blog/mcp-events-agents-temporal-workflows

---

TechFabric's engineers build applied AI for companies that have more work than engineers, and lately most of that work has been autonomous agents that sit inside a loan process, a warehouse system or a support queue and finish a business workflow end to end. We'd build them on several agent harnesses. Every one of them had the same blind spot, because MCP, the protocol they use to reach a company's systems, was request and response. An agent called a tool, got data back, and went quiet until a person prompted it again.

A draft extension called MCP Events is meant to close that gap. It's a draft extension to the Model Context Protocol where your server notifies the client when something changes in a connected app. The client subscribes with `events/subscribe`, and your server stores that subscription and delivers matching events to a webhook until it expires or is ended, as [WorkOS sets out in its walkthrough of the draft](https://workos.com/blog/mcp-events-chatgpt-subscription-revocation). If you own the platform other people's agents connect to, that turns those agents from things a person queries into things that act the minute a payoff quote lands or a shipment slips.

The other half of this is durable execution, which is ground we've covered. For [Location Services](/case-studies/mitigating-risk-in-vehicle-repossession-using-temporals-durable-execution) we'd rebuild assignment closure as a durable workflow, so a single unretried API call can't turn into real repossession risk.

In this post I'll cover what an event subscription leaves in your database, where Temporal belongs behind it, how Databricks fits when the event has to be judged against data instead of just forwarded, and the request I'd refuse to grant: a subscription with no expiry.

## What a subscription leaves in your database

The client calls `events/subscribe` with an event name, filter arguments and a delivery destination. Here is the shape, as an illustration of what a lending platform would receive:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "events/subscribe",
  "params": {
    "name": "payoff_quote.returned",
    "arguments": { "deal_id": "deal_4471" },
    "delivery": {
      "mode": "webhook",
      "url": "https://agent.example.com/hooks/mcp",
      "secret": "whsec_..."
    },
    "cursor": null
  }
}
```

Read what that leaves behind. You now hold a durable row naming a user, a destination on the public internet and a signing secret, created under an access token that will be dead long before the row is.

That asymmetry is the thing to design around. A subscription is keyed on the principal, the callback URL, the event name and the arguments, a tuple that carries no token, no scopes and no expiry of its own, and it keeps delivering for whatever TTL you granted.

It does mean the defaults are yours to pick.

```mermaid
%% caption: The access token behind a subscribe call is rechecked on every request and soon expires, while the subscription it created keeps delivering until its TTL lapses or a scheduled reverification ends it.
flowchart TD
  A["Agent calls events subscribe"] --> B["Access token checked each request"]
  A --> C["Subscription row stored on your server"]
  B --> D["Token expires and fails"]
  C --> E["Deliveries continue for the granted TTL"]
  E --> F["Scheduled reverification ends it early"]
  D --> G["No signal reaches the subscription"]
  class F accent
```

## Grant short TTLs and refuse subscriptions with no expiry

My position, before anyone at a client writes the handler. Refuse `ttlMs: null` and grant short, finite TTLs, because the TTL you grant is the window in which a revoked subscription is still live and nobody can see it. Then reverify access on a schedule you can say out loud.

Grant no expiry and there's no refresh, so there's no error either.

The failure that costs money runs the other way. If your server only authorised at subscribe time, which is all the draft strictly requires, every matching change keeps being posted to a callback that still answers until the subscription lapses, and your audit log shows a successful webhook delivery and nothing else.

Two indexes earn their keep on that table. One on the user, so offboarding ends every subscription that user created instead of assuming it died with their account, and one on the time of last reverification, so a sweep turns periodically into a number you can state. Wire the per-user delete into whatever already handles deprovisioning. On the client side the same discipline applies in reverse, where the agent's own identity decides what it may subscribe to and a shared service account doesn't. Build it with one token for everybody and you'll build it twice.

## Let the event start a durable workflow and nothing else

This is where an implementation can fall over. Teams handle the notification inside the agent loop. Receive event, read the record, call the model, call your APIs, write something back. That works in a demo. It breaks when a model call times out mid-sequence, when the worker is redeployed between one API call and the next, or when the same event arrives twice because a delivery was retried. The agent loop keeps its progress in memory, and a restart loses it.

So we'd give the webhook one job. Verify the signature, deduplicate, start or signal a workflow, return. The reasoning, the API calls, the retries and the human approval all live in the workflow, which is exactly the split that made failed steps retry on their own in the Athlinks pipeline instead of taking the run down with them.

That maps cleanly onto the event arguments. Derive the workflow ID from the case the event is about, signal that workflow as the case changes, and carry your own idempotency key in the signal payload so a redelivered event is harmless. The AI SDK's MCP Events documentation is explicit that [deliveries may be duplicated or reordered](https://ai-sdk.dev/docs/ai-sdk-core/mcp-events), so dedupe on the subscription ID and the event ID rather than trusting arrival order. Keep the payload itself thin, a summary plus an identifier, and let the workflow fetch the full record through a governed read. Event receipt isn't authorisation to act, so the action the agent takes goes through the same checks any tool call would.

What this buys is a history you can show an auditor. Every event that arrived, every tool the agent called, every retry and every approval sits in one history attached to one case. Autonomy without that history is an unlogged robot.

## Where Databricks comes in

Most events worth reacting to need a judgement, and a judgement needs data the triggering system doesn't hold. A payoff figure means nothing until you compare it with what the deal was structured on. A late shipment means nothing until you know what else is in the yard and which orders it blocks.

So I'd make the workflow's first real step a read against the lakehouse, through Unity Catalog, under an identity that belongs to the agent rather than to whoever happened to set the subscription up. That's the same governance question we'd ask of any agent reading client data, and it gets harder, not easier, when nobody is sitting there watching the agent work.

The reconciliation layer matters as much as the stream. The AI SDK documentation notes that deliveries may be duplicated or reordered and that some events cannot be replayed, so I'd write a Delta table of everything the platform emitted, from the same place the notifications come from, and let a scheduled workflow ask what it should have seen and didn't. That table is also what you calibrate against later. Which events led to an action, which actions a human reversed, and where the agent was wrong.

```mermaid
%% caption: A thin signed notification is verified and deduplicated, starts or signals one durable workflow per case, and the judgement happens through governed reads on the lakehouse.
flowchart TD
  A["Change inside your platform"] --> B["Signed webhook delivery"]
  B --> C["Verify and deduplicate the event"]
  B --> H["Handling it in the agent loop"]
  C --> D["Temporal workflow per case"]
  D --> E["Governed reads in Unity Catalog"]
  E --> F["Delta tables for the judgement"]
  D --> G["Delta event log for reconciliation"]
  class D accent
  class H legacy
```

## Start with the event that costs the most when it's late

Pick the one event in your platform where a slow reaction costs real money, and build that path only. Run it for a few weeks before you expose a second event type. You'll learn more from it, including how often deliveries repeat and whether your reverification sweep catches a revoked user, than from a design document covering every event you can imagine.

The trigger is the cheap part. What makes it trustworthy is everything that happens after it. If you want the longer version of how we think about agent identity on the data side, [our Unity Catalog write-up](/blog/unity-catalog-agent-identity-row-filters-databricks) covers the reads this workflow depends on.

## Frequently asked questions

### What are MCP Events?

MCP Events is a draft extension to the Model Context Protocol that lets an MCP server notify a client when something changes in a connected app. The client subscribes with `events/subscribe`, and the server stores that subscription and delivers matching events to a webhook until it expires or is ended.

### Does an MCP event subscription expire with the access token that created it?

No. The subscription is keyed on the principal, the callback URL, the event name and the arguments, a tuple that includes no token, no scopes and no token expiry, and `ttlMs: null` requests a subscription with no expiry. The TTL you grant is the window in which a revoked user's subscription is still live, which is why short finite TTLs and a scheduled reverification sweep are the safer default.

### Why put Temporal behind an MCP event instead of handling it in the agent?

Because the work after the event has to survive crashes, redeploys and repeated deliveries, which is what a durable workflow engine is built for. An agent loop holds its progress in memory, so a redeploy between two API calls loses it, while a workflow resumes from where it stopped.
