Compare commits

...

18 Commits

Author SHA1 Message Date
hongming-pc2 e267875fb3 Merge PR #18 via Gitea merge queue
CI / test (push) Successful in 36s
Serialized merge by gitea-merge-queue after current-main, SOP, and required CI checks were green.
2026-05-17 19:13:13 +00:00
sdk-dev 68e0505d9f docs(mcp): fix stale SDK reference in Platform Integration section
[Do] Manual gate post
sop-checklist / all-items-acked Manual gate post
CI / test (pull_request) Successful in 19s
PR #17 updated known-issues.md but dropped the CLAUDE.md fix.
The Platform Integration section incorrectly claimed the server uses
the Python SDK (molecule-sdk-python). The MCP server has its own
TypeScript client in src/api.ts — the Python SDK is for remote agents.

Also fixed "reads data via the platform SDK" → "reads data via the
platform REST API" in the Postgres section.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 00:14:04 +00:00
sdk-dev d86fc82014 docs(mcp): fix KI-007 — heartbeat issue belongs in Python SDK, not MCP server
CI / test (push) Successful in 1m11s
KI-007 was mislocated: the MCP server exposes read-only heartbeat query
tools (list_remote_agents, get_remote_agent_state, etc.) — it has no
background heartbeat loop. The actual orphaned-heartbeat problem lives in
the Python SDK's run_heartbeat_loop().

Reclassified: clarified the MCP server scope and pointed to SDK KI-009.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 04:50:19 +00:00
sdk-lead 0ce17a6938 Merge pull request 'docs(mcp): sync README.md — 22→87 tools, fix env var and pip package name' (#6) from docs/sync-readme into main
CI / test (push) Waiting to run
2026-05-11 04:43:09 +00:00
sdk-dev 1b0ff4e780 docs(mcp): sync README.md — 22→87 tools, fix env var and pip package name
CI / test (pull_request) Successful in 43s
README.md listed only 22 of 87 tools. Regenerated to:
- Header: "87 Tools Available" with link to CLAUDE.md full registry
- Highlights table: shows all 12 tool categories
- Env vars: MOLECULE_URL → MOLECULE_API_URL (matches current API)
- MCP host configs: updated to MOLECULE_API_URL
- Remote agents example: molecule-sdk → molecule-ai-sdk

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 04:33:05 +00:00
sdk-lead 8b8f1b4387 Merge pull request 'docs(mcp): regenerate tool registry table — CLAUDE.md listed 29/87 tools' (#5) from docs/sync-tool-registry into main
CI / test (push) Successful in 54s
2026-05-11 04:25:41 +00:00
sdk-dev d9e3f1d191 style(mcp): move import to top of src/api.ts
CI / test (pull_request) Successful in 1m16s
Move `import { error as logError }` from mid-file (after PLATFORM_URL
declaration) to the conventional top-of-file position. ESM hoisting makes
it functionally equivalent but the mid-file placement is confusing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 04:16:14 +00:00
sdk-dev e2505de394 docs(mcp): regenerate tool registry table — CLAUDE.md listed 29/87 tools
CI / test (pull_request) Successful in 1m17s
CLAUDE.md was out of sync with the codebase. The tool registry table only
listed 29 of the 87 registered tools, with several tools under wrong
categories (e.g. send_message under Agent, list_peers under Agent).
Regenerated the table from source using:

  grep -h 'srv.tool(name, desc, ...)' src/tools/*.ts

Full tool registry now correct:
- Workspace: 6 → 8 (added pause_workspace, resume_workspace)
- Agent: 4 → 6 (replaced send_message with 6 real agent tools)
- Delegation: 2 → 8 (replaced delegate_task with 8 delegation tools)
- Secrets: 3 → 6 (added global secrets trio)
- Files: 4 → 7 (added replace_all_files, get_config, update_config)
- Memory: 2 → 9 (added session_search, get_shared_context, memory_* K/V tools)
- Plugins: 1 → 7 (added list_installed, uninstall, sources, available, compatibility)
- Channels: 3 → 8 (added list_channel_adapters, update, remove, test, discover)
- Schedules: 3 → 6 (added update_schedule, run_schedule, get_schedule_history)
- Discovery: 1 → 14 (was only check_access; added peers, canvas, import/export, etc.)
- Remote Agents: 2 → 4 (added list_remote_agents, get_remote_agent_state, freshness)
- Approvals: 3 → 4 (added get_workspace_approvals)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 04:11:51 +00:00
sdk-lead 3cae41253d Merge pull request 'fix(mcp): correct pip install package name in setup command' (#4) from fix/correct-pip-package-name into main
CI / test (push) Successful in 1m19s
2026-05-11 03:54:35 +00:00
sdk-dev bf6f68a3b8 fix(mcp): correct pip install package name in setup command
CI / test (pull_request) Successful in 1m12s
`pip install molecule-sdk` was wrong — the PyPI package is
`molecule-ai-sdk`.  Also updated the example path comment from
`sdk/python/` to `molecule-sdk-python/` to match the actual repo name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 03:33:26 +00:00
sdk-lead 996a9f6230 Merge pull request 'fix(deps): npm audit fix — resolve 5 transitive vulnerabilities' (#3) from fix/audit-fix-vulnerabilities into main
CI / test (push) Successful in 1m20s
2026-05-11 03:26:33 +00:00
sdk-dev 4272978ad2 fix(deps): npm audit fix — resolve 5 transitive vulnerabilities
CI / test (pull_request) Successful in 1m26s
Patched 5 vulnerabilities (4 moderate, 1 high) in transitive deps:
- hono: JSX injection, CSS injection, JWT validation, cache leakage
- ip-address: XSS in HTML-emitting methods
- express-rate-limit: depends on vulnerable ip-address

Tests: 128 passed, 1 skipped (3 suites) — unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 03:15:50 +00:00
sdk-lead 85b6b1e115 Merge pull request 'ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)' (#2) from ci-rename-github-to-gitea into main
CI / test (push) Successful in 1m17s
2026-05-10 21:18:05 +00:00
infra-sre c6068b2b9f ci: rename .github/workflows -> .gitea/workflows (post-suspension sweep)
CI / test (pull_request) Successful in 1m17s
GitHub org Molecule-AI was suspended 2026-05-06; SCM moved to Gitea
(git.moleculesai.app). The wholesale `git push --mirror` migration left
workflow files under .github/workflows/, which Gitea Actions does NOT
read - it reads .gitea/workflows/ exclusively.

This rename + the cross-repo `uses:` path rewrite are the minimum
edits to make CI fire on this repo again. The workflow content itself
is not modified (other than the path rewrites and lowercasing of the
old `Molecule-AI` org reference to the post-suspension `molecule-ai`).

Refs: feedback_post_suspension_migration_must_sweep_dormant_repos
2026-05-10 14:13:01 -07:00
claude-ceo-assistant 717abd19d1 chore(ci): remove recovery marker (rerun delivered, see internal#233)
CI / test (push) Failing after 1s
2026-05-10 19:52:11 +00:00
claude-ceo-assistant 708535b798 chore(ci): re-fire after incident recovery 2026-05-10 (see internal#233; revert me)
CI / test (push) Failing after 1s
2026-05-10 19:51:32 +00:00
devops-engineer e17a54b7b6 Merge pull request 'fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)' (#1) from fix/post-suspension-github-urls into main
CI / test (push) Successful in 1m27s
2026-05-07 20:03:01 +00:00
devops-engineer 9de811596d fix(post-suspension): migrate github.com/Molecule-AI refs to git.moleculesai.app (Class G #168)
CI / test (pull_request) Successful in 1m33s
The GitHub org Molecule-AI was suspended on 2026-05-06; canonical SCM
is now Gitea at https://git.moleculesai.app/molecule-ai/. Stale
github.com/Molecule-AI/... URLs return 404 and break tooling that
clones / pip-installs / curls them.

This bundles all non-Go-module URL fixes for this repo into a single PR.
Go module path references (in *.go, go.mod, go.sum) are out of scope
here -- tracked separately under Task #140.

Token-auth clone URLs also flip ${GITHUB_TOKEN} -> ${GITEA_TOKEN} since
the GitHub token does not auth against Gitea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:02:55 -07:00
10 changed files with 181 additions and 128 deletions
+104 -48
View File
@@ -96,7 +96,10 @@ The workflow:
### APIs Connected
The server connects to the Molecule AI platform REST API. See the platform SDK (`../molecule-sdk-python`) for the underlying API client used.
The server connects to the Molecule AI platform REST API via its own TypeScript
client (`src/api.ts`). It does not use the Python SDK (`molecule-sdk-python`) —
the Python SDK is for remote agents that run outside the platform; this server
runs as an MCP bridge *on* the operator side.
### Environment Variables
@@ -110,7 +113,7 @@ For local development, copy `.env.example` → `.env` and fill in values.
### Postgres
Platform data lives in Postgres (source of truth). The server reads data via the platform SDK — it does not connect to Postgres directly.
Platform data lives in Postgres (source of truth). The server reads data via the platform REST API — it does not connect to Postgres directly.
## TypeScript Conventions
@@ -145,89 +148,142 @@ src/
## MCP Tool Registry
Full list of tools exposed by this server. Each is implemented in `src/tools/<name>.ts`.
Full list of tools exposed by this server (87 total). Each is implemented in `src/tools/<name>.ts`.
### Workspace Tools
### Workspace Tools (8)
| Tool | Description |
|------|-------------|
| `list_workspaces` | List all workspaces accessible to the authenticated user |
| `create_workspace` | Create a new workspace with name, role, tier, and template |
| `get_workspace` | Get workspace details by ID |
| `update_workspace` | Patch workspace fields (name, tier, parent_id, etc.) |
| `list_workspaces` | List all workspaces with their status, skills, and hierarchy |
| `create_workspace` | Create a new workspace node on the canvas |
| `get_workspace` | Get detailed information about a specific workspace |
| `update_workspace` | Update workspace fields (name, role, tier, parent_id, position) |
| `delete_workspace` | Delete a workspace (cascades to children) |
| `restart_workspace` | Restart all agents in a workspace (picks up new secrets/prompts) |
| `restart_workspace` | Restart an offline or failed workspace |
| `pause_workspace` | Pause a workspace (stops container, preserves config) |
| `resume_workspace` | Resume a paused workspace |
### Agent Tools
### Agent Tools (6)
| Tool | Description |
|------|-------------|
| `list_agents` | List agents in a workspace |
| `get_agent` | Get agent details by ID |
| `send_message` | Send an A2A message to an agent (returns structured response) |
| `list_peers` | List peer agents discoverable by a given agent |
| `chat_with_agent` | Send a message to a workspace agent and get a response |
| `assign_agent` | Assign an AI model to a workspace |
| `replace_agent` | Replace the model on an existing workspace agent |
| `remove_agent` | Remove the agent from a workspace |
| `move_agent` | Move an agent from one workspace to another |
| `get_model` | Get current model configuration for a workspace |
### Delegation Tools
### Delegation Tools (8)
| Tool | Description |
|------|-------------|
| `delegate_task` | Delegate a task to a child workspace (sync, waits for response) |
| `delegate_task_async` | Delegate a task to a child workspace (fire-and-forget, returns task_id) |
| `async_delegate` | Delegate a task to another workspace (non-blocking, returns delegation_id) |
| `check_delegations` | Check status of delegated tasks for a workspace |
| `record_delegation` | Register an agent-initiated delegation with the activity log |
| `update_delegation_status` | Mirror delegation status to activity_logs (completed or failed) |
| `report_activity` | Write an arbitrary activity log row from an agent |
| `list_activity` | List activity logs for a workspace (A2A, tasks, errors) |
| `notify_user` | Push a notification from the agent to the canvas via WebSocket |
| `list_traces` | List recent LLM traces from Langfuse for a workspace |
### Secrets Tools
### Secrets Tools (6)
| Tool | Description |
|------|-------------|
| `get_secret` | Retrieve a secret value for a workspace |
| `set_secret` | Set a key/value secret for a workspace |
| `delete_secret` | Delete a secret |
| `set_secret` | Set an API key or environment variable for a workspace |
| `list_secrets` | List secret keys for a workspace (values never exposed) |
| `delete_secret` | Delete a secret from a workspace |
| `list_global_secrets` | List global secret keys (values never exposed) |
| `set_global_secret` | Set a global secret (available to all workspaces) |
| `delete_global_secret` | Delete a global secret |
### Files Tools
### Files Tools (7)
| Tool | Description |
|------|-------------|
| `list_files` | List files in a workspace container |
| `get_file` | Read a file's content |
| `put_file` | Write or update a file in the container |
| `delete_file` | Delete a file |
| `list_files` | List workspace config files (skills, prompts, config.yaml) |
| `read_file` | Read a workspace config file |
| `write_file` | Write or create a workspace config file |
| `delete_file` | Delete a workspace file or folder |
| `replace_all_files` | Replace all workspace config files at once |
| `get_config` | Get workspace runtime config as JSON |
| `update_config` | Update workspace runtime config |
### Memory Tools
### Memory Tools (9)
| Tool | Description |
|------|-------------|
| `commit_memory` | Commit a structured memory entry (with optional namespace) |
| `recall_memory` | Search previously committed memories |
| `commit_memory` | Store a fact in workspace memory (LOCAL, TEAM, or GLOBAL scope) |
| `search_memory` | Search workspace memories |
| `delete_memory` | Delete a specific memory entry |
| `session_search` | Search recent session activity and memory (FTS) |
| `get_shared_context` | Get the shared-context blob for a workspace |
| `memory_set` | Set a key-value memory entry with optional TTL |
| `memory_get` | Read a single K/V memory entry |
| `memory_list` | List all K/V memory entries for a workspace |
| `memory_delete_kv` | Delete a single K/V memory entry |
### Plugins Tools
### Plugins Tools (7)
| Tool | Description |
|------|-------------|
| `install_plugin` | Download and install a plugin into a workspace from the registry |
| `list_plugin_registry` | List all available plugins from the registry |
| `list_installed_plugins` | List plugins installed in a workspace |
| `install_plugin` | Install a plugin into a workspace (auto-restarts) |
| `uninstall_plugin` | Remove a plugin from a workspace (auto-restarts) |
| `list_plugin_sources` | List registered plugin install-source schemes |
| `list_available_plugins` | List plugins from registry filtered by workspace runtime |
| `check_plugin_compatibility` | Preflight: which installed plugins would break if runtime changed? |
### Channels Tools
### Channels Tools (8)
| Tool | Description |
|------|-------------|
| `list_channels` | List communication channels |
| `get_channel` | Get channel details |
| `post_message` | Post a message to a channel |
| `list_channel_adapters` | List available social channel adapters (Telegram, Slack, etc.) |
| `list_channels` | List social channels connected to a workspace |
| `add_channel` | Connect a social channel to a workspace |
| `update_channel` | Update a channel's config, enabled state, or allowed users |
| `remove_channel` | Remove a social channel from a workspace |
| `send_channel_message` | Send an outbound message from a workspace to a channel |
| `test_channel` | Send a test message to verify a channel connection |
| `discover_channel_chats` | Auto-detect chat IDs for a given bot token |
### Schedules Tools
### Schedules Tools (6)
| Tool | Description |
|------|-------------|
| `list_schedules` | List scheduled tasks |
| `create_schedule` | Create a new scheduled task |
| `delete_schedule` | Delete a scheduled task |
| `list_schedules` | List cron schedules for a workspace |
| `create_schedule` | Create a cron schedule that fires a prompt on a recurring timer |
| `update_schedule` | Update fields on an existing schedule |
| `delete_schedule` | Delete a schedule |
| `run_schedule` | Fire a schedule manually, bypassing its cron expression |
| `get_schedule_history` | Get past runs of a schedule — status, start/end, output |
### Discovery Tools
### Discovery Tools (14)
| Tool | Description |
|------|-------------|
| `check_access` | Verify A2A access between two workspace IDs |
| `list_peers` | List reachable peer workspaces (siblings, children, parent) |
| `discover_workspace` | Resolve a workspace URL by ID (for A2A communication) |
| `check_access` | Check if two workspaces can communicate |
| `list_events` | List structure events (global or per workspace) |
| `list_templates` | List available workspace templates |
| `list_org_templates` | List available org templates |
| `import_org` | Import an org template to create an entire workspace hierarchy |
| `import_template` | Import agent files as a new workspace template |
| `export_bundle` | Export a workspace as a portable .bundle.json |
| `import_bundle` | Import a workspace from a bundle JSON object |
| `get_canvas_viewport` | Get the current canvas viewport (x, y, zoom) |
| `set_canvas_viewport` | Persist the canvas viewport (x, y, zoom) |
| `expand_team` | Expand a workspace into a team of sub-workspaces |
| `collapse_team` | Collapse a team back to a single workspace |
### Remote Agents Tools
### Remote Agents Tools (4)
| Tool | Description |
|------|-------------|
| `get_remote_agent_info` | Get runtime info for a remote agent |
| `heartbeat` | Send a heartbeat to the platform |
| `list_remote_agents` | List all workspaces with runtime='external' (Phase 30 remote agents) |
| `get_remote_agent_state` | Lightweight state poll for a remote workspace |
| `get_remote_agent_setup_command` | Build a bash command to register an agent on a remote machine |
| `check_remote_agent_freshness` | Check if a remote agent's heartbeat is recent |
### Approvals Tools
### Approvals Tools (4)
| Tool | Description |
|------|-------------|
| `list_approvals` | List pending approvals for a workspace |
| `approve` | Approve a pending item |
| `reject` | Reject a pending item |
| `list_pending_approvals` | List all pending approval requests across workspaces |
| `decide_approval` | Approve or deny a pending approval request |
| `create_approval` | Create an approval request for a workspace |
| `get_workspace_approvals` | List approval requests for a specific workspace |
## MCP Transport Gotchas
+40 -39
View File
@@ -2,42 +2,24 @@
MCP server that exposes Molecule AI platform operations as tools for AI coding agents.
## 20 Tools Available
## 87 Tools Available
| Tool | Description |
|------|-------------|
| `list_workspaces` | List all workspaces with status and skills |
| `create_workspace` | Create a new workspace (with optional template) |
| `get_workspace` | Get workspace details |
| `delete_workspace` | Delete workspace (cascades to children) |
| `restart_workspace` | Restart offline/failed workspace |
| `chat_with_agent` | Send message and get AI response |
| `assign_agent` | Assign model to workspace |
| `set_secret` | Set API key or env var |
| `list_secrets` | List secret keys (no values) |
| `list_files` | List workspace config files |
| `read_file` | Read a config file |
| `write_file` | Create or update a file |
| `delete_file` | Delete file or folder |
| `commit_memory` | Store fact (LOCAL/TEAM/GLOBAL) |
| `search_memory` | Search workspace memories |
| `list_templates` | List available templates |
| `expand_team` | Expand workspace to team |
| `collapse_team` | Collapse team to single workspace |
| `list_pending_approvals` | List pending approval requests |
| `decide_approval` | Approve or deny a request |
See the [full tool registry](CLAUDE.md#mcp-tool-registry) for all tools. Highlights:
### Phase 30 — Remote agent (SaaS) management
Tools that surface workspaces with `runtime='external'` (agents that run on
machines outside this platform's Docker network and join via HTTP).
| Tool | Description |
|------|-------------|
| `list_remote_agents` | Filter the workspace list to remote agents only — id / status / url / heartbeat |
| `get_remote_agent_state` | Lightweight `{status, paused, deleted}` projection — faster than `get_workspace` when you only need lifecycle |
| `get_remote_agent_setup_command` | Emit a `WORKSPACE_ID=… PLATFORM_URL=… python3 …` bash one-liner an operator can paste into a remote shell |
| `check_remote_agent_freshness` | Compare `last_heartbeat_at` against a threshold (default 90s) — returns `{fresh, seconds_since_heartbeat}` |
| Category | Tools |
|----------|-------|
| Workspace | list, create, get, update, delete, restart, pause, resume |
| Agent | chat_with, assign, replace, remove, move, get_model |
| Delegation | async_delegate, check_delegations, record_delegation, notify_user, list_activity |
| Secrets | set, list, delete (workspace + global variants) |
| Files | list, read, write, delete, replace_all, get_config, update_config |
| Memory | commit, search, delete (HMA scopes) + memory_set/get/list/delete (K/V) |
| Plugins | list registry, list installed, install, uninstall, list sources, check compatibility |
| Channels | list adapters, list, add, update, remove, send, test, discover chats |
| Schedules | list, create, update, delete, run, get history |
| Discovery | list peers, discover, check_access, list events, import/export, canvas viewport |
| Approvals | list pending, decide, create, get workspace approvals |
| Remote Agents | list (runtime=external), get state, setup command, check freshness |
## Setup
@@ -52,7 +34,7 @@ Add to your project's `.mcp.json`:
"command": "node",
"args": ["./mcp-server/dist/index.js"],
"env": {
"MOLECULE_URL": "http://localhost:8080"
"MOLECULE_API_URL": "http://localhost:8080"
}
}
}
@@ -70,7 +52,7 @@ Add to `.cursor/mcp.json`:
"command": "node",
"args": ["./mcp-server/dist/index.js"],
"env": {
"MOLECULE_URL": "http://localhost:8080"
"MOLECULE_API_URL": "http://localhost:8080"
}
}
}
@@ -80,15 +62,22 @@ Add to `.cursor/mcp.json`:
### Codex / OpenCode
```bash
# Run directly
MOLECULE_URL=http://localhost:8080 node mcp-server/dist/index.js
MOLECULE_API_URL=http://localhost:8080 node mcp-server/dist/index.js
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MOLECULE_URL` | `http://localhost:8080` | Platform API URL |
| `MOLECULE_API_URL` | `http://localhost:8080` | Platform API base URL |
| `MOLECULE_API_KEY` | — | API key for platform authentication |
| `MCP_SERVER_PORT` | `3000` | Port (for HTTP/SSE transport) |
## Quick Start
1. `npm install && npm run build`
2. Set `MOLECULE_API_URL` and `MOLECULE_API_KEY`
3. `npm start` (stdio mode) or use an MCP host config
## Examples
@@ -105,3 +94,15 @@ Agent: [calls chat_with_agent with message="Audit https://example.com for SEO"]
You: "What skills does the coding agent have?"
Agent: [calls get_workspace, reads agent_card.skills]
```
## Remote Agents (Phase 30)
For agents running outside the platform's Docker network, the `get_remote_agent_setup_command`
tool generates a bash one-liner:
```bash
pip install molecule-ai-sdk
WORKSPACE_ID=... PLATFORM_URL=... python3 -c "from molecule_agent import RemoteAgentClient; ..."
```
See the full tool registry in `CLAUDE.md` for all 87 tools.
+15 -14
View File
@@ -177,23 +177,24 @@ test documenting the known `optional().nullable()` zod-to-json-schema quirk.
---
## KI-007 — Heartbeat cleanup fires after SSE stream closes
## KI-007 — MCP server heartbeat tools are read-only; actual heartbeat lives in the Python SDK
**File:** `src/tools/remote_agents.ts` (heartbeat tool)
**Status:** Identified
**Status:** Resolved — clarified scope
**Severity:** Low
### Symptom
When using SSE transport, the heartbeat mechanism does not immediately clean up
when a stream closes. A background timer or goroutine may continue sending heartbeats
to workspaces whose SSE connections have been closed by the client.
### Clarification
The MCP server's remote-agent tools (`list_remote_agents`, `get_remote_agent_state`,
`check_remote_agent_freshness`, `get_remote_agent_setup_command`) are **read-only
queries** — they do not drive any background heartbeat loop. The actual
`run_heartbeat_loop()` that sends heartbeats from a remote agent lives in the
Python SDK (`molecule_sdk_python/molecule_agent/client.py`).
### Impact
Orphaned heartbeat calls continue consuming platform API quota after the MCP client
has disconnected. Over time this can cause the workspace to accumulate heartbeat
sessions that never expire on the platform side.
The heartbeat cleanup issue (heartbeat loop continues after the controlling MCP
client disconnects) is tracked as **SDK KI-009** in `molecule-sdk-python/known-issues.md`.
### Suggested fix
Attach a cleanup function to the SSE stream `close` event. Invalidate the heartbeat
timer when the stream ends so no further calls are made. Document the expected
SSE session lifecycle in the streaming convention section of CLAUDE.md.
### Suggested fix (SDK side)
Expose a `stop_event` parameter or `stop()` method on `RemoteAgentClient` so the
callers (MCP client, shell wrapper) can signal the loop to exit cleanly. The
Python SDK's `run_heartbeat_loop()` should check `threading.Event` or accept a
`stop_on: asyncio.Event` argument. See `molecule-sdk-python/known-issues.md`.
+17 -22
View File
@@ -555,10 +555,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.12",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.12.tgz",
"integrity": "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==",
"license": "MIT",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"engines": {
"node": ">=18.14.1"
},
@@ -2555,12 +2554,11 @@
}
},
"node_modules/express-rate-limit": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz",
"integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==",
"license": "MIT",
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz",
"integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==",
"dependencies": {
"ip-address": "10.1.0"
"ip-address": "^10.2.0"
},
"engines": {
"node": ">= 16"
@@ -2596,9 +2594,9 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
@@ -2608,8 +2606,7 @@
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
]
},
"node_modules/fb-watchman": {
"version": "2.0.2",
@@ -2905,10 +2902,9 @@
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="
},
"node_modules/hono": {
"version": "4.12.10",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.10.tgz",
"integrity": "sha512-mx/p18PLy5og9ufies2GOSUqep98Td9q4i/EF6X7yJgAiIopxqdfIO3jbqsi3jRgTgw88jMDEzVKi+V2EF+27w==",
"license": "MIT",
"version": "4.12.18",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
"engines": {
"node": ">=16.9.0"
}
@@ -3015,10 +3011,9 @@
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"engines": {
"node": ">= 12"
}
+1 -1
View File
@@ -29,6 +29,6 @@
},
"repository": {
"type": "git",
"url": "https://github.com/Molecule-AI/molecule-mcp-server.git"
"url": "https://git.moleculesai.app/molecule-ai/molecule-mcp-server.git"
}
}
+2 -2
View File
@@ -1,3 +1,5 @@
import { error as logError } from "./utils/logger.js";
// Read the platform API base URL from environment.
// Priority: MOLECULE_API_URL (canonical CLI/SDK env var, per platform docs)
//
@@ -19,8 +21,6 @@ export const PLATFORM_URL =
* Shape returned by apiCall when the request fails (network error, non-2xx,
* or non-JSON body with no error). Returned-by-value — apiCall never throws.
*/
import { error as logError } from "./utils/logger.js";
export type ApiError = { error: string; detail?: string; raw?: string; status?: number };
export function isApiError(v: unknown): v is ApiError {
+2 -2
View File
@@ -85,7 +85,7 @@ export async function handleGetRemoteAgentSetupCommand(params: {
const setupCmd = [
`# Run on the remote machine where the agent will live.`,
`# Requires Python 3.11+ and bash (the SDK invokes setup.sh via bash).`,
`pip install molecule-sdk # (or: pip install -e <molecule-checkout>/sdk/python)`,
`pip install molecule-ai-sdk # (or: pip install -e <molecule-checkout>/molecule-sdk-python)`,
``,
`WORKSPACE_ID=${w.id} \\`,
`PLATFORM_URL=${targetUrl} \\`,
@@ -95,7 +95,7 @@ export async function handleGetRemoteAgentSetupCommand(params: {
` c.run_heartbeat_loop()"`,
``,
`# For a richer demo (logging, graceful shutdown) see`,
`# sdk/python/examples/remote-agent/run.py in the molecule-monorepo checkout.`,
`# examples/remote-agent/run.py in the molecule-sdk-python checkout.`,
`# The agent will register, mint its bearer token (cached at`,
`# ~/.molecule/${w.id}/.auth_token), pull secrets, then heartbeat.`,
].join("\n");