Initial commit

This commit is contained in:
Ed Zynda
2024-12-08 19:27:53 +03:00
commit d80a14f660
8 changed files with 1399 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.aider*
.env
aidocs/
+129
View File
@@ -0,0 +1,129 @@
# MCPHost
A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). Currently supports both Claude 3.5 Sonnet and Ollama models.
## Overview
MCPHost acts as a host in the MCP client-server architecture, where:
- **Hosts** (like MCPHost) are LLM applications that manage connections and interactions
- **Clients** maintain 1:1 connections with MCP servers
- **Servers** provide context, tools, and capabilities to the LLMs
This architecture allows language models to:
- Access external tools and data sources
- Maintain consistent context across interactions
- Execute commands and retrieve information safely
## Features
- Interactive conversations with either Claude 3.5 Sonnet or Ollama models
- Support for multiple concurrent MCP servers
- Dynamic tool discovery and integration
- Streaming responses from both Claude and Ollama
- Tool calling capabilities for both model types
- Configurable MCP server locations and arguments
- Consistent command interface across model types
## Installation
```bash
go install github.com/mark3labs/mcphost@latest
```
## Configuration
1. For Claude access, set your Anthropic API key as an environment variable:
```bash
export ANTHROPIC_API_KEY='your-api-key'
```
2. For Ollama access, ensure you have Ollama installed and running locally with your desired models.
3. Create an MCP configuration file at `~/mcp.json` (or specify location with `--config`):
```json
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": [
"mcp-server-sqlite",
"--db-path",
"/tmp/foo.db"
]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
]
}
}
}
```
Each MCP server entry requires:
- `command`: The command to run (e.g., `uvx`, `npx`)
- `args`: Array of arguments for the command:
- For SQLite server: `mcp-server-sqlite` with database path
- For filesystem server: `@modelcontextprotocol/server-filesystem` with directory path
## Usage
### Using Claude 3.5 Sonnet
Run the tool with default config location (`~/mcp.json`):
```bash
mcphost
```
### Using Ollama
Run with a specific Ollama model:
```bash
mcphost ollama --model mistral
```
### Using a Custom Config File
```bash
mcphost --config /path/to/config.json
```
## Available Commands
While chatting, you can use these commands:
- `/help`: Show available commands
- `/tools`: List all available tools
- `/servers`: List configured MCP servers
- `/quit`: Exit the application
- `Ctrl+C`: Exit at any time
## Requirements
- Go 1.18 or later
- For Claude: An Anthropic API key
- For Ollama: Local Ollama installation with desired models
- One or more MCP-compatible tool servers
## MCP Server Compatibility
MCPHost can work with any MCP-compliant server. For examples and reference implementations, see the [MCP Servers Repository](https://github.com/modelcontextprotocol/servers).
## Contributing
Contributions are welcome! Feel free to:
- Submit bug reports or feature requests through issues
- Create pull requests for improvements
- Share your custom MCP servers
- Improve documentation
Please ensure your contributions follow good coding practices and include appropriate tests.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Thanks to the Anthropic team for Claude and the MCP specification
- Thanks to the Ollama team for their local LLM runtime
- Thanks to all contributors who have helped improve this tool
+110
View File
@@ -0,0 +1,110 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/charmbracelet/log"
"github.com/anthropics/anthropic-sdk-go"
mcpclient "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
type MCPConfig struct {
MCPServers map[string]struct {
Command string `json:"command"`
Args []string `json:"args"`
} `json:"mcpServers"`
}
func mcpToolsToAnthropicTools(serverName string, mcpTools []mcp.Tool) []anthropic.ToolParam {
anthropicTools := make([]anthropic.ToolParam, len(mcpTools))
for i, tool := range mcpTools {
namespacedName := fmt.Sprintf("%s__%s", serverName, tool.Name)
schemaMap := map[string]interface{}{
"type": tool.InputSchema.Type,
"properties": tool.InputSchema.Properties,
}
if len(tool.InputSchema.Required) > 0 {
schemaMap["required"] = tool.InputSchema.Required
}
anthropicTools[i] = anthropic.ToolParam{
Name: anthropic.F(namespacedName),
Description: anthropic.F(tool.Description),
InputSchema: anthropic.Raw[interface{}](schemaMap),
}
}
return anthropicTools
}
func loadMCPConfig() (*MCPConfig, error) {
var configPath string
if configFile != "" {
configPath = configFile
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("error getting home directory: %w", err)
}
configPath = filepath.Join(homeDir, "mcp.json")
}
configData, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("error reading config file %s: %w", configPath, err)
}
var config MCPConfig
if err := json.Unmarshal(configData, &config); err != nil {
return nil, fmt.Errorf("error parsing config file: %w", err)
}
return &config, nil
}
func createMCPClients(config *MCPConfig) (map[string]*mcpclient.StdioMCPClient, error) {
clients := make(map[string]*mcpclient.StdioMCPClient)
for name, server := range config.MCPServers {
client, err := mcpclient.NewStdioMCPClient(server.Command, server.Args...)
if err != nil {
for _, c := range clients {
c.Close()
}
return nil, fmt.Errorf("failed to create MCP client for %s: %w", name, err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
log.Info("Initializing server...", "name", name)
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "mcphost",
Version: "0.1.0",
}
_, err = client.Initialize(ctx, initRequest)
if err != nil {
client.Close()
for _, c := range clients {
c.Close()
}
return nil, fmt.Errorf("failed to initialize MCP client for %s: %w", name, err)
}
clients[name] = client
}
return clients, nil
}
+400
View File
@@ -0,0 +1,400 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/huh/spinner"
"github.com/charmbracelet/log"
mcpclient "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
api "github.com/ollama/ollama/api"
"github.com/spf13/cobra"
)
// F is a helper function to get a pointer to a value
func F[T any](v T) *T {
return &v
}
var (
modelName string
ollamaCmd = &cobra.Command{
Use: "ollama",
Short: "Chat using an Ollama model",
Long: `Use a local Ollama model for chat with MCP tool support`,
RunE: func(cmd *cobra.Command, args []string) error {
return runOllama()
},
}
)
func init() {
ollamaCmd.Flags().
StringVar(&modelName, "model", "", "Ollama model to use (required)")
ollamaCmd.MarkFlagRequired("model")
rootCmd.AddCommand(ollamaCmd)
}
func mcpToolsToOllamaTools(serverName string, mcpTools []mcp.Tool) []api.Tool {
ollamaTools := make([]api.Tool, len(mcpTools))
for i, tool := range mcpTools {
namespacedName := fmt.Sprintf("%s__%s", serverName, tool.Name)
ollamaTools[i] = api.Tool{
Type: "function",
Function: api.ToolFunction{
Name: namespacedName,
Description: tool.Description,
Parameters: struct {
Type string `json:"type"`
Required []string `json:"required"`
Properties map[string]struct {
Type string `json:"type"`
Description string `json:"description"`
Enum []string `json:"enum,omitempty"`
} `json:"properties"`
}{
Type: tool.InputSchema.Type,
Required: tool.InputSchema.Required,
Properties: make(map[string]struct {
Type string `json:"type"`
Description string `json:"description"`
Enum []string `json:"enum,omitempty"`
}),
},
},
}
// Convert properties
for propName, prop := range tool.InputSchema.Properties {
propMap, ok := prop.(map[string]interface{})
if !ok {
log.Error("Invalid property type", "property", propName)
continue
}
propType, _ := propMap["type"].(string)
propDesc, _ := propMap["description"].(string)
propEnumRaw, hasEnum := propMap["enum"]
var enumVals []string
if hasEnum {
if enumSlice, ok := propEnumRaw.([]interface{}); ok {
enumVals = make([]string, len(enumSlice))
for i, v := range enumSlice {
if str, ok := v.(string); ok {
enumVals[i] = str
}
}
}
}
ollamaTools[i].Function.Parameters.Properties[propName] = struct {
Type string `json:"type"`
Description string `json:"description"`
Enum []string `json:"enum,omitempty"`
}{
Type: propType,
Description: propDesc,
Enum: enumVals,
}
}
}
return ollamaTools
}
func runOllama() error {
mcpConfig, err := loadMCPConfig()
if err != nil {
return fmt.Errorf("error loading MCP config: %v", err)
}
mcpClients, err := createMCPClients(mcpConfig)
if err != nil {
return fmt.Errorf("error creating MCP clients: %v", err)
}
defer func() {
log.Info("Shutting down MCP servers...")
for name, client := range mcpClients {
if err := client.Close(); err != nil {
log.Error("Failed to close server", "name", name, "error", err)
} else {
log.Info("Server closed", "name", name)
}
}
}()
client, err := api.ClientFromEnvironment()
if err != nil {
return fmt.Errorf("error creating Ollama client: %v", err)
}
var allTools []api.Tool
for serverName, mcpClient := range mcpClients {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
toolsResult, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
cancel()
if err != nil {
log.Error(
"Error fetching tools",
"server",
serverName,
"error",
err,
)
continue
}
serverTools := mcpToolsToOllamaTools(serverName, toolsResult.Tools)
allTools = append(allTools, serverTools...)
log.Info(
"Tools loaded",
"server",
serverName,
"count",
len(toolsResult.Tools),
)
}
if err := updateRenderer(); err != nil {
return fmt.Errorf("error initializing renderer: %v", err)
}
// Initialize messages with system prompt
messages := []api.Message{
{
Role: "system",
Content: `You are a helpful AI assistant with access to external tools. Respond directly to questions and requests.
Only use tools when specifically needed to accomplish a task. If you can answer without using tools, do so.
When you do need to use a tool, explain what you're doing first.`,
},
}
// Main interaction loop
for {
width := getTerminalWidth()
var prompt string
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Key("prompt").
Title("Enter your prompt (Type /help for commands, Ctrl+C to quit)").
Value(&prompt),
),
).WithWidth(width)
err := form.Run()
if err != nil {
if err.Error() == "user aborted" {
fmt.Println("\nGoodbye!")
return nil
}
return err
}
prompt = form.GetString("prompt")
if prompt == "" {
continue
}
// Handle slash commands
if strings.HasPrefix(prompt, "/") {
switch strings.ToLower(strings.TrimSpace(prompt)) {
case "/tools":
handleToolsCommand(mcpClients)
continue
case "/help":
handleHelpCommand()
continue
case "/servers":
handleServersCommand(mcpConfig)
continue
case "/quit":
fmt.Println("\nGoodbye!")
return nil
default:
fmt.Printf("%s\nType /help to see available commands\n\n",
errorStyle.Render("Unknown command: "+prompt))
continue
}
}
err = runOllamaPrompt(client, mcpClients, allTools, prompt, &messages)
if err != nil {
return err
}
}
}
func runOllamaPrompt(
client *api.Client,
mcpClients map[string]*mcpclient.StdioMCPClient,
tools []api.Tool,
prompt string,
messages *[]api.Message,
) error {
if prompt != "" {
fmt.Printf("\n%s\n", promptStyle.Render("You: "+prompt))
*messages = append(*messages, api.Message{
Role: "user",
Content: prompt,
})
}
var err error
var responseContent string
var toolCalls []api.ToolCall
action := func() {
err = client.Chat(context.Background(), &api.ChatRequest{
Model: modelName,
Messages: *messages,
Tools: tools,
Stream: F(false), // Disable streaming
}, func(response api.ChatResponse) error {
if response.Done {
responseContent = response.Message.Content
toolCalls = response.Message.ToolCalls
if len(toolCalls) > 0 && responseContent == "" {
responseContent = "Using tools..."
}
*messages = append(*messages, response.Message)
}
return nil
})
}
_ = spinner.New().Title("Thinking...").Action(action).Run()
if err != nil {
return err
}
// Print the response
if err := updateRenderer(); err != nil {
return fmt.Errorf("error updating renderer: %v", err)
}
fmt.Print(responseStyle.Render("\nAssistant: "))
rendered, err := renderer.Render(responseContent + "\n")
if err != nil {
log.Error("Failed to render response", "error", err)
fmt.Print(responseContent + "\n")
} else {
fmt.Print(rendered)
}
// Handle tool calls if present
if len(toolCalls) > 0 {
for _, toolCall := range toolCalls {
log.Info(
"🔧 Using tool",
"name",
toolCall.Function.Name,
)
parts := strings.Split(toolCall.Function.Name, "__")
if len(parts) != 2 {
fmt.Printf(
"Error: Invalid tool name format: %s\n",
toolCall.Function.Name,
)
continue
}
serverName, toolName := parts[0], parts[1]
mcpClient, ok := mcpClients[serverName]
if !ok {
fmt.Printf("Error: Server not found: %s\n", serverName)
continue
}
var toolResultPtr *mcp.CallToolResult
action := func() {
ctx, cancel := context.WithTimeout(
context.Background(),
10*time.Second,
)
defer cancel()
toolResultPtr, err = mcpClient.CallTool(
ctx,
mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
}{
Name: toolName,
Arguments: toolCall.Function.Arguments,
},
},
)
}
_ = spinner.New().
Title(fmt.Sprintf("Running tool %s...", toolName)).
Action(action).
Run()
if err != nil {
errMsg := fmt.Sprintf(
"Error calling tool %s: %v",
toolName,
err,
)
fmt.Printf("\n%s\n", errorStyle.Render(errMsg))
// Add error message directly to messages array as JSON string
*messages = append(*messages, api.Message{
Role: "tool",
Content: fmt.Sprintf(`{"error": "%s"}`, errMsg),
})
continue
}
toolResult := *toolResultPtr
log.Debug("Tool result details",
"isError", toolResult.IsError,
"result", toolResult.Result,
"raw", fmt.Sprintf("%+v", toolResult))
// Check if there's an error in the tool result
if toolResult.IsError {
errMsg := fmt.Sprintf("Tool error: %v", toolResult.Result)
fmt.Printf("\n%s\n", errorStyle.Render(errMsg))
*messages = append(*messages, api.Message{
Role: "tool",
Content: fmt.Sprintf(`{"error": %q}`, errMsg),
})
continue
}
// Add the tool result directly to messages array as JSON string
resultJSON, err := json.Marshal(toolResult.Content)
if err != nil {
errMsg := fmt.Sprintf("Error marshaling tool result: %v", err)
fmt.Printf("\n%s\n", errorStyle.Render(errMsg))
continue
}
*messages = append(*messages, api.Message{
Role: "tool",
Content: string(resultJSON),
})
}
// Make another call to get Ollama's response to the tool results
return runOllamaPrompt(client, mcpClients, tools, "", messages)
}
fmt.Println() // Add spacing
return nil
}
+565
View File
@@ -0,0 +1,565 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/huh/spinner"
"github.com/charmbracelet/log"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss"
mcpclient "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var (
renderer *glamour.TermRenderer
// Tokyo Night theme colors
tokyoPurple = lipgloss.Color("99") // #9d7cd8
tokyoCyan = lipgloss.Color("73") // #7dcfff
tokyoBlue = lipgloss.Color("111") // #7aa2f7
tokyoGreen = lipgloss.Color("120") // #73daca
tokyoRed = lipgloss.Color("203") // #f7768e
tokyoOrange = lipgloss.Color("215") // #ff9e64
tokyoFg = lipgloss.Color("189") // #c0caf5
tokyoGray = lipgloss.Color("237") // #3b4261
tokyoBg = lipgloss.Color("234") // #1a1b26
serverCommandStyle = lipgloss.NewStyle().
Foreground(tokyoOrange).
Bold(true)
serverArgumentsStyle = lipgloss.NewStyle().
Foreground(tokyoFg)
serverHeaderStyle = lipgloss.NewStyle().
Foreground(tokyoCyan).
Bold(true)
promptStyle = lipgloss.NewStyle().
Foreground(tokyoBlue).
PaddingLeft(2)
responseStyle = lipgloss.NewStyle().
Foreground(tokyoFg).
PaddingLeft(2)
errorStyle = lipgloss.NewStyle().
Foreground(tokyoRed).
Bold(true)
serverBox = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(tokyoPurple).
Padding(1).
MarginBottom(1).
AlignHorizontal(lipgloss.Left) // Force left alignment
toolNameStyle = lipgloss.NewStyle().
Foreground(tokyoCyan).
Bold(true)
descriptionStyle = lipgloss.NewStyle().
Foreground(tokyoFg).
PaddingBottom(1)
configFile string
)
var rootCmd = &cobra.Command{
Use: "mcphost",
Short: "Chat with Claude 3.5 Sonnet or Ollama models",
Long: `MCPHost is a CLI tool that allows you to interact with Claude 3.5 Sonnet or Ollama models.
It supports various tools through MCP servers and provides streaming responses.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runMCPHost()
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().
StringVar(&configFile, "config", "", "config file (default is $HOME/mcp.json)")
}
func getTerminalWidth() int {
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
return 80 // Fallback width
}
return width - 20
}
func updateRenderer() error {
width := getTerminalWidth()
var err error
renderer, err = glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(width),
)
return err
}
func handleHelpCommand() {
if err := updateRenderer(); err != nil {
fmt.Printf(
"\n%s\n",
errorStyle.Render(fmt.Sprintf("Error updating renderer: %v", err)),
)
return
}
var markdown strings.Builder
markdown.WriteString("# Available Commands\n\n")
markdown.WriteString("The following commands are available:\n\n")
markdown.WriteString("- **/help**: Show this help message\n")
markdown.WriteString("- **/tools**: List all available tools\n")
markdown.WriteString("- **/servers**: List configured MCP servers\n")
markdown.WriteString("- **/quit**: Exit the application\n")
markdown.WriteString("\nYou can also press Ctrl+C at any time to quit.\n")
markdown.WriteString("\n## Subcommands\n\n")
markdown.WriteString(
"- **ollama**: Use an Ollama model instead of Claude\n",
)
markdown.WriteString(" Example: `mcphost ollama --model mistral`\n")
rendered, err := renderer.Render(markdown.String())
if err != nil {
fmt.Printf(
"\n%s\n",
errorStyle.Render(fmt.Sprintf("Error rendering help: %v", err)),
)
return
}
fmt.Print(rendered)
}
func handleServersCommand(config *MCPConfig) {
if err := updateRenderer(); err != nil {
fmt.Printf(
"\n%s\n",
errorStyle.Render(fmt.Sprintf("Error updating renderer: %v", err)),
)
return
}
var markdown strings.Builder
action := func() {
if len(config.MCPServers) == 0 {
markdown.WriteString("No servers configured.\n")
} else {
for name, server := range config.MCPServers {
markdown.WriteString(fmt.Sprintf("# %s\n\n", name))
markdown.WriteString("*Command*\n")
markdown.WriteString(fmt.Sprintf("`%s`\n\n", server.Command))
markdown.WriteString("*Arguments*\n")
if len(server.Args) > 0 {
markdown.WriteString(fmt.Sprintf("`%s`\n", strings.Join(server.Args, " ")))
} else {
markdown.WriteString("*None*\n")
}
}
}
}
_ = spinner.New().
Title("Loading server configuration...").
Action(action).
Run()
rendered, err := renderer.Render(markdown.String())
if err != nil {
fmt.Printf(
"\n%s\n",
errorStyle.Render(fmt.Sprintf("Error rendering servers: %v", err)),
)
return
}
// Calculate width with proper margins
termWidth := getTerminalWidth()
contentWidth := termWidth - 20 // Reserve space for margins
// Create a box style with the calculated width
boxStyle := serverBox.Width(contentWidth)
// Wrap the rendered markdown in the box
boxedContent := boxStyle.Render(rendered)
fmt.Print(boxedContent)
}
func handleToolsCommand(mcpClients map[string]*mcpclient.StdioMCPClient) {
if err := updateRenderer(); err != nil {
fmt.Printf(
"\n%s\n",
errorStyle.Render(fmt.Sprintf("Error updating renderer: %v", err)),
)
return
}
// Calculate widths with proper margins
termWidth := getTerminalWidth()
contentWidth := termWidth - 20 // Reserve space for margins
// Update styles with calculated widths
serverBoxStyle := serverBox.Width(contentWidth)
type serverTools struct {
tools []mcp.Tool
err error
}
results := make(map[string]serverTools)
action := func() {
for serverName, mcpClient := range mcpClients {
ctx, cancel := context.WithTimeout(
context.Background(),
10*time.Second,
)
defer cancel() // Move defer here to ensure it's called
toolsResult, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
if err != nil {
results[serverName] = serverTools{
tools: nil,
err: err,
}
continue
}
// Only access Tools if toolsResult is not nil
var tools []mcp.Tool
if toolsResult != nil {
tools = toolsResult.Tools
}
results[serverName] = serverTools{
tools: tools,
err: nil,
}
}
}
_ = spinner.New().
Title("Fetching tools from all servers...").
Action(action).
Run()
// Now display all results
for serverName, result := range results {
if result.err != nil {
errMsg := errorStyle.Render(
fmt.Sprintf(
"Error fetching tools from %s: %v",
serverName,
result.err,
),
)
fmt.Printf("\n%s\n", serverBox.Render(errMsg))
continue
}
serverHeader := fmt.Sprintf("# %s\n", serverName)
renderedHeader, err := renderer.Render(serverHeader)
if err != nil {
errMsg := errorStyle.Render(
fmt.Sprintf("Error rendering server header: %v", err),
)
fmt.Printf("\n%s\n", serverBox.Render(errMsg))
continue
}
var content strings.Builder
content.WriteString(renderedHeader)
if len(result.tools) == 0 {
content.WriteString("\nNo tools available.\n")
} else {
for _, tool := range result.tools {
toolDisplay := fmt.Sprintf("%s\n%s",
toolNameStyle.Render("🔧 "+tool.Name),
descriptionStyle.Render(tool.Description),
)
content.WriteString(toolDisplay + "\n")
}
}
boxedContent := serverBoxStyle.Render(content.String())
// Print directly without the centeredBox wrapper
fmt.Print(boxedContent)
}
}
func runPrompt(
client *anthropic.Client,
mcpClients map[string]*mcpclient.StdioMCPClient,
tools []anthropic.ToolParam,
prompt string,
messages *[]anthropic.MessageParam,
) error {
// Display the user's prompt if it's not empty (i.e., not a tool response)
if prompt != "" {
fmt.Printf("\n%s\n", promptStyle.Render("You: "+prompt))
*messages = append(
*messages,
anthropic.NewUserMessage(anthropic.NewTextBlock(prompt)),
)
}
var messagePtr *anthropic.Message
var err error
action := func() {
messagePtr, err = client.Messages.New(
context.Background(),
anthropic.MessageNewParams{
Model: anthropic.F(
anthropic.ModelClaude_3_5_Sonnet_20240620,
),
MaxTokens: anthropic.F(int64(4096)),
Messages: anthropic.F(*messages),
Tools: anthropic.F(tools),
},
)
}
_ = spinner.New().Title("Thinking...").Action(action).Run()
if err != nil {
return err
}
message := *messagePtr // Dereference the pointer
fmt.Print(responseStyle.Render("\nClaude: "))
toolResults := []anthropic.ContentBlockParamUnion{}
for _, block := range message.Content {
switch block := block.AsUnion().(type) {
case anthropic.TextBlock:
if err := updateRenderer(); err != nil {
return fmt.Errorf("error updating renderer: %v", err)
}
str, err := renderer.Render(block.Text + "\n")
if err != nil {
log.Error("Failed to render response", "error", err)
fmt.Print(block.Text + "\n")
continue
}
fmt.Print(str)
case anthropic.ToolUseBlock:
log.Info("🔧 Using tool", "name", block.Name)
parts := strings.Split(block.Name, "__")
if len(parts) != 2 {
fmt.Printf("Error: Invalid tool name format: %s\n", block.Name)
continue
}
serverName, toolName := parts[0], parts[1]
mcpClient, ok := mcpClients[serverName]
if !ok {
fmt.Printf("Error: Server not found: %s\n", serverName)
continue
}
var toolArgs map[string]interface{}
if err := json.Unmarshal(block.Input, &toolArgs); err != nil {
fmt.Printf("Error parsing tool arguments: %v\n", err)
continue
}
var toolResultPtr *mcp.CallToolResult
action := func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
toolResultPtr, err = mcpClient.CallTool(ctx, mcp.CallToolRequest{
Params: struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
}{
Name: toolName,
Arguments: toolArgs,
},
})
}
_ = spinner.New().
Title(fmt.Sprintf("Running tool %s...", toolName)).
Action(action).
Run()
if err != nil {
errMsg := fmt.Sprintf("Error calling tool %s: %v", toolName, err)
fmt.Printf("\n%s\n", errorStyle.Render(errMsg))
// Add error message as tool result
toolResults = append(toolResults,
anthropic.NewToolResultBlock(block.ID, errMsg, true))
continue
}
toolResult := *toolResultPtr // Dereference the pointer
resultJSON, err := json.Marshal(toolResult)
if err != nil {
fmt.Printf("Error marshaling tool result: %v\n", err)
continue
}
toolResults = append(toolResults,
anthropic.NewToolResultBlock(block.ID, string(resultJSON), toolResult.IsError))
}
}
*messages = append(*messages, message.ToParam())
if len(toolResults) > 0 {
*messages = append(*messages, anthropic.NewUserMessage(toolResults...))
// Make another call to get Claude's response to the tool results
return runPrompt(client, mcpClients, tools, "", messages)
}
fmt.Println() // Add spacing
return nil
}
func runMCPHost() error {
apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
return fmt.Errorf("ANTHROPIC_API_KEY environment variable not set")
}
mcpConfig, err := loadMCPConfig()
if err != nil {
return fmt.Errorf("error loading MCP config: %v", err)
}
mcpClients, err := createMCPClients(mcpConfig)
if err != nil {
return fmt.Errorf("error creating MCP clients: %v", err)
}
defer func() {
log.Info("Shutting down MCP servers...")
for name, client := range mcpClients {
if err := client.Close(); err != nil {
log.Error("Failed to close server", "name", name, "error", err)
} else {
log.Info("Server closed", "name", name)
}
}
}()
for name := range mcpClients {
log.Info("Server connected", "name", name)
}
client := anthropic.NewClient(
option.WithAPIKey(apiKey),
)
var allTools []anthropic.ToolParam
for serverName, mcpClient := range mcpClients {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
toolsResult, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
cancel()
if err != nil {
log.Error(
"Error fetching tools",
"server",
serverName,
"error",
err,
)
continue
}
serverTools := mcpToolsToAnthropicTools(serverName, toolsResult.Tools)
allTools = append(allTools, serverTools...)
log.Info(
"Tools loaded",
"server",
serverName,
"count",
len(toolsResult.Tools),
)
}
if err := updateRenderer(); err != nil {
return fmt.Errorf("error initializing renderer: %v", err)
}
messages := make([]anthropic.MessageParam, 0)
// Main interaction loop
for {
width := getTerminalWidth()
var prompt string
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Key("prompt").
Title("Enter your prompt (Type /help for commands, Ctrl+C to quit)").
Value(&prompt),
),
).WithWidth(width)
err := form.Run()
if err != nil {
// Check if it's a user abort (Ctrl+C)
if err.Error() == "user aborted" {
fmt.Println("\nGoodbye!")
return nil // Exit cleanly
}
return err // Return other errors normally
}
prompt = form.GetString("prompt")
if prompt == "" {
continue
}
// Handle slash commands
if strings.HasPrefix(prompt, "/") {
switch strings.ToLower(strings.TrimSpace(prompt)) {
case "/tools":
handleToolsCommand(mcpClients)
continue
case "/help":
handleHelpCommand()
continue
case "/servers":
handleServersCommand(mcpConfig)
continue
case "/quit":
fmt.Println("\nGoodbye!")
return nil
default:
fmt.Printf("%s\nType /help to see available commands\n\n",
errorStyle.Render("Unknown command: "+prompt))
continue
}
}
err = runPrompt(client, mcpClients, allTools, prompt, &messages)
if err != nil {
return err
}
}
}
+58
View File
@@ -0,0 +1,58 @@
module github.com/mark3labs/mcphost
go 1.23.3
require (
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.5
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/huh/spinner v0.0.0-20241127125741-aad810dfbce6
github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0
github.com/mark3labs/mcp-go v0.5.3
github.com/ollama/ollama v0.5.1
github.com/spf13/cobra v1.8.1
golang.org/x/term v0.22.0
)
require (
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/yuin/goldmark v1.7.4 // indirect
github.com/yuin/goldmark-emoji v1.0.3 // indirect
golang.org/x/net v0.27.0 // indirect
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/bubbletea v1.2.4 // indirect
github.com/charmbracelet/glamour v0.8.0
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
)
+127
View File
@@ -0,0 +1,127 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.5 h1:Ew8EGOH+FUI5fsJmpM03jkQFpXkxY82fGrXE/3aaq9U=
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.5/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=
github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=
github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE=
github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA=
github.com/charmbracelet/huh/spinner v0.0.0-20241127125741-aad810dfbce6 h1:btKBXcuvUcXRT0VVk850BOpov6wjCLAPoJuaPm+sCKU=
github.com/charmbracelet/huh/spinner v0.0.0-20241127125741-aad810dfbce6/go.mod h1:3/xTBdgqRzAb+eUKRAGi9ix/K6QxsS0nGtd4zp+/tJs=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mark3labs/mcp-go v0.5.3 h1:RGLO3vqB2t2rXFidwIm7vFvtJdcSAXVWklTVxcbNWYs=
github.com/mark3labs/mcp-go v0.5.3/go.mod h1:ePkDSyplFbA306xRgyp587+q/vpdgxuswwjZqTQ+I8Q=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
github.com/ollama/ollama v0.5.1 h1:Ug4y/5UZZoTgetMklZslAlEdaCnYEX9qZJ/aTsM4+xc=
github.com/ollama/ollama v0.5.1/go.mod h1:wrgnDTdogU9yeFOj/Jc8BpRBJrWu+Ox4eGyHxqiaQDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=
github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+7
View File
@@ -0,0 +1,7 @@
package main
import "github.com/mark3labs/mcphost/cmd"
func main() {
cmd.Execute()
}