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
4.4 KiB
4.4 KiB
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.mdand.gitkeep. - Added
scripts/setup-mcp-servers.shto clone/build the Gitea MCP server (mcp-servers/gitea-mcp/) into a runnable binary.
- MCP servers are cloned/built locally under
-
Docker mounts for local MCP servers
- Multi-bot compose files mount
./mcp-serversinto containers at/app/mcp-servers:ro. - This makes config entries like
/app/mcp-servers/gitea-mcp/gitea-mcpwork inside the container.
- Multi-bot compose files mount
-
MCP env var expansion
nanobot/agent/tools/mcp.pyexpands$VARSin MCP serverenvusing the container environment, so configs can safely reference secrets without duplicating them:- Example:
"GITEA_ACCESS_TOKEN": "$NANOBOT_GITLE_TOKEN"
- Example:
-
Local-provider tool calling reliability
nanobot/agent/context.pynow includes a strict JSON tool-call protocol for providers that don’t do native function calling.nanobot/providers/custom_provider.pywas 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.pyincludes 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.pyretries 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/
- Example:
- Inside the nanobot container:
/app/mcp-servers/(mounted read-only)- Example:
/app/mcp-servers/gitea-mcp/gitea-mcp
- Example:
Minimal Gitea MCP setup
- Build the local server (host):
./scripts/setup-mcp-servers.sh gitea
- Add to per-bot config (host)
~/.nanobot-user1/config.json:
{
"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"
}
}
}
}
}
- Ensure your compose file mounts the local MCP servers directory:
./mcp-servers:/app/mcp-servers:ro
Tool profiles: what they do (and what they don’t)
- 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
mcpServersset does not include a previously-connected server, nanobot disconnects that MCP server. - On gateway shutdown, nanobot disconnects all MCP servers.
/newdoes 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"]).
- If the router selects a profile with
-
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
GetUserByNamecommonly mean theownerstring doesn’t exist or the token cannot see it. - Resolve the canonical
owner/repofirst (search/list repos), then call list PRs with the correct pair.
- Errors like