Files
Sidekick/mcp.go
T

142 lines
3.9 KiB
Go
Raw Normal View History

2026-03-21 16:20:16 +02:00
package sidekick
import (
"context"
"fmt"
"github.com/mark3labs/mcp-go/client"
2026-03-21 16:29:28 +02:00
"github.com/mark3labs/mcp-go/client/transport"
2026-03-21 16:20:16 +02:00
"github.com/mark3labs/mcp-go/mcp"
)
// MCPClientInterface represents a scoped connection to an MCP server
type MCPClientInterface interface {
CallTool(ctx context.Context, name string, args map[string]interface{}) (string, error)
Close() error
}
type mcpGoClientWrapper struct {
client *client.Client
}
func (w *mcpGoClientWrapper) CallTool(ctx context.Context, name string, args map[string]interface{}) (string, error) {
req := mcp.CallToolRequest{}
req.Params.Name = name
req.Params.Arguments = args
2026-03-21 16:29:28 +02:00
2026-03-21 16:20:16 +02:00
res, err := w.client.CallTool(ctx, req)
if err != nil {
return "", err
}
2026-03-21 16:29:28 +02:00
2026-03-21 16:20:16 +02:00
if res.IsError {
return "", fmt.Errorf("tool returned error")
}
// Extract text from result content
var output string
for _, c := range res.Content {
if textContent, ok := c.(mcp.TextContent); ok {
output += textContent.Text + "\n"
} else {
output += fmt.Sprintf("%v\n", c)
}
}
return output, nil
}
func (w *mcpGoClientWrapper) Close() error {
// Client does not expose a close method directly in all transports, or we close transport
return nil
}
// Global server configurations
var mcpServers map[string]MCPServerConfig
// InitMCPServers sets up the global registry for MCP factories
func InitMCPServers(servers map[string]MCPServerConfig) {
mcpServers = servers
}
// defaultMCPFactory creates a real MCP client based on the global configuration
var defaultMCPFactory MCPClientFactory = func(toolset string) (MCPClientInterface, error) {
cfg, ok := mcpServers[toolset]
if !ok {
return nil, fmt.Errorf("unknown MCP server: %s", toolset)
}
var c *client.Client
var err error
switch cfg.Transport {
case "stdio":
c, err = client.NewStdioMCPClient(cfg.Command, cfg.Env, cfg.Args...)
case "sse":
2026-03-21 16:29:28 +02:00
var opts []transport.ClientOption
headers := make(map[string]string)
if cfg.AuthToken != "" {
headers["Authorization"] = "Bearer " + cfg.AuthToken
}
for k, v := range cfg.Headers {
headers[k] = v
}
if len(headers) > 0 {
opts = append(opts, transport.WithHeaders(headers))
}
c, err = client.NewSSEMCPClient(cfg.URL, opts...)
2026-03-21 16:20:16 +02:00
case "http":
2026-03-21 16:29:28 +02:00
var opts []transport.StreamableHTTPCOption
headers := make(map[string]string)
if cfg.AuthToken != "" {
headers["Authorization"] = "Bearer " + cfg.AuthToken
}
for k, v := range cfg.Headers {
headers[k] = v
}
if len(headers) > 0 {
opts = append(opts, transport.WithHTTPHeaders(headers))
}
c, err = client.NewStreamableHttpClient(cfg.URL, opts...)
2026-03-21 16:20:16 +02:00
default:
return nil, fmt.Errorf("unsupported MCP transport: %s", cfg.Transport)
}
if err != nil {
return nil, err
}
// Initialize the client session
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "Sidekick",
Version: "1.0.0",
}
2026-03-21 16:29:28 +02:00
2026-03-21 16:20:16 +02:00
_, err = c.Initialize(context.Background(), initReq)
if err != nil {
return nil, fmt.Errorf("failed to initialize MCP client: %w", err)
}
return &mcpGoClientWrapper{client: c}, nil
}
// MCPClientFactory is a function that creates a new MCP client for a specific toolset
type MCPClientFactory func(toolset string) (MCPClientInterface, error)
// SetMCPClientFactory allows overriding the factory for testing
func SetMCPClientFactory(factory MCPClientFactory) {
defaultMCPFactory = factory
}
// CallExternalTool creates a scoped MCP connection, calls the tool, and cleans up
func CallExternalTool(ctx context.Context, toolset string, toolName string, args map[string]interface{}) (string, error) {
c, err := defaultMCPFactory(toolset)
if err != nil {
return "", fmt.Errorf("failed to create MCP client for %s: %w", toolset, err)
}
defer c.Close()
return c.CallTool(ctx, toolName, args)
}