diff --git a/.gitignore b/.gitignore index 94d0e6b..79bed4d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ aidocs/ .mcp.json *.log +mcphost +.idea +test/ +build/ +scripts/ \ No newline at end of file diff --git a/README.md b/README.md index 189b210..885cdca 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,15 @@ mcphost -m openai:gpt-4 ``` ### Flags +- `--anthropic-url string`: Base URL for Anthropic API (defaults to api.anthropic.com) +- `--anthropic-api-key string`: Anthropic API key (can also be set via ANTHROPIC_API_KEY environment variable) - `--config string`: Config file location (default is $HOME/mcp.json) - `--debug`: Enable debug logging - `--message-window int`: Number of messages to keep in context (default: 10) - `-m, --model string`: Model to use (format: provider:model) (default "anthropic:claude-3-5-sonnet-latest") - `--openai-url string`: Base URL for OpenAI API (defaults to api.openai.com) +- `--openai-api-key string`: OpenAI API key (can also be set via OPENAI_API_KEY environment variable) + ### Interactive Commands @@ -154,4 +158,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - 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 +- Thanks to all contributors who have helped improve this tool \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index acdfb47..9baa5f1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,11 +26,14 @@ import ( ) var ( - renderer *glamour.TermRenderer - configFile string - messageWindow int - modelFlag string // New flag for model selection - openaiBaseURL string // Base URL for OpenAI API + renderer *glamour.TermRenderer + configFile string + messageWindow int + modelFlag string // New flag for model selection + openaiBaseURL string // Base URL for OpenAI API + anthropicBaseURL string // Base URL for Anthropic API + openaiAPIKey string + anthropicAPIKey string ) const ( @@ -79,8 +82,12 @@ func init() { // Add debug flag rootCmd.PersistentFlags(). BoolVar(&debugMode, "debug", false, "enable debug logging") - rootCmd.PersistentFlags(). - StringVar(&openaiBaseURL, "openai-url", "", "base URL for OpenAI API (defaults to api.openai.com)") + + flags := rootCmd.PersistentFlags() + flags.StringVar(&openaiBaseURL, "openai-url", "", "base URL for OpenAI API (defaults to api.openai.com)") + flags.StringVar(&anthropicBaseURL, "anthropic-url", "", "base URL for Anthropic API (defaults to api.anthropic.com)") + flags.StringVar(&openaiAPIKey, "openai-api-key", "", "OpenAI API key") + flags.StringVar(&anthropicAPIKey, "anthropic-api-key", "", "Anthropic API key") } // Add new function to create provider @@ -98,22 +105,30 @@ func createProvider(modelString string) (llm.Provider, error) { switch provider { case "anthropic": - apiKey := os.Getenv("ANTHROPIC_API_KEY") + apiKey := anthropicAPIKey + if apiKey == "" { + apiKey = os.Getenv("ANTHROPIC_API_KEY") + } + if apiKey == "" { return nil, fmt.Errorf( - "ANTHROPIC_API_KEY environment variable not set", + "Anthropic API key not provided. Use --anthropic-api-key flag or ANTHROPIC_API_KEY environment variable", ) } - return anthropic.NewProvider(apiKey), nil + return anthropic.NewProvider(apiKey, anthropicBaseURL, model), nil case "ollama": return ollama.NewProvider(model) case "openai": - apiKey := os.Getenv("OPENAI_API_KEY") + apiKey := openaiAPIKey + if apiKey == "" { + apiKey = os.Getenv("OPENAI_API_KEY") + } + if apiKey == "" { return nil, fmt.Errorf( - "OPENAI_API_KEY environment variable not set", + "OpenAI API key not provided. Use --openai-api-key flag or OPENAI_API_KEY environment variable", ) } return openai.NewProvider(apiKey, openaiBaseURL, model), nil diff --git a/pkg/llm/anthropic/client.go b/pkg/llm/anthropic/client.go index 6e35200..f9c260d 100644 --- a/pkg/llm/anthropic/client.go +++ b/pkg/llm/anthropic/client.go @@ -6,17 +6,25 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) type Client struct { - apiKey string - client *http.Client + apiKey string + client *http.Client + baseURL string } -func NewClient(apiKey string) *Client { +func NewClient(apiKey string, baseURL string) *Client { + if baseURL == "" { + baseURL = "https://api.anthropic.com/v1" + } else if !strings.HasSuffix(baseURL, "/v1") { + baseURL = strings.TrimSuffix(baseURL, "/") + "/v1" + } return &Client{ - apiKey: apiKey, - client: &http.Client{}, + apiKey: apiKey, + baseURL: baseURL, + client: &http.Client{}, } } @@ -26,7 +34,7 @@ func (c *Client) CreateMessage(ctx context.Context, req CreateRequest) (*APIMess return nil, fmt.Errorf("error marshaling request: %w", err) } - httpReq, err := http.NewRequestWithContext(ctx, "POST", "https://api.anthropic.com/v1/messages", bytes.NewReader(body)) + httpReq, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/messages", c.baseURL), bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("error creating request: %w", err) } diff --git a/pkg/llm/anthropic/provider.go b/pkg/llm/anthropic/provider.go index 9897635..75b395e 100644 --- a/pkg/llm/anthropic/provider.go +++ b/pkg/llm/anthropic/provider.go @@ -16,10 +16,13 @@ type Provider struct { model string } -func NewProvider(apiKey string) *Provider { +func NewProvider(apiKey string, baseURL string, model string) *Provider { + if model == "" { + model = "claude-3-5-sonnet-20240620" // 默认模型 + } return &Provider{ - client: NewClient(apiKey), - model: "claude-3-5-sonnet-20240620", + client: NewClient(apiKey, baseURL), + model: model, } } diff --git a/pkg/llm/openai/client.go b/pkg/llm/openai/client.go index 88c2d0d..f6790c3 100644 --- a/pkg/llm/openai/client.go +++ b/pkg/llm/openai/client.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" ) type Client struct { @@ -17,6 +18,8 @@ type Client struct { func NewClient(apiKey string, baseURL string) *Client { if baseURL == "" { baseURL = "https://api.openai.com/v1" + } else if !strings.HasSuffix(baseURL, "/v1") { + baseURL = strings.TrimSuffix(baseURL, "/") + "/v1" } return &Client{ apiKey: apiKey,