vet/agent/mcp.go
Oleksandr Redko 4e39cebe61
chore: add formatters to golangci-lint config (#643)
Signed-off-by: Oleksandr Redko <oleksandr.red+github@gmail.com>
2025-11-27 14:58:24 +05:30

146 lines
4.1 KiB
Go

package agent
import (
"context"
"fmt"
"os"
"path/filepath"
einomcp "github.com/cloudwego/eino-ext/components/tool/mcp"
"github.com/cloudwego/eino/components/tool"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
type McpClientToolBuilderConfig struct {
// Common config
ClientName string
ClientVersion string
// SSE client config
SseURL string
Headers map[string]string
// Stdout client config
SkipDefaultTools bool
SQLQueryToolEnabled bool
SQLQueryToolDBPath string
PackageRegistryToolEnabled bool
// Enable debug mode for the MCP client.
Debug bool
}
type mcpClientToolBuilder struct {
config McpClientToolBuilderConfig
}
var _ ToolBuilder = (*mcpClientToolBuilder)(nil)
// NewMcpClientToolBuilder creates a new MCP client tool builder for `vet` MCP server.
// This basically connects to vet MCP server over SSE or executes the `vet server mcp` command
// to start a MCP server in stdio mode. We maintain loose coupling between the MCP client and the MCP server
// by allowing the client to be configured with a set of flags to enable/disable specific tools. We do this
// to ensure vet MCP contract is not violated and evolves independently. vet Agents will in turn depend on
// vet MCP server for data access.
func NewMcpClientToolBuilder(config McpClientToolBuilderConfig) (*mcpClientToolBuilder, error) {
return &mcpClientToolBuilder{
config: config,
}, nil
}
func (b *mcpClientToolBuilder) Build(ctx context.Context) ([]tool.BaseTool, error) {
var cli *client.Client
var err error
if b.config.SseURL != "" {
cli, err = b.buildSseClient()
if err != nil {
return nil, fmt.Errorf("failed to create sse client: %w", err)
}
} else {
cli, err = b.buildStdioClient()
if err != nil {
return nil, fmt.Errorf("failed to create stdio client: %w", err)
}
}
err = cli.Start(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start mcp client: %w", err)
}
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: b.config.ClientName,
Version: b.config.ClientVersion,
}
_, err = cli.Initialize(ctx, initRequest)
if err != nil {
return nil, fmt.Errorf("failed to initialize mcp client: %w", err)
}
tools, err := einomcp.GetTools(ctx, &einomcp.Config{
Cli: cli,
})
if err != nil {
return nil, fmt.Errorf("failed to get tools: %w", err)
}
return tools, nil
}
func (b *mcpClientToolBuilder) buildSseClient() (*client.Client, error) {
cli, err := client.NewSSEMCPClient(b.config.SseURL, client.WithHeaders(b.config.Headers))
if err != nil {
return nil, fmt.Errorf("failed to create sse client: %w", err)
}
return cli, nil
}
// buildStdioClient is used to start vet mcp server with arguments
// based on the configuration.
func (b *mcpClientToolBuilder) buildStdioClient() (*client.Client, error) {
binaryPath, err := os.Executable()
if err != nil {
return nil, fmt.Errorf("failed to get running binary path: %w", err)
}
// vet-mcp server defaults to stdio transport. See cmd/server/mcp.go
vetMcpServerCommandArgs := []string{"server", "mcp"}
if b.config.Debug {
vetMcpServerLogFile := filepath.Join(os.TempDir(), "vet-mcp-server.log")
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "-l", vetMcpServerLogFile)
}
if b.config.SQLQueryToolEnabled {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--sql-query-tool")
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--sql-query-tool-db-path",
b.config.SQLQueryToolDBPath)
}
if b.config.PackageRegistryToolEnabled {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--package-registry-tool")
}
if b.config.SkipDefaultTools {
vetMcpServerCommandArgs = append(vetMcpServerCommandArgs, "--skip-default-tools")
}
environmentVariables := []string{}
if b.config.Debug {
environmentVariables = append(environmentVariables, "APP_LOG_LEVEL=debug")
}
cli, err := client.NewStdioMCPClient(binaryPath, environmentVariables, vetMcpServerCommandArgs...)
if err != nil {
return nil, fmt.Errorf("failed to create stdio client: %w", err)
}
return cli, nil
}