Quickstart
Offer a ci.finished event from your MCP server. An agent that subscribes to it wakes up when CI finishes on the repo it asked about. If you already have an MCP server, this takes about five minutes.
What you'll build
- A user asks their agent to watch CI on
acme/api. - The agent subscribes to your
ci.finishedevent foracme/api. ChatGPT does it natively; every other agent uses thesubscribe_eventstool the SDK adds to your server. - When CI finishes, your code emits
ci.finished, and every agent subscribed to that repo wakes with the result.
Before you start
- An invite. Wakeshift is in beta and sign-up is invite only: you need an invite link from someone who uses Wakeshift, then you sign in. The SDK,
@wakeshift/sdk, comes with the invite. It isn't on npm. - Node 22 or later, and an MCP server built with
@modelcontextprotocol/sdk(1.23 or later) andzod(3.25 or later, or 4). Not on Node? There are SDKs for Go and Python, and the HTTP API for anything else. - Claude Code to test with. Any agent works, but Claude Code needs no install.
1. Register your app
Sign in to the console and choose New app. You get a test key, shown once; make more under Keys on the app's page. Put it in your environment, with the address of the console it came from:
WAKESHIFT_KEY=wst_test_…
WAKESHIFT_CORE=https://app.wakeshift.aiLoad the file however you load environment variables. Node can do it for you: node --env-file=.env server.ts. A test key needs WAKESHIFT_CORE; without it the SDK stops with an error that says so.
The key is your app's private key. It never leaves your server: the SDK signs each request with it.
2. Install the SDK
Your invite comes with the SDK as a tarball, wakeshift-sdk-0.1.0.tgz. Install it with its peer dependencies:
npm install ./wakeshift-sdk-0.1.0.tgz @modelcontextprotocol/sdk zodIf you have the SDK's source instead, run npm install && npm pack in sdk/typescript to make the tarball. Don't install the source directory itself (a file: dependency): it brings the SDK's own copy of zod, and the MCP SDK then fails with "Mixed Zod versions detected".
3. Define the event and serve it
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
import { Wakeshift } from "@wakeshift/sdk"
const wake = new Wakeshift({ key: process.env.WAKESHIFT_KEY!, core: process.env.WAKESHIFT_CORE })
const ciFinished = wake.defineEvent("ci.finished", {
description: "CI finished on a GitHub repo",
args: z.object({
owner: z.string().describe("The repo's owner, lowercase: acme"),
repo: z.string().describe("The repo's name, lowercase: api"),
branch: z.string().optional().describe("Only this branch"),
}),
payload: z.object({ branch: z.string(), conclusion: z.string(), name: z.string(), url: z.string() }),
authorize: (subscriber, { owner, repo }) => canSeeRepo(subscriber, `${owner}/${repo}`),
text: (run, { owner, repo }) => `CI ${run.conclusion} on ${owner}/${repo} ${run.branch}: ${run.name}`,
})
// One McpServer per MCP session; serve the events before connecting it.
const server = new McpServer({ name: "ci-bot", version: "1.0.0" })
wake.serveEvents(server)argsis what a subscriber picks. The required fields,ownerandrepo, decide who gets an occurrence, so their values are lowercase IDs (a-z 0-9 . _ -). The optionalbranchfilters: a subscriber that names a branch only gets occurrences whose payload has thatbranch.authorizeruns before every subscription.canSeeRepostands for your own check; Wakeshift refuses the subscription when it returnsfalse.textis the line the agent reads first. Put the repo in it: an agent watching two repos reads the text to tell them apart.serveEventsadds three tools to your server,subscribe_events,unsubscribe_eventsandlist_subscriptions, and answers MCP Events requests from clients that speak it. You write no subscribe tool, and no code for any particular agent.- Native subscriptions need a signed-in user. ChatGPT subscribes natively once your server signs people in, with your own OAuth or with guest sign-in. Without sign-in, every other agent still subscribes with
subscribe_events.
4. Emit when CI finishes
Emit from anywhere in your code. Here it's your GitHub webhook handler:
// After checking X-Hub-Signature-256:
if (req.headers["x-github-event"] === "workflow_run" && body.action === "completed") {
const run = body.workflow_run
const [owner, repo] = body.repository.full_name.toLowerCase().split("/")
await ciFinished.emit(
{ owner, repo },
{ branch: run.head_branch, conclusion: run.conclusion, name: run.name, url: run.html_url },
{ id: String(req.headers["x-github-delivery"]) }, // retries with the same id emit once
)
}emit checks the payload against your schema, then every agent subscribed to acme/api wakes with the text and the payload, if the payload matches its branch. The occurrence is marked as coming from your app, not as an instruction from the agent's user.
5. Try it
Start your server and add it to Claude Code:
claude mcp add --transport http ci-bot <your MCP URL>Ask Claude Code to watch CI:
Watch CI on acme/api, main branch.It calls subscribe_events, then asks you to approve a background monitor. Approve it once; after that it wakes within seconds of each occurrence, even while it's idle.
You don't need a real CI run to test it. Emit one yourself, for example from a temporary route in your server:
await ciFinished.emit({ owner: "acme", repo: "api" }, { branch: "main", conclusion: "success", name: "tests", url: "https://github.com/acme/api/actions" })Claude Code wakes with CI success on acme/api main: tests.
Next steps
- Events: personal events, subscribing from your own tools, and when subscriptions end.
- Your users: who a subscriber is, guest sign-in, and forgetting a user.
- Agents: how fast each agent wakes, and the one-time install some of them need.
- HTTP API: what not to log.