feat: add NanoClaw server-side skill and update docs
Adds the complete server-side MCP implementation as a NanoClaw feature skill (nanoclaw-skill/). Includes source code, modification intents, and step-by-step SKILL.md for installation. README updated to cover both server (NanoClaw) and client (pi.dev) setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7fa0201a6b
commit
3fa0ec8f71
@@ -0,0 +1,210 @@
|
||||
---
|
||||
name: add-mcp
|
||||
description: Add an MCP (Model Context Protocol) server to NanoClaw so external agents can chat with the assistant via Streamable HTTP. Includes SSH tunnel setup for secure access.
|
||||
---
|
||||
|
||||
# Add MCP Server
|
||||
|
||||
This skill adds an MCP server to NanoClaw, allowing external agents (Claude Code, pi.dev, custom clients) to have a dialog with the assistant.
|
||||
|
||||
## Phase 1: Pre-flight
|
||||
|
||||
### Check if already applied
|
||||
|
||||
Check if `src/mcp-server.ts` exists. If it does, skip to Phase 3 (Setup). The code changes are already in place.
|
||||
|
||||
## Phase 2: Apply Code Changes
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk zod
|
||||
```
|
||||
|
||||
### Copy source file
|
||||
|
||||
Copy `src/mcp-server.ts` from the skill directory into the project:
|
||||
|
||||
```bash
|
||||
cp ${CLAUDE_SKILL_DIR}/src/mcp-server.ts src/mcp-server.ts
|
||||
```
|
||||
|
||||
### Modify channel barrel file
|
||||
|
||||
Read the intent file at `${CLAUDE_SKILL_DIR}/modify/src/channels/index.ts.intent.md`, then apply it to `src/channels/index.ts`.
|
||||
|
||||
Add this import line (before or after existing channel imports):
|
||||
|
||||
```typescript
|
||||
// mcp
|
||||
import '../mcp-server.js';
|
||||
```
|
||||
|
||||
### Modify config.ts
|
||||
|
||||
Add `'MCP_API_KEY'` and `'MCP_PORT'` to the `readEnvFile` array in `src/config.ts`:
|
||||
|
||||
```typescript
|
||||
const envConfig = readEnvFile([
|
||||
// ... existing keys ...
|
||||
'MCP_API_KEY',
|
||||
'MCP_PORT',
|
||||
]);
|
||||
```
|
||||
|
||||
Add these exports (before or after existing config exports):
|
||||
|
||||
```typescript
|
||||
// MCP server for external agent access
|
||||
export const MCP_API_KEY =
|
||||
process.env.MCP_API_KEY || envConfig.MCP_API_KEY || '';
|
||||
export const MCP_PORT = parseInt(
|
||||
process.env.MCP_PORT || envConfig.MCP_PORT || '3002',
|
||||
10,
|
||||
);
|
||||
```
|
||||
|
||||
### Modify index.ts
|
||||
|
||||
Add imports at the top:
|
||||
|
||||
```typescript
|
||||
import { startMcpServer } from './mcp-server.js';
|
||||
import { MCP_API_KEY, MCP_PORT } from './config.js';
|
||||
```
|
||||
|
||||
Note: `MCP_API_KEY` and `MCP_PORT` should be added to the existing config import, not a separate one.
|
||||
|
||||
In the `main()` function, after `startCredentialProxy(...)`, add:
|
||||
|
||||
```typescript
|
||||
// Start external MCP server if API key is configured
|
||||
let mcpServer: ReturnType<typeof startMcpServer> | undefined;
|
||||
if (MCP_API_KEY) {
|
||||
mcpServer = startMcpServer(MCP_PORT, MCP_API_KEY, {
|
||||
registeredGroups: () => registeredGroups,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the `shutdown()` function, add before `queue.shutdown(...)`:
|
||||
|
||||
```typescript
|
||||
(await mcpServer)?.close();
|
||||
```
|
||||
|
||||
### Validate
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Build must succeed before proceeding.
|
||||
|
||||
## Phase 3: Setup
|
||||
|
||||
### Generate API Key
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
### Configure environment
|
||||
|
||||
Add to `.env`:
|
||||
|
||||
```
|
||||
MCP_API_KEY=<generated-key>
|
||||
MCP_PORT=3002
|
||||
```
|
||||
|
||||
### Rebuild and restart
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then restart the service:
|
||||
|
||||
```bash
|
||||
# Linux (systemd)
|
||||
sudo systemctl restart nanoclaw
|
||||
|
||||
# macOS (launchd)
|
||||
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
# Check MCP server is listening
|
||||
ss -tlnp | grep 3002 # Linux
|
||||
lsof -i :3002 # macOS
|
||||
|
||||
# Check logs
|
||||
grep "MCP server listening" logs/nanoclaw.log | tail -1
|
||||
grep "MCP channel connected" logs/nanoclaw.log | tail -1
|
||||
```
|
||||
|
||||
Both log lines should be present.
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
export MCP_API_KEY="<your-key>"
|
||||
|
||||
curl -s -X POST http://localhost:3002/mcp \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-H "Authorization: Bearer $MCP_API_KEY" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
||||
```
|
||||
|
||||
Should return `chat` and `list_groups` tools.
|
||||
|
||||
## Connecting External Agents
|
||||
|
||||
The MCP server binds to `127.0.0.1` only (not publicly accessible). External clients connect via SSH tunnel:
|
||||
|
||||
```bash
|
||||
ssh -L 3002:localhost:3002 user@your-server -N
|
||||
```
|
||||
|
||||
Then configure the MCP client:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"nanoclaw": {
|
||||
"type": "streamable-http",
|
||||
"url": "http://localhost:3002/mcp",
|
||||
"headers": {
|
||||
"Authorization": "Bearer <MCP_API_KEY>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the [pi.dev extension](../index.ts) in this repo for a working client implementation.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Registers as a NanoClaw **Channel** (like Telegram or WhatsApp)
|
||||
- Uses `mcp:` JID namespace — isolated from all other channels
|
||||
- Same container, workspace, tools, and CLAUDE.md as the target group
|
||||
- Container reuse via GroupQueue (cold start ~2-3 min, warm ~5-10s)
|
||||
- Responses debounced (3s) for multi-part agent output
|
||||
- Bearer token auth + SSH tunnel for security
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**MCP server not starting:** Check `MCP_API_KEY` is set in `.env` and NanoClaw was rebuilt.
|
||||
|
||||
**401 Unauthorized:** Bearer token doesn't match `MCP_API_KEY`.
|
||||
|
||||
**Connection refused:** SSH tunnel not running, or NanoClaw not running.
|
||||
|
||||
**Slow first response:** Expected — cold start boots a container (~2-3 min). Subsequent calls reuse it.
|
||||
|
||||
**"(no response)":** Container timed out or crashed. Check `docker logs <container-name>`.
|
||||
Reference in New Issue
Block a user