Documentation
¶
Index ¶
- Variables
- func Execute() error
- func ExtractFlagName(errStr string) string
- func FindSimilarCommands(commandName string, cmd *cobra.Command) []string
- func FindSimilarFlags(flagName string, cmd *cobra.Command) []string
- func ProcessOptions(configFile, serverFlag string, disableFilesystem bool, provider string, ...) ([]string, map[string]bool)
- type CLIError
- func NewInvalidValueError(flagName string, value string, validValues []string, commandName string) *CLIError
- func NewMissingArgumentError(argumentName string, commandName string, examples []string) *CLIError
- func NewUnknownCommandError(commandName string, parentCmd *cobra.Command) *CLIError
- func NewUnknownFlagError(flagName string, cmd *cobra.Command) *CLIError
- type HostServerAdapter
- func (hsa *HostServerAdapter) ExecuteTool(ctx context.Context, toolName string, arguments map[string]interface{}) (string, error)
- func (hsa *HostServerAdapter) GetConfig() *config.ServerConfig
- func (hsa *HostServerAdapter) GetServerName() string
- func (hsa *HostServerAdapter) GetTools() ([]domain.Tool, error)
- func (hsa *HostServerAdapter) IsRunning() bool
- func (hsa *HostServerAdapter) Start(ctx context.Context) error
- func (hsa *HostServerAdapter) Stop() error
- type HostServerManager
- func (hsm *HostServerManager) ExecuteTool(ctx context.Context, toolName string, arguments map[string]interface{}) (string, error)
- func (hsm *HostServerManager) GetAvailableTools() ([]domain.Tool, error)
- func (hsm *HostServerManager) GetServer(serverName string) (domain.MCPServer, bool)
- func (hsm *HostServerManager) ListServers() map[string]domain.MCPServer
- func (hsm *HostServerManager) StartServer(ctx context.Context, serverName string, cfg *config.ServerConfig) (domain.MCPServer, error)
- func (hsm *HostServerManager) StopAll() error
- func (hsm *HostServerManager) StopServer(serverName string) error
- type InitConfig
Constants ¶
This section is empty.
Variables ¶
var ( Version = "dev" BuildTime = "unknown" GitCommit = "unknown" )
Version information - set from main package
var ChatCmd = &cobra.Command{ Use: "chat", Short: "Enter interactive chat mode with the LLM", Long: `Chat mode provides a conversational interface with the LLM and is the primary way to interact with the client. The LLM can execute queries, access data, and leverage other capabilities provided by the server. This command uses the modern interface-based approach for LLM providers, supporting all configured provider types including OpenAI, Anthropic, Ollama, and others.`, RunE: func(cmd *cobra.Command, args []string) error { chatConfig := parseChatConfig(cmd, args) outputMgr := output.GetGlobalManager() if outputMgr.ShouldShowStartupInfo() { bold := color.New(color.Bold) serversText := strings.Join(chatConfig.ServerNames, ", ") if serversText == "" { serversText = "none" } bold.Printf("Starting chat mode with servers: %s, provider: %s, model: %s\n\n", serversText, chatConfig.ProviderName, chatConfig.ModelName) } chatService := chat.NewService() return chatService.StartChat(chatConfig) }, }
ChatCmd represents the unified chat command
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "Configuration management commands",
Long: `Manage mcp-cli configuration files.
Available subcommands:
validate - Validate configuration file and check for security issues
Examples:
mcp-cli config validate
mcp-cli config validate --config custom-config.yaml`,
}
ConfigCmd represents the config command
var ConfigValidateCmd = &cobra.Command{ Use: "validate", Short: "Validate configuration file", Long: `Validates the configuration file for: - Syntax errors - Missing required fields - Exposed API keys (security check) - Template validation Examples: mcp-cli config validate mcp-cli config validate --config custom-config.json`, RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Validating configuration...") configService := config.NewService() appConfig, err := configService.LoadConfig(configFile) if err != nil { fmt.Printf("❌ Failed to load config: %v\n", err) return err } if err := configService.ValidateConfig(appConfig); err != nil { fmt.Printf("❌ Configuration validation failed: %v\n", err) return err } fmt.Println("✓ Configuration syntax is valid") hasExposedKeys := false if appConfig.AI != nil && appConfig.AI.Interfaces != nil { for interfaceType, interfaceConfig := range appConfig.AI.Interfaces { for providerName, providerConfig := range interfaceConfig.Providers { if isExposedKey(providerConfig.APIKey) { fmt.Printf("⚠️ Warning: API key for %s/%s appears to be hardcoded\n", interfaceType, providerName) fmt.Println(" Consider moving to .env file: " + providerName + "_API_KEY") hasExposedKeys = true } } } } if appConfig.Embeddings != nil && appConfig.Embeddings.Interfaces != nil { for interfaceType, interfaceConfig := range appConfig.Embeddings.Interfaces { for providerName, providerConfig := range interfaceConfig.Providers { if isExposedKey(providerConfig.APIKey) { fmt.Printf("⚠️ Warning: Embedding API key for %s/%s appears to be hardcoded\n", interfaceType, providerName) fmt.Println(" Consider moving to .env file") hasExposedKeys = true } } } } if hasExposedKeys { fmt.Println("\n💡 Security Tip:") fmt.Println(" 1. Create a .env file: cp .env.example .env") fmt.Println(" 2. Add your keys: OPENAI_API_KEY=sk-...") fmt.Println(" 3. Update config: \"api_key\": \"${OPENAI_API_KEY}\"") fmt.Println(" 4. Add .env to .gitignore (already done)") } else { fmt.Println("✓ No exposed API keys found") } envPath := ".env" if _, err := os.Stat(envPath); os.IsNotExist(err) { fmt.Println("\n💡 Tip: Create a .env file for API keys") fmt.Println(" cp .env.example .env") } else { fmt.Println("✓ .env file found") } fmt.Println("\n✅ Configuration is valid!") if hasExposedKeys { fmt.Println("\n⚠️ However, you should move hardcoded API keys to .env file for security") os.Exit(1) } return nil }, }
ConfigValidateCmd validates the configuration file
var EmbeddingsCmd = &cobra.Command{
Use: "embeddings [text]",
Short: "Generate vector embeddings from text input",
Long: `Generate vector embeddings from text input using various embedding models.
Supports multiple input sources:
- Direct text argument
- Standard input (stdin)
- File input
Text is automatically chunked using configurable strategies to handle
large inputs and optimize embedding quality.
Examples:
# Basic usage with stdin
echo "Your text here" | mcp-cli embeddings
# File input with specific model
mcp-cli embeddings --input-file document.txt --model text-embedding-3-large
# Advanced chunking and output
mcp-cli embeddings --chunk-strategy sentence --max-chunk-size 512 --output-format json --overlap 50
# Direct text input
mcp-cli embeddings "Analyze this specific text"
# Show available models and strategies
mcp-cli embeddings --show-models
mcp-cli embeddings --show-strategies`,
RunE: executeEmbeddings,
}
EmbeddingsCmd represents the embeddings command
var InitCmd = &cobra.Command{
Use: "init",
Short: "Initialize mcp-cli configuration",
Long: `Interactive setup wizard for mcp-cli configuration.
Creates a modular configuration structure with separate directories for
providers, embeddings, servers, and workflows.
Modes:
--quick Quick setup with minimal questions (uses ollama, no API keys)
--full Full setup with all configuration options
(default) Standard interactive setup
Examples:
mcp-cli init # Interactive setup
mcp-cli init --quick # Quick setup (ollama only)
mcp-cli init --full # Complete setup wizard`,
RunE: runInit,
}
InitCmd initializes a new mcp-cli configuration
var InteractiveCmd = &cobra.Command{ Use: "interactive", Short: "Enter interactive mode with slash commands", Long: `Interactive mode provides a command-line interface with slash commands for direct interaction with the server. You can query server information, list available tools and resources, and more.`, RunE: func(cmd *cobra.Command, args []string) error { serverNames, userSpecified := host.ProcessOptions(configFile, serverName, disableFilesystem, providerName, modelName) bold := color.New(color.Bold) bold.Printf("Starting interactive mode with server: %s, provider: %s, model: %s\n\n", serverName, providerName, modelName) logging.Info("Starting interactive mode") err := host.RunCommand(runInteractiveMode, configFile, serverNames, userSpecified) if err != nil { logging.Error("Error in interactive mode: %v", err) fmt.Fprintf(os.Stderr, "Error in interactive mode: %v\n", err) return err } return nil }, }
InteractiveCmd represents the interactive command
var QueryCmd = &cobra.Command{ Use: "query [question] or --input-data \"question\"", Short: "Ask a single question and get a response", Long: `Query mode asks a single question to the AI model and returns a response without entering an interactive session. Perfect for scripting, automation, and integration with other tools. The question can be provided either as: • Positional argument: query "question" • --input-data flag: query --input-data "question" • stdin: echo "question" | query --input-data - The query command supports: • Multiple MCP servers for tool access • Context from files (--context) • Custom system prompts (--system-prompt) • JSON output for parsing (--json) • Raw tool data output (--raw-data) • File output (--output) Examples: # Basic query mcp-cli query "What is the current time?" # With specific servers and provider mcp-cli query --server filesystem,brave-search \ --provider openai --model gpt-4o \ "Search for MCP information and summarize" # With context file mcp-cli query --context context.txt \ --system-prompt "You are a coding assistant" \ "How do I implement a binary tree in Go?" # JSON output for parsing mcp-cli query --json "List the top 5 cloud providers" > results.json # Verbose mode (show all operations) mcp-cli query --noisy "What files are in this directory?" # Raw tool data (bypass AI summarization) mcp-cli query --raw-data "Show latest security incidents" # Output to file mcp-cli query "Analyze this code" --output analysis.txt # Using --input-data flag instead of positional argument mcp-cli query --input-data "What is the weather today?" # Both work the same way mcp-cli query "question" --provider anthropic mcp-cli query --provider anthropic --input-data "question"`, RunE: func(cmd *cobra.Command, args []string) error { redirectStdinIfNotTerminal() if noisy && !verbose { logging.SetDefaultLevel(logging.INFO) logging.Info("Noisy mode enabled for query command") } if maxTokens != 0 && maxTokens < 1 { if errorCodeOnly { os.Exit(query.ErrInvalidArgumentCode) } return fmt.Errorf("--max-tokens must be positive, got %d", maxTokens) } if contextFile != "" { if _, err := os.Stat(contextFile); os.IsNotExist(err) { if errorCodeOnly { os.Exit(query.ErrContextNotFoundCode) } return fmt.Errorf("context file does not exist: %s", contextFile) } } if outputFile != "" { outputDir := outputFile if idx := strings.LastIndex(outputFile, "/"); idx != -1 { outputDir = outputFile[:idx] } else if idx := strings.LastIndex(outputFile, "\\"); idx != -1 { outputDir = outputFile[:idx] } else { outputDir = "." } if stat, err := os.Stat(outputDir); err != nil { if errorCodeOnly { os.Exit(query.ErrOutputWriteCode) } return fmt.Errorf("output directory does not exist: %s", outputDir) } else if !stat.IsDir() { if errorCodeOnly { os.Exit(query.ErrOutputWriteCode) } return fmt.Errorf("output path is not a directory: %s", outputDir) } } // Get question from either positional args, query-specific --input-data, or root --input-data flag var question string if len(args) > 0 { question = strings.Join(args, " ") } else if queryInputData != "" { question = queryInputData } else if inputData != "" { question = inputData } else { cliErr := NewMissingArgumentError("question", "query", []string{ `mcp-cli query "What is the capital of France?"`, `mcp-cli query --input-data "What is the capital of France?"`, `echo "What is the capital of France?" | mcp-cli query --input-data -`, }) fmt.Fprintln(os.Stderr, cliErr.Format()) if errorCodeOnly { os.Exit(query.ErrInvalidArgumentCode) } os.Exit(1) } serverNames, userSpecified := ProcessOptions(configFile, serverName, disableFilesystem, providerName, modelName) logging.Debug("Server names: %v", serverNames) logging.Debug("Using provider from config: %s", providerName) externalServers, needsSkills := infraSkills.SeparateSkillsFromServers(serverNames) logging.Debug("External servers: %v, needs built-in skills: %v", externalServers, needsSkills) externalUserSpecified := make(map[string]bool) for _, server := range externalServers { if userSpecified[server] { externalUserSpecified[server] = true } } enhancedAIOptions, err := host.GetEnhancedAIOptions(configFile, providerName, modelName) if err != nil { if errorCodeOnly { os.Exit(query.ErrConfigNotFoundCode) } return fmt.Errorf("error loading enhanced AI options: %w", err) } aiOptions := &host.AIOptions{ Provider: enhancedAIOptions.Provider, Model: enhancedAIOptions.Model, APIKey: enhancedAIOptions.APIKey, APIEndpoint: enhancedAIOptions.APIEndpoint, InterfaceType: enhancedAIOptions.Interface, } if providerName != "" { aiOptions.Provider = providerName enhancedAIOptions.Provider = providerName } if modelName != "" { aiOptions.Model = modelName enhancedAIOptions.Model = modelName } if aiOptions.APIKey == "" { logging.Debug("No API key configured for provider %s (may not be required)", aiOptions.Provider) } // Load context file if provided var contextContent string if contextFile != "" { content, err := os.ReadFile(contextFile) if err != nil { if errorCodeOnly { os.Exit(query.ErrContextNotFoundCode) } return fmt.Errorf("failed to read context file: %w", err) } contextContent = string(content) } oldCfg, err := config.LoadConfig(configFile) if err == nil { if systemPrompt == "" { if len(serverNames) == 1 { configPrompt := oldCfg.GetSystemPrompt(serverNames[0]) if configPrompt != "" { systemPrompt = configPrompt logging.Debug("Using system prompt from config for server: %s", serverNames[0]) } } if systemPrompt == "" { if oldCfg.AI != nil && oldCfg.AI.DefaultSystemPrompt != "" { systemPrompt = oldCfg.AI.DefaultSystemPrompt logging.Debug("Using default system prompt from config") } } } } serverRawDataOverride := make(map[string]bool) if oldCfg != nil { settings := oldCfg.GetSettings() if settings != nil && settings.RawDataOverride { rawDataOutput = true logging.Debug("Raw data output enabled from global settings") } for _, name := range serverNames { serverSettings, err := oldCfg.GetServerSettings(name) if err == nil && serverSettings != nil && serverSettings.RawDataOverride { serverRawDataOverride[name] = true logging.Debug("Raw data output enabled for server: %s", name) } } } // ARCHITECTURAL FIX: Choose command options based on verbosity for clean output var commandOptions *host.CommandOptions if noisy || verbose { commandOptions = host.DefaultCommandOptions() } else { commandOptions = host.QuietCommandOptions() } // Initialize built-in skills service if needed var skillService *skillsvc.Service if needsSkills { configService := config.NewService() appConfig, err := configService.LoadConfig(configFile) if err != nil { if errorCodeOnly { os.Exit(query.ErrConfigNotFoundCode) } return fmt.Errorf("failed to load config for skills: %w", err) } skillService, err = infraSkills.InitializeBuiltinSkills(configFile, appConfig) if err != nil { if errorCodeOnly { os.Exit(query.ErrInitializationCode) } return fmt.Errorf("failed to initialize built-in skills: %w", err) } logging.Info("Built-in skills service initialized successfully") } // Run the query command with the given options (ONLY external servers) var result *query.QueryResult err = host.RunCommandWithOptions(func(conns []*host.ServerConnection) error { aiService := ai.NewService() llmProvider, err := aiService.InitializeProvider(configFile, providerName, modelName) if err != nil { if errorCodeOnly { os.Exit(query.ErrInitializationCode) } return fmt.Errorf("failed to initialize AI provider: %w", err) } // ARCHITECTURAL FIX: Create server manager (with skills if needed) var serverManager domain.MCPServerManager = NewHostServerManager(conns) if skillService != nil { logging.Info("Wrapping query server manager with built-in skills support") serverManager = infraSkills.NewSkillsAwareServerManager(serverManager, skillService) } handler := query.NewQueryHandlerWithServerManager(serverManager, llmProvider, aiOptions, systemPrompt) if contextContent != "" { handler.AddContext(contextContent) } if maxTokens > 0 { handler.SetMaxTokens(maxTokens) } result, err = handler.Execute(question) if err != nil { if errorCodeOnly { exitCode := query.GetExitCode(err) os.Exit(exitCode) } return fmt.Errorf("query failed: %w", err) } return nil }, configFile, externalServers, externalUserSpecified, commandOptions) if err != nil { return err } if result != nil && len(result.ToolCalls) > 0 { applyRawDataOutput := rawDataOutput for _, conn := range result.ServerConnections { if serverRawDataOverride[conn] { applyRawDataOutput = true break } } if applyRawDataOutput { rawData := extractRawData(result.ToolCalls) if rawData != "" { result.Response = rawData } } } if result != nil { if jsonOutput { jsonData, err := json.MarshalIndent(result, "", " ") if err != nil { if errorCodeOnly { os.Exit(query.ErrOutputFormatCode) } return fmt.Errorf("failed to format JSON response: %w", err) } if outputFile != "" { err = os.WriteFile(outputFile, jsonData, 0644) if err != nil { if errorCodeOnly { os.Exit(query.ErrOutputWriteCode) } return fmt.Errorf("failed to write output file: %w", err) } } else { fmt.Println(string(jsonData)) } } else { if outputFile != "" { err = os.WriteFile(outputFile, []byte(result.Response), 0644) if err != nil { if errorCodeOnly { os.Exit(query.ErrOutputWriteCode) } return fmt.Errorf("failed to write output file: %w", err) } } else { writer := output.NewWriter() defer writer.Close() writer.Println(result.Response) } } } return nil }, }
QueryCmd represents the query command
var RagCmd = &cobra.Command{
Use: "rag",
Short: "RAG (Retrieval-Augmented Generation) operations",
Long: `Perform RAG operations using MCP vector servers.
RAG enables semantic search across vector databases connected via MCP.
Supports multi-strategy search, query expansion, and result fusion.
Examples:
# Show RAG configuration
mcp-cli rag config
# Search directly
mcp-cli rag search "What are the MFA requirements?"`,
}
RagCmd represents the rag command
var RagConfigCmd = &cobra.Command{
Use: "config",
Short: "Show RAG configuration",
Long: `Display the current RAG configuration including servers, strategies, and fusion settings.`,
RunE: executeRagConfig,
}
RagConfigCmd shows RAG configuration
var RagSearchCmd = &cobra.Command{ Use: "search [query]", Short: "Search using RAG", Long: `Perform a RAG search against configured vector databases. RAG (Retrieval-Augmented Generation) uses semantic similarity to find relevant documents from vector databases. Results are ranked by relevance. Examples: # Basic search mcp-cli rag search "authentication requirements" # Search with more results mcp-cli rag search "access control policies" --top-k 10 # Use specific server mcp-cli rag search "encryption" --server pgvector # Multi-strategy search with fusion mcp-cli rag search "security controls" --strategies default,context --fusion rrf # Enable query expansion mcp-cli rag search "MFA" --expand Output: Returns JSON with query, results, and metadata including: - Matched document identifiers and text - Similarity scores (higher = more relevant) - Total results and execution time`, Args: cobra.ExactArgs(1), RunE: executeRagSearch, }
RagSearchCmd performs RAG search
var ( // RootCmd represents the base command when called without any subcommands RootCmd = &cobra.Command{ Use: "mcp-cli", Short: "MCP Command-Line Tool - Interact with AI models and MCP servers", Long: getColorizedHelp(), PersistentPreRun: func(cmd *cobra.Command, args []string) { cmdName := cmd.Name() if cmdName == "init" || cmdName == "help" || cmdName == "completion" || cmdName == "serve" { return } checkConfigExists(configFile) // Determine output configuration based on command and flags var outputConfig *models.OutputConfig isQueryCommand := cmd.Name() == "query" isTemplateMode := workflowName != "" isEmbeddingsCommand := cmd.Name() == "embeddings" if verbose { outputConfig = models.NewVerboseOutputConfig() } else if isQueryCommand || isTemplateMode || isEmbeddingsCommand { outputConfig = models.NewQuietOutputConfig() } else { outputConfig = models.NewDefaultOutputConfig() } if noColor { outputConfig.ShowColors = false } outputManager := output.NewManager(outputConfig) output.SetGlobalManager(outputManager) configureLegacyLogging(outputConfig) if providerName == "" { configService := config.NewService() if appConfig, err := configService.LoadConfig(configFile); err == nil { if appConfig.AI != nil && appConfig.AI.DefaultProvider != "" { providerName = appConfig.AI.DefaultProvider logging.Debug("Using default provider from config: %s", providerName) } } } }, Run: func(cmd *cobra.Command, args []string) { if workflowName != "" { if err := executeWorkflow(); err != nil { logging.Error("Template execution failed: %v", err) os.Exit(1) } return } if err := ChatCmd.RunE(cmd, args); err != nil { os.Exit(1) } }, } )
var ServeCmd = &cobra.Command{ Use: "serve [runas-config]", Short: "Run as an MCP server exposing workflow templates as tools", Long: `Serve mode runs mcp-cli as an MCP server, exposing your workflow templates as callable MCP tools that other applications can use. This allows applications like Claude Desktop, IDEs, or other MCP clients to: • Execute your custom workflow templates as tools • Chain multiple AI operations together • Access your configured AI providers and MCP servers The serve command requires a "runas" configuration file that defines: • Server name and version • Which templates to expose as tools • Input/output mappings for each tool • Optional provider/model overrides Example usage: # Start MCP server with specific config mcp-cli serve config/runas/research_agent.yaml # With verbose logging for debugging mcp-cli serve --verbose config/runas/code_reviewer.yaml # Using the --serve flag mcp-cli --serve config/runas/data_analyst.yaml Claude Desktop Configuration: Add to your Claude Desktop config (claude_desktop_config.json): { "mcpServers": { "research-agent": { "command": "/path/to/mcp-cli", "args": ["serve", "/path/to/config/runas/research_agent.yaml"] } } }`, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { runasConfigPath := serveConfig if len(args) > 0 { runasConfigPath = args[0] } if runasConfigPath == "" { return fmt.Errorf("runas config file is required") } if !verbose { logging.SetDefaultLevel(logging.ERROR) } logging.Info("Starting MCP server mode with config: %s", runasConfigPath) runasLoader := runas.NewLoader() runasConfig, created, err := runasLoader.LoadOrDefault(runasConfigPath) if err != nil { return fmt.Errorf("failed to load runas config: %w", err) } if created { fmt.Fprintf(os.Stderr, "Created example runas config at: %s\n", runasConfigPath) fmt.Fprintf(os.Stderr, "Please edit the file to configure your MCP server.\n") return nil } logging.Info("Loaded runas config: %s", runasConfig.ServerInfo.Name) actualConfigFile := configFile if actualConfigFile == "config.yaml" { exePath, err := os.Executable() if err != nil { return fmt.Errorf("failed to determine executable path: %w", err) } exeDir := filepath.Dir(exePath) actualConfigFile = filepath.Join(exeDir, "config.yaml") logging.Info("Using config file: %s", actualConfigFile) } configDir := filepath.Dir(actualConfigFile) originalWd, _ := os.Getwd() if err := os.Chdir(configDir); err != nil { return fmt.Errorf("failed to change to config directory %s: %w", configDir, err) } logging.Info("Changed working directory to: %s (was: %s)", configDir, originalWd) configService := infraConfig.NewService() appConfig, err := configService.LoadConfig(actualConfigFile) if err != nil { return fmt.Errorf("failed to load application config from %s: %w", actualConfigFile, err) } logging.Info("Loaded %d workflows from config", len(appConfig.Workflows)) if len(runasConfig.Templates) > 0 { logging.Info("Processing %d template source(s)...", len(runasConfig.Templates)) for _, templateSrc := range runasConfig.Templates { basename := filepath.Base(templateSrc.ConfigSource) templateName := strings.TrimSuffix(basename, filepath.Ext(basename)) _, existsV1 := appConfig.Workflows[templateName] templateV2, existsV2 := appConfig.Workflows[templateName] if !existsV1 && !existsV2 { return fmt.Errorf("template source '%s' points to unknown template: %s", templateSrc.ConfigSource, templateName) } toolName := templateSrc.Name if toolName == "" { toolName = templateName } toolDescription := templateSrc.Description if toolDescription == "" && existsV2 { toolDescription = templateV2.Description } inputSchema := map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "input_data": map[string]interface{}{ "type": "string", "description": "Input data for the template workflow", }, }, "required": []string{"input_data"}, } tool := runas.ToolExposure{ Template: templateName, Name: toolName, Description: toolDescription, InputSchema: inputSchema, InputMapping: map[string]string{ "input_data": "{{input_data}}", }, } runasConfig.Tools = append(runasConfig.Tools, tool) logging.Info("Created tool '%s' from template '%s' (source: %s)", toolName, templateName, templateSrc.ConfigSource) } logging.Info("Processed %d template(s) into %d total tool(s)", len(runasConfig.Templates), len(runasConfig.Tools)) } skillService, err := infraSkills.InitializeBuiltinSkills(configFile, appConfig) if err != nil { return fmt.Errorf("failed to initialize built-in skills: %w", err) } logging.Info("Built-in skills initialized successfully") if runasConfig.RunAsType == runas.RunAsTypeMCPSkills || runasConfig.RunAsType == runas.RunAsTypeProxySkills { logging.Info("Auto-discovering skills for mcp-skills server type") logging.Info("Generating MCP tools from already-initialized skills") discoveredSkills := skillService.ListSkills() if skillNames != "" { requestedSkills := strings.Split(skillNames, ",") for i := range requestedSkills { requestedSkills[i] = strings.TrimSpace(requestedSkills[i]) } if runasConfig.SkillsConfig == nil { runasConfig.SkillsConfig = &runas.SkillsConfig{} } runasConfig.SkillsConfig.IncludeSkills = requestedSkills runasConfig.SkillsConfig.ExcludeSkills = nil logging.Info("Using skills from command-line flag: %v", requestedSkills) } // Filter skills based on include/exclude lists var filteredSkills []string for _, skillName := range discoveredSkills { if runasConfig.ShouldIncludeSkill(skillName) { filteredSkills = append(filteredSkills, skillName) } else { logging.Info("Excluding skill: %s", skillName) } } logging.Info("Exposing %d skills as MCP tools", len(filteredSkills)) runasConfig.Tools = make([]runas.ToolExposure, 0, len(filteredSkills)+1) for _, skillName := range filteredSkills { skill, exists := skillService.GetSkill(skillName) if !exists { continue } tool := runas.ToolExposure{ Name: skill.GetMCPToolName(), Description: skill.GetToolDescription(), Template: "load_skill", InputSchema: skill.GetMCPInputSchema(), InputMapping: map[string]string{ "skill_name": skillName, }, } runasConfig.Tools = append(runasConfig.Tools, tool) logging.Info("Created tool '%s' for skill '%s'", tool.Name, skillName) } executeCodeTool := runas.ToolExposure{ Name: "execute_skill_code", Description: "[SKILL CODE EXECUTION] Execute code with access to a skill's helper libraries. " + "Use this to: (1) Create documents dynamically, (2) Process files with custom logic, " + "(3) Use skill helper libraries (e.g., Document class from docx skill). " + "The code executes in a sandboxed environment with the skill's scripts/ directory " + "available for imports via PYTHONPATH.", Template: "execute_skill_code", InputSchema: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "skill_name": map[string]interface{}{ "type": "string", "description": "Name of skill whose helper libraries to use (e.g., 'docx', 'pdf', 'xlsx')", }, "language": map[string]interface{}{ "type": "string", "enum": []string{"python", "bash"}, "description": "Programming language ('python' or 'bash')", "default": "python", }, "code": map[string]interface{}{ "type": "string", "description": "Code to execute (Python or Bash). Can import from 'scripts' module to use skill helper libraries.", }, "files": map[string]interface{}{ "type": "object", "description": "Optional files to make available in workspace (filename -> base64 content)", }, }, "required": []string{"skill_name", "code"}, }, } runasConfig.Tools = append(runasConfig.Tools, executeCodeTool) logging.Info("Generated %d MCP tools from skills (including execute_skill_code)", len(runasConfig.Tools)) } for i, tool := range runasConfig.Tools { if tool.Template == "load_skill" || tool.Template == "execute_skill_code" { continue } logging.Debug("Checking tool %d: name=%s, template=%s", i, tool.Name, tool.Template) logging.Debug("Total workflows loaded: %d", len(appConfig.Workflows)) _, existsV1 := appConfig.Workflows[tool.Template] _, existsV2 := appConfig.Workflows[tool.Template] if !existsV1 && !existsV2 { logging.Error("Template '%s' not found. Loaded workflows:", tool.Template) count := 0 for key := range appConfig.Workflows { if count < 10 { logging.Error(" - %s", key) count++ } } return fmt.Errorf("tool %d (%s) references unknown template: %s", i, tool.Name, tool.Template) } } if runasConfig.RunAsType == runas.RunAsTypeProxy || runasConfig.RunAsType == runas.RunAsTypeProxySkills { return startProxyServer(runasConfig, appConfig, configService, skillService) } return startStdioServer(runasConfig, appConfig, configService, skillService) }, }
ServeCmd represents the serve command
var ServersCmd = &cobra.Command{ Use: "servers", Short: "List all available MCP servers", Long: `List all MCP servers configured in the system. This includes: - Servers from config/servers/*.yaml - RunAs servers from config/runas/*.yaml (templates as MCP servers) Use these server names with --server flag in chat and query modes.`, RunE: func(cmd *cobra.Command, args []string) error { return listServers() }, }
ServersCmd lists all available MCP servers
var SkillsCmd = &cobra.Command{ Use: "skills", Short: "List all available skills", Long: `List all skills configured in the system. Skills are defined in config/skills/ directory and can be used to extend Claude's capabilities with specialized knowledge and helper functions.`, RunE: func(cmd *cobra.Command, args []string) error { return executeListSkills() }, }
SkillsCmd lists all available skills
var VersionCmd = &cobra.Command{ Use: "version", Short: "Print version information", Long: `Print detailed version information including build time and git commit.`, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("mcp-cli version %s\n", Version) fmt.Printf("Built: %s\n", BuildTime) fmt.Printf("Commit: %s\n", GitCommit) }, }
VersionCmd represents the version command
var WorkflowsCmd = &cobra.Command{ Use: "workflows", Short: "List all available workflows", Long: `List all workflow templates configured in the system. Workflows are defined in YAML files in config/workflows/ directory. Use these workflow names with --workflow flag on the root command.`, RunE: func(cmd *cobra.Command, args []string) error { return executeListWorkflows() }, }
WorkflowsCmd lists all available workflows
Functions ¶
func Execute ¶
func Execute() error
Execute adds all child commands to the root command and sets flags appropriately.
func ExtractFlagName ¶
ExtractFlagName extracts the flag name from an error string
func FindSimilarCommands ¶
FindSimilarCommands finds commands similar to the given name
func FindSimilarFlags ¶
FindSimilarFlags finds flags similar to the given name using Levenshtein distance
Types ¶
type CLIError ¶
type CLIError struct {
Type string // "unknown_flag", "missing_arg", "invalid_value", "unknown_command"
Message string // The error message
Suggestions []string // Possible corrections
Examples []string // Usage examples
HelpCommand string // Command to get more help
}
CLIError represents a user-friendly CLI error
func NewInvalidValueError ¶
func NewInvalidValueError(flagName string, value string, validValues []string, commandName string) *CLIError
NewInvalidValueError creates an error for invalid flag values
func NewMissingArgumentError ¶
NewMissingArgumentError creates an error for missing arguments
func NewUnknownCommandError ¶
NewUnknownCommandError creates an error for unknown commands
func NewUnknownFlagError ¶
NewUnknownFlagError creates an error for unknown flags
type HostServerAdapter ¶
type HostServerAdapter struct {
// contains filtered or unexported fields
}
HostServerAdapter adapts host.ServerConnection to domain.MCPServer interface
func (*HostServerAdapter) ExecuteTool ¶
func (*HostServerAdapter) GetConfig ¶
func (hsa *HostServerAdapter) GetConfig() *config.ServerConfig
func (*HostServerAdapter) GetServerName ¶
func (hsa *HostServerAdapter) GetServerName() string
func (*HostServerAdapter) GetTools ¶
func (hsa *HostServerAdapter) GetTools() ([]domain.Tool, error)
func (*HostServerAdapter) IsRunning ¶
func (hsa *HostServerAdapter) IsRunning() bool
func (*HostServerAdapter) Stop ¶
func (hsa *HostServerAdapter) Stop() error
type HostServerManager ¶
type HostServerManager struct {
// contains filtered or unexported fields
}
HostServerManager adapts host.ServerConnection to domain.MCPServerManager interface
func NewHostServerManager ¶
func NewHostServerManager(connections []*host.ServerConnection) *HostServerManager
func (*HostServerManager) ExecuteTool ¶
func (*HostServerManager) GetAvailableTools ¶
func (hsm *HostServerManager) GetAvailableTools() ([]domain.Tool, error)
func (*HostServerManager) GetServer ¶
func (hsm *HostServerManager) GetServer(serverName string) (domain.MCPServer, bool)
func (*HostServerManager) ListServers ¶
func (hsm *HostServerManager) ListServers() map[string]domain.MCPServer
func (*HostServerManager) StartServer ¶
func (hsm *HostServerManager) StartServer(ctx context.Context, serverName string, cfg *config.ServerConfig) (domain.MCPServer, error)
func (*HostServerManager) StopAll ¶
func (hsm *HostServerManager) StopAll() error
func (*HostServerManager) StopServer ¶
func (hsm *HostServerManager) StopServer(serverName string) error
type InitConfig ¶
type InitConfig struct {
Providers []string
Servers []string
IncludeOllama bool
IncludeOpenAI bool
IncludeAnthropic bool
IncludeDeepSeek bool
IncludeGemini bool
IncludeOpenRouter bool
IncludeLMStudio bool
IncludeMoonshot bool
IncludeBedrock bool
IncludeAzureFoundry bool
IncludeVertexAI bool
DefaultProvider string
IncludeSkills bool
IncludeRAG bool
}
InitConfig holds configuration choices