Conversation
Implement comprehensive MCP support with stdio/HTTP/SSE transports, environment variable configuration (env and envFile), custom headers, tool registration, and automatic resource cleanup. Includes full test coverage and VSCode-compatible configuration. - Added pkg/mcp/manager.go for server lifecycle management - Added pkg/tools/mcp_tool.go for tool wrapping - Integrated into agent loop with cleanup - Support for envFile loading (.env format) - Headers injection for HTTP/SSE authentication - Example configs for filesystem, github, brave-search, postgres
Add Dockerfile.full with Debian-based runtime including git, nodejs, npm, python3, and uv for MCP servers. Add docker-compose.full.yml with npm cache optimization. Add Makefile targets for docker-build-full, docker-run-full, and docker-test. Add test script for MCP tools validation.
Replace debian:bookworm-slim with node:24-bookworm-slim to: - Use latest Node.js 24 LTS and npm - Fix npm version compatibility issues (npm@11 requires node >=20.17) - Simplify Dockerfile by removing nodejs/npm installation - Better optimization from official Node.js image Changes: - Dockerfile.full: use node:24-bookworm-slim base - Remove nodejs, npm installation steps - Remove npm upgrade step (included in base image) - Update Makefile descriptions to reflect Node.js 24
…mode Add --entrypoint sh flag to docker-compose run commands in test script to bypass picoclaw agent's interactive mode. This allows direct command execution for testing MCP tools. Changes: - Add --entrypoint sh to all docker-compose run commands - Use SERVICE variable for better maintainability - Simplify command syntax: sh -c 'cmd' → -c 'cmd'
Replace docker-compose (v1) with docker compose (v2) command syntax across all files. Docker Compose v2 is now the default in modern Docker installations and uses 'docker compose' instead of 'docker-compose'. Changes: - scripts/test-docker-mcp.sh: update all 8 docker-compose commands - Makefile: update all 8 docker-compose commands in docker-* targets - No changes to file names (docker-compose.full.yml remains as-is) Compatibility: Requires Docker with Compose v2 plugin (Docker Desktop or docker-compose-plugin package)
Symlink uv from /root/.cargo/bin to /usr/local/bin to make it accessible without relying on ENV PATH setting. Add version check to verify successful installation during build. Changes: - Symlink uv to /usr/local/bin/uv - Add 'uv --version' validation step - Remove ENV PATH setting (no longer needed) Fixes: uv: not found error in test script
Fix uv symlink path from /root/.cargo/bin to /root/.local/bin. The uv installer puts binaries in ~/.local/bin, not ~/.cargo/bin. Changes: - Update uv symlink source: /root/.local/bin/uv - Add uvx symlink as well (installed alongside uv) Fixes: /bin/sh: 1: uv: not found error during build
Add --profile gateway --profile agent flags to docker build commands to ensure services are built even when using profiles in compose files. Without profiles specified, docker compose build skips all services that have a profile defined, resulting in 'No services to build' warning. Changes: - docker-build: add --profile flags - docker-build-full: add --profile flags Fixes: WARN[0000] No services to build
Replace --profile flags with explicit service names in build commands. The 'docker compose build' command does not support --profile flag; profiles are only used for runtime operations like 'up' and 'run'. Changes: - docker-build: specify picoclaw-agent picoclaw-gateway - docker-build-full: specify picoclaw-agent picoclaw-gateway Fixes: unknown flag: --profile error
Add blank line for better formatting consistency
Make scripts/test-docker-mcp.sh executable
There was a problem hiding this comment.
Pull request overview
This PR adds Model Context Protocol (MCP) support to picoclaw by introducing an MCP connection manager, wrapping MCP tools into the existing tool system, and providing “full” Docker tooling/runtime artifacts to run MCP servers (e.g., via npx) in containers.
Changes:
- Added MCP server manager (
pkg/mcp) with stdio and SSE/HTTP transports, env/envFile support, and tool discovery. - Added MCP tool wrapper (
pkg/tools) and integrated MCP tool registration + cleanup into the agent loop. - Added Docker “full” runtime support (Dockerfile + compose + test script) and updated config examples / Makefile targets.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/test-docker-mcp.sh | Adds a Docker Compose-based validation script for MCP runtime tools (npx/npm/node/git/python/uv). |
| pkg/tools/mcp_tool.go | Introduces MCPTool wrapper implementing the project Tool interface. |
| pkg/tools/mcp_tool_test.go | Adds unit/integration-style tests for MCPTool behavior and content extraction. |
| pkg/mcp/manager.go | Adds MCP Manager to connect to servers, list tools, call tools, and close connections. |
| pkg/mcp/manager_test.go | Adds tests for .env parsing and envFile/config merge priority. |
| pkg/agent/loop.go | Initializes MCP manager, registers MCP tools, and attempts cleanup on Stop(). |
| pkg/config/config.go | Extends config schema with tools.mcp and server definitions. |
| config/config.example.json | Adds MCP configuration examples (including envFile and headers). |
| Dockerfile.full | Adds a Node-based runtime image with additional tooling for MCP servers. |
| docker-compose.full.yml | Adds a “full” compose setup for agent and gateway using Dockerfile.full. |
| Makefile | Adds docker build/run/test/clean targets (minimal + full). |
| go.mod | Adds MCP SDK dependency and an extra indirect require line. |
| go.sum | Adds checksums for newly introduced dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add errors.Join to return aggregated error when all enabled MCP servers fail - Track enabled server count separately from total configured servers - Return error only when all servers fail, not for partial failures - Improve logging with accurate server counts (enabled vs connected) - Maintains fault tolerance: partial failures don't stop initialization
- Resolve relative envFile paths relative to workspace instead of CWD - Add filepath import for path operations - Pass workspace path to goroutines for path resolution - Improves portability in Docker environments where CWD may vary - Absolute envFile paths continue to work as before
- Move standalone indirect require line into existing require block - Maintain alphabetical ordering of dependencies - Keep module file stable and avoid churn
- Change NewMCPTool to accept MCPManager interface instead of concrete *mcp.Manager - Remove unused mcpPkg import from mcp_tool.go - Remove newMCPToolForTest helper function as NewMCPTool now accepts interface - Update all tests to use NewMCPTool directly with MockMCPManager - Improves testability and follows dependency inversion principle
- Handle json.RawMessage and []byte types by direct unmarshal
- Use JSON marshal/unmarshal for struct types to preserve schema
- Add test case for json.RawMessage schema
- Fixes issue where non-map schemas returned empty object
This fixes GitHub Copilot feedback that Parameters() was dropping
tool schema when InputSchema wasn't already map[string]interface{}
- Defer MCP server initialization to Run() using agent's context - Add mcpConfig and mcpInitOnce fields to AgentLoop - Use sync.Once to ensure MCP loads exactly once with proper context - Prevents orphaned subprocesses and resource leaks on cancellation This fixes GitHub Copilot feedback that MCP connections with context.Background() won't terminate when the agent stops, causing potential resource leaks and orphaned stdio/SSE connections.
- Add defer in Run() to guarantee MCP connection cleanup - Handles both normal termination and context cancellation - Prevents resource leaks when Run() exits via ctx.Done() - MCP Manager.Close() is idempotent, safe to call from both defer and Stop() This fixes GitHub Copilot feedback that MCP cleanup only happened in Stop() but Run() could return on ctx.Done() without cleanup, causing subprocess/session leaks on normal cancellation shutdown.
Critical bug fix: - MCP tools were never registered because servers loaded in Run() but tool registration happened in NewAgentLoop() with empty manager - Move MCP tool registration from createToolRegistry to Run() - Register MCP tools for both main agent and subag after successful server loading - Add subagentManager field to AgentLoop for dynamic registration - Add tool_count logging for better observability This ensures MCP tools are properly available to both agent and subagents.
|
I reviewed this PR from the perspective of PicoClaw's resource optimization goals (RAM usage > binary size). The MCP integration itself is a valuable feature, and the overall architecture is solid lazy initialization via sync.Once, parallel server startup with sync.WaitGroup, and proper cleanup in both defer and
Each MCP tool discovered from a server is instantiated twice -- once for the main agent and once for the subagent: mcpTool := tools.NewMCPTool(al.mcpManager, serverName, tool)
al.tools.Register(mcpTool)
if al.subagentManager != nil {
al.subagentManager.RegisterTool(tools.NewMCPTool(al.mcpManager, serverName, tool))
}While the underlying MCPManager is shared (good), each MCPTool wrapper struct is allocated separately. For a setup with, say, 4 MCP servers exposing 10 tools each, that is 80 struct allocations instead of 40. Each wrapper carries a pointer to the manager, the server name string, and the full *mcp.Tool reference (which includes the input schema). This compounds an existing architectural issue in PicoClaw where Recommendation: consider sharing a single read-only tool registry between the agent and subagent, or at minimum use a ToolRegistryView wrapper that filters out spawn/subagent tools instead of cloning the entire registry. If that is out of scope for this PR, it should at least be documented as tracked tech debt.
The PR adds a new field to mcpConfig *config.ConfigThis stores a reference to the entire application config struct for lazy initialization in Since the only usage is al.mcpManager.LoadFromConfig(ctx, al.mcpConfig), and LoadFromConfig only reads cfg.Tools.MCP and cfg.WorkspacePath(), consider storing just the MCP-specific config: mcpConfig config.MCPConfig
workspacePath stringThis makes the dependency explicit and allows the rest of the config to be GC'd if nothing else holds it.
DefaultConfig() now ships with four pre-populated MCP server entries (filesystem, github, brave-search, postgres), all disabled: MCP: MCPConfig{
Enabled: false,
Servers: map[string]MCPServerConfig{
"filesystem": { Enabled: false, Command: "npx", Args: [...] },
"github": { Enabled: false, Command: "npx", Args: [...], Env: {...} },
"brave-search": { Enabled: false, Command: "npx", Args: [...], Env: {...} },
"postgres": { Enabled: false, Command: "npx", Args: [...] },
},
},Every PicoClaw instance allocates this map, its string keys, the MCPServerConfig structs, and their nested slices/maps, even if MCP is never used. The cost per instance is small (a few hundred bytes), but it is unnecessary since these examples already exist in
The new Dockerfile.full switches from alpine:3.23 (the base for the existing Dockerfile) to node:24-bookworm-slim and installs Python 3, pip, venv, git, curl, and uv. This is understandable since many MCP servers are Node.js or Python processes, but it transforms PicoClaw's Docker footprint from roughly 15-20MB (Alpine + static Go binary) to an estimated 800MB+.
When MCP is enabled and servers are connected, every MCP tool's schema is serialized and sent to the LLM on every chat request. For a setup with many MCP servers, this could add significant token overhead. This is inherent to how tool-use works with LLMs, but it is worth considering: A configurable limit on the maximum number of MCP tools exposed to the LLM at once. |
|
@Zepan This PR directly addresses roadmap issue #290 (MCP Support — priority: high). MCP is one of the most requested features and is explicitly on the project board. +1693 lines is substantial but MCP requires protocol implementation, transport layer, and tool registration. Worth a careful review given the scope. Recommendation: Review and merge. This is a high-priority roadmap item. Consider whether this implementation aligns with the architecture vision before merging. |
Replace full *config.Config reference with config.MCPConfig value type in AgentLoop to allow garbage collection of unused configuration data. Changes: - AgentLoop now stores only MCPConfig and workspacePath (minimal deps) - Add mcp.Manager.LoadFromMCPConfig() for minimal dependency version - Keep LoadFromConfig() for backward compatibility - Full Config object can be GC'd after NewAgentLoop() returns This optimization reduces memory usage by not holding references to unused channel, provider, gateway, and device configurations.
Remove pre-populated example servers (filesystem, github, brave-search, postgres) from DefaultConfig() to reduce memory footprint per instance. Changes: - Set MCP.Servers to empty map instead of 4 example servers - Reduces default config size by ~500 bytes per instance - Users should add MCP servers via config.json or documentation Example configurations are still available in: - README.md MCP section - config.example.json - Official MCP documentation This optimization benefits deployments with many agent instances.
Replace node:24-bookworm-slim with node:24-alpine3.23 to reduce image size and improve build efficiency. Changes: - Base image: node:24-bookworm-slim → node:24-alpine3.23 - Package manager: apt-get → apk - Package names: python3-pip → py3-pip - Remove python3-venv (included in Alpine Python3) - Use apk --no-cache for cleaner image layers Expected benefits: - Reduce base image size by ~100-200MB (30-40% reduction) - Faster image pulls and container startup - Full MCP support maintained (Node.js, Python, uv) Estimated final image size: ~600-700MB (vs ~800MB before)
Add Model Context Protocol (MCP) Integration with Docker Support
Overview
This PR introduces comprehensive Model Context Protocol (MCP) integration to picoclaw, enabling dynamic tool registration from external MCP servers. Additionally, it provides production-ready Docker deployment options with full MCP runtime support.
Key Features
MCP Integration
github.com/modelcontextprotocol/go-sdk v1.3.0mcp_{server}_{tool}naming convention.envfile loading with proper priority handlingDocker Support
docker-compose.yml(minimal) anddocker-compose.full.yml(full-featured)Configuration Enhancements
tools.mcpsection to configuration schemaconfig.example.jsontools.mcp.enabledTechnical Changes
Core Implementation
Docker Infrastructure
Testing
Statistics
Bug Fixes
/usr/local/bindocker composeinstead ofdocker-compose)Testing
Usage Examples
Build and Run with Docker
Configuration Example
Add MCP servers to your
config.json:{ "tools": { "mcp": { "enabled": true, "servers": { "filesystem": { "enabled": true, "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"], "env": {} }, "brave-search": { "enabled": true, "command": "npx", "args": ["-y", "@modelcontextprotocol/server-brave-search"], "env": { "BRAVE_API_KEY": "YOUR_BRAVE_API_KEY" }, "envFile": ".env" } } } } }Breaking Changes
None. This is a purely additive feature that maintains full backward compatibility.