@sym-bot/mesh-channel 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sym-mesh-channel",
3
3
  "owner": {
4
- "name": "SYM.BOT Ltd",
4
+ "name": "SYM.BOT",
5
5
  "email": "info@sym.bot"
6
6
  },
7
7
  "metadata": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sym-mesh-channel",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Real-time Claude-to-Claude mesh. Agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay.",
5
5
  "author": {
6
6
  "name": "Hongwei Xu",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Fixed
6
+
7
+ - **Installer no longer silently ships a broken MCP config.** Previously,
8
+ if `~/.claude.json` already contained a `claude-sym-mesh` entry,
9
+ `npm install -g @sym-bot/mesh-channel` (via postinstall) and
10
+ `npx @sym-bot/mesh-channel init` both skipped with "already configured"
11
+ — even when the entry's `args[0]` server.js path no longer existed on
12
+ disk (common after moving or reinstalling the repo). Users saw
13
+ `/mcp` report "Failed to reconnect" with no diagnostic hint.
14
+
15
+ The installer now classifies entries whose `args[0]` is missing as
16
+ **stale** and rewrites them automatically without `--force`, preserving
17
+ `SYM_NODE_NAME` from the prior entry so mesh identity doesn't drift
18
+ back to the hostname-based default. Live entries continue to require
19
+ `--force` for overwrite.
20
+
21
+ - **Stale project-scoped entries are now healed too.** `~/.claude.json`
22
+ can carry per-project `mcpServers` overrides under
23
+ `projects.<dir>.mcpServers`, and Claude Code prefers those over the
24
+ user-global entry when launched from that directory. A healthy
25
+ user-global entry was therefore being silently shadowed by stale
26
+ project entries. `init` now scans every project, rewrites any stale
27
+ `claude-sym-mesh` entry, and preserves each project's `SYM_NODE_NAME`.
28
+
29
+ ### Added
30
+
31
+ - **`sym-mesh-channel doctor` subcommand.** Read-only diagnostic that
32
+ lists every `claude-sym-mesh` entry in `~/.claude.json` (user-global
33
+ and every project scope) with `[live]` or `[STALE]` plus its
34
+ `SYM_NODE_NAME` and configured path. Point users here when `/mcp`
35
+ reports "Failed to reconnect". No writes, safe to run any time.
36
+
37
+ - **README troubleshooting section** covering the `/mcp` failure path,
38
+ how to run `doctor`, and when restart is needed after a config change.
39
+
40
+ ### Changed
41
+
42
+ - `.claude-plugin/plugin.json` version field bumped to `0.3.1` to match
43
+ `package.json`. Previous drift (`plugin.json` stuck at `0.2.0`, package
44
+ at `0.3.0`) was caught by the in-repo version-parity test.
45
+
46
+ ## 0.3.0
47
+
48
+ ### Added
49
+
50
+ - **Startup remix-memory primer — automates agent memory recall on
51
+ session/agent restart (MMP §4.2 O2, rejoin-without-replay).** As the
52
+ final step of plugin initialisation (after `node.start()` and before
53
+ the MCP transport connects), the plugin calls
54
+ `node.buildStartupPrimer()` and appends the returned text to the MCP
55
+ server's `instructions` field. A fresh Claude Code session wakes
56
+ with the agent's own remix memory — own observations plus peer
57
+ observations admitted by SVAF — already loaded into context. No
58
+ first-turn `sym_recall` required; agent acts from prior state
59
+ immediately.
60
+
61
+ Default caps: last 24 hours OR 20 most recent CMBs, whichever is
62
+ tighter. The primer lists each entry as `[timestamp] source · key —
63
+ focus` and surfaces a dropped-count line when caps elide older
64
+ entries. Empty store is a silent no-op.
65
+
66
+ ### Changed
67
+
68
+ - **`@sym-bot/sym` dep bumped to `^0.5.0`** to pick up the
69
+ `buildStartupPrimer` helper and to keep every plugin on the
70
+ sym.day platform pinned to the same substrate SDK version
71
+ (no drift across mesh-channel / melotune-plugin / future
72
+ specialised plugins).
73
+
3
74
  ## 0.2.0
4
75
 
5
76
  ### Breaking
package/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2026 SYM.BOT Ltd
189
+ Copyright 2026 SYM.BOT
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,65 +1,46 @@
1
1
  # sym-mesh-channel
2
2
 
3
+ > Two Claude Code sessions on different machines discover each other on wifi, form a mesh, and **think together in real-time**. Messages arrive mid-conversation with no polling and no tool call. This README was co-authored by two Claude Code sessions working through the mesh it describes.
4
+
5
+ ```bash
6
+ npm install -g @sym-bot/mesh-channel && claude
7
+ ```
8
+
3
9
  [![npm](https://img.shields.io/npm/v/@sym-bot/mesh-channel)](https://www.npmjs.com/package/@sym-bot/mesh-channel)
10
+ [![Plugin Directory](https://img.shields.io/badge/Anthropic_Plugin_Directory-approved-success)](https://claude.ai/settings/plugins/submit)
4
11
  [![MMP Spec](https://img.shields.io/badge/protocol-MMP_v0.2.3-purple)](https://sym.bot/spec/mmp)
5
- [![arXiv](https://img.shields.io/badge/arXiv-2604.03955-b31b1b.svg)](https://arxiv.org/abs/2604.03955)
12
+ [![SVAF arXiv](https://img.shields.io/badge/arXiv-2604.03955-b31b1b.svg)](https://arxiv.org/abs/2604.03955)
13
+ [![MMP arXiv](https://img.shields.io/badge/arXiv-2604.19540-b31b1b.svg)](https://arxiv.org/abs/2604.19540)
6
14
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
7
15
  [![Node](https://img.shields.io/badge/node-%3E%3D18-green)](https://nodejs.org)
8
16
 
9
- > MCP server that turns Claude Code into a peer node on the [SYM mesh](https://sym.bot) — the first non-Anthropic implementation of Claude Code Channels for real-time agent-to-agent cognition.
10
-
11
- Two Claude Code sessions on different machines discover each other via Bonjour mDNS, form a mesh, and exchange structured agent-to-agent cognitive signals in real-time. Each side is a full peer with its own cryptographic identity, its own [SVAF](https://arxiv.org/abs/2604.03955) receiver-side gating, and its own memory — not a thin client. Signals arrive mid-conversation as `<channel>` notifications. No polling, no shared server, no orchestrator.
12
-
13
- **Verified cross-platform:** Mac ↔ Windows on the same wifi, pure Bonjour, no relay, no token. Cross-network via optional WebSocket relay.
14
-
15
- - **SVAF paper**: [arxiv.org/abs/2604.03955](https://arxiv.org/abs/2604.03955)
16
- - **MMP spec**: [sym.bot/spec/mmp](https://sym.bot/spec/mmp)
17
+ ---
17
18
 
18
19
  ## What this looks like
19
20
 
20
- A Claude Code session on Mac broadcasts a structured signal: `focus: "echo loop between same-domain agents"`, `intent: "need architecture review before implementation"`. A session on Windows receives it in real-time as a `<channel>` notification — no tool call, it just appears mid-conversation. The Windows Claude reviews, responds with a detailed architecture analysis, and the Mac session sees the response land mid-turn. Two agents coordinated through typed cognitive signals on an open protocol, across machines, with zero human copy-paste.
21
+ A Claude Code session on your Mac broadcasts: `focus: "echo loop between same-domain agents"`, `intent: "need architecture review before implementation"`. A session on your colleague's Windows laptop receives it in real-time — no tool call, it just appears mid-conversation. Their Claude reviews the problem, replies with a detailed architecture analysis, and your Mac session sees the response land mid-turn.
21
22
 
22
- This isn't hypothetical. This README was coordinated by two Claude Code sessions working through the mesh it describes.
23
+ Two agents coordinated through typed cognitive signals, across machines, with zero human copy-paste.
23
24
 
24
- ## How real-time push works (Claude Code Channels + MMP)
25
+ Verified working: Mac Windows on the same wifi, pure Bonjour, no relay, no token. Cross-network via optional WebSocket relay.
25
26
 
26
- This MCP server composes two things:
27
+ ## Who this is for
27
28
 
28
- **[Claude Code Channels](https://code.claude.com/docs/en/mcp)** (Anthropic, shipped 2026-03-20) an MCP capability that lets servers push events directly into Claude's conversation context mid-turn via `notifications/claude/channel`. Anthropic built it for the Telegram/Discord/iMessage integrations. We use it for agent-to-agent cognitive coupling.
29
-
30
- **[MMP the Mesh Memory Protocol](https://sym.bot/spec/mmp)** defines what gets pushed: typed seven-field cognitive bundles (CAT7: focus, issue, intent, motivation, commitment, perspective, mood), how receivers gate incoming signals ([SVAF](https://arxiv.org/abs/2604.03955)), and how peers maintain identity without a central orchestrator. MMP is the protocol; this MCP server is the reference implementation for Claude Code hosts.
31
-
32
- **The composition:** when a peer on the mesh broadcasts a CMB (Cognitive Memory Block), the SymNode inside this MCP evaluates it via SVAF. If accepted, the MCP fires a `notifications/claude/channel` notification to Claude Code, which surfaces it as a `<channel>` block in the conversation. Claude sees it, can react, and can broadcast back via `sym_send` or `sym_observe`. No polling. No tool calls. The mesh thinks together.
29
+ - **Small engineering teams** whose Claude Code sessions currently copy-paste findings over Slack. Replace that loop with direct agent-to-agent coordination.
30
+ - **Distributed teams** running Claude Code across offices, home networks, and coffee shops. Isolated team channels via mesh groups, no shared server.
31
+ - **Multi-agent developers** prototyping cognitive architectures`sym-mesh-channel` is the reference Claude Code host for the [Mesh Memory Protocol](https://sym.bot/spec/mmp).
32
+ - **Not for:** single-user Claude sessions that don't need to coordinate with anyone. You'd get MCP tools but nothing to coordinate with.
33
33
 
34
34
  ## Quick start
35
35
 
36
- ### Via Claude Code plugin marketplace (recommended)
37
-
38
- Two commands, straight from this repo — no waiting on any external directory:
39
-
40
- ```
41
- /plugin marketplace add sym-bot/sym-mesh-channel
42
- /plugin install sym-mesh-channel@sym-mesh-channel
43
- ```
44
-
45
- Claude Code clones the repo, reads `.claude-plugin/marketplace.json`, and installs the plugin into your user scope. Launch with:
36
+ One command, zero flags, works today:
46
37
 
47
38
  ```bash
48
- claude --dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel
39
+ npm install -g @sym-bot/mesh-channel
40
+ claude
49
41
  ```
50
42
 
51
- ### Via npm
52
-
53
- ```bash
54
- npm install -g @sym-bot/mesh-channel # install + auto-configure ~/.claude.json
55
- claude --dangerously-load-development-channels server:claude-sym-mesh # launch
56
- ```
57
-
58
- > **Why the dev flag?** Claude Code Channels are in [research preview](https://code.claude.com/docs/en/channels#research-preview) with an Anthropic-maintained allowlist that gates in-conversation `<channel>` push notifications. The MCP tools themselves (`sym_send`, `sym_peers`, `sym_status`, `sym_recall`, `sym_observe`, `sym_fetch`, `sym_group_info`, `sym_invite_info`) work immediately after install without any flag. The `--dangerously-load-development-channels` flag is only needed to enable the async-push behaviour (peer messages arriving mid-turn without a tool call). The plugin is approved on the [Anthropic Plugin Directory](https://claude.ai/settings/plugins/submit); plugin-directory approval and Channels-allowlist inclusion are independent gates.
59
-
60
- ---
61
-
62
- Install auto-detects your hostname, creates a unique node identity (`claude-<hostname>`), and configures the MCP server globally in `~/.claude.json`. To customize your node name, set `SYM_NODE_NAME` before installing. If two people are on the same wifi, their sessions discover each other automatically. Verify inside Claude Code:
43
+ The postinstall script configures the MCP server in `~/.claude.json` using `claude-<your-hostname>` as your mesh identity. Launch Claude Code from any directory. Verify:
63
44
 
64
45
  ```
65
46
  > sym_status
@@ -70,84 +51,94 @@ Memories: 0
70
51
 
71
52
  > sym_peers
72
53
  1 peer(s):
73
- claude-theirhostname via bonjour
54
+ claude-theirhostname via bonjour
74
55
 
75
56
  > sym_send "reviewing the auth module — found a race condition"
76
57
  Message delivered to 1 peer(s).
77
58
  ```
78
59
 
79
- The other peer sees it arrive **in their Claude Code context as a real-time `<channel>` notification** — no polling, no tool call. It just appears mid-conversation. Their Claude can reason about it, respond, or act on it autonomously.
60
+ To customise your mesh identity, set `SYM_NODE_NAME` before running init:
61
+
62
+ ```bash
63
+ SYM_NODE_NAME=claude-alice npx @sym-bot/mesh-channel init --force
64
+ ```
65
+
66
+ **Real-time push is a separate upgrade.** The command above gives you all 11 MCP tools immediately. To additionally have peer messages *appear in Claude's context mid-turn without a tool call* (the "Claude thinks with the mesh" experience), launch Claude Code with the Channels flag:
67
+
68
+ ```bash
69
+ claude --dangerously-load-development-channels server:claude-sym-mesh
70
+ ```
71
+
72
+ Why the flag: Claude Code Channels is in Anthropic's research preview and real-time push is gated behind a dev flag during allowlist propagation — tracked in [anthropics/claude-plugins-official#1512](https://github.com/anthropics/claude-plugins-official/issues/1512). The plugin is already approved on the Anthropic Plugin Directory; the flag is temporary.
73
+
74
+ ## What you get
75
+
76
+ Eleven MCP tools exposed to Claude Code, namespaced under `mcp__claude-sym-mesh__`:
77
+
78
+ | Tool | What it does |
79
+ |---|---|
80
+ | `sym_send` | Broadcast a free-text message to all mesh peers. Arrives in receivers' contexts as a `<channel>` notification. |
81
+ | `sym_observe` | Share a structured CAT7 observation: focus, issue, intent, motivation, commitment, perspective, mood. SVAF-gated on the receiving side. |
82
+ | `sym_recall` | Search mesh memory for past cognitive memory blocks. |
83
+ | `sym_fetch` | Fetch the full content of a single CMB by its compact channel-header ID. |
84
+ | `sym_peers` | List discovered peers (via bonjour or relay). |
85
+ | `sym_status` | Node identity, relay state, peer count, memory count, current mesh group. |
86
+ | `sym_group_info` | Report the mesh group this node is in, with service type and peer roster scoped to the group. |
87
+ | `sym_invite_create` | Generate a shareable invite URL for a named group. LAN-only or cross-network flavour. |
88
+ | `sym_invite_info` | Parse a mesh invite URL and return a ready-to-use `sym_join_group` call. |
89
+ | `sym_join_group` | **Hot-swap** this node into a different mesh group at runtime — no Claude Code restart. |
90
+ | `sym_groups_discover` | List SYM-mesh groups currently advertising on the local network via Bonjour / mDNS. |
80
91
 
81
- For cross-network setup (different offices, remote team), see [Cross-network setup](#cross-network-setup-optional) below.
92
+ With the Channels flag enabled, real-time push is bidirectional: peer events arrive in Claude's context without any tool call, while the session is mid-turn. Without the flag, the same tools are available on demand — you just don't get the async push surface.
82
93
 
83
- ## Dev-team groups
94
+ ## Team mesh groups
84
95
 
85
- By default every sym-mesh-channel node joins the global `_sym._tcp` mesh — every peer on the network sees every other peer. For a big company with multiple dev teams, this is too noisy. Mesh groups (MMP §5.8) isolate each team at the mDNS layer so `backend-team` and `frontend-team` can't see each other's CMBs at all.
96
+ By default every `sym-mesh-channel` node joins the global `_sym._tcp` mesh — every peer on the network sees every other peer. For a company with multiple teams, that's too noisy. Mesh groups (MMP §5.8) isolate each team at the mDNS layer so `backend-team` and `frontend-team` can't see each other's signals at all.
86
97
 
87
- ### LAN dev-team group (same office)
98
+ ### Same office (LAN)
88
99
 
89
- **Team lead creates the group:**
100
+ **Team lead creates the group from any Claude Code session:**
90
101
 
91
102
  ```
92
- # In Claude Code:
93
103
  > sym_invite_create { "group": "backend-team" }
94
104
 
95
105
  Invite URL (LAN-only (Bonjour)):
96
-
97
106
  sym://group/backend-team
98
107
 
99
- Share this URL with teammates...
100
-
101
108
  > sym_join_group { "group": "backend-team" }
102
-
103
109
  Hot-swapped from group "default" (_sym._tcp) to "backend-team" (_backend-team._tcp).
104
110
  ```
105
111
 
106
- **Team lead shares the URL** (Slack, email, however) with teammates.
112
+ **Team lead shares the URL** over Slack, email, whatever.
107
113
 
108
114
  **Each teammate pastes the URL into their Claude Code session:**
109
115
 
110
116
  ```
111
117
  > sym_invite_info { "url": "sym://group/backend-team" }
112
-
113
118
  Parsed invite: sym://group/backend-team
114
- { "app": "sym", "group": "backend-team", "service_type": "_backend-team._tcp" }
115
-
116
- To join, call sym_join_group:
117
- { "group": "backend-team" }
118
119
 
119
120
  > sym_join_group { "group": "backend-team" }
120
-
121
121
  Hot-swapped from group "default" to "backend-team".
122
122
  ```
123
123
 
124
- No restart, no `~/.claude.json` editing. Teammates on the same LAN now see each other via Bonjour. `backend-team` and `frontend-team` live in isolated mDNS spaces.
124
+ No restart. No `~/.claude.json` editing. Teammates on the same LAN now see each other; `backend-team` and `frontend-team` live in isolated mDNS spaces.
125
125
 
126
- ### Cross-network team group (distributed team via relay)
126
+ ### Distributed team (via relay)
127
127
 
128
- Same story, but the team crosses network boundaries (home ↔ office, coffee shop ↔ client site). You need a relay so members can find each other over the internet. We host one at `wss://sym-relay.onrender.com`; you can run your own from the [sym-relay](https://github.com/sym-bot/sym-relay) repo.
129
-
130
- **Team lead creates the relay-backed invite:**
128
+ Same pattern, but the team crosses network boundaries (home ↔ office, coffee shop ↔ client site). You need a relay so members can find each other over the internet. We host one at `wss://sym-relay.onrender.com`; you can run your own from the [sym-relay](https://github.com/sym-bot/sym-relay) repo.
131
129
 
132
130
  ```
133
131
  > sym_invite_create {
134
132
  "group": "eng-team",
135
133
  "relay_url": "wss://sym-relay.onrender.com",
136
- "relay_token": "a-shared-secret-any-string-teammates-agree-on"
134
+ "relay_token": "any-shared-secret-the-team-agrees-on"
137
135
  }
138
136
 
139
137
  Invite URL (cross-network (relay)):
140
-
141
- sym://team/eng-team?relay=wss%3A%2F%2Fsym-relay.onrender.com&token=a-shared-secret-...
142
-
143
- > sym_join_group {
144
- "group": "eng-team",
145
- "relay_url": "wss://sym-relay.onrender.com",
146
- "relay_token": "a-shared-secret-any-string-teammates-agree-on"
147
- }
138
+ sym://team/eng-team?relay=wss%3A%2F%2Fsym-relay.onrender.com&token=any-shared-secret-...
148
139
  ```
149
140
 
150
- Teammate pastes the URL, `sym_invite_info` extracts the relay + token from the query string, `sym_join_group` hot-swaps with the same args. All members with the same token share one relay channel — different tokens = different channels on the same relay host.
141
+ Teammate pastes the URL, `sym_invite_info` extracts the relay and token from the query string, `sym_join_group` hot-swaps with the same args. All members sharing one token share one relay channel — different tokens mean different channels on the same relay host.
151
142
 
152
143
  ### Discovering what's out there
153
144
 
@@ -160,20 +151,74 @@ SYM-mesh groups visible on LAN (3):
160
151
  _frontend-team._tcp group="frontend-team"
161
152
  ```
162
153
 
163
- Only shows groups with at least one node online right now — there's no central directory of offline-but-known groups (decentralised architecture). For cross-network relay-backed groups, you must know the relay URL + token out of band (someone shares the invite URL).
154
+ Only shows groups with at least one node online right now — there's no central directory of offline-but-known groups (decentralised architecture). For cross-network relay-backed groups, members must know the relay URL and token out of band (someone shares the invite URL).
155
+
156
+ ## How it works
157
+
158
+ ```
159
+ Claude Code A Claude Code B
160
+ ↕ (stdio + MCP) ↕
161
+ sym-mesh-channel ←—— Bonjour mDNS ——→ sym-mesh-channel
162
+ ↕ (LAN discovery) ↕
163
+ └──────────── optional WebSocket relay ───────────────┘
164
+ (cross-network)
165
+ ```
164
166
 
165
- ### Advanced: per-project node identity
167
+ The plugin composes two open specs:
168
+
169
+ - **[Claude Code Channels](https://code.claude.com/docs/en/mcp)** (Anthropic, 2026-03-20) — an MCP capability that lets servers push events directly into Claude's conversation context mid-turn via `notifications/claude/channel`. Anthropic built it for the Telegram/Discord/iMessage integrations. We use it for agent-to-agent cognitive coupling.
170
+ - **[MMP — the Mesh Memory Protocol](https://sym.bot/spec/mmp)** — defines *what* gets pushed: typed seven-field cognitive bundles (CAT7: focus, issue, intent, motivation, commitment, perspective, mood), how receivers gate incoming signals ([SVAF](https://arxiv.org/abs/2604.03955)), and how peers maintain identity without a central orchestrator.
171
+
172
+ **What happens on each message.** When a peer broadcasts a cognitive memory block (CMB), the local SymNode evaluates it via SVAF — Symbolic-Vector Attention Fusion, a receiver-side relevance gate that rejects low-signal messages before they reach Claude's context. If accepted, the MCP server fires a `notifications/claude/channel` notification to Claude Code, which surfaces it as a `<channel>` block in the conversation. Claude sees it, can react, and can broadcast back via `sym_send` or `sym_observe`. No polling. No tool calls. The mesh thinks together.
173
+
174
+ **Identity and transport.** Each peer has its own Ed25519 keypair stored at `~/.sym/nodes/<name>/identity.json`. Node IDs are UUID v7 + Ed25519 signatures, gossiped through the relay's directory or via Bonjour TXT records. Full architecture in MMP §4–§6.
175
+
176
+ ## Advanced: per-project node identity
166
177
 
167
178
  By default every Claude Code session on a machine shares one mesh identity (set globally in `~/.claude.json`). If you run several Claude Code sessions in parallel from distinct project directories and want each to appear as its own peer on the mesh — e.g. a "research" session and a "strategy" session on the same laptop — install per-project instead:
168
179
 
169
180
  ```bash
170
181
  cd path/to/your/project
171
- SYM_NODE_NAME=claude-myproject-win sym-mesh-channel init --project
182
+ SYM_NODE_NAME=claude-myproject-win npx @sym-bot/mesh-channel init --project
172
183
  ```
173
184
 
174
- This writes `<project>/.mcp.json` and merges `<project>/.claude/settings.local.json` instead of touching `~/.claude.json`. Claude Code loads project-scoped `.mcp.json` on launch and its entries override the global one when you're running from that directory, so each project gets its own `SYM_NODE_NAME` without stepping on siblings. Rerun from each project root with a distinct `SYM_NODE_NAME` to register each one as a separate peer.
185
+ This writes `<project>/.mcp.json` and merges `<project>/.claude/settings.local.json` instead of touching `~/.claude.json`. Claude Code loads project-scoped `.mcp.json` on launch and those entries override the global one when you're running from that directory, so each project gets its own `SYM_NODE_NAME` without stepping on siblings.
186
+
187
+ Normal one-machine-one-peer usage does **not** need `--project`.
188
+
189
+ ## Cross-network setup (own-hosted relay)
190
+
191
+ LAN-only is enough for two people sitting next to each other. To connect across networks without relying on our hosted relay, run your own:
192
+
193
+ ```bash
194
+ git clone https://github.com/sym-bot/sym-relay
195
+ cd sym-relay && npm install && npm start
196
+ # or deploy the included Dockerfile to Render / Fly / Railway / etc
197
+ ```
175
198
 
176
- Normal one-machine-one-peer usage does **not** need `--project` the default global install is correct for most users.
199
+ Then point peers at the relay inline when joining a group (see [Team mesh groups → Distributed team](#distributed-team-via-relay)) or set the env vars globally in your `claude-sym-mesh` entry in `~/.claude.json`:
200
+
201
+ ```json
202
+ "env": {
203
+ "SYM_NODE_NAME": "claude-mac",
204
+ "SYM_RELAY_URL": "wss://your-relay.example.com",
205
+ "SYM_RELAY_TOKEN": "your-shared-token"
206
+ }
207
+ ```
208
+
209
+ Both peers must use the same relay URL and token to land on the same channel. The relay supports per-token channel isolation so you can run a single relay for multiple groups.
210
+
211
+ ## Security
212
+
213
+ Defence in depth. Three layers, all must pass before a mesh signal reaches Claude's context:
214
+
215
+ 1. **Transport.** Ed25519 peer identity on LAN + relay-token authentication on cross-network. Unauthenticated sources cannot reach `pushChannel()`.
216
+ 2. **Protocol.** [SVAF](https://arxiv.org/abs/2604.03955) per-field content gating — evaluates each incoming CMB across 7 semantic dimensions and rejects irrelevant signals before they enter cognitive state.
217
+ 3. **Application.** Text-only context injection, no code execution, no permission relay (`claude/channel/permission` is explicitly not declared).
218
+
219
+ **Optional peer allowlist.** Set `SYM_ALLOWED_PEERS=claude-mac,claude-win` to restrict which authenticated peers can push to Claude's context. When empty (default), all authenticated peers are accepted.
220
+
221
+ See [SECURITY.md](SECURITY.md) for the full threat model.
177
222
 
178
223
  ## Requirements
179
224
 
@@ -183,112 +228,83 @@ Normal one-machine-one-peer usage does **not** need `--project` — the default
183
228
  | Claude Code ≥ 2.1.97 (Channels feature) | ✓ | ✓ | ✓ |
184
229
  | Bonjour / mDNS for LAN discovery | built-in | install `avahi-daemon` | built-in (Windows 10+) |
185
230
 
186
- The plugin is approved on the Anthropic Plugin Directory. The `--dangerously-load-development-channels` flag remains necessary while Claude Code Channels are in research preview with a separate allowlist that gates the `<channel>` push-notification feature specifically; the MCP tools themselves do not require the flag.
231
+ ## Limitations
187
232
 
188
- ## What you get
233
+ Clear-eyed about what's not there yet:
189
234
 
190
- Eleven MCP tools exposed to Claude Code, namespaced under `mcp__claude-sym-mesh__`:
235
+ - **Channels still needs a dev flag** for real-time push. The MCP tools work without it; the async push UX does not. Tracking: [anthropics/claude-plugins-official#1512](https://github.com/anthropics/claude-plugins-official/issues/1512).
236
+ - **Corporate networks often block mDNS multicast.** If LAN discovery fails on the same wifi, fall back to a relay.
237
+ - **No offline directory of known groups.** `sym_groups_discover` only shows groups with at least one node currently online. For cross-network relay-backed groups, invite URLs must be shared out of band.
238
+ - **One mesh identity per process.** Two Claude Code sessions on the same machine with the same `SYM_NODE_NAME` will collide — the second one exits with `EIDENTITYLOCK`. Use distinct `SYM_NODE_NAME`s or install per-project (above).
239
+ - **No end-to-end encryption of CMB payloads.** Transport is authenticated (Ed25519, relay tokens) but relay operators can read message bodies. E2E encryption is on the MMP roadmap.
191
240
 
192
- | Tool | What it does |
193
- |---|---|
194
- | `sym_send` | Broadcast a free-text message to all mesh peers. Arrives in receivers' contexts as a `<channel>` notification. |
195
- | `sym_observe` | Share a structured CAT7 observation: focus, issue, intent, motivation, commitment, perspective, mood. SVAF-gated on the receiving side. |
196
- | `sym_recall` | Search mesh memory for past CMBs. |
197
- | `sym_fetch` | Fetch the full content of a single CMB by its compact channel-header ID. Lets receivers pull deep context on demand rather than receiving every field on every push. |
198
- | `sym_peers` | List discovered peers (via bonjour or relay). |
199
- | `sym_status` | Node identity, relay state, peer count, memory count. Includes current mesh group (MMP §5.8). |
200
- | `sym_group_info` | Report the mesh group this node is in, with service type + group name + peer roster scoped to the group. |
201
- | `sym_invite_create` | Generate a shareable invite URL for a named group. LAN-only `sym://group/{name}` or cross-network `sym://team/{name}?relay=&token=` flavor. Team leads share this with teammates. |
202
- | `sym_invite_info` | Parse a mesh invite URL and return group + service type + relay creds + a ready-to-use `sym_join_group` call. Read-only; does not switch groups. |
203
- | `sym_join_group` | **Hot-swap** this node into a different mesh group at runtime — no Claude Code restart. Works for LAN groups and cross-network relay-backed teams. |
204
- | `sym_groups_discover` | List SYM-mesh groups currently advertising on the local network via Bonjour / mDNS. Only shows groups with at least one node online right now. |
241
+ ## Troubleshooting
205
242
 
206
- Real-time push is bidirectional: peer events arrive in Claude's context without any tool call, while the session is mid-turn. This is the "Claude thinks with the mesh" property — not "Claude pokes the mesh occasionally."
243
+ ### `/mcp` reports "Failed to reconnect to claude-sym-mesh"
207
244
 
208
- ## How it works
245
+ Run the diagnostic:
209
246
 
247
+ ```bash
248
+ npx -y @sym-bot/mesh-channel doctor
210
249
  ```
211
- Claude Code A Claude Code B
212
- ↕ (stdio + MCP) ↕
213
- sym-mesh-channel (SymNode) ←— Bonjour mDNS —→ sym-mesh-channel (SymNode)
214
- ↕ (LAN discovery) ↕
215
- └──────────── optional WebSocket relay ────────────────┘
216
- (cross-network, see below)
217
- ```
218
-
219
- - **Stdio half**: Claude Code spawns the MCP server as a child process. MCP tool calls flow over stdio.
220
- - **Push half**: when a CMB arrives at the SymNode (via Bonjour or relay), the MCP server fires a `notifications/claude/channel` notification back over stdio. Claude Code surfaces it as a `<channel>` block in the conversation context.
221
- - **Identity**: each peer has its own Ed25519 keypair stored at `~/.sym/nodes/<name>/identity.json`. NodeIDs are UUID v7 + Ed25519 signatures, gossiped through the relay's directory and/or via Bonjour TXT records.
222
- - **SVAF**: incoming CMBs are evaluated by Symbolic-Vector Attention Fusion before they enter cognitive state. Low-relevance CMBs are gated out so the receiver's context doesn't drown.
223
250
 
224
- For the full architecture, see MMP spec sections 4-6.
251
+ It lists every `claude-sym-mesh` entry in `~/.claude.json` (user-global plus every project-scope) with `[live]` or `[STALE]` next to each. A stale entry is one whose configured `server.js` path no longer exists on disk — common after moving or reinstalling the repo.
225
252
 
226
- ## Cross-network setup (optional)
227
-
228
- LAN-only is enough for two people sitting next to each other. To connect across networks (different offices, coffee shop ↔ home, etc.) you need a relay:
253
+ Heal every stale entry in one pass:
229
254
 
230
255
  ```bash
231
- # Run your own relay (Render-friendly Dockerfile included)
232
- git clone https://github.com/sym-bot/sym-relay
233
- cd sym-relay && npm install && npm start
234
- # or deploy the Dockerfile to Render / Fly / Railway / etc
256
+ npx -y @sym-bot/mesh-channel init
235
257
  ```
236
258
 
237
- Then add the relay env vars to your `claude-sym-mesh` entry in `~/.claude.json`:
238
-
239
- ```json
240
- "env": {
241
- "SYM_NODE_NAME": "claude-mac",
242
- "SYM_RELAY_URL": "wss://your-relay.example.com",
243
- "SYM_RELAY_TOKEN": "your-shared-token"
244
- }
245
- ```
259
+ `init` preserves each entry's `SYM_NODE_NAME` so your mesh identity doesn't drift. Live entries are left alone; `--force` is only needed to overwrite a live entry deliberately. Restart Claude Code after healing — MCP servers are spawned at session start and won't pick up config changes mid-session.
246
260
 
247
- Both peers must use the same relay URL and token to be on the same channel. The relay supports per-token channel isolation so you can run a single relay for multiple groups.
261
+ ### Peers don't see each other on the same wifi
248
262
 
249
- ## Troubleshooting
263
+ Check Bonjour is running:
250
264
 
251
- **Peers don't see each other on the same wifi.** Check Bonjour is running:
252
265
  - macOS: `dns-sd -B _sym._tcp` (built-in)
253
266
  - Linux: `avahi-browse -r _sym._tcp` (needs `avahi-daemon` running)
254
- - Windows 10+: mDNS is built-in. If discovery fails, check Windows Firewall allows mDNS (port 5353 UDP).
267
+ - Windows 10+: built-in. If discovery fails, check Windows Firewall allows mDNS (port 5353 UDP).
255
268
 
256
- Some corporate networks block mDNS multicast — try a hotspot or home wifi to verify. If LAN is blocked, fall back to a relay.
269
+ Some corporate networks block mDNS multicast entirely — try a hotspot or home wifi to verify. If LAN is blocked, fall back to a relay.
257
270
 
258
- **`<channel>` notifications never arrive even though peers are connected.** Verify Claude Code was launched with the development-channels flag matching your install path:
271
+ ### `<channel>` notifications never arrive even though peers are connected
259
272
 
260
- * plugin install: `--dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel`
261
- * npm install: `--dangerously-load-development-channels server:claude-sym-mesh`
273
+ Verify Claude Code was launched with the development-channels flag matching your install path:
262
274
 
263
- Without the exact flag for your install path, MCP push notifications are silently dropped.
275
+ - plugin install: `--dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel`
276
+ - npm install: `--dangerously-load-development-channels server:claude-sym-mesh`
264
277
 
265
- **`sym_status` says "Peers: 0" but `sym_peers` lists peers.** Snapshot timing both views read the same `_peers` map at slightly different moments. The peer set is dynamic. If counts disagree consistently, file an issue.
278
+ Without the exact flag for your install path, MCP push notifications are silently dropped. The tools still work; only the async push surface is gated.
266
279
 
267
- **`sym_status` says "Relay: connected" even though you didn't configure a relay.** Your shell profile (`~/.zshrc`, `~/.bashrc`, etc.) exports `SYM_RELAY_URL`. Claude Code's MCP env block is **additive** — omitting a key doesn't remove it from the child process. Fix: set `SYM_RELAY_URL` and `SYM_RELAY_TOKEN` to `""` (empty string) in the MCP env block to override the shell. The installer (`npx @sym-bot/mesh-channel init`) does this automatically as of v0.1.8.
280
+ ### `sym_status` says "Relay: connected" when you didn't configure one
268
281
 
269
- **Multiple Claude Code sessions on the same machine want to share an identity.** Don't. Each session should have a distinct `SYM_NODE_NAME`. As of `@sym-bot/sym 0.3.70`, the SymNode acquires an exclusive lockfile on its identity (`~/.sym/nodes/<name>/lock.pid`) and refuses to start a second process with the same name. If you see `EIDENTITYLOCK`, find and kill the other process or pick a different name.
282
+ Your shell profile (`~/.zshrc`, `~/.bashrc`) exports `SYM_RELAY_URL`. Claude Code's MCP env block is **additive** omitting a key doesn't remove it from the child process. Fix: set `SYM_RELAY_URL` and `SYM_RELAY_TOKEN` to `""` in the MCP env block. The installer does this automatically as of v0.1.8.
270
283
 
271
- ## Security
284
+ ### Multiple Claude Code sessions on the same machine want to share an identity
272
285
 
273
- Defense in depth three layers, all must pass before a mesh signal reaches Claude's context:
286
+ Don't. Each session should have a distinct `SYM_NODE_NAME`. The SymNode acquires an exclusive lockfile on its identity (`~/.sym/nodes/<name>/lock.pid`) and refuses to start a second process with the same name. If you see `EIDENTITYLOCK`, kill the other process or pick a different name. For multiple parallel sessions with their own identities, use the per-project install above.
274
287
 
275
- 1. **Transport**: Ed25519 peer identity (LAN) + relay token auth (cross-network). Unauthenticated sources cannot reach `pushChannel()`.
276
- 2. **Protocol**: [SVAF](https://arxiv.org/abs/2604.03955) per-field content gating — evaluates each incoming CMB across 7 semantic dimensions and rejects irrelevant signals.
277
- 3. **Application**: text-only context injection, no code execution, no permission relay (`claude/channel/permission` is explicitly not declared).
288
+ ## Other install paths
278
289
 
279
- **Optional peer allowlist**: set `SYM_ALLOWED_PEERS=claude-mac,claude-win` to restrict which authenticated peers can push to Claude's context. When empty (default), all authenticated peers are accepted.
290
+ ### Via the Claude Code plugin marketplace
280
291
 
281
- See [SECURITY.md](SECURITY.md) for the full security model.
292
+ ```
293
+ /plugin marketplace add sym-bot/sym-mesh-channel
294
+ /plugin install sym-mesh-channel@sym-mesh-channel
295
+ claude --dangerously-load-development-channels plugin:sym-mesh-channel@sym-mesh-channel
296
+ ```
297
+
298
+ Use this if you prefer the plugin surface for install and update management. The npm path is simpler for most users.
282
299
 
283
300
  ## References
284
301
 
285
- - [SVAF paper (arXiv:2604.03955)](https://arxiv.org/abs/2604.03955) — Xu, 2026. Symbolic-Vector Attention Fusion for Collective Intelligence.
286
- - [MMP spec v0.2.3](https://sym.bot/spec/mmp) — Mesh Memory Protocol specification.
302
+ - [SVAF paper](https://arxiv.org/abs/2604.03955) — Xu, 2026. *Symbolic-Vector Attention Fusion for Collective Intelligence*. arXiv:2604.03955.
303
+ - [MMP paper](https://arxiv.org/abs/2604.19540) — Xu, 2026. *Mesh Memory Protocol: Semantic Infrastructure for Multi-Agent LLM Systems*. arXiv:2604.19540.
304
+ - [MMP spec v0.2.3](https://sym.bot/spec/mmp) — Mesh Memory Protocol specification (canonical web version).
287
305
  - [sym-swift](https://github.com/sym-bot/sym-swift) — iOS/macOS SDK implementing the same protocol.
288
306
  - [sym-relay](https://github.com/sym-bot/sym-relay) — WebSocket relay for cross-network mesh.
289
307
 
290
- **Verified cross-platform:** Mac ↔ Windows on the same wifi (April 2026).
291
-
292
308
  ## License
293
309
 
294
- Apache 2.0 — SYM.BOT Ltd
310
+ Apache 2.0 — [SYM.BOT](https://sym.bot).
package/bin/install.js CHANGED
@@ -21,10 +21,19 @@
21
21
  * any write.
22
22
  * - Validates JSON parses round-trip before writing.
23
23
  * - Atomic via write-to-tmp + rename.
24
- * - Refuses to overwrite an existing claude-sym-mesh entry without
25
- * --force.
24
+ * - Refuses to overwrite a LIVE claude-sym-mesh entry without --force.
25
+ * An entry whose args[0] server.js path no longer exists on disk is
26
+ * treated as STALE and rewritten in place — a stale entry guarantees
27
+ * a broken MCP transport, so "preserving" it is never what the user
28
+ * wants. SYM_NODE_NAME from the stale entry is preserved so the
29
+ * mesh identity doesn't drift to the hostname-based default.
30
+ * - Also scans every project-scoped mcpServers entry and rewrites any
31
+ * project entry whose claude-sym-mesh.args[0] path has gone stale,
32
+ * again preserving each project's SYM_NODE_NAME. This prevents the
33
+ * "ghost project" failure mode where user-global was fixed but
34
+ * project-scoped entries silently continue to point at the old path.
26
35
  *
27
- * Copyright (c) 2026 SYM.BOT Ltd. Apache 2.0 License.
36
+ * Copyright (c) 2026 SYM.BOT. Apache 2.0 License.
28
37
  */
29
38
 
30
39
  const fs = require('fs');
@@ -37,11 +46,34 @@ const isPostinstall = args.includes('--postinstall');
37
46
  const isProject = args.includes('--project');
38
47
  const cmd = args.find((a) => !a.startsWith('--')) || 'init';
39
48
 
40
- if (cmd !== 'init') {
41
- process.stderr.write(`Unknown command: ${cmd}\nUsage: sym-mesh-channel init [--project] [--force]\n`);
49
+ if (cmd !== 'init' && cmd !== 'doctor') {
50
+ process.stderr.write(`Unknown command: ${cmd}\nUsage: sym-mesh-channel init [--project] [--force]\n sym-mesh-channel doctor\n`);
42
51
  process.exit(1);
43
52
  }
44
53
 
54
+ // ── isStaleEntry: a claude-sym-mesh entry whose server.js path is gone ──
55
+ // Returns true when the entry exists but its args[0] path does not resolve
56
+ // to a file on disk. Such an entry can never spawn the MCP server — every
57
+ // launch yields "Failed to reconnect" in /mcp. Treating it as rewritable
58
+ // on postinstall means users who move or uninstall an old copy of the repo
59
+ // get healed automatically on the next `npm install -g @sym-bot/mesh-channel`
60
+ // without needing to know about --force.
61
+ function isStaleEntry(entry) {
62
+ if (!entry || !Array.isArray(entry.args) || entry.args.length === 0) return false;
63
+ const p = entry.args[0];
64
+ if (typeof p !== 'string' || !p) return false;
65
+ try { return !fs.existsSync(p); } catch { return true; }
66
+ }
67
+
68
+ // preserveNodeName: return the SYM_NODE_NAME from an existing entry's env
69
+ // so rewrites keep the mesh identity. Falls back to nothing if absent; the
70
+ // caller then uses the computed default.
71
+ function preserveNodeName(entry) {
72
+ if (!entry || !entry.env || typeof entry.env.SYM_NODE_NAME !== 'string') return null;
73
+ const n = entry.env.SYM_NODE_NAME.trim();
74
+ return n || null;
75
+ }
76
+
45
77
  // --postinstall always runs global install (npm postinstall runs from
46
78
  // npm's staging directory, not the user's project dir). If both flags
47
79
  // are passed, the --project flag is ignored during postinstall.
@@ -106,19 +138,27 @@ if (useProjectMode) {
106
138
  mcpJson = mcpJson || {};
107
139
  if (!mcpJson.mcpServers) mcpJson.mcpServers = {};
108
140
 
109
- // Refuse to overwrite an existing claude-sym-mesh entry without --force
110
- if (mcpJson.mcpServers['claude-sym-mesh'] && !force) {
141
+ // Refuse to overwrite a LIVE claude-sym-mesh entry without --force.
142
+ // Stale entries (args[0] missing on disk) are always rewritable —
143
+ // see isStaleEntry comment above.
144
+ const existingProjectEntry = mcpJson.mcpServers['claude-sym-mesh'];
145
+ const projectEntryIsStale = isStaleEntry(existingProjectEntry);
146
+ if (existingProjectEntry && !force && !projectEntryIsStale) {
111
147
  process.stderr.write(`'claude-sym-mesh' is already configured in ${mcpJsonPath}.\n`);
112
148
  process.stderr.write('Re-run with --force to overwrite, or remove the existing entry first.\n');
113
149
  process.exit(2);
114
150
  }
115
151
 
152
+ // Preserve the prior node name on rewrite so mesh identity doesn't drift
153
+ // back to the hostname default on every reinstall.
154
+ const projectNodeName = preserveNodeName(existingProjectEntry) || nodeName;
155
+
116
156
  // Build the MCP entry (identical shape to global mode)
117
157
  const projectEntry = {
118
158
  command: 'node',
119
159
  args: [serverJsPath],
120
160
  env: {
121
- SYM_NODE_NAME: nodeName,
161
+ SYM_NODE_NAME: projectNodeName,
122
162
  // Explicitly blank relay env vars — see comment on the global
123
163
  // install path below for why.
124
164
  SYM_RELAY_URL: '',
@@ -195,7 +235,7 @@ if (useProjectMode) {
195
235
  '',
196
236
  `✓ sym-mesh-channel configured for project: ${projectDir}`,
197
237
  '',
198
- ` Node name: ${nodeName}`,
238
+ ` Node name: ${projectNodeName}${projectEntryIsStale ? ' (preserved from stale entry)' : ''}`,
199
239
  ` Server path: ${serverJsPath}`,
200
240
  ` Wrote: ${mcpJsonPath}`,
201
241
  ];
@@ -258,11 +298,67 @@ fs.copyFileSync(claudeJsonPath, backupPath);
258
298
 
259
299
  if (!claudeJson.mcpServers) claudeJson.mcpServers = {};
260
300
 
261
- // ── Refuse to overwrite without --force ──────────────────────────
301
+ // ── doctor: report-only scan, no writes ──────────────────────────
302
+ // Surface every claude-sym-mesh entry (user-global + every project-scope)
303
+ // with whether its server.js is reachable and what node name it uses.
304
+ // Useful when /mcp reports "Failed to reconnect" and the user wants to
305
+ // inspect scope conflicts without mutating state.
306
+
307
+ if (cmd === 'doctor') {
308
+ const rows = [];
309
+ const topEntry = claudeJson.mcpServers['claude-sym-mesh'];
310
+ if (topEntry) {
311
+ rows.push({
312
+ scope: 'user-global',
313
+ path: (topEntry.args || [])[0] || '(no path)',
314
+ node: preserveNodeName(topEntry) || '(no SYM_NODE_NAME)',
315
+ live: !isStaleEntry(topEntry),
316
+ });
317
+ }
318
+ const projects = claudeJson.projects && typeof claudeJson.projects === 'object' ? claudeJson.projects : {};
319
+ for (const [projPath, proj] of Object.entries(projects)) {
320
+ const e = proj && proj.mcpServers && proj.mcpServers['claude-sym-mesh'];
321
+ if (!e) continue;
322
+ rows.push({
323
+ scope: `project ${projPath}`,
324
+ path: (e.args || [])[0] || '(no path)',
325
+ node: preserveNodeName(e) || '(no SYM_NODE_NAME)',
326
+ live: !isStaleEntry(e),
327
+ });
328
+ }
329
+ if (rows.length === 0) {
330
+ console.log('No claude-sym-mesh entries found in ~/.claude.json.');
331
+ console.log('Run `sym-mesh-channel init` to configure.');
332
+ process.exit(0);
333
+ }
334
+ console.log('');
335
+ console.log('claude-sym-mesh entries in ~/.claude.json:');
336
+ console.log('');
337
+ for (const r of rows) {
338
+ console.log(` [${r.live ? 'live ' : 'STALE'}] ${r.scope}`);
339
+ console.log(` node: ${r.node}`);
340
+ console.log(` path: ${r.path}`);
341
+ }
342
+ const staleCount = rows.filter((r) => !r.live).length;
343
+ console.log('');
344
+ if (staleCount > 0) {
345
+ console.log(`${staleCount} stale entr${staleCount === 1 ? 'y' : 'ies'} — run \`sym-mesh-channel init\` to heal.`);
346
+ } else {
347
+ console.log('All entries are live.');
348
+ }
349
+ process.exit(0);
350
+ }
351
+
352
+ // ── Classify the top-level entry ─────────────────────────────────
262
353
 
263
- if (claudeJson.mcpServers['claude-sym-mesh'] && !force) {
354
+ const existingTopEntry = claudeJson.mcpServers['claude-sym-mesh'];
355
+ const topEntryIsStale = isStaleEntry(existingTopEntry);
356
+
357
+ // Refuse to overwrite a LIVE entry without --force. A stale entry is
358
+ // always rewritable — see isStaleEntry comment at top of file.
359
+ if (existingTopEntry && !force && !topEntryIsStale) {
264
360
  if (isPostinstall) {
265
- // During postinstall, silently skip if already configured
361
+ // During postinstall, silently skip if already configured and live
266
362
  console.log('sym-mesh-channel: already configured in ~/.claude.json (skipping)');
267
363
  process.exit(0);
268
364
  }
@@ -271,13 +367,16 @@ if (claudeJson.mcpServers['claude-sym-mesh'] && !force) {
271
367
  process.exit(2);
272
368
  }
273
369
 
370
+ // Preserve the prior node name on rewrite so mesh identity doesn't drift.
371
+ const topNodeName = preserveNodeName(existingTopEntry) || nodeName;
372
+
274
373
  // ── Build the entry ───────────────────────────────────────────────
275
374
 
276
375
  const entry = {
277
376
  command: 'node',
278
377
  args: [serverJsPath],
279
378
  env: {
280
- SYM_NODE_NAME: nodeName,
379
+ SYM_NODE_NAME: topNodeName,
281
380
  // Explicitly blank the relay vars so the MCP doesn't inherit them
282
381
  // from the parent shell (e.g. ~/.zshrc exports). Claude Code's env
283
382
  // block is ADDITIVE — omitting a key doesn't remove it from the
@@ -293,6 +392,33 @@ const entry = {
293
392
 
294
393
  claudeJson.mcpServers['claude-sym-mesh'] = entry;
295
394
 
395
+ // ── Heal stale project-scoped entries ─────────────────────────────
396
+ // ~/.claude.json can contain per-project mcpServers overrides under
397
+ // claudeJson.projects[<path>].mcpServers. Claude Code prefers project-scoped
398
+ // over user-global when launched from that directory, so a stale project
399
+ // entry silently shadows a fresh user-global heal. Scan every project,
400
+ // rewrite any claude-sym-mesh entry whose args[0] is missing on disk,
401
+ // preserving the project's SYM_NODE_NAME.
402
+
403
+ const healedProjects = [];
404
+ const projects = claudeJson.projects && typeof claudeJson.projects === 'object' ? claudeJson.projects : {};
405
+ for (const [projPath, proj] of Object.entries(projects)) {
406
+ const projEntry = proj && proj.mcpServers && proj.mcpServers['claude-sym-mesh'];
407
+ if (!projEntry) continue;
408
+ if (!isStaleEntry(projEntry)) continue;
409
+ const projNodeName = preserveNodeName(projEntry) || nodeName;
410
+ proj.mcpServers['claude-sym-mesh'] = {
411
+ command: 'node',
412
+ args: [serverJsPath],
413
+ env: {
414
+ SYM_NODE_NAME: projNodeName,
415
+ SYM_RELAY_URL: projEntry.env && typeof projEntry.env.SYM_RELAY_URL === 'string' ? projEntry.env.SYM_RELAY_URL : '',
416
+ SYM_RELAY_TOKEN: projEntry.env && typeof projEntry.env.SYM_RELAY_TOKEN === 'string' ? projEntry.env.SYM_RELAY_TOKEN : '',
417
+ },
418
+ };
419
+ healedProjects.push({ path: projPath, node: projNodeName });
420
+ }
421
+
296
422
  // ── Atomic write ──────────────────────────────────────────────────
297
423
 
298
424
  const serialized = JSON.stringify(claudeJson, null, 2);
@@ -331,13 +457,20 @@ try {
331
457
 
332
458
  const launchCmd = `claude --dangerously-load-development-channels server:claude-sym-mesh`;
333
459
 
460
+ const healedLines = healedProjects.length
461
+ ? '\n Healed stale project-scoped entries (now pointing at fresh server.js):\n' +
462
+ healedProjects.map((p) => ` • ${p.path} (node: ${p.node})`).join('\n') + '\n'
463
+ : '';
464
+
465
+ const nodeNameSuffix = topEntryIsStale ? ' (preserved from stale entry)' : '';
466
+
334
467
  console.log(`
335
468
  ✓ sym-mesh-channel configured globally in ~/.claude.json
336
469
 
337
- Node name: ${nodeName}
470
+ Node name: ${topNodeName}${nodeNameSuffix}
338
471
  Server path: ${serverJsPath}
339
472
  Backup: ${backupPath}
340
-
473
+ ${healedLines}
341
474
  Launch Claude Code with the Channels flag:
342
475
 
343
476
  ${launchCmd}
@@ -347,4 +480,8 @@ Inside Claude Code, verify:
347
480
  sym_status → node id, relay state, peer count
348
481
  sym_peers → discovered peers via Bonjour or relay
349
482
  sym_send "hello mesh" → broadcast to all peers
483
+
484
+ Troubleshoot a broken install with:
485
+
486
+ sym-mesh-channel doctor
350
487
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/mesh-channel",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "MCP server — real-time agent-to-agent cognition for Claude Code remote teams via the SYM mesh.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -22,11 +22,28 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@modelcontextprotocol/sdk": "^1.12.1",
25
- "@sym-bot/sym": "^0.3.81"
25
+ "@sym-bot/sym": "^0.5.0"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=18"
29
29
  },
30
+ "keywords": [
31
+ "claude-code",
32
+ "claude-plugin",
33
+ "mcp",
34
+ "model-context-protocol",
35
+ "mesh-memory-protocol",
36
+ "mmp",
37
+ "cat7",
38
+ "svaf",
39
+ "collective-intelligence",
40
+ "agent-to-agent",
41
+ "multi-agent",
42
+ "cognitive-coupling",
43
+ "sym",
44
+ "sym-bot",
45
+ "mesh"
46
+ ],
30
47
  "author": "Hongwei Xu <hongwei@sym.bot> (https://sym.bot)",
31
48
  "license": "Apache-2.0"
32
49
  }
package/server.js CHANGED
@@ -19,7 +19,7 @@ if (process.argv[2] === 'init') {
19
19
  * its own relay connection, and its own SVAF evaluation with engineering-domain
20
20
  * field weights. Per MMP Section 3: every participant is a peer.
21
21
  *
22
- * Copyright (c) 2026 SYM.BOT Ltd. Apache 2.0 License.
22
+ * Copyright (c) 2026 SYM.BOT. Apache 2.0 License.
23
23
  */
24
24
 
25
25
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -253,6 +253,35 @@ function registerNodeHandlers(n) {
253
253
 
254
254
  // ── MCP Server ───────────────────────────────────────────────
255
255
 
256
+ // Base instructions shown to the agent at every MCP initialize.
257
+ const BASE_INSTRUCTIONS =
258
+ `You are a peer node on the SYM mesh (identity: ${NODE_NAME}). ` +
259
+ 'Mesh events arrive as <channel> notifications in real-time. ' +
260
+ 'When you see a CMB from another node, respond via sym_send targeted at that node by name if the reply is for that specific peer (MMP §4.4.4 targeted CMB). ' +
261
+ 'Share observations about your own state with the whole mesh via sym_observe (MMP §9.2 receiver-autonomous SVAF evaluation). ' +
262
+ 'Both sym_send and sym_observe emit CAT7 CMBs; receivers run SVAF and, if admitted, remix-store with lineage pointing back to your CMB. ' +
263
+ 'Search mesh memory via sym_recall. ' +
264
+ 'Messages arrive as compact headers with [mNNN] IDs — use sym_fetch to read the full content when the header is relevant to your current task.';
265
+
266
+ // Final startup step (MMP §4.2 O2 — rejoin-without-replay). The SymNode
267
+ // constructor builds the memory-store index from disk, so the primer is
268
+ // available synchronously without needing node.start(). Appending it to
269
+ // the MCP instructions payload means a fresh Claude Code session wakes
270
+ // with prior remix memory — own observations plus peer observations
271
+ // admitted by SVAF — already loaded into context, zero first-turn
272
+ // sym_recall overhead.
273
+ //
274
+ // MCP SDK reads `instructions` at Server construction time (storing it in
275
+ // a private field) and emits it only on initialize-response; mutations on
276
+ // the public property after construction are ignored. Compute once, pass in.
277
+ let primerText = '';
278
+ try {
279
+ const primer = node.buildStartupPrimer();
280
+ if (primer && primer.count > 0) primerText = `\n\n${primer.text}`;
281
+ } catch (err) {
282
+ process.stderr.write(`sym-mesh-channel startup primer skipped: ${err?.message || err}\n`);
283
+ }
284
+
256
285
  const mcp = new Server(
257
286
  { name: 'sym-mesh', version: '0.1.0' },
258
287
  {
@@ -260,14 +289,7 @@ const mcp = new Server(
260
289
  tools: {},
261
290
  experimental: { 'claude/channel': {} },
262
291
  },
263
- instructions:
264
- `You are a peer node on the SYM mesh (identity: ${NODE_NAME}). ` +
265
- 'Mesh events arrive as <channel> notifications in real-time. ' +
266
- 'When you see a CMB from another node, respond via sym_send targeted at that node by name if the reply is for that specific peer (MMP §4.4.4 targeted CMB). ' +
267
- 'Share observations about your own state with the whole mesh via sym_observe (MMP §9.2 receiver-autonomous SVAF evaluation). ' +
268
- 'Both sym_send and sym_observe emit CAT7 CMBs; receivers run SVAF and, if admitted, remix-store with lineage pointing back to your CMB. ' +
269
- 'Search mesh memory via sym_recall. ' +
270
- 'Messages arrive as compact headers with [mNNN] IDs — use sym_fetch to read the full content when the header is relevant to your current task.',
292
+ instructions: BASE_INSTRUCTIONS + primerText,
271
293
  },
272
294
  );
273
295
 
@@ -840,7 +862,9 @@ process.on('SIGINT', () => shutdown('SIGINT'));
840
862
  process.on('SIGHUP', () => shutdown('SIGHUP'));
841
863
 
842
864
  async function main() {
843
- // Start SymNode — connects to relay as a peer
865
+ // Start SymNode — connects to relay as a peer. The startup primer is
866
+ // computed at module-load time (see BASE_INSTRUCTIONS above) and is
867
+ // already embedded in the MCP server's initialize-response payload.
844
868
  await node.start();
845
869
 
846
870
  // Start MCP server — communicates with Claude Code via stdio