From f8a9b063233b23b47bc76382bddc5a265d4c442c Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 18:43:03 -0700 Subject: [PATCH 01/10] docs: add Bring Your Own Runtime (MCP) page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The universal molecule-mcp wheel is the recommended path for any MCP-aware runtime — Claude Code, hermes-agent, OpenCode, Cursor, Cline, custom MCP clients — to join the canvas as a first-class external workspace. Until now this path had no docs page; users either inferred it from internal PRs or got pointed at external-agents.mdx (the manual HTTP+heartbeat path that pre-dates the wheel). New runtime-mcp.mdx covers: * Single install (pip install --user molecule-ai-workspace-runtime) * Per-runtime config snippets (Claude Code, Hermes, generic JSON, Cursor/Cline) * Tool surface (delegate_task, wait_for_message, inbox_peek/pop, send_message_to_user, commit_memory/recall_memory) * Heartbeat/lifecycle behaviour and the new escalation message landed in molecule-core PR #2425 * When to use this vs. the manual external-agents path * Troubleshooting: stale MCP cache, 401 register failure, PATH issues Cross-links: * external-agents.mdx now leads with a Callout pointing MCP-runtime users at the new page; keeps the manual path for non-MCP agents * meta.json registers the new page under the main docs nav between schedules and external-agents (related onboarding flow) Build verified: `npm run build` generates 106 pages including the new /docs/runtime-mcp. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/external-agents.mdx | 13 ++ content/docs/meta.json | 1 + content/docs/runtime-mcp.mdx | 209 +++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 content/docs/runtime-mcp.mdx diff --git a/content/docs/external-agents.mdx b/content/docs/external-agents.mdx index 0656174..1602bab 100644 --- a/content/docs/external-agents.mdx +++ b/content/docs/external-agents.mdx @@ -3,11 +3,24 @@ title: External Agents description: Register agents running outside the platform's Docker network as first-class workspaces on the canvas. --- +import { Callout } from 'fumadocs-ui/components/callout'; + External agents are AI agents running on your own infrastructure — a different cloud, an edge device, or your laptop — that join the Molecule AI canvas as first-class workspaces. They communicate with other agents via A2A, appear on the canvas with a purple **REMOTE** badge, and are managed like any other workspace. + +**Using an MCP-aware agent runtime** (Claude Code, Hermes, OpenCode, Cursor, +Cline, etc.)? The universal `molecule-mcp` wheel handles registration, +heartbeat, inbox polling, and A2A routing automatically — no manual HTTP +server required. See [Bring Your Own Runtime (MCP)](/docs/runtime-mcp). + +This page covers the **manual A2A path** — bring-your-own HTTP server, +register and heartbeat by hand. Use it when your agent can't run an MCP +stdio server. + + ## Prerequisites - A running Molecule AI platform (default `http://localhost:8080`) diff --git a/content/docs/meta.json b/content/docs/meta.json index e9ea82e..908d8eb 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -11,6 +11,7 @@ "plugins", "channels", "schedules", + "runtime-mcp", "external-agents", "tokens", "api-reference", diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx new file mode 100644 index 0000000..c6e5d9d --- /dev/null +++ b/content/docs/runtime-mcp.mdx @@ -0,0 +1,209 @@ +--- +title: Bring Your Own Runtime (MCP) +description: Add Claude Code, Hermes, OpenCode, Cursor, or any MCP-aware agent to the Molecule canvas as a first-class workspace using the universal molecule-mcp wheel. +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +The universal `molecule-mcp` wheel lets any MCP-aware agent runtime +join the Molecule canvas as a first-class external workspace. The wheel +runs locally inside your runtime's MCP server slot, registers + heartbeats +to the platform, and exposes the same tool surface that in-container +workspaces have — `delegate_task`, `list_peers`, `wait_for_message`, +`send_message_to_user`, and friends. + +Same install path works for Claude Code, hermes-agent, OpenCode, Cursor, +Cline, and any other runtime that speaks MCP stdio. + +``` +Your local runtime ──stdio──> molecule-mcp (wheel) ──HTTPS──> Molecule platform + (Claude Code, hermes, (canvas, peers, + opencode, cursor...) A2A proxy) +``` + +## Prerequisites + +- A Molecule platform you can reach (SaaS at `https://.moleculesai.app` or a self-hosted instance) +- A workspace ID + token from the canvas → **Tokens** tab +- Python 3.11+ to install the wheel +- An MCP-aware agent runtime + +## Step 1 — Install the wheel + +```bash +pip install --user molecule-ai-workspace-runtime +``` + +This installs the `molecule-mcp` console script. By default it lands at +`~/Library/Python/3.x/bin/molecule-mcp` on macOS or `~/.local/bin/molecule-mcp` +on Linux. Add that directory to your `PATH` or use the full path in your +runtime's MCP config. + +```bash +which molecule-mcp +# /Users/you/Library/Python/3.13/bin/molecule-mcp +``` + +## Step 2 — Add it to your runtime + +Pick the snippet for your runtime. The contract is the same in all of +them: spawn `molecule-mcp` as an MCP stdio server with three env vars +set. + +### Claude Code + +```bash +claude mcp add molecule -s user -- env \ + WORKSPACE_ID= \ + PLATFORM_URL=https://.moleculesai.app \ + MOLECULE_WORKSPACE_TOKEN= \ + molecule-mcp +``` + +Reconnect with `/mcp` (or restart the Claude Code session) and the tools +appear in the next turn. + +### Hermes Agent + +```bash +hermes mcp add molecule \ + --command molecule-mcp \ + --env WORKSPACE_ID= \ + --env PLATFORM_URL=https://.moleculesai.app \ + --env MOLECULE_WORKSPACE_TOKEN= +``` + +Or hot-reload an existing session with `/reload-mcp`. + +### OpenCode / generic MCP config (stdio) + +For runtimes that read a JSON MCP config (`.mcp.json`, `mcp_servers.yaml`, +or similar): + +```json +{ + "mcpServers": { + "molecule": { + "command": "molecule-mcp", + "env": { + "WORKSPACE_ID": "", + "PLATFORM_URL": "https://.moleculesai.app", + "MOLECULE_WORKSPACE_TOKEN": "" + } + } + } +} +``` + +### Cursor / Cline / other MCP clients + +Most MCP clients accept the same `command` + `env` shape as the JSON +example above. Drop it into your client's MCP settings file +(typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for +Cline) and restart the client. + +## Step 3 — Verify + +After your runtime reconnects, the workspace should flip to **online** +on the canvas. From inside your agent: + +``` +list_peers() +``` + +You should see your team — siblings, parent, and children — with their +status. If the workspace is still offline after ~30s, check +[Troubleshooting](#troubleshooting) below. + +## Tools exposed + +| Tool | What it does | +|---|---| +| `list_peers` | List workspaces this agent can A2A-message | +| `get_workspace_info` | Own identity (id, name, role, tier, parent) | +| `delegate_task` | Send a task to a peer and wait for the reply | +| `delegate_task_async` | Fire-and-forget delegation; result lands in inbox | +| `check_task_status` | Poll an async delegation | +| `wait_for_message` | Block until the next inbound A2A message arrives | +| `inbox_peek` / `inbox_pop` | Inspect / acknowledge queued inbound messages | +| `send_message_to_user` | Push a chat bubble to the user's canvas | +| `commit_memory` / `recall_memory` | Persistent KV (local / team / global scope) | + +External runtimes can't accept inbound HTTP, so the wheel polls +`/activity?type=a2a_receive` in a daemon thread and surfaces messages +through `wait_for_message` + `inbox_peek` / `inbox_pop`. Use those +instead of waiting for an HTTP webhook — there isn't one. + +## Heartbeat & lifecycle + +The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every +20 seconds. Your runtime stays `online` on the canvas as long as that +heartbeat lands. + +If the heartbeat starts returning 401, the wheel logs a clear ERROR +after 3 consecutive failures with re-onboard instructions: + +``` +molecule-mcp: 3 consecutive heartbeat auth failures (HTTP 401) — the +token in MOLECULE_WORKSPACE_TOKEN has been REVOKED, likely because +workspace was deleted server-side. The MCP server is still running +but every platform call will fail. Regenerate the workspace + token +from the canvas (Tokens tab), update your MCP config, and restart your +runtime. +``` + +This is the canonical signal that you need to regenerate from the canvas +**Tokens** tab. The MCP server keeps running so in-flight tool calls +don't crash, but every platform-side operation will fail until you +re-onboard. + +## Troubleshooting + +### Workspace stays offline after `/mcp` connect + +Most likely the runtime is still using a cached MCP config from session +start. Fully exit and relaunch the runtime — `/mcp` reconnect re-reads +the running session's in-memory config, not the on-disk file. + +### `molecule-mcp: register rejected with HTTP 401` + +The token in `MOLECULE_WORKSPACE_TOKEN` doesn't match the workspace. +Regenerate from the canvas Tokens tab. + +### `Tools unavailable` / `MCP server disconnected` + +Check that `molecule-mcp` resolves on `PATH` from inside the runtime's +environment (it may differ from your interactive shell). If unsure, use +the full path to the binary in your MCP config: + +```bash +which molecule-mcp +# Use this absolute path in your config's "command" field +``` + +### `Origin header is required` in logs + +Don't pass `Origin` manually — the wheel sets it. If you see this, your +runtime is calling the platform directly instead of through the MCP +tools. Use the tools (`delegate_task`, `send_message_to_user`, etc.) +rather than hand-rolling HTTP calls. + +## When to use this vs. the manual A2A path + +| Scenario | Use | +|---|---| +| You're using an MCP-aware agent runtime | This page (universal `molecule-mcp` wheel) | +| You're building a custom agent without MCP support | [External Agents](/docs/external-agents) (manual register + heartbeat + HTTP server) | +| You want the platform to expose MCP tools your agent connects to (inverse direction) | [MCP Server](/docs/mcp-server) | + +The universal wheel is the recommended path for almost every modern +runtime — it handles registration, heartbeat, inbox polling, A2A +routing, and Origin/WAF headers for you. The manual path is only +needed when you can't run an MCP stdio server inside your agent (rare). + +## See also + +- [External Agents](/docs/external-agents) — manual A2A path for non-MCP runtimes +- [Tokens](/docs/tokens) — token management and rotation +- [Concepts — Workspaces](/docs/concepts#workspaces) +- [API Reference](/docs/api-reference) — raw HTTP endpoints behind the wheel From fa6db57daf9b4d579ca3e14e3a5c9b196cd3ac4f Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 19:00:44 -0700 Subject: [PATCH 02/10] docs(runtime-mcp): document MOLECULE_AGENT_NAME / DESCRIPTION / SKILLS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an "Optional — declare your identity & capabilities" section to the Bring Your Own Runtime page covering the three new env vars landing in molecule-core PR #2428: * MOLECULE_AGENT_NAME — display name on canvas card * MOLECULE_AGENT_DESCRIPTION — one-liner in Details/Skills tabs * MOLECULE_AGENT_SKILLS — comma-separated skills Includes a worked example for Claude Code's add command and explains the two surfaces these populate (canvas Skills tab, peer agents' list_peers output) so readers understand why declaring skills matters for routing. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/runtime-mcp.mdx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index c6e5d9d..bfca8fd 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -102,6 +102,40 @@ example above. Drop it into your client's MCP settings file (typically `~/.cursor/mcp.json` for Cursor, the MCP Servers panel for Cline) and restart the client. +## Optional — declare your identity & capabilities + +Three additional env vars control how your workspace appears on the +canvas and to peer agents calling `list_peers`: + +| Env var | What it sets | Default | +|---|---|---| +| `MOLECULE_AGENT_NAME` | Display name on the canvas card | `molecule-mcp-{id[:8]}` | +| `MOLECULE_AGENT_DESCRIPTION` | One-line description in Details/Skills tabs | empty | +| `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` | + +Skills are surfaced two places: + +1. **Canvas Skills tab** — each skill renders as a chip with the name +2. **Peer agents calling `list_peers`** — they see `{name, skills: [...]}` for each peer, so other agents can route delegations to the right specialist instead of guessing from name alone + +Example with all three set: + +```bash +claude mcp add molecule -s user -- env \ + WORKSPACE_ID= \ + PLATFORM_URL=https://.moleculesai.app \ + MOLECULE_WORKSPACE_TOKEN= \ + MOLECULE_AGENT_NAME='Research Assistant' \ + MOLECULE_AGENT_DESCRIPTION='Reads, summarises, cites.' \ + MOLECULE_AGENT_SKILLS=research,summarisation,citations \ + molecule-mcp +``` + +A peer agent's `list_peers()` call would then surface this workspace +as `Research Assistant — skills: [research, summarisation, citations]`, +which it can use to route a research task without first asking "what +can you do?". + ## Step 3 — Verify After your runtime reconnects, the workspace should flip to **online** From 6a99eb0896284baca4487a342a130d44d2d098d7 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 20:11:44 -0700 Subject: [PATCH 03/10] docs(runtime-mcp): document push-UX + expand troubleshooting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two additions to the universal MCP runtime page: 1. New "Push-UX for notification-capable hosts" subsection explains that the wheel emits notifications/claude/channel on every new inbound message — Claude Code (and any compliant client) gets push-style interrupts, poll-only runtimes silently fall back to wait_for_message / inbox_peek. Same wheel for both, no config flag. 2. Three new troubleshooting entries from real install pitfalls: - Tools 401 after working: workspace was deleted from the canvas (token revoked); regenerate from Tokens tab - claude mcp list shows new config but /mcp reconnect still uses cache — must fully exit + relaunch the runtime - command not found from inside runtime: PATH differs from interactive shell (esp. macOS GUI-launched apps); use the absolute path from `which molecule-mcp` Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/runtime-mcp.mdx | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index bfca8fd..d980a99 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -168,6 +168,20 @@ External runtimes can't accept inbound HTTP, so the wheel polls through `wait_for_message` + `inbox_peek` / `inbox_pop`. Use those instead of waiting for an HTTP webhook — there isn't one. +### Push-UX for notification-capable hosts + +On top of the polling tools, the wheel emits a JSON-RPC notification +(`notifications/claude/channel`) on every new inbound message. Hosts +that recognise that method (Claude Code today; any compliant client +tomorrow) treat the notification as a conversation interrupt — the +message text becomes the next agent turn without the agent having to +call `wait_for_message` first. + +Hosts that don't recognise the method silently ignore it, so the same +wheel works for both push-capable and poll-only runtimes. There is no +config flag to toggle: pollers keep polling, notification-capable hosts +get push automatically. + ## Heartbeat & lifecycle The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every @@ -222,6 +236,43 @@ runtime is calling the platform directly instead of through the MCP tools. Use the tools (`delegate_task`, `send_message_to_user`, etc.) rather than hand-rolling HTTP calls. +### Tools call returns 401 / `workspace not found` after working before + +The workspace was probably deleted from the canvas (or via DELETE +`/workspaces/:id`). Deleting a workspace revokes its token immediately, +even if the workspace card still appears with a "removed" badge for a +short window. The MCP server itself keeps running, so tool listing +still succeeds, but every platform call fails. + +Regenerate from the canvas **Tokens** tab — a deleted workspace can't +be brought back; you'll get a new workspace + token pair. Update your +MCP config and restart your runtime. + +### `claude mcp list` shows the new config but tools still 401 + +`/mcp` reconnect re-spawns the **cached** MCP config from session +start, not the latest on-disk config. After editing `claude mcp add` +or your `~/.cursor/mcp.json`, fully exit and relaunch the runtime — +not just `/mcp`. + +A quick way to confirm: `ps aux | grep molecule-mcp` and check the +PID hasn't changed across `/mcp` reconnects. If the same PID stays +alive, the runtime is still using the old config. + +### `command not found: molecule-mcp` from inside the runtime + +The runtime's `PATH` may differ from your interactive shell — common +on macOS where `~/Library/Python/3.x/bin` is added to login shells but +not to GUI-launched apps. Use the absolute path in your MCP config: + +```bash +which molecule-mcp +# /Users/you/Library/Python/3.13/bin/molecule-mcp +``` + +Then point `command` at that absolute path in `claude mcp add` / +`.cursor/mcp.json` / `mcp_servers.yaml`. + ## When to use this vs. the manual A2A path | Scenario | Use | From 798294b62a661b67b34b717dcd945203ca1bbd29 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 20:28:32 -0700 Subject: [PATCH 04/10] docs(runtime-mcp): document MCP 2024-11-05 spec compliance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "MCP spec compliance" subsection to runtime-mcp.mdx that: - Lists which MCP methods the wheel implements + how - Notes the wheel speaks protocol version 2024-11-05 with only the `tools` capability (no streaming, no logging) - Clarifies that notifications/claude/channel is the only non-spec method emitted, and that clients which don't handle it discard per JSON-RPC semantics - States explicitly that any spec-compliant MCP client can drive the wheel (Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else that opens MCP stdio) This is the deliverable for verifying cross-client compatibility. The wheel uses no client-specific behavior, so the verification reduces to "does your client speak MCP 2024-11-05?" — which all the listed clients do. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/runtime-mcp.mdx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index d980a99..7ec6d7d 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -182,6 +182,30 @@ wheel works for both push-capable and poll-only runtimes. There is no config flag to toggle: pollers keep polling, notification-capable hosts get push automatically. +### MCP spec compliance + +The wheel speaks MCP protocol version **2024-11-05** over stdio +JSON-RPC, declaring only the `tools` capability. It implements the +standard request methods and nothing client-specific: + +| MCP method | Behavior | +|---|---| +| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` capability | +| `notifications/initialized` | No-op (no response — per spec) | +| `tools/list` | Returns all exposed tools in one response (no pagination cursor — surface is small) | +| `tools/call` | Dispatches by name, returns `content: [{ type: "text", text: ... }]` | +| _(unknown method)_ | Returns JSON-RPC error code `-32601` (Method not found) | + +The push-UX notification (`notifications/claude/channel`) is the only +non-standard method emitted, and it's a one-way notification — clients +that don't handle it discard it per JSON-RPC semantics. No part of the +wheel's tool surface depends on a client recognizing it. + +This means **any spec-compliant MCP client** can drive the wheel: +Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else +that opens an MCP stdio connection. If your client speaks MCP, it +speaks the wheel. + ## Heartbeat & lifecycle The wheel spawns a daemon thread that POSTs `/registry/heartbeat` every From f1ed8784ffe5331a6da90662975a2caf526c41c8 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 22:13:04 -0700 Subject: [PATCH 05/10] docs(2429): document 410 Gone for removed workspaces Follow-up C to molecule-core#2449 + #2451 (a2a-client) + molecule-mcp-claude-channel#22 (channel bridge): - runtime-mcp.mdx Troubleshooting: new section explaining the 410 startup-time error from `get_workspace_info`, contrasting it with the heartbeat-401 escalation (which is the steady-state cure), and documenting the `?include_removed=true` opt-in for audit tooling. - external-agents.mdx Lifecycle: expanded the `removed` status with a per-caller behavior table so operators know exactly what each surface (wheel heartbeat, MCP tool, channel bridge, raw curl) looks like for a removed workspace. Both pages link back to the underlying PR so the audit trail is single-click navigable from the docs. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/external-agents.mdx | 18 ++++++++++++++++++ content/docs/runtime-mcp.mdx | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/content/docs/external-agents.mdx b/content/docs/external-agents.mdx index 1602bab..85e7b99 100644 --- a/content/docs/external-agents.mdx +++ b/content/docs/external-agents.mdx @@ -244,6 +244,24 @@ create (POST /workspaces) → online (register) → offline (heartbeat expires) - No auto-restart (agent manages its own process) - Paused external workspaces skip heartbeat monitoring +### `removed` status — 410 Gone (#2429) + +Once a workspace transitions to `removed`: + +- **Tokens are revoked immediately**, but the row stays in the DB for audit-trail purposes +- `GET /workspaces/:id` returns **410 Gone** with `{error: "workspace removed", id, removed_at, hint}` so callers fail fast at startup instead of after ~60s of heartbeat 401s +- `DELETE /workspaces/:id` is the canonical removal trigger +- Audit / admin tooling that intentionally wants the legacy 200 + body shape opts in via `GET /workspaces/:id?include_removed=true` + +| Caller behavior on a removed workspace | What you should see | +|---|---| +| Wheel heartbeat | Logs ERROR after 3 consecutive 401s with re-onboard text | +| `get_workspace_info` MCP tool | Returns `{"error": "removed", "id", "removed_at", "hint"}` | +| Channel bridge `getWorkspaceInfo` | Throws `Workspace was deleted on the platform...` | +| Direct `curl /workspaces/:id` | 410 Gone with the same body shape | + +A removed workspace can't be brought back — regenerate a new workspace + token from the canvas **Tokens** tab. + ## Security - Bearer token required on all authenticated endpoints diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index 7ec6d7d..ae4d510 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -272,6 +272,28 @@ Regenerate from the canvas **Tokens** tab — a deleted workspace can't be brought back; you'll get a new workspace + token pair. Update your MCP config and restart your runtime. +### `Workspace was deleted on the platform...` from `get_workspace_info` + +Since [#2429](https://github.com/Molecule-AI/molecule-core/pull/2449), +`GET /workspaces/:id` returns **410 Gone** (not 200 + `status:"removed"`) +when the workspace has been deleted. The MCP wheel's `get_workspace_info` +tool surfaces this as a tailored error message: + +``` +Workspace was deleted on the platform at . +Regenerate workspace + token from the canvas → Tokens tab. +``` + +This is the **startup-time** counterpart to the heartbeat-401 escalation +above. If you see it within seconds of starting your runtime (rather +than after ~60s of heartbeat failures), the workspace was already gone +when you connected — regenerate as instructed. + +Audit-trail tools that intentionally want to inspect a removed workspace's +metadata (admin dashboards, "show me deleted workspaces" tooling) can +opt back into the legacy 200 + body shape via +`GET /workspaces/?include_removed=true`. + ### `claude mcp list` shows the new config but tools still 401 `/mcp` reconnect re-spawns the **cached** MCP config from session From 403725b970e76033d8971f52ce8a742d3972b239 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 22:42:42 -0700 Subject: [PATCH 06/10] feat(docs): align doc.moleculesai.app chrome with landing's warm-paper design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the docs site in visual parity with moleculesai.app so docs, marketing, and the canvas read as one product. Five focused changes inside the existing fumadocs shell — no MDX or content touched, no runtime/build dep changes: - global.css: override fumadocs @theme tokens with the warm-paper palette (#fafaf7 bg, #15181c ink, #3b5bdb governance blue, #efece4 muted, #e6e2d8 border). Dark mode keeps fumadocs' neutral defaults so dark-pref readers still get a readable docs site. - layout.tsx: swap Inter → Geist (sans) + JetBrains Mono (code), matching the landing's font stack. Wired through @theme so Tailwind's font-sans / font-mono utilities pick them up. - layout.config.tsx: brand the topbar — inline Molecule logo SVG + "Molecule AI · DOCS" lockup, plus three external links to the rest of the surface (Platform → app, Marketplace → market, Landing → www) and the org GitHub. Mirrors the landing's collapsed nav. - (home)/page.tsx: replace the stock fumadocs landing with a hero-style page matching the landing — statusbar strip, "Phase 35 Marketplace public beta" eyebrow, the same shimmering h1 copy, three quick-start lane cards (Build a workspace / Run an organisation / Publish to the Marketplace) pointing into the docs tree. Build is clean (106 static pages still generate). Existing /docs/* pages inherit the new tokens via fumadocs' DocsLayout, so the entire site shifts to the warm-paper aesthetic without touching MDX. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/(home)/page.tsx | 113 ++++++++++++++++++++++++++++++++++-------- app/global.css | 29 +++++++++++ app/layout.config.tsx | 45 ++++++++++++++++- app/layout.tsx | 18 +++++-- 4 files changed, 179 insertions(+), 26 deletions(-) diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index f5a4fb9..64982c1 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -1,29 +1,100 @@ import Link from 'next/link'; +// Three quick-start lanes — keeps the home page from being a wall of text +// and lets builders, operators, and integrators each find their entry +// point in one click. +const lanes = [ + { + kicker: '01', + title: 'Build a workspace', + body: 'Pick a runtime template (Claude Code, LangGraph, CrewAI, Hermes, …), wire your tools, and ship.', + href: '/docs/workspace', + cta: 'Workspace guide →', + }, + { + kicker: '02', + title: 'Run an organisation', + body: 'Topology, A2A, three-tier memory, governance — the platform layer that ties multi-agent teams together.', + href: '/docs/platform', + cta: 'Platform reference →', + }, + { + kicker: '03', + title: 'Publish to the Marketplace', + body: 'Plugins, agents, and org bundles ship as signed manifests. Authors keep 80%, paid via Stripe Connect.', + href: '/docs/marketplace', + cta: 'Author guide →', + }, +]; + export default function HomePage() { return ( -
-

- Molecule AI -

-

- Build and run multi-agent organisations. Templates, plugins, channels, - and the runtime that ties them together — documented end to end. -

-
- - Read the docs - - - View on GitHub - +
+ {/* Statusbar — mirrors the landing's "All systems · status.* · phase" strip */} +
+ + + All systems · status.moleculesai.app + + Phase 33 shipped · Phase 35 Marketplace public beta
+ + {/* Hero */} +
+
+ + Documentation +
+

+ The operating system for{' '} + AI agent organizations. +

+

+ Build and run multi-agent organisations the way you'd staff a company. + Templates, plugins, channels, runtimes, governance — documented end + to end. +

+
+ + Read the docs + + + View on GitHub + +
+
+ + {/* Three lanes */} +
+
+ {lanes.map((lane) => ( + +
+ {lane.kicker} +
+

{lane.title}

+

+ {lane.body} +

+
+ {lane.cta} +
+ + ))} +
+
); } diff --git a/app/global.css b/app/global.css index 50b3bc2..b4ee06e 100644 --- a/app/global.css +++ b/app/global.css @@ -1,3 +1,32 @@ @import 'tailwindcss'; @import 'fumadocs-ui/css/neutral.css'; @import 'fumadocs-ui/css/preset.css'; + +/* Warm-paper light theme — aligned with the landing page (moleculesai.app). + Tokens map fumadocs' @theme variables onto our brand palette so docs, + marketing, and the canvas read as one product. */ +@theme { + --font-sans: var(--font-geist), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-mono), ui-monospace, SFMono-Regular, monospace; + + --color-fd-background: #fafaf7; + --color-fd-foreground: #15181c; + --color-fd-muted: #f3f1ec; + --color-fd-muted-foreground: #5a5e66; + --color-fd-popover: #ffffff; + --color-fd-popover-foreground: #15181c; + --color-fd-card: #ffffff; + --color-fd-card-foreground: #15181c; + --color-fd-border: #e6e2d8; + --color-fd-primary: #3b5bdb; + --color-fd-primary-foreground: #ffffff; + --color-fd-secondary: #efece4; + --color-fd-secondary-foreground: #15181c; + --color-fd-accent: #efece4; + --color-fd-accent-foreground: #15181c; + --color-fd-ring: #3b5bdb; + --color-fd-overlay: hsla(0, 0%, 0%, 0.18); +} + +/* Dark mode keeps fumadocs' neutral defaults — readers expect docs sites + to honor their system preference, and our landing only ships light. */ diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 9e29374..8fd60ff 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -1,7 +1,50 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; +// Molecule logo — the same triangle-of-nodes mark used on moleculesai.app. +// Inlined as a JSX element so fumadocs renders it in the topbar without a +// separate asset request. +const MoleculeLogo = ( + +); + export const baseOptions: BaseLayoutProps = { nav: { - title: 'Molecule AI', + title: ( + + {MoleculeLogo} + Molecule AI + + Docs + + + ), + url: 'https://doc.moleculesai.app', }, + links: [ + { text: 'Platform', url: 'https://app.moleculesai.app', external: true }, + { text: 'Marketplace', url: 'https://market.moleculesai.app', external: true }, + { text: 'Landing', url: 'https://www.moleculesai.app', external: true }, + ], + githubUrl: 'https://github.com/Molecule-AI', }; diff --git a/app/layout.tsx b/app/layout.tsx index 90f4d40..25d5217 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,10 +1,16 @@ import './global.css'; import { RootProvider } from 'fumadocs-ui/provider/next'; -import { Inter } from 'next/font/google'; +import { Geist, JetBrains_Mono } from 'next/font/google'; import type { ReactNode } from 'react'; -const inter = Inter({ +const geist = Geist({ subsets: ['latin'], + variable: '--font-geist', +}); + +const jetbrains = JetBrains_Mono({ + subsets: ['latin'], + variable: '--font-mono', }); export const metadata = { @@ -19,8 +25,12 @@ export const metadata = { export default function Layout({ children }: { children: ReactNode }) { return ( - - + + {children} From 1ccd92e0c8081e735e1671082ae1e3ae21127f83 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 22:48:53 -0700 Subject: [PATCH 07/10] docs(workspace-runtime): document MOLECULE_SMOKE_MODE adapter contract The publish-image boot-smoke gate (molecule-core#2275) invokes the runtime with MOLECULE_SMOKE_MODE=1 + stub creds to catch lazy imports inside executor.execute(). Adapters whose setup() does real I/O (subprocess spawn, network calls, uid-sensitive writes) need to opt out of that I/O when MOLECULE_SMOKE_MODE=1, otherwise the gate fails before reaching the runtime's smoke short-circuit. This documents: - the contract (one-line opt-out for Python adapter.setup() and shell entrypoints that wrap molecule-runtime) - which boot stages the gate exercises - the stub env the harness sets so adapters can reason about what they can rely on under smoke mode Surfaced when running publish-image across all 8 workspace templates: openclaw and hermes hit the contract gap because both spawn real gateway subprocesses in setup; six others passed without any contract awareness because their setup is light. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../docs/agent-runtime/workspace-runtime.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/content/docs/agent-runtime/workspace-runtime.md b/content/docs/agent-runtime/workspace-runtime.md index fa59694..8b87721 100644 --- a/content/docs/agent-runtime/workspace-runtime.md +++ b/content/docs/agent-runtime/workspace-runtime.md @@ -73,6 +73,58 @@ At a high level, `workspace/main.py` does this: 10. Start the skill watcher when skills are configured. 11. Serve the A2A app through Uvicorn. +## Boot-Smoke Contract (`MOLECULE_SMOKE_MODE`) + +The image-publish CI pipeline runs each template's image with `MOLECULE_SMOKE_MODE=1` to exercise lazy imports inside `executor.execute()` against stub credentials and no network. The runtime detects the env var, invokes `executor.execute()` once with a stubbed `RequestContext` and a short timeout, then exits — registration, heartbeats, and the A2A server are skipped. + +This catches lazy imports that pure `python3 -c "import adapter"` smokes miss: imports nested inside `if`-branches, deferred until first call, or behind `importlib.import_module()`. + +### What adapter authors need to do + +**Most adapters need to do nothing.** If `setup()` only writes files, parses config, or instantiates Python objects, the smoke gate just works. + +**Adapters whose `setup()` does real I/O must opt out of that I/O under smoke mode.** This applies to: + +- spawning subprocesses that require valid credentials (e.g. a gateway daemon) +- making real network calls +- writing to filesystem locations that need a specific uid/gid the smoke harness can't guarantee + +The contract: + +```python +async def setup(self, config: AdapterConfig) -> None: + if os.environ.get("MOLECULE_SMOKE_MODE") == "1": + return # skip real I/O; runtime's smoke short-circuit handles the rest + # ... real setup ... +``` + +For shell entrypoints that wrap `molecule-runtime`: + +```bash +if [ "${MOLECULE_SMOKE_MODE:-0}" = "1" ]; then + exec molecule-runtime +fi +``` + +### What gets exercised under smoke mode + +- All `/app/*.py` modules import cleanly (covered by a separate static-import smoke step) +- `adapter.setup()` runs (with the opt-out above for I/O-heavy adapters) +- `adapter.create_executor()` runs +- `executor.execute()` is invoked once against a stub `RequestContext`/`EventQueue` with `MOLECULE_SMOKE_TIMEOUT_SECS` (default 5s); a clean timeout exits 0, an import error exits non-zero + +### Stub env the smoke harness sets + +| Var | Value | +|---|---| +| `MOLECULE_SMOKE_MODE` | `1` | +| `MOLECULE_SMOKE_TIMEOUT_SECS` | `10` (CI default) | +| `WORKSPACE_ID` | `fake-smoke` | +| `PYTHONPATH` | `/app` (mirrors the platform provisioner) | +| `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `OPENAI_API_KEY` | `sk-fake-smoke-*` | + +A `config.yaml` from the template repo's root is mounted at `/configs/config.yaml`. + ## Core Runtime Pieces | File | Responsibility | From ec78c7637bce3bf6d4ddf9d4965282512102deaa Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Thu, 30 Apr 2026 23:54:57 -0700 Subject: [PATCH 08/10] docs(workspace-runtime): clarify what the boot-smoke gate does NOT prove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A green gate means imports are healthy enough that executor.execute() reaches its body. It does NOT prove that execute() produces the right output: adapters with real I/O inside execute() (subprocess to a gateway, httpx call upstream) time out under the 5s harness window, and the gate treats a clean timeout as success. Surfaced while running publish-image across all 8 templates: the openclaw smoke "passed" with timing-out behavior in execute() because OpenClawA2AExecutor proxies to a subprocess that doesn't exist in the smoke env. Reading the green check, future operators might over-trust it as a runtime-correctness signal — it isn't. Add a "What the gate does NOT prove" subsection so readers don't mistake the import-regression coverage for an integration test. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/agent-runtime/workspace-runtime.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/content/docs/agent-runtime/workspace-runtime.md b/content/docs/agent-runtime/workspace-runtime.md index 8b87721..da331e2 100644 --- a/content/docs/agent-runtime/workspace-runtime.md +++ b/content/docs/agent-runtime/workspace-runtime.md @@ -113,6 +113,14 @@ fi - `adapter.create_executor()` runs - `executor.execute()` is invoked once against a stub `RequestContext`/`EventQueue` with `MOLECULE_SMOKE_TIMEOUT_SECS` (default 5s); a clean timeout exits 0, an import error exits non-zero +### What the gate does NOT prove + +A green gate means **"imports are healthy enough that `executor.execute()` reaches its body"** — that's the regression class the gate exists to catch (lazy `from x import y` inside an `if`-branch, or `importlib.import_module()` on a path that breaks after a wheel bump). + +It does **not** prove that `execute()` produces the right output for real input. Adapters that make real I/O calls inside `execute()` (subprocess to a gateway, httpx call to an upstream LLM) will time out under the harness's default 5s window, and the gate treats a clean timeout as success. The stub `RequestContext` carries an empty user message and the harness never inspects what `execute()` writes back. + +If you need correctness coverage, write a separate integration test that runs the workspace against real or mocked infrastructure — the smoke gate is a strict subset. + ### Stub env the smoke harness sets | Var | Value | From 28600d79562eda3daeeaa2910167caebfc49d566 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Fri, 1 May 2026 00:00:02 -0700 Subject: [PATCH 09/10] docs(workspace-runtime): correct smoke-gate caveat factual errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two errors in the merged caveat (#107): 1. Claimed the stub RequestContext "carries an empty user message" — actually carries "smoke test" text (smoke_mode.py:76 calls `new_text_message("smoke test")`, with the explicit comment that it's "enough that extract_message_text(context) returns non-empty input"). Adapter authors gating smoke-mode behavior on extract_message_text(ctx) == "" would have a logic that never fires. 2. Described only the timeout-pass path. The harness also returns 0 on ANY non-import exception (smoke_mode.py:135-143) — the bare `except Exception` block treats RuntimeError, auth errors, validation errors etc. as "downstream of the import gate" and exits clean. Spelling out all three pass cases (clean return, timeout, non-import exception) is the honest description. Caught while re-reading smoke_mode.py to verify claims for a review pass — found I had asserted both behaviors from memory without checking, exactly the failure mode my e2e-test memory just got a worked-example update about. Co-Authored-By: Claude Opus 4.7 (1M context) --- content/docs/agent-runtime/workspace-runtime.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/content/docs/agent-runtime/workspace-runtime.md b/content/docs/agent-runtime/workspace-runtime.md index da331e2..8580a72 100644 --- a/content/docs/agent-runtime/workspace-runtime.md +++ b/content/docs/agent-runtime/workspace-runtime.md @@ -117,7 +117,13 @@ fi A green gate means **"imports are healthy enough that `executor.execute()` reaches its body"** — that's the regression class the gate exists to catch (lazy `from x import y` inside an `if`-branch, or `importlib.import_module()` on a path that breaks after a wheel bump). -It does **not** prove that `execute()` produces the right output for real input. Adapters that make real I/O calls inside `execute()` (subprocess to a gateway, httpx call to an upstream LLM) will time out under the harness's default 5s window, and the gate treats a clean timeout as success. The stub `RequestContext` carries an empty user message and the harness never inspects what `execute()` writes back. +It does **not** prove that `execute()` produces the right output for real input. The harness reports PASS in three distinct cases: + +1. **Clean return** — execute() ran to completion within the timeout. +2. **Timeout** — execute() was still running when the timer fired (typical for adapters that do real I/O inside execute(): subprocess to a gateway, httpx call to an upstream LLM). +3. **Any non-import exception** — execute() raised `RuntimeError`, auth errors, validation errors, etc. The harness only fails on `ImportError`/`ModuleNotFoundError`. + +The stub `RequestContext` carries a non-empty `"smoke test"` text message (so adapters relying on `extract_message_text(ctx)` returning input still work), and the harness never drains the `EventQueue` — what `execute()` writes back is ignored. If you need correctness coverage, write a separate integration test that runs the workspace against real or mocked infrastructure — the smoke gate is a strict subset. From b26d7ee9b2d43449c75e99e6f2eebe05e1e04c78 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Fri, 1 May 2026 15:33:07 -0700 Subject: [PATCH 10/10] docs(mcp): rewrite inbound-delivery section for dual push+poll contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors molecule-core feat/universal-push-via-instructions: documents the universal poll path (instructions field → every MCP client) plus the optional push path (notifications/claude/channel for Claude Code with the dev-channels flag or a future allowlist entry). Honesty pass on the prior text: previous version claimed push works "automatically" and "there is no config flag to toggle." That was true for the wire shape but false for live UX — standard `claude` launches without --dangerously-load-development-channels silently drop the notification, and non-Claude clients ignore the Claude- namespaced method entirely. New text spells out exactly which clients get push, which get poll, and the per-client capability matrix. Adds MOLECULE_MCP_POLL_TIMEOUT_SECS to the env-var table — the new operator knob landed in the wheel. Successor to #44/#49 (which closed without the flag caveat). --- content/docs/runtime-mcp.mdx | 75 +++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/content/docs/runtime-mcp.mdx b/content/docs/runtime-mcp.mdx index ae4d510..b6d9e53 100644 --- a/content/docs/runtime-mcp.mdx +++ b/content/docs/runtime-mcp.mdx @@ -104,14 +104,15 @@ Cline) and restart the client. ## Optional — declare your identity & capabilities -Three additional env vars control how your workspace appears on the -canvas and to peer agents calling `list_peers`: +Four additional env vars control how your workspace appears on the +canvas and how the wheel's inbound-delivery contract behaves: | Env var | What it sets | Default | |---|---|---| | `MOLECULE_AGENT_NAME` | Display name on the canvas card | `molecule-mcp-{id[:8]}` | | `MOLECULE_AGENT_DESCRIPTION` | One-line description in Details/Skills tabs | empty | | `MOLECULE_AGENT_SKILLS` | Comma-separated skill names — e.g. `research,code-review,memory-curation` | `[]` | +| `MOLECULE_MCP_POLL_TIMEOUT_SECS` | How long the agent blocks on `wait_for_message` per turn (the universal poll path). `0` disables polling for push-only mode (Claude Code with `--dangerously-load-development-channels`). Above 60 clamps to 60. | `2` | Skills are surfaced two places: @@ -158,7 +159,7 @@ status. If the workspace is still offline after ~30s, check | `delegate_task` | Send a task to a peer and wait for the reply | | `delegate_task_async` | Fire-and-forget delegation; result lands in inbox | | `check_task_status` | Poll an async delegation | -| `wait_for_message` | Block until the next inbound A2A message arrives | +| `wait_for_message` | Block until the next inbound A2A message arrives — the universal inbound-delivery primitive (see [Inbound delivery](#inbound-delivery-universal-poll-optional-push)) | | `inbox_peek` / `inbox_pop` | Inspect / acknowledge queued inbound messages | | `send_message_to_user` | Push a chat bubble to the user's canvas | | `commit_memory` / `recall_memory` | Persistent KV (local / team / global scope) | @@ -168,29 +169,63 @@ External runtimes can't accept inbound HTTP, so the wheel polls through `wait_for_message` + `inbox_peek` / `inbox_pop`. Use those instead of waiting for an HTTP webhook — there isn't one. -### Push-UX for notification-capable hosts +### Inbound delivery: universal poll, optional push -On top of the polling tools, the wheel emits a JSON-RPC notification -(`notifications/claude/channel`) on every new inbound message. Hosts -that recognise that method (Claude Code today; any compliant client -tomorrow) treat the notification as a conversation interrupt — the -message text becomes the next agent turn without the agent having to -call `wait_for_message` first. +Inbound messages reach the agent via one of two paths. The wheel +exposes both; which one fires depends on the host's capabilities. +Both paths converge on the same `inbox_pop` ack so dedup is automatic. -Hosts that don't recognise the method silently ignore it, so the same -wheel works for both push-capable and poll-only runtimes. There is no -config flag to toggle: pollers keep polling, notification-capable hosts -get push automatically. +**Poll path (universal default — works on every spec-compliant MCP +client).** The wheel's `initialize` handshake includes an `instructions` +field telling the agent: *"At the start of every turn, before producing +your final response, call `wait_for_message(timeout_secs=N)` to check +for inbound messages."* Every MCP client surfaces `instructions` to +the agent's system prompt automatically, so Claude Code, Cursor, Cline, +OpenCode, hermes-agent, and codex all receive the polling contract +without any per-client wiring. The 2-second default is tuned for the +"peer A2A landed seconds before my turn started" common case; tune +via the `MOLECULE_MCP_POLL_TIMEOUT_SECS` env var +(see "Optional — declare your identity & capabilities" above). + +**Push path (Claude Code with channel push enabled — strictly +better when available).** On top of the poll path, the wheel emits a +JSON-RPC notification (`notifications/claude/channel`) on every new +inbound message and declares the matching `experimental.claude/channel` +capability in `initialize`. Claude Code with channel push enabled +turns the notification into an inline `` synthetic user turn — zero agent-side polling cost, zero +per-turn stall. + +**Today (research preview), Claude Code's channel push requires +either the `--dangerously-load-development-channels` launch flag OR +an entry on Claude Code's approved channel-server allowlist.** The +wheel ships the wire shape correctly, but a standard `claude` launch +without the flag silently drops the notification — which is why the +poll path has to be the floor. + +Set `MOLECULE_MCP_POLL_TIMEOUT_SECS=0` to disable polling entirely +when you're running Claude Code with the dev-channels flag and don't +want the per-turn stall. The instructions adapt automatically: with +polling disabled, the agent is told push is the only delivery path. + +| Client | Push path | Poll path | +|---|---|---| +| Claude Code with `--dangerously-load-development-channels` | ✅ inline tag | ✅ also works | +| Claude Code (standard launch) | ❌ silently dropped | ✅ via instructions | +| Cursor / Cline / OpenCode / codex | ❌ method ignored | ✅ via instructions | +| hermes-agent | ❌ method ignored | ✅ naturally polls every cycle | ### MCP spec compliance The wheel speaks MCP protocol version **2024-11-05** over stdio -JSON-RPC, declaring only the `tools` capability. It implements the -standard request methods and nothing client-specific: +JSON-RPC. It declares the standard `tools` capability plus the +`experimental.claude/channel` capability for the optional push path +(see [Inbound delivery](#inbound-delivery-universal-poll-optional-push)). +It implements the standard request methods and nothing client-specific: | MCP method | Behavior | |---|---| -| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` capability | +| `initialize` | Echoes `protocolVersion: "2024-11-05"`, `serverInfo`, declares `tools` + `experimental.claude/channel` capabilities, returns the dual-path delivery `instructions` | | `notifications/initialized` | No-op (no response — per spec) | | `tools/list` | Returns all exposed tools in one response (no pagination cursor — surface is small) | | `tools/call` | Dispatches by name, returns `content: [{ type: "text", text: ... }]` | @@ -198,8 +233,10 @@ standard request methods and nothing client-specific: The push-UX notification (`notifications/claude/channel`) is the only non-standard method emitted, and it's a one-way notification — clients -that don't handle it discard it per JSON-RPC semantics. No part of the -wheel's tool surface depends on a client recognizing it. +that don't handle it discard it per JSON-RPC semantics. The poll path +(via the standard `instructions` field) carries delivery for those +clients, so no part of the wheel's tool surface depends on a client +recognizing the notification. This means **any spec-compliant MCP client** can drive the wheel: Claude Code, Cursor, Cline, OpenCode, hermes-agent, or anything else