feat: real channel-bridge client + reply tool
- WS client to OpenclawPlugin's bridge (auto-reconnect with exponential backoff) - On hello: send (openclaw_session, claude_session, pid) — no auth token - On inbound frame: emit notifications/claude/channel into MCP so Claude Code opens a new turn - Single MCP tool `reply(text, final?)` — model calls it to send a reply back via the bridge. final=false streams progress chunks - Capability declared under `experimental.claude/channel` (top-level was silently ignored — bug confirmed against Anthropic's discord plugin) - Dropped auth handshake + permission_request reverse channel per the same-machine, full-perms design call Add .mcp.json so the package is also a valid Claude Code plugin (for potential plugin: form deployment later).
This commit is contained in:
86
README.md
86
README.md
@@ -1,58 +1,64 @@
|
||||
# SynthesisAgent.ClaudePlugin
|
||||
|
||||
The **Claude Code side** of SynthesisAgent. A stdio MCP server that registers as a `--channels` plugin and bridges to `SynthesisAgent.OpenclawPlugin`.
|
||||
Stdio MCP server registered as a Claude Code `--channels server:<name>` source. Lives inside every long-lived Claude process spawned by SynthesisAgent.OpenclawPlugin.
|
||||
|
||||
## What it does
|
||||
|
||||
- Receives inbound messages from OpenClaw and emits `notifications/claude/channel` so Claude Code starts a new turn.
|
||||
- Receives the OpenClaw tool catalog at `hello_ack` time and exposes them as MCP tools.
|
||||
- Forwards every `tools/call` over the bridge for OpenClaw to execute.
|
||||
- Forwards Claude's `permission_request` notifications to OpenClaw, and surfaces the user's reply back.
|
||||
- Connects out (WebSocket) to OpenclawPlugin's bridge on startup, sends `hello`
|
||||
- Listens for `inbound` frames → emits `notifications/claude/channel` so Claude opens a new turn
|
||||
- Exposes a single `reply(text, final?)` MCP tool — the model calls it to send its answer back to the OpenClaw side
|
||||
|
||||
## Capability declaration (important detail)
|
||||
|
||||
```ts
|
||||
new Server({...}, {
|
||||
capabilities: {
|
||||
tools: {},
|
||||
experimental: {
|
||||
'claude/channel': {},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Top-level placement of `'claude/channel'` is silently ignored — Claude Code logs `"server did not declare claude/channel capability"`. Verified 2026-05-14 against the official Anthropic discord plugin source.
|
||||
|
||||
## Required env vars
|
||||
|
||||
Set by OpenclawPlugin when it spawns Claude:
|
||||
Set by OpenclawPlugin's process-manager when spawning claude:
|
||||
|
||||
| Var | Example | Meaning |
|
||||
|-----|---------|---------|
|
||||
| `SYNTHESIS_BRIDGE_URL` | `ws://127.0.0.1:18801/bridge` | Where to connect |
|
||||
| `SYNTHESIS_BRIDGE_TOKEN` | `...` | Shared secret |
|
||||
| `SYNTHESIS_OPENCLAW_SESSION` | `discord:alice` | This Claude process serves *this* session |
|
||||
| `SYNTHESIS_CLAUDE_SESSION` | `<uuid>` | The Claude session UUID, for traceability |
|
||||
| Var | Example |
|
||||
|-----|---------|
|
||||
| `SYNTHESIS_WS_URL` | `ws://127.0.0.1:18901/bridge` |
|
||||
| `SYNTHESIS_OPENCLAW_SESSION` | `developer::discord:channel:123` |
|
||||
| `SYNTHESIS_CLAUDE_SESSION` | `<uuid>` |
|
||||
|
||||
## Spawn shape (done by OpenclawPlugin)
|
||||
## Manual registration (laptop smoke test)
|
||||
|
||||
To use this MCP server with `claude --channels server:synthesis`, register it once globally:
|
||||
|
||||
```bash
|
||||
claude \
|
||||
--channels plugin:synthesis-claude@local \
|
||||
--resume "$SYNTHESIS_CLAUDE_SESSION" \
|
||||
--allowed-tools "Read Edit Bash mcp__synthesis__*" \
|
||||
--append-system-prompt-file ./session-context.md
|
||||
claude mcp add --scope user synthesis -- bun run /absolute/path/to/server.ts
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Verify exact semantics of `--channels plugin:foo@bar` against the running Claude Code build.
|
||||
- [ ] Verify `mcp.notification('notifications/claude/channel', ...)` triggers a new turn (vs being silently accepted). May need to first land on `claude/channel` capability declaration.
|
||||
- [ ] Implement attachment download flow (`download_attachment` style) — currently passthrough.
|
||||
- [ ] Implement `fetch_messages` to query OpenClaw history.
|
||||
- [ ] Wire `bun-types` install in `package.json`.
|
||||
|
||||
## Manual test (once OpenclawPlugin is wired up)
|
||||
Then OpenclawPlugin can spawn:
|
||||
|
||||
```bash
|
||||
# Terminal 1: start OpenclawPlugin bridge server
|
||||
openclaw # with SynthesisAgent.OpenclawPlugin loaded
|
||||
|
||||
# Terminal 2: simulate the spawn manually
|
||||
SYNTHESIS_BRIDGE_URL=ws://127.0.0.1:18801/bridge \
|
||||
SYNTHESIS_BRIDGE_TOKEN=local-dev \
|
||||
SYNTHESIS_OPENCLAW_SESSION=test:1 \
|
||||
SYNTHESIS_CLAUDE_SESSION=$(uuidgen) \
|
||||
claude --channels plugin:synthesis-claude@local
|
||||
|
||||
# Terminal 3: push a fake inbound message via OpenclawPlugin admin CLI
|
||||
openclaw synthesis push --session test:1 --content "hello"
|
||||
claude --channels server:synthesis \
|
||||
--dangerously-load-development-channels server:synthesis \
|
||||
--resume <uuid> \
|
||||
--permission-mode bypassPermissions
|
||||
```
|
||||
|
||||
A successful run: Claude Code visibly starts a new turn in Terminal 2 reacting to "hello".
|
||||
(The `--dangerously-load-development-channels` flag triggers a one-time interactive dev-mode confirmation. OpenclawPlugin's process-manager pipes "1\n" to stdin shortly after spawn to dismiss it automatically.)
|
||||
|
||||
## Tool surface (v1)
|
||||
|
||||
Just `reply`. The model uses Claude Code's built-in tools (Read, Edit, Bash, etc.) for any actual work; `reply` is purely the output channel.
|
||||
|
||||
`reply(text, final=true)` sends a complete answer.
|
||||
`reply(text, final=false)` queues a progress chunk; OpenclawPlugin buffers chunks until a final call arrives.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0 (modeled after Anthropic's official `discord@claude-plugins-official` plugin).
|
||||
|
||||
Reference in New Issue
Block a user