mctxdocs
Building mcp servers

Scheduled Event Delivery

Emit events that the platform holds and delivers at a specific time. Use deliverAt to defer events, key to supersede stale ones, and ctx.cancel() to remove pending events before they arrive.

Need help? Connect help.mctx.ai for instant answers.

Sometimes you want an event to arrive later -- a reminder in 30 minutes, an alert when a deadline passes, a daily digest. Scheduled delivery lets you emit events that the platform holds and delivers at a specific time.

Your App calls ctx.emit() with a deliverAt option. mctx queues the event and holds it until the scheduled time, then delivers it to the subscriber's thin client on its next poll after that time arrives.


deliverAt

Pass a Date object or an ISO 8601 string to deliverAt to schedule an event for future delivery.

Using a Date object

// Remind in 30 minutes
const thirtyMin = new Date(Date.now() + 30 * 60 * 1000);
ctx.emit("Stand-up starts in 5 minutes", { deliverAt: thirtyMin, eventType: "reminder" });

Using an ISO 8601 string

// Deliver at a specific time
ctx.emit("Sprint review begins now", {
  deliverAt: "2026-03-25T14:00:00Z",
  eventType: "meeting",
});

The platform queues the event and delivers it when the time arrives. The subscriber's thin client picks it up on its next poll after the deliverAt time. The event appears in the subscriber's Claude Code session without any additional action on their part.

Maximum schedule-ahead window: 30 days. Events scheduled beyond 30 days from the current time are rejected.


key

Pass a key to make an event supersede a previous one. When you emit with a key that matches an existing pending event for the same App, the new event replaces the old one.

// Each new deploy status replaces the previous one
ctx.emit("Deploy to staging: building...", {
  key: "deploy-staging",
  eventType: "deploy",
});

// Later, this replaces the event above -- the subscriber only sees the latest
ctx.emit("Deploy to staging: complete", {
  key: "deploy-staging",
  eventType: "deploy",
});

This is useful for status updates where only the latest state matters. Rather than delivering a stream of intermediate states, you emit each update with the same key and the subscriber sees only the most recent one when it arrives.

Key constraints:

ConstraintLimit
Key format[a-zA-Z0-9_]+ (alphanumeric and underscores only)
Maximum length100 characters

Keys are scoped per App. Two different Apps can use the same key without collision. A key on your App never interferes with the same key on a different App.


Combining deliverAt and key

Use both options together to schedule a future event that you can update or cancel before it arrives.

// Schedule a review reminder 24 hours from now
ctx.emit("PR #42 needs review -- 24 hours since opened", {
  key: "pr-review-42",
  deliverAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
  eventType: "reminder",
});

// PR gets reviewed before the deadline -- cancel the reminder
ctx.cancel("pr-review-42");

This pattern is common for deadline-aware notifications: schedule the alert up front, then cancel it if the triggering condition resolves before the deadline arrives.


ctx.cancel()

ctx.cancel() removes a pending event by key before it is delivered.

ctx.cancel("deploy-staging");

Behavior:

  • Cancels any pending (not yet delivered) event with the given key for the current App
  • No-ops silently if the key does not exist or the event was already delivered
  • Only affects events that have not been delivered yet -- you cannot recall an event that has already reached the subscriber

ctx.cancel() is fire-and-forget, the same as ctx.emit(). It does not block your handler and does not throw if the cancel cannot reach the events service.


Use cases

Reminders. Schedule a notification at a meaningful time in the future:

// Remind the subscriber 15 minutes before their meeting
const meetingTime = new Date(meeting.startsAt);
const reminderTime = new Date(meetingTime.getTime() - 15 * 60 * 1000);

ctx.emit(`${meeting.title} starts in 15 minutes`, {
  key: `meeting-reminder-${meeting.id}`,
  deliverAt: reminderTime,
  eventType: "reminder",
});

Alerts with deadlines. Schedule an escalation that cancels if the issue resolves itself:

// Alert if deployment is still running after 10 minutes
ctx.emit("Deployment taking longer than expected", {
  key: "deploy-timeout",
  deliverAt: new Date(Date.now() + 10 * 60 * 1000),
  eventType: "alert",
});

// Deployment completes -- cancel the alert before it fires
ctx.cancel("deploy-timeout");

Periodic status updates. Combine with key to show only the latest state:

// Each status update supersedes the previous one
ctx.emit(`Build #${buildId}: ${status}`, {
  key: `build-status-${buildId}`,
  eventType: "ci",
});

Deadline notifications. Schedule a notification at sprint or milestone creation time:

// Notify when the sprint ends, scheduled at the time the sprint is created
ctx.emit(`Sprint ${sprint.name} ends today`, {
  key: `sprint-end-${sprint.id}`,
  deliverAt: new Date(sprint.endsAt),
  eventType: "sprint",
});

Next steps


See something wrong? Report it or suggest an improvement -- your feedback helps make these docs better.