nanobot/docs/mcp_local_clone_and_tool_profiles.md
tanyar09 4b808f9a30 Docs: MCP local clones and tool profiles
Document local-cloned MCP server layout, docker mounts, tool-call JSON protocol for local providers, profile routing behavior, and common gotchas. Add a brief pointer from the multi-bot Docker guide.

Made-with: Cursor
2026-03-31 12:53:34 -04:00

96 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MCP local clones + tool profiles (local LLM friendly)
This documents the MCP/tool-profile work done to make MCP servers usable with local LLM providers that have **low tool limits** (e.g. ~20 tools) and/or unreliable native function-calling.
## What we changed
- **Local-clone policy for MCP servers**
- MCP servers are cloned/built locally under `./mcp-servers/` (not installed from npm/PyPI at runtime).
- Repo gitignore keeps the clones out of git while keeping `mcp-servers/README.md` and `.gitkeep`.
- Added `scripts/setup-mcp-servers.sh` to clone/build the Gitea MCP server (`mcp-servers/gitea-mcp/`) into a runnable binary.
- **Docker mounts for local MCP servers**
- Multi-bot compose files mount `./mcp-servers` into containers at `/app/mcp-servers:ro`.
- This makes config entries like `/app/mcp-servers/gitea-mcp/gitea-mcp` work inside the container.
- **MCP env var expansion**
- `nanobot/agent/tools/mcp.py` expands `$VARS` in MCP server `env` using the container environment, so configs can safely reference secrets without duplicating them:
- Example: `"GITEA_ACCESS_TOKEN": "$NANOBOT_GITLE_TOKEN"`
- **Local-provider tool calling reliability**
- `nanobot/agent/context.py` now includes a strict JSON tool-call protocol for providers that dont do native function calling.
- `nanobot/providers/custom_provider.py` was improved to parse tool calls when the model returns:
- Embedded JSON tool calls in content, or
- A message that is *only* a JSON object (`{"name": "...", "parameters": {...}}`)
- **Tool profile routing improvements**
- `nanobot/agent/tool_routing.py` includes a heuristic fast-path to pick an MCP-capable profile (e.g. `workspace_mcp`) when the user intent includes PRs/issues/repos/Gitea terms.
- The LLM router still applies for general cases; this heuristic prevents obvious “forge” intents from being routed to a no-MCP profile.
- **LLM empty-response retry**
- `nanobot/agent/loop.py` retries once if the provider returns an empty final message, nudging the model to either call a tool or respond with text.
## Where MCP servers live (host vs container)
- **Host repo path**: `./mcp-servers/`
- Example: `./mcp-servers/gitea-mcp/`
- **Inside the nanobot container**: `/app/mcp-servers/` (mounted read-only)
- Example: `/app/mcp-servers/gitea-mcp/gitea-mcp`
## Minimal Gitea MCP setup
1. Build the local server (host):
```bash
./scripts/setup-mcp-servers.sh gitea
```
2. Add to per-bot config (host) `~/.nanobot-user1/config.json`:
```jsonc
{
"tools": {
"mcpServers": {
"gitea": {
"command": "/app/mcp-servers/gitea-mcp/gitea-mcp",
"args": ["-t", "stdio", "--host", "http://10.0.30.169:3000", "-r"],
"env": {
"GITEA_ACCESS_TOKEN": "$NANOBOT_GITLE_TOKEN"
}
}
}
}
}
```
3. Ensure your compose file mounts the local MCP servers directory:
- `./mcp-servers:/app/mcp-servers:ro`
## Tool profiles: what they do (and what they dont)
- **Built-in tools** are registered once at gateway startup and do not “shut down”; profiles only control **exposure to the LLM per turn**.
- **MCP tools/servers** are connected lazily and can be **connected/disconnected per turn** based on the selected profile.
### When MCP servers disconnect
- **On a later message**, if a new profile is selected whose `mcpServers` set does not include a previously-connected server, nanobot disconnects that MCP server.
- On **gateway shutdown**, nanobot disconnects all MCP servers.
- `/new` **does not** disconnect MCP servers; it only clears session history and triggers memory consolidation.
## Common gotchas
- **Router picked `workspace` (no MCP)**
- If the router selects a profile with `"mcpServers": []`, MCP tools are hidden even if the servers are configured.
- Either improve routing, or (if acceptable) allowlist the specific MCP server in that profile (`"mcpServers": ["gitea"]`).
- **Tool count limits on local models**
- A single MCP server can register 30+ tools (Gitea MCP: ~30), which can exceed local providers tool limits.
- If you must stay under ~20 tools, prefer:
- Narrow profiles, or
- A “dispatcher tool” approach (one tool per MCP server) instead of registering every MCP tool individually.
- **Repo owner/repo mismatch**
- Errors like `GetUserByName` commonly mean the `owner` string doesnt exist or the token cannot see it.
- Resolve the canonical `owner/repo` first (search/list repos), then call list PRs with the correct pair.