diff --git a/canvas/src/components/ExternalConnectModal.tsx b/canvas/src/components/ExternalConnectModal.tsx index af51c447..a9b1f360 100644 --- a/canvas/src/components/ExternalConnectModal.tsx +++ b/canvas/src/components/ExternalConnectModal.tsx @@ -18,6 +18,157 @@ import { useCallback, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; +type Tab = "python" | "curl" | "claude" | "mcp" | "hermes" | "codex" | "openclaw" | "fields"; + +// Per-tab help metadata: docs link, where-to-install link, common errors. +// All URLs verified against repo content (docs/guides/* file paths map to +// docs.molecule.ai/docs/guides/*; canonical hostname confirmed by existing +// blog post canonical metadata) or against the snippet text the operator +// just copied. Never linking to a URL that wasn't already in product — +// dead links here defeat the purpose of "more comprehensive instructions." +const TAB_HELP: Record< + Tab, + { + docsUrl?: string; + docsLabel?: string; + downloadUrl?: string; + downloadLabel?: string; + commonIssues?: { symptom: string; check: string }[]; + } +> = { + mcp: { + docsUrl: "https://docs.molecule.ai/docs/guides/mcp-server-setup", + docsLabel: "MCP server setup guide", + downloadUrl: "https://pypi.org/project/molecule-ai-workspace-runtime/", + downloadLabel: "molecule-ai-workspace-runtime on PyPI", + commonIssues: [ + { + symptom: "Tools not appearing in your agent", + check: + "Run `claude mcp list` (or your runtime's equivalent) — the molecule entry should be listed. If missing, re-run the `claude mcp add` line.", + }, + { + symptom: "ConnectionRefused / DNS error on first call", + check: + "PLATFORM_URL must include the scheme (https://) and have no trailing slash. Verify with `curl $PLATFORM_URL/healthz`.", + }, + ], + }, + python: { + docsUrl: + "https://docs.molecule.ai/docs/guides/external-agent-registration", + docsLabel: "External agent registration guide", + downloadUrl: "https://pypi.org/project/molecule-ai-workspace-runtime/", + downloadLabel: "molecule-ai-workspace-runtime on PyPI", + commonIssues: [ + { + symptom: "401 from /heartbeat", + check: + "AUTH_TOKEN expired or wrong workspace_id. Tokens are shown only once at create time — re-create the workspace to get a fresh token.", + }, + { + symptom: "AGENT_URL not reachable from platform", + check: + "Public HTTPS URL required for inbound A2A. Use ngrok or Cloudflare Tunnel if your agent is behind NAT.", + }, + ], + }, + claude: { + docsUrl: + "https://docs.molecule.ai/docs/guides/external-agent-registration", + docsLabel: "External agent registration guide", + downloadUrl: "https://claude.com/claude-code", + downloadLabel: "Claude Code (claude.com)", + commonIssues: [ + { + symptom: "plugin not installed", + check: + "Run `/plugin marketplace add Molecule-AI/molecule-mcp-claude-channel` then `/plugin install molecule@molecule-mcp-claude-channel` inside Claude Code, then `/reload-plugins`.", + }, + { + symptom: "not on the approved channels allowlist", + check: + "Custom channels need `--dangerously-load-development-channels` on the launch command. Team/Enterprise orgs need admin to set `channelsEnabled` + `allowedChannelPlugins` in claude.ai admin settings.", + }, + { + symptom: "Inbound messages not arriving", + check: + "Check stderr for `molecule channel: connected — watching N workspace(s)`. Verify ~/.claude/channels/molecule/.env has the right PLATFORM_URL + token.", + }, + ], + }, + hermes: { + docsUrl: + "https://docs.molecule.ai/docs/guides/external-agent-registration", + docsLabel: "External agent registration guide", + downloadUrl: "https://github.com/NousResearch/hermes-agent", + downloadLabel: "hermes-agent (NousResearch)", + commonIssues: [ + { + symptom: "Gateway start failure", + check: + "Tail ~/.hermes/gateway.log. YAML duplicate-key in config.yaml is the most common cause — `gateway:` block must appear exactly once.", + }, + { + symptom: "Plugin not discovered after install", + check: + "Run `pip show hermes-channel-molecule` to confirm install. Some hermes builds need `hermes plugin reload` before the new platform_plugins entry takes effect.", + }, + ], + }, + codex: { + docsUrl: "https://docs.molecule.ai/docs/guides/mcp-server-setup", + docsLabel: "MCP server setup guide", + downloadUrl: "https://github.com/openai/codex", + downloadLabel: "openai/codex", + commonIssues: [ + { + symptom: "[mcp_servers.molecule] not loaded", + check: + "Codex must be ≥ 0.57. Check with `codex --version`; upgrade via `npm install -g @openai/codex@latest`.", + }, + { + symptom: "TOML parse error after re-running setup", + check: + "TOML rejects duplicate `[mcp_servers.molecule]` tables. Open ~/.codex/config.toml and remove the old block before pasting the new one.", + }, + ], + }, + openclaw: { + docsUrl: "https://docs.molecule.ai/docs/guides/mcp-server-setup", + docsLabel: "MCP server setup guide", + commonIssues: [ + { + symptom: "Gateway not starting", + check: + "Tail ~/.openclaw/gateway.log. The loopback bind requires :18789 to be free — check with `lsof -iTCP:18789`.", + }, + { + symptom: "openclaw mcp set rejected", + check: + "The heredoc generates JSON; verify it parsed by running `jq < ~/.openclaw/mcp/molecule.json`. Re-run `openclaw mcp set` if the file is malformed.", + }, + ], + }, + curl: { + docsUrl: + "https://docs.molecule.ai/docs/guides/external-agent-registration", + docsLabel: "External agent registration guide", + commonIssues: [ + { + symptom: "401 / 403 on register", + check: + "WORKSPACE_AUTH_TOKEN must be the value shown at workspace create. Tokens are shown only once.", + }, + ], + }, + fields: { + docsUrl: + "https://docs.molecule.ai/docs/guides/external-agent-registration", + docsLabel: "External agent registration guide", + }, +}; + export interface ExternalConnectionInfo { workspace_id: string; platform_url: string; @@ -63,8 +214,6 @@ interface Props { onClose: () => void; } -type Tab = "python" | "curl" | "claude" | "mcp" | "hermes" | "codex" | "openclaw" | "fields"; - export function ExternalConnectModal({ info, onClose }: Props) { // Default to Universal MCP when the platform offers it — runtime- // agnostic outbound tool path that works for any MCP-aware runtime @@ -303,6 +452,7 @@ export function ExternalConnectModal({ info, onClose }: Props) { copy(info.heartbeat_endpoint, "hb")} copied={copiedKey === "hb"} /> )} +
@@ -351,6 +501,70 @@ function SnippetBlock({ ); } +// HelpBlock — collapsible "Need help?" section under each tab's snippet. +// Renders only the keys present in the per-tab help metadata (no empty +// sections). Closed by default so the snippet stays the visual focus; +// operators with a working setup never see this. Uses native
+// for keyboard accessibility (Tab + Enter) without extra ARIA wiring. +function HelpBlock({ + help, +}: { + help: (typeof TAB_HELP)[Tab] | undefined; +}) { + if (!help) return null; + const { docsUrl, docsLabel, downloadUrl, downloadLabel, commonIssues } = help; + if (!docsUrl && !downloadUrl && !commonIssues?.length) return null; + + return ( +
+ + Need help? — install link, docs, common errors + +
+ {downloadUrl && ( +
+ Where to install: + + {downloadLabel || downloadUrl} + +
+ )} + {docsUrl && ( +
+ Documentation: + + {docsLabel || docsUrl} + +
+ )} + {commonIssues && commonIssues.length > 0 && ( +
+
Common errors:
+
    + {commonIssues.map((issue, i) => ( +
  • + {issue.symptom} + — {issue.check} +
  • + ))} +
+
+ )} +
+
+ ); +} + function Field({ label, value, diff --git a/workspace-server/internal/handlers/external_connection.go b/workspace-server/internal/handlers/external_connection.go index f3ff3553..847ebee6 100644 --- a/workspace-server/internal/handlers/external_connection.go +++ b/workspace-server/internal/handlers/external_connection.go @@ -83,7 +83,20 @@ curl -fsS -X POST "{{PLATFORM_URL}}/registry/register" \ const externalChannelTemplate = `# Claude Code channel — bridges this workspace's A2A traffic into your # Claude Code session. No tunnel/public URL needed (polling-based). # -# 1. Save this token + workspace_id, then create ~/.claude/channels/molecule/.env: +# Prereq: Bun installed (channel plugins are Bun scripts). +# bun --version # must print a version number +# +# 1. Inside Claude Code, install the channel plugin from its GitHub repo. +# The plugin is NOT on Anthropic's default allowlist, so a one-time +# marketplace-add is needed before install: +# +# /plugin marketplace add Molecule-AI/molecule-mcp-claude-channel +# /plugin install molecule@molecule-mcp-claude-channel +# +# Then either run /reload-plugins or restart Claude Code so the +# plugin is registered. +# +# 2. Create the per-watched-workspace config file: mkdir -p ~/.claude/channels/molecule cat > ~/.claude/channels/molecule/.env <<'EOF' MOLECULE_PLATFORM_URL={{PLATFORM_URL}} @@ -92,13 +105,32 @@ MOLECULE_WORKSPACE_TOKENS= EOF chmod 600 ~/.claude/channels/molecule/.env -# 2. Launch Claude Code with the channel enabled: -claude --channels plugin:molecule@Molecule-AI/molecule-mcp-claude-channel +# 3. Launch Claude Code with the channel enabled. Custom (non-Anthropic- +# allowlisted) channels need the --dangerously-load-development-channels +# flag to opt in — without it, you'll see "not on the approved channels +# allowlist" on startup. +claude --dangerously-load-development-channels \ + --channels plugin:molecule@molecule-mcp-claude-channel +# You should see on stderr: +# molecule channel: connected — watching 1 workspace(s) at {{PLATFORM_URL}} +# # Inbound A2A messages now surface as conversation turns. Claude's # replies route back via the reply_to_workspace MCP tool — no extra # wiring on your side. # +# Common errors: +# "plugin not installed" → Step 1 didn't run; run /plugin install +# inside Claude Code, then /reload-plugins. +# "not on approved channels allowlist" → Add --dangerously-load-development-channels +# to the launch command (Step 3). +# "config-missing" → ~/.claude/channels/molecule/.env not +# readable; re-run Step 2 and check chmod. +# +# Team/Enterprise orgs: the --dangerously-load-development-channels flag is +# blocked by managed settings. Your admin must set channelsEnabled=true and +# add the plugin to allowedChannelPlugins in claude.ai admin settings. +# # Multi-workspace: comma-separate IDs and tokens (same order). See # https://github.com/Molecule-AI/molecule-mcp-claude-channel for # pairing flow, push-mode upgrade, and v0.2 roadmap.