Get started / Quickstart
Get started

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

  1. A user asks their agent to watch CI on acme/api.
  2. The agent subscribes to your ci.finished event for acme/api. ChatGPT does it natively; every other agent uses the subscribe_events tool the SDK adds to your server.
  3. 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) and zod (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:

.env
WAKESHIFT_KEY=wst_test_…
WAKESHIFT_CORE=https://app.wakeshift.ai

Load 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:

Terminal
npm install ./wakeshift-sdk-0.1.0.tgz @modelcontextprotocol/sdk zod

If 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

server.ts
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)
  • args is what a subscriber picks. The required fields, owner and repo, decide who gets an occurrence, so their values are lowercase IDs (a-z 0-9 . _ -). The optional branch filters: a subscriber that names a branch only gets occurrences whose payload has that branch.
  • authorize runs before every subscription. canSeeRepo stands for your own check; Wakeshift refuses the subscription when it returns false.
  • text is the line the agent reads first. Put the repo in it: an agent watching two repos reads the text to tell them apart.
  • serveEvents adds three tools to your server, subscribe_events, unsubscribe_events and list_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:

webhook.ts
// 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:

Terminal
claude mcp add --transport http ci-bot <your MCP URL>

Ask Claude Code to watch CI:

Prompt
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:

TypeScript
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.