Files
claudemesh/.artifacts/specs/2026-05-01-mcp-tool-surface-trim.md
Alejandro Gutiérrez b4f457fceb
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
feat(cli): 1.5.0 — CLI-first architecture, tool-less MCP, policy engine
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>
2026-05-02 01:18:19 +01:00

9.4 KiB

title, status, target, author, date
title status target author date
MCP tool surface trim + multi-mesh push proposed claudemesh-cli 1.1.0 Alejandro 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):

// 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:

{
  "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.tsvault_set / vault_list / vault_delete (encrypted, structured payloads).
  • vectors.tsvector_store / vector_search / vector_delete (typed embeddings, ranked results Claude reasons over).
  • graph.tsgraph_query / graph_execute (returns structured graph results).
  • files.tsshare_file / get_file / list_files / list_peer_files / read_peer_file / grant_file_access / file_status / delete_file (binary payloads, ACL semantics).
  • skills.tsshare_skill / list_skills / get_skill / remove_skill / mesh_skill_deploy (typed skill metadata).
  • streams.tscreate_stream / list_streams / publish / subscribe (event stream cursor semantics).
  • contexts.tsshare_context / get_context / list_contexts (context-passing payloads).
  • mcp-registry-*.tsmesh_mcp_* (the ~14 mesh-MCP-services tools — these are platform-defining, MCP-native).
  • clock-write.tsmesh_set_clock / mesh_pause_clock / mesh_resume_clock (logical-clock writes that Claude composes with reads).
  • sql.tsmesh_query / mesh_schema / mesh_execute (typed SQL results).
  • webhooks.tscreate_webhook / list_webhooks / delete_webhook (typed webhook metadata).
  • url-watch.tsmesh_watch / mesh_unwatch / mesh_watches (returns watch state).
  • tasks.tscreate_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:

// 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.