Skip to main content

wakeshift

Let agents subscribe to things in your app. You define the events your app offers and emit them when they happen; Wakeshift wakes every agent subscribed to them. Clients that speak MCP Events, such as ChatGPT, subscribe natively; every other agent (Claude Code, Codex, Cursor and the rest) subscribes with a tool the SDK adds, and Wakeshift wakes it its own way. You write no subscribe tool and handle no harnesses.

Python 3.10 or later, on the official MCP Python SDK (mcp 2.3 or later). The same API as the Node and Go SDKs.

Install

pip install wakeshift

Or uv add wakeshift.

Quickstart: events

Register an app in the Wakeshift console (Build) and put its key in WAKESHIFT_KEY. The SDK talks to https://app.wakeshift.ai unless WAKESHIFT_CORE names another address.

import os

from mcp.server.mcpserver import MCPServer
from pydantic import BaseModel

from wakeshift import GuestAuth, Wakeshift

wake = Wakeshift(os.environ["WAKESHIFT_KEY"])


class Channel(BaseModel):
    channel: str


class Message(BaseModel):
    sender: str
    text: str


message = wake.define_event(
    "chat.message",
    description="A new message in a channel",
    args=Channel,
    payload=Message,
    text=lambda m, a: f"{m.sender} in #{a.channel}: {m.text}",
)


class Table(BaseModel):
    table: str


class Turn(BaseModel):
    hole: list[str]
    to_call: int


# A personal event: each subscription belongs to its subscriber, and an
# occurrence goes to one of them, so it can carry their own private view.
turn = wake.define_event(
    "game.turn",
    description="It's your move at a table",
    args=Table,
    payload=Turn,
    text=lambda t, a: f"Your move at {a.table}: {t.to_call} to call",
    personal=True,
)

server = MCPServer("chat")
wake.attach(server)  # define every event first

# Guests: MCP clients sign in with no account, each in its own workspace.
guests = GuestAuth(wake, "https://chat.example.com/mcp")
app = guests.wrap(server.streamable_http_app())  # run with uvicorn


# When something happens:
async def on_message(channel: str, sender: str, text: str) -> None:
    await message.emit({"channel": channel}, {"sender": sender, "text": text})


async def on_turn(table: str, player: str, hole: list[str], to_call: int) -> None:
    await turn.emit({"table": table}, {"hole": hole, "to_call": to_call}, to=player)

That's all. An agent subscribes to chat.message for eng, and each message in #eng wakes it with the text line and the payload, framed as information from your app, not as instructions from its user.

  • wake.define_event(name, description=, args=, payload=, text=, authorize=None, personal=False, delete_on_delivery=False) returns the event. args and payload are pydantic models or JSON Schemas. args is what a subscriber picks; its required string, integer and boolean fields route occurrences, so their values are lowercase IDs (a-z 0-9 . _ -), and an optional one filters on the payload field of the same name. text(payload, args) is the line the agent reads first. authorize(subscriber, args) (sync or async) decides who may subscribe. With delete_on_delivery=True, Wakeshift deletes each occurrence's text and payload once it's delivered.
  • await event.emit(args, payload, to=None, id=None) checks the payload and sends it to everyone subscribed with those arguments; a personal event goes to the one subscriber to names. id makes retries emit once.
  • wake.attach(server, identify=None) attaches Wakeshift to your MCP server. Today it serves events/list, events/subscribe and events/unsubscribe to clients that speak MCP Events, and adds the tools subscribe_events (for everyone else), unsubscribe_events and list_subscriptions (for every client, so a person can always see and stop what they're subscribed to). identify(ctx) says who is behind a request, as an Identity(user=…) with your ID for the person; by default, the guest GuestAuth signed in. Each connection is answered for its own client, so serve over stateful Streamable HTTP (the default) or stdio.
  • await event.subscribe(ctx, args, user=, wake_args=) subscribes the conversation calling one of your own tools, the way subscribe_events would, so a tool like "join" sets up its wakes in one step. result(text) is your tool's text with what the agent needs added. await event.unsubscribe(ctx, args, user=, wake_args=) ends the calling conversation's subscription with those arguments, for a tool like "unwatch". await event.end(user, id) ends one of a user's subscriptions by its ID.

Sign-in

  • GuestAuth(wake, mcp_url) is a small OAuth server inside your app for MCP clients: dynamic client registration, authorize with PKCE, tokens, all signed with a key derived from your app key, so it keeps no state. Each registered client is a guest with its own workspace. guests.wrap(app) serves its endpoints and requires a token on /mcp; inside a tool, guest_of(ctx) is who's calling. Or call handle(request) and authenticate(request) from your own routes, or pass verify_token to the MCP SDK's own auth. Registration and token requests are rate-limited per address (IPv6 per /64); behind a proxy, set proxy_hops. grants=True gives each guest a grant under your manifest's agent scope, checked at every refresh.
  • Your app identifies its own users, with GuestAuth or its own sign-in, and acts for the agents in the workspaces it keeps.

Topics

Events are built on topics, which you can use directly: subscribe the conversation calling one of your tools, and publish to it.

from mcp.server.mcpserver import Context
from mcp.types import CallToolResult

from wakeshift import ConversationId, Harness, SessionKey


@server.tool()
async def watch_ci(
    repo: str, ctx: Context, session_key: SessionKey = None, harness: Harness = None, conversation_id: ConversationId = None
) -> CallToolResult:
    """Wake me when CI finishes on a repo"""
    call = wake.call(ctx, session=session_key, harness=harness, conversation=conversation_id)
    sub = await wake.subscribe(call, f"ci/{repo.lower()}", replay="5m")
    return sub.result(f"Watching CI on {repo}.")


async def on_ci(repo: str, conclusion: str, url: str) -> None:
    await wake.publish(f"ci/{repo.lower()}", text=f"CI {conclusion} on {repo}", data={"url": url})

SessionKey, Harness and ConversationId add the optional arguments Wakeshift needs from the agent: the session key an earlier result gave it (a secret: don't log tool arguments that carry it), the program it runs in, and its own conversation ID. The other calls: unsubscribe, send (one subscription), topic_status, subscription_status, publish_url and revoke_publish_url, annotate (adds held messages to another tool's result), close_topic, end_subscription, unsubscribe_user, forget_user (deletes everything Wakeshift keeps for one of your users) and publish_manifest.

Errors

Calls raise WakeshiftError with the API's code (private_topic, bad_request…) and HTTP status; is_code(err, code) checks one. A core that doesn't answer is unreachable (status 0), and the message names the address it tried.

Keys

An app key is wst_ followed by 50 letters and digits: your app's Ed25519 private key, with a checksum, so a mistyped key fails when you create Wakeshift. It never goes over the wire: each request carries a fresh assertion signed with it, valid two minutes. A key goes to https://app.wakeshift.ai unless you pass core or set WAKESHIFT_CORE.

The key doesn't name your app. await wake.app() asks Wakeshift which app it is and returns its id and name; after that, wake.app_id is the app's ID (before it, reading wake.app_id raises).

Older keys, wst_test_<app>_<seed> and wst_live_<app>_<seed>, keep working. They name the app, so wake.app_id works at once. A wst_test_ key needs core or WAKESHIFT_CORE. wake.mode is "test" or "live" for an older key and None for a current one; it's deprecated.

Documentation

The guides and the full reference are at wakeshift.ai/docs: the quickstart, events, your users and the Python SDK. The same calls are in the TypeScript and Go SDKs.

Development

uv sync
uv run ruff check
uv run pyright
uv run pytest

The end-to-end tests run against a Wakeshift server, so they run in Wakeshift's own repository and are skipped here.

Contributing and security

This repository is generated from Wakeshift's main repository. Pull requests are welcome: we apply them there and publish the result. See CONTRIBUTING. Report a security problem privately to support@wakeshift.ai, not in an issue (SECURITY).

License

Apache License 2.0; see LICENSE.

Metadata

Release files for wakeshift 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wakeshift 0.2.0
File Size Uploaded Provenance
wakeshift-0.2.0.tar.gz 45.4 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for wakeshift 0.2.0
File Interpreter ABI Platform
wakeshift-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 93.8 kB

Release files / wakeshift-0.2.0.tar.gz

Download URL wakeshift-0.2.0.tar.gz
Size 45.4 kB
Tags Source
SHA-256 checksum
How to use checksums
10c923809ba05eca285bad9880d0208894fac65d5052b05ffd57800aca80986f
BLAKE2b-256 checksum
How to use checksums
1d31f3071814386188a9baa79c2fd67b38a56c63a26c67da119eadcb5c3b84f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Release files / wakeshift-0.2.0-py3-none-any.whl

Download URL wakeshift-0.2.0-py3-none-any.whl
Size 48.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
944d60da50163b207e925e925bd8e6e71711a2bcddffcd20ac12a67594be0829
BLAKE2b-256 checksum
How to use checksums
74d20f7fd80461d74f3b2af698495d569845180bcca22ba3398b26f6cec65d5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

Security for wakeshift 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for wakeshift-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page