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} diff --git a/content/docs/agent-runtime/workspace-runtime.md b/content/docs/agent-runtime/workspace-runtime.md index fa59694..8580a72 100644 --- a/content/docs/agent-runtime/workspace-runtime.md +++ b/content/docs/agent-runtime/workspace-runtime.md @@ -73,6 +73,72 @@ 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 + +### 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. 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. + +### 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 | diff --git a/content/docs/external-agents.mdx b/content/docs/external-agents.mdx index 0656174..85e7b99 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`) @@ -231,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/meta.json b/content/docs/meta.json index e705359..9c3abf8 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..b6d9e53 --- /dev/null +++ b/content/docs/runtime-mcp.mdx @@ -0,0 +1,377 @@ +--- +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. + +## Optional — declare your identity & capabilities + +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: + +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** +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 — 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) | + +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. + +### Inbound delivery: universal poll, optional push + +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. + +**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. 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` + `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: ... }]` | +| _(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. 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 +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 +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. + +### 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. + +### `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 +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 | +|---|---| +| 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