feat(mcp): add Model Context Protocol integration

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
This commit is contained in:
yuchou87
2026-02-15 17:26:36 +08:00
parent 9a3f3611c3
commit 91c168db20
9 changed files with 1366 additions and 8 deletions
+43 -3
View File
@@ -22,6 +22,7 @@ import (
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/constants"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/mcp"
"github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/session"
"github.com/sipeed/picoclaw/pkg/state"
@@ -40,6 +41,7 @@ type AgentLoop struct {
state *state.Manager
contextBuilder *ContextBuilder
tools *tools.ToolRegistry
mcpManager *mcp.Manager // MCP server manager for resource cleanup
running atomic.Bool
summarizing sync.Map // Tracks which sessions are currently being summarized
}
@@ -58,7 +60,7 @@ type processOptions struct {
// createToolRegistry creates a tool registry with common tools.
// This is shared between main agent and subagents.
func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msgBus *bus.MessageBus) *tools.ToolRegistry {
func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msgBus *bus.MessageBus, mcpManager *mcp.Manager) *tools.ToolRegistry {
registry := tools.NewToolRegistry()
// File system tools
@@ -99,6 +101,23 @@ func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msg
})
registry.Register(messageTool)
// Register MCP tools from all connected servers
if mcpManager != nil {
servers := mcpManager.GetServers()
for serverName, conn := range servers {
for _, tool := range conn.Tools {
mcpTool := tools.NewMCPTool(mcpManager, serverName, tool)
registry.Register(mcpTool)
logger.DebugCF("agent", "Registered MCP tool",
map[string]interface{}{
"server": serverName,
"tool": tool.Name,
"name": mcpTool.Name(),
})
}
}
}
return registry
}
@@ -108,12 +127,22 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
restrict := cfg.Agents.Defaults.RestrictToWorkspace
// Initialize MCP Manager and load servers
mcpManager := mcp.NewManager()
ctx := context.Background()
if err := mcpManager.LoadFromConfig(ctx, cfg); err != nil {
logger.WarnCF("agent", "Failed to load MCP servers, MCP tools will not be available",
map[string]interface{}{
"error": err.Error(),
})
}
// Create tool registry for main agent
toolsRegistry := createToolRegistry(workspace, restrict, cfg, msgBus)
toolsRegistry := createToolRegistry(workspace, restrict, cfg, msgBus, mcpManager)
// Create subagent manager with its own tool registry
subagentManager := tools.NewSubagentManager(provider, cfg.Agents.Defaults.Model, workspace, msgBus)
subagentTools := createToolRegistry(workspace, restrict, cfg, msgBus)
subagentTools := createToolRegistry(workspace, restrict, cfg, msgBus, mcpManager)
// Subagent doesn't need spawn/subagent tools to avoid recursion
subagentManager.SetTools(subagentTools)
@@ -145,6 +174,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
state: stateManager,
contextBuilder: contextBuilder,
tools: toolsRegistry,
mcpManager: mcpManager,
summarizing: sync.Map{},
}
}
@@ -193,6 +223,16 @@ func (al *AgentLoop) Run(ctx context.Context) error {
func (al *AgentLoop) Stop() {
al.running.Store(false)
// Clean up MCP connections
if al.mcpManager != nil {
if err := al.mcpManager.Close(); err != nil {
logger.ErrorCF("agent", "Failed to close MCP manager",
map[string]interface{}{
"error": err.Error(),
})
}
}
}
func (al *AgentLoop) RegisterTool(tool tools.Tool) {