From 4b808f9a30abdc38352c699fcc5987b0376e9b9e Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Tue, 31 Mar 2026 12:53:34 -0400 Subject: [PATCH] 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 --- .gitignore | 2 + DOCKER_MULTI_BOT_GUIDE.md | 10 +++ docs/mcp_local_clone_and_tool_profiles.md | 95 +++++++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 docs/mcp_local_clone_and_tool_profiles.md diff --git a/.gitignore b/.gitignore index f6c033e..401eab7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ dist/ build/ docs/ +!docs/*.md +!docs/**/*.md *.egg-info/ *.egg *.pyc diff --git a/DOCKER_MULTI_BOT_GUIDE.md b/DOCKER_MULTI_BOT_GUIDE.md index 7fa4bfc..ee131d8 100644 --- a/DOCKER_MULTI_BOT_GUIDE.md +++ b/DOCKER_MULTI_BOT_GUIDE.md @@ -95,6 +95,16 @@ nanobot/ - Environment variables (from Docker env files) - Config file: `/root/.nanobot/config.json` (mounted from host) +### MCP servers + tool profiles (local LLM note) + +If you’re using a local LLM provider with a **low tool limit** (often ~20 tools), MCP servers (which can register 30+ tools) can exceed the limit unless you use tool profiles carefully. + +See `docs/mcp_local_clone_and_tool_profiles.md` for: +- Local-clone MCP layout (`./mcp-servers` → `/app/mcp-servers`) +- How MCP env vars like `$NANOBOT_GITLE_TOKEN` are expanded into server env +- Tool-call JSON protocol for local providers +- Profile routing behavior and when MCP servers disconnect + --- ## Setup Instructions diff --git a/docs/mcp_local_clone_and_tool_profiles.md b/docs/mcp_local_clone_and_tool_profiles.md new file mode 100644 index 0000000..66f5ed6 --- /dev/null +++ b/docs/mcp_local_clone_and_tool_profiles.md @@ -0,0 +1,95 @@ +# 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 don’t 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 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 `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 doesn’t exist or the token cannot see it. + - Resolve the canonical `owner/repo` first (search/list repos), then call list PRs with the correct pair. +