sketch: add MCP support
Lets you initialize extra tools via MCP. This is additive, so it's
harmless enough.
Here are some examples of the kind of things you can pass to the -mcp
flag.
{"name": "context7", "type": "http", "url": "https://mcp.context7.com/mcp"}
{"name": "context7-http", "type": "http", "url": "https://mcp.context7.com/mcp"}
{"name": "context7-stdio", "type": "stdio", "command": "npx", "args": ["-y", "@upstash/context7-mcp"]}
{"name": "context7-sse", "type": "sse", "url": "https://mcp.context7.com/sse"}
{"name": "local-tool", "type": "stdio", "command": "my_tool", "args": ["--option", "value"], "env": {"TOKEN": "secret"}}
{ "name": "playwright", "command": "npx", "args": [ "@playwright/mcp@latest" ]}
Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s259a35d11e7bd660k
diff --git a/loop/agent.go b/loop/agent.go
index 5c36bcf..ee6d8d7 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -28,6 +28,7 @@
"sketch.dev/llm"
"sketch.dev/llm/ant"
"sketch.dev/llm/conversation"
+ "sketch.dev/mcp"
"sketch.dev/skabandclient"
)
@@ -425,6 +426,8 @@
outsideWorkingDir string
// URL of the git remote 'origin' if it exists
gitOrigin string
+ // MCP manager for handling MCP server connections
+ mcpManager *mcp.MCPManager
// Time when the current turn started (reset at the beginning of InnerLoop)
startOfTurn time.Time
@@ -1028,6 +1031,8 @@
SSHConnectionString string
// Skaband client for session history (optional)
SkabandClient *skabandclient.SkabandClient
+ // MCP server configurations
+ MCPServers []string
}
// NewAgent creates a new Agent.
@@ -1059,6 +1064,7 @@
workingDir: config.WorkingDir,
outsideHTTP: config.OutsideHTTP,
portMonitor: NewPortMonitor(),
+ mcpManager: mcp.NewMCPManager(),
}
return agent
}
@@ -1273,6 +1279,37 @@
convo.Tools = append(convo.Tools, sessionHistoryTools...)
}
+ // Add MCP tools if configured
+ if len(a.config.MCPServers) > 0 {
+ slog.InfoContext(ctx, "Initializing MCP connections", "servers", len(a.config.MCPServers))
+ mcpConnections, mcpErrors := a.mcpManager.ConnectToServers(ctx, a.config.MCPServers, 10*time.Second)
+
+ if len(mcpErrors) > 0 {
+ for _, err := range mcpErrors {
+ slog.ErrorContext(ctx, "MCP connection error", "error", err)
+ // Send agent message about MCP connection failures
+ a.pushToOutbox(ctx, AgentMessage{
+ Type: ErrorMessageType,
+ Content: fmt.Sprintf("MCP server connection failed: %v", err),
+ })
+ }
+ }
+
+ if len(mcpConnections) > 0 {
+ // Add tools from all successful connections
+ totalTools := 0
+ for _, connection := range mcpConnections {
+ convo.Tools = append(convo.Tools, connection.Tools...)
+ totalTools += len(connection.Tools)
+ // Log tools per server using structured data
+ slog.InfoContext(ctx, "Added MCP tools from server", "server", connection.ServerName, "count", len(connection.Tools), "tools", connection.ToolNames)
+ }
+ slog.InfoContext(ctx, "Total MCP tools added", "count", totalTools)
+ } else {
+ slog.InfoContext(ctx, "No MCP tools available after connection attempts")
+ }
+ }
+
convo.Listener = a
return convo
}
@@ -1448,6 +1485,13 @@
a.portMonitor.Start(ctxOuter)
}
+ // Set up cleanup when context is done
+ defer func() {
+ if a.mcpManager != nil {
+ a.mcpManager.Close()
+ }
+ }()
+
for {
select {
case <-ctxOuter.Done():