Other languages
There are SDKs for TypeScript, Go and Python. From any other language, call the HTTP API yourself. This page says what your server does; the HTTP API page has every endpoint.
What your server does
- Signs each request with a fresh assertion made from your key (the format).
- Declares its events in its manifest,
PUT /manifest, once at startup: each event's name, description, arguments and payload as JSON Schemas, and the topic template it routes by. - Lets agents subscribe. Give your MCP server a
subscribe_eventstool (andunsubscribe_eventsandlist_subscriptions) that callsPOST /event-subscriptionswith acallbuilt from the tool call, and returns your text followed byaddition.text, withaddition.metaas the result's_meta. Use the tools' wording from the SDKs (event-tools.json), so agents see the same tools from every app. - Optionally, serves MCP Events to clients that speak it, such as ChatGPT: advertise the events capability, answer
events/listfrom your event types, and forwardevents/subscribeandevents/unsubscribetoPOST /events/subscribeandPOST /events/unsubscribe. - Emits with
POST /events/emitwherever events happen.
If you get the call wrong, nothing fails: the agent is treated as one Wakeshift can't wake. Check what each subscribe returns as you go.
Sign requests
An example in Python, with PyNaCl and httpx:
import base64, json, os, secrets, time
import httpx
from nacl.signing import SigningKey
def b64u(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
class Wakeshift:
def __init__(self):
key = os.environ["WAKESHIFT_KEY"] # wst_test_<app>_<seed>
self.core = os.environ["WAKESHIFT_CORE"].rstrip("/")
_mode, self.app_id, seed = key.removeprefix("wst_").split("_", 2)
self.key = SigningKey(base64.urlsafe_b64decode(seed + "=" * (-len(seed) % 4)))
self.http = httpx.Client(base_url=self.core + "/api/apps/v1", timeout=20)
def _assertion(self) -> str:
now = int(time.time()) # an integer: a float is refused
head = {"alg": "EdDSA", "typ": "app+jwt"}
claims = {"iss": self.app_id, "sub": self.app_id, "aud": self.core + "/api/apps",
"iat": now, "exp": now + 120, "jti": b64u(secrets.token_bytes(16))}
signing_input = b64u(json.dumps(head).encode()) + "." + b64u(json.dumps(claims).encode())
return signing_input + "." + b64u(self.key.sign(signing_input.encode()).signature)
def request(self, method: str, path: str, body: dict) -> dict:
r = self.http.request(method, path, json=body, headers={"Authorization": "Bearer " + self._assertion()})
if r.status_code >= 400:
err = r.json().get("error", {})
raise RuntimeError(f"wakeshift: {err.get('code')} ({r.status_code}): {err.get('message')}")
return r.json()Where the call comes from
Each subscribe carries a call (its fields), built from four things every MCP framework exposes:
- the
clientInfo.namethe client sent ininitialize; - the
_metaobject in thetools/callrequest'sparams; - the HTTP request's
User-Agentheader; - the tool's
session_key,harnessandconversation_idarguments, with the descriptions on the HTTP API page.
Add your own ID for the person as user when you have one. Native subscriptions and personal events need it.
Stateless servers
The client's name comes from initialize, which the client sends once, when it connects. A server that handles each request on its own never sees it.
Stateless servers work for Claude Code, Codex and Antigravity, which Wakeshift recognises from the tool call's meta, and for opencode, Cursor and GitHub Copilot CLI, from user_agent. So send both on every call. For any other client, keep a session per client (behind a load balancer, route each Mcp-Session-Id to the same instance), or pass client yourself.
A subscribe says what Wakeshift recognised: agent: { harness, via, claimed }, with harness and via both null when it recognised nothing. Log a warning then, unless the agent said which program it runs in (claimed). The SDKs do this for you.
Handle secrets
The session_key argument, and the text and _meta Wakeshift adds to results, are credentials for that conversation. Don't log tool arguments or results that carry them. See handling secrets.