Files

211 lines
4.7 KiB
Markdown
Raw Permalink Normal View History

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