CLI becomes the API; MCP becomes a tool-less push-pipe. Bundle -42% (250 KB → 146 KB) after stripping ~1700 lines of dead tool handlers. - Tool-less MCP: tools/list returns []. Inbound peer messages still arrive as experimental.claude/channel notifications mid-turn. - Resource-noun-verb CLI: peer list, message send, memory recall, etc. Legacy flat verbs (peers, send, remember) remain as aliases. - Bundled claudemesh skill auto-installed by `claudemesh install` — sole CLI-discoverability surface for Claude. - Unix-socket bridge: CLI invocations dial the push-pipe's warm WS (~220 ms warm vs ~600 ms cold). - --mesh <slug> flag: connect a session to multiple meshes. - Policy engine: every broker-touching verb runs through a YAML gate at ~/.claudemesh/policy.yaml (auto-created). Destructive verbs prompt; non-TTY auto-denies. Audit log at ~/.claudemesh/audit.log. - --approval-mode plan|read-only|write|yolo + --policy <path>. Spec: .artifacts/specs/2026-05-02-architecture-north-star.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
163 lines
9.4 KiB
Markdown
163 lines
9.4 KiB
Markdown
---
|
|
title: MCP tool surface trim + multi-mesh push
|
|
status: proposed
|
|
target: claudemesh-cli 1.1.0
|
|
author: Alejandro
|
|
date: 2026-05-01
|
|
---
|
|
|
|
# MCP tool surface trim + multi-mesh push
|
|
|
|
## Problem
|
|
|
|
Two issues with the current `claudemesh mcp` server:
|
|
|
|
1. **80+ tools registered.** Every Claude session that has claudemesh installed pays the deferred-tool-list cost (~80 entries surfacing in `ToolSearch`). Most of those tools are CLI-verb-wrappers that already have a perfect Bash equivalent — no structured I/O is gained by exposing them as MCP tools.
|
|
|
|
2. **Single-mesh push only.** A session launched with `claudemesh launch` opens its WS to one mesh. Peer messages from any other joined mesh arrive only if the user manually runs `claudemesh inbox`. The MCP push pipeline doesn't fan out across meshes.
|
|
|
|
The cleanest framing: **MCP earns its keep when a tool returns structured data Claude reads. CLI is better for fire-and-forget verbs.** Today's tool surface ignores that distinction.
|
|
|
|
## Non-goals
|
|
|
|
- **Don't redesign the architecture as "CLI-only with a daemon."** That trades warm-WS sends (~5ms in-process) for cold Bash spawns (~300-500ms) and forces a Unix-socket bridge to recover state coherence. See discussion 2026-05-01 — the platform vision (vectors, graph, files, mesh-services) genuinely benefits from typed tool I/O.
|
|
- **Don't break MCP backward compat in 1.x.** Existing scripts calling `mcp__claudemesh__send_message` keep working until 2.0; in 1.1 they're soft-deprecated with a stderr warning.
|
|
|
|
## Proposal
|
|
|
|
Three patches, ship together as 1.1.0:
|
|
|
|
### Patch 1: `--mesh <slug>` flag on `claudemesh mcp`
|
|
|
|
Today `claudemesh mcp` calls `readConfig()` and `startClients(config)` — connects to every mesh in `~/.claudemesh/config.json`. The `claudemesh launch` flow writes a per-session tmpdir config with one mesh, so practically the MCP server binds to one mesh per session.
|
|
|
|
Add an explicit flag for non-launch contexts (manual `~/.claude.json` editing):
|
|
|
|
```ts
|
|
// apps/cli/src/mcp/server.ts, near line 244
|
|
export async function startMcpServer(): Promise<void> {
|
|
const serviceIdx = process.argv.indexOf("--service");
|
|
if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
|
|
return startServiceProxy(process.argv[serviceIdx + 1]!);
|
|
}
|
|
|
|
const meshIdx = process.argv.indexOf("--mesh");
|
|
const onlyMesh = meshIdx !== -1 ? process.argv[meshIdx + 1] : null;
|
|
|
|
const config = readConfig();
|
|
if (onlyMesh) {
|
|
const before = config.meshes.length;
|
|
config.meshes = config.meshes.filter((m) => m.slug === onlyMesh);
|
|
if (config.meshes.length === 0) {
|
|
throw new Error(
|
|
`--mesh "${onlyMesh}" not found in config (have: ${
|
|
config.meshes.map((m) => m.slug).join(", ") || "none"
|
|
})`,
|
|
);
|
|
}
|
|
}
|
|
// ...rest unchanged
|
|
}
|
|
```
|
|
|
|
Enables this `~/.claude.json` pattern for users who want push from N meshes simultaneously without launching N Claude sessions:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"claudemesh:flexicar": { "command": "claudemesh", "args": ["mcp", "--mesh", "flexicar"] },
|
|
"claudemesh:openclaw": { "command": "claudemesh", "args": ["mcp", "--mesh", "openclaw"] },
|
|
"claudemesh:prueba1": { "command": "claudemesh", "args": ["mcp", "--mesh", "prueba1"] }
|
|
}
|
|
}
|
|
```
|
|
|
|
Each instance opens one WS, holds it for the session, decrypts and forwards `claude/channel` notifications independently. Channel events already carry `[meshSlug]` in `formatPush()` (server.ts:240), so Claude knows which mesh a message came from.
|
|
|
|
**LoC:** ~10. **Risk:** very low — additive flag, default behavior unchanged.
|
|
|
|
### Patch 2: trim 25 messaging tools from MCP surface
|
|
|
|
Move these tools from "registered MCP tool" to "soft-deprecated CLI shim":
|
|
|
|
| Module | Tool | CLI replacement | Rationale |
|
|
|---|---|---|---|
|
|
| messaging.ts | `send_message` | `claudemesh send <to> <msg> [--mesh X] [--priority Y]` | Pure verb, no structured return. |
|
|
| messaging.ts | `list_peers` | `claudemesh peers --json` | One-shot, easy to parse. |
|
|
| messaging.ts | `check_messages` | `claudemesh inbox --json` | One-shot. |
|
|
| messaging.ts | `message_status` | `claudemesh msg-status <id>` (new) | One-shot lookup. |
|
|
| profile.ts | `set_profile` | `claudemesh profile --avatar X --bio Y ...` | Pure write. |
|
|
| profile.ts | `set_status` | `claudemesh status set <state>` (new) | Pure write. |
|
|
| profile.ts | `set_summary` | `claudemesh summary <text>` (new) | Pure write. |
|
|
| profile.ts | `set_visible` | `claudemesh visible <true\|false>` (new) | Pure write. |
|
|
| groups.ts | `join_group` | `claudemesh group join @<name> [--role X]` (new) | Pure write. |
|
|
| groups.ts | `leave_group` | `claudemesh group leave @<name>` (new) | Pure write. |
|
|
| state.ts | `get_state` | `claudemesh state get <key> --json` | Already exists. |
|
|
| state.ts | `set_state` | `claudemesh state set <key> <value>` | Already exists. |
|
|
| state.ts | `list_state` | `claudemesh state list --json` | Already exists. |
|
|
| memory.ts | `remember` | `claudemesh remember <text>` | Already exists. |
|
|
| memory.ts | `recall` | `claudemesh recall <query> --json` | Already exists. |
|
|
| memory.ts | `forget` | `claudemesh forget <id>` (new) | Pure write. |
|
|
| scheduling.ts | `schedule_reminder` | `claudemesh remind <msg> --in/--at/--cron` | Already exists. |
|
|
| scheduling.ts | `list_scheduled` | `claudemesh remind list --json` | Already exists. |
|
|
| scheduling.ts | `cancel_scheduled` | `claudemesh remind cancel <id>` | Already exists. |
|
|
| mesh-meta.ts | `mesh_info` | `claudemesh info --json` | One-shot read. |
|
|
| mesh-meta.ts | `mesh_stats` | `claudemesh stats --json` (new) | One-shot read. |
|
|
| mesh-meta.ts | `mesh_clock` | `claudemesh clock --json` (new) | One-shot read. |
|
|
| mesh-meta.ts | `ping_mesh` | `claudemesh ping` (new) | Pure verb. |
|
|
| tasks.ts | `claim_task` / `complete_task` | `claudemesh task claim/complete <id>` (new) | Pure write. |
|
|
|
|
**Keep as MCP tools (~50):**
|
|
|
|
- **vault.ts** — `vault_set / vault_list / vault_delete` (encrypted, structured payloads).
|
|
- **vectors.ts** — `vector_store / vector_search / vector_delete` (typed embeddings, ranked results Claude reasons over).
|
|
- **graph.ts** — `graph_query / graph_execute` (returns structured graph results).
|
|
- **files.ts** — `share_file / get_file / list_files / list_peer_files / read_peer_file / grant_file_access / file_status / delete_file` (binary payloads, ACL semantics).
|
|
- **skills.ts** — `share_skill / list_skills / get_skill / remove_skill / mesh_skill_deploy` (typed skill metadata).
|
|
- **streams.ts** — `create_stream / list_streams / publish / subscribe` (event stream cursor semantics).
|
|
- **contexts.ts** — `share_context / get_context / list_contexts` (context-passing payloads).
|
|
- **mcp-registry-*.ts** — `mesh_mcp_*` (the ~14 mesh-MCP-services tools — these are platform-defining, MCP-native).
|
|
- **clock-write.ts** — `mesh_set_clock / mesh_pause_clock / mesh_resume_clock` (logical-clock writes that Claude composes with reads).
|
|
- **sql.ts** — `mesh_query / mesh_schema / mesh_execute` (typed SQL results).
|
|
- **webhooks.ts** — `create_webhook / list_webhooks / delete_webhook` (typed webhook metadata).
|
|
- **url-watch.ts** — `mesh_watch / mesh_unwatch / mesh_watches` (returns watch state).
|
|
- **tasks.ts** — `create_task / list_tasks` (typed task records — only the writes go to CLI).
|
|
|
|
### Patch 3: tool-call → CLI shim with deprecation warning
|
|
|
|
For the trimmed tools, keep the registration but route through the CLI:
|
|
|
|
```ts
|
|
// apps/cli/src/mcp/tools/messaging.ts (sketch)
|
|
async function sendMessageDeprecated(args: SendMessageArgs): Promise<ToolResult> {
|
|
process.stderr.write(
|
|
`[claudemesh] mcp__claudemesh__send_message is soft-deprecated in 1.1. ` +
|
|
`Use \`claudemesh send\` via Bash instead — it's faster and cleaner.\n`,
|
|
);
|
|
return originalSendMessageHandler(args); // unchanged behavior
|
|
}
|
|
```
|
|
|
|
In 2.0 the registrations get deleted entirely.
|
|
|
|
## Migration plan
|
|
|
|
1. **1.1.0** — ship all three patches. Existing users see deprecation warnings; nothing breaks.
|
|
2. **1.1.x** — collect feedback. If anyone has scripts hard-wired to the deprecated tools, surface in CHANGELOG.
|
|
3. **1.2.0** (~6 weeks later) — flip deprecation warnings to "removal in 2.0" messaging.
|
|
4. **2.0.0** — delete the 25 tool registrations. ToolSearch surface drops to ~50 entries.
|
|
|
|
## Open questions
|
|
|
|
- **Do we need a Unix-socket bridge between CLI sends and the MCP push-pipe** so they share one WS connection per mesh per session? Probably yes for `claudemesh send` warm-path performance, but it's a separate spec — file under `socket-bridge` after this lands.
|
|
- **Should `claudemesh launch` keep writing one MCP server entry** (current behavior, default for new users) or switch to the per-mesh-N-entries pattern from Patch 1? Recommend keeping single-entry default — Patch 1 is for advanced users who manually edit `~/.claude.json`.
|
|
- **Do `mesh_mcp_*` tools really belong in the keep list?** They're MCP-on-mesh management — their bias is RPC-shaped, not stream-shaped. Provisional yes; revisit if 1.1 reduces their use.
|
|
|
|
## Effort
|
|
|
|
- Patch 1: ~10 LoC + 1 test. ~30 min.
|
|
- Patch 2: ~25 tool-handler refactors (registration removed, CLI verb confirmed/added). Some new verbs (`status set`, `summary`, `visible`, `group join/leave`, `forget`, `stats`, `clock`, `ping`, `task claim/complete`, `msg-status`) need wiring through to existing broker-client methods. ~150 LoC, half a day.
|
|
- Patch 3: deprecation shim per trimmed tool. ~50 LoC, 1 hour.
|
|
|
|
**Total:** ~1 dev-day for 1.1.0. ToolSearch surface drops by ~30%, multi-mesh push works, no architectural disruption, platform tools stay typed.
|