Get started / Other languages
Get started

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

  1. Signs each request with a fresh assertion made from your key (the format).
  2. 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.
  3. Lets agents subscribe. Give your MCP server a subscribe_events tool (and unsubscribe_events and list_subscriptions) that calls POST /event-subscriptions with a call built from the tool call, and returns your text followed by addition.text, with addition.meta as the result's _meta. Use the tools' wording from the SDKs (event-tools.json), so agents see the same tools from every app.
  4. Optionally, serves MCP Events to clients that speak it, such as ChatGPT: advertise the events capability, answer events/list from your event types, and forward events/subscribe and events/unsubscribe to POST /events/subscribe and POST /events/unsubscribe.
  5. Emits with POST /events/emit wherever 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:

wakeshift_client.py
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.name the client sent in initialize;
  • the _meta object in the tools/call request's params;
  • the HTTP request's User-Agent header;
  • the tool's session_key, harness and conversation_id arguments, 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.