@sym-bot/mesh-channel 0.3.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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +43 -0
- package/README.md +167 -151
- package/bin/install.js +151 -14
- package/package.json +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
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
|
+
|
|
3
46
|
## 0.3.0
|
|
4
47
|
|
|
5
48
|
### Added
|
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
|
[](https://www.npmjs.com/package/@sym-bot/mesh-channel)
|
|
10
|
+
[](https://claude.ai/settings/plugins/submit)
|
|
4
11
|
[](https://sym.bot/spec/mmp)
|
|
5
|
-
[](https://arxiv.org/abs/2604.03955)
|
|
12
|
+
[](https://arxiv.org/abs/2604.03955)
|
|
13
|
+
[](https://arxiv.org/abs/2604.19540)
|
|
6
14
|
[](LICENSE)
|
|
7
15
|
[](https://nodejs.org)
|
|
8
16
|
|
|
9
|
-
|
|
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
|
|
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
|
-
|
|
23
|
+
Two agents coordinated through typed cognitive signals, across machines, with zero human copy-paste.
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Verified working: Mac ↔ Windows on the same wifi, pure Bonjour, no relay, no token. Cross-network via optional WebSocket relay.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
## Who this is for
|
|
27
28
|
|
|
28
|
-
**
|
|
29
|
-
|
|
30
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
npm install -g @sym-bot/mesh-channel
|
|
40
|
+
claude
|
|
49
41
|
```
|
|
50
42
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
-
###
|
|
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**
|
|
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
|
|
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
|
-
###
|
|
126
|
+
### Distributed team (via relay)
|
|
127
127
|
|
|
128
|
-
Same
|
|
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": "
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
231
|
+
## Limitations
|
|
187
232
|
|
|
188
|
-
|
|
233
|
+
Clear-eyed about what's not there yet:
|
|
189
234
|
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
+
### `/mcp` reports "Failed to reconnect to claude-sym-mesh"
|
|
207
244
|
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
+
### Peers don't see each other on the same wifi
|
|
248
262
|
|
|
249
|
-
|
|
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+:
|
|
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
|
-
|
|
271
|
+
### `<channel>` notifications never arrive even though peers are connected
|
|
259
272
|
|
|
260
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
+
### `sym_status` says "Relay: connected" when you didn't configure one
|
|
268
281
|
|
|
269
|
-
|
|
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
|
-
|
|
284
|
+
### Multiple Claude Code sessions on the same machine want to share an identity
|
|
272
285
|
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
+
### Via the Claude Code plugin marketplace
|
|
280
291
|
|
|
281
|
-
|
|
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
|
|
286
|
-
- [MMP
|
|
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
|
|
310
|
+
Apache 2.0 — [SYM.BOT](https://sym.bot).
|
package/bin/install.js
CHANGED
|
@@ -21,8 +21,17 @@
|
|
|
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
|
|
25
|
-
*
|
|
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
36
|
* Copyright (c) 2026 SYM.BOT. Apache 2.0 License.
|
|
28
37
|
*/
|
|
@@ -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
|
|
110
|
-
|
|
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:
|
|
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: ${
|
|
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
|
-
// ──
|
|
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
|
-
|
|
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:
|
|
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: ${
|
|
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.3.
|
|
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": {
|
|
@@ -27,6 +27,23 @@
|
|
|
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
|
}
|