Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ This architecture allows language models to:
Currently supports:
- Claude 3.5 Sonnet (claude-3-5-sonnet-20240620)
- Any Ollama-compatible model with function calling support
- Google Gemini models
- Any OpenAI-compatible local or online model with function calling support


## Features ✨

- Interactive conversations with support models
Expand All @@ -35,6 +35,7 @@ Currently supports:
- Go 1.23 or later
- For Claude: An Anthropic API key
- For Ollama: Local Ollama installation with desired models
- For Google/Gemini: Google API key (see https://aistudio.google.com/app/apikey)
- One or more MCP-compatible tool servers

## Environment Setup 🔧
Expand All @@ -55,9 +56,13 @@ ollama pull mistral
ollama serve
```

3. OpenAI compatible online Setup
- Get your api server base url, api key and model name
3. Google API Key (for Gemini):
```bash
export GOOGLE_API_KEY='your-api-key'
```

4. OpenAI compatible online Setup
- Get your api server base url, api key and model name

## Installation 📦

Expand Down Expand Up @@ -107,6 +112,7 @@ Models can be specified using the `--model` (`-m`) flag:
- Anthropic Claude (default): `anthropic:claude-3-5-sonnet-latest`
- OpenAI or OpenAI-compatible: `openai:gpt-4`
- Ollama models: `ollama:modelname`
- Google: `google:gemini-2.0-flash`

### Examples
```bash
Expand All @@ -131,6 +137,7 @@ mcphost --model openai:<your-model-name> \
- `-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)
- `--google-api-key string`: Google API key (can also be set via GOOGLE_API_KEY environment variable)


### Interactive Commands
Expand Down
59 changes: 36 additions & 23 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/mark3labs/mcphost/pkg/history"
"github.com/mark3labs/mcphost/pkg/llm"
"github.com/mark3labs/mcphost/pkg/llm/anthropic"
"github.com/mark3labs/mcphost/pkg/llm/google"
"github.com/mark3labs/mcphost/pkg/llm/ollama"
"github.com/mark3labs/mcphost/pkg/llm/openai"
"github.com/spf13/cobra"
Expand All @@ -34,6 +36,7 @@ var (
anthropicBaseURL string // Base URL for Anthropic API
openaiAPIKey string
anthropicAPIKey string
googleAPIKey string
)

const (
Expand All @@ -53,12 +56,14 @@ Available models can be specified using the --model flag:
- Anthropic Claude (default): anthropic:claude-3-5-sonnet-latest
- OpenAI: openai:gpt-4
- Ollama models: ollama:modelname
- Google: google:modelname

Example:
mcphost -m ollama:qwen2.5:3b
mcphost -m openai:gpt-4`,
mcphost -m openai:gpt-4
mcphost -m google:gemini-2.0-flash`,
RunE: func(cmd *cobra.Command, args []string) error {
return runMCPHost()
return runMCPHost(context.Background())
},
}

Expand Down Expand Up @@ -88,10 +93,11 @@ func init() {
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")
flags.StringVar(&googleAPIKey, "google-api-key", "", "Google (Gemini) API key")
}

// Add new function to create provider
func createProvider(modelString string) (llm.Provider, error) {
func createProvider(ctx context.Context, modelString string) (llm.Provider, error) {
parts := strings.SplitN(modelString, ":", 2)
if len(parts) < 2 {
return nil, fmt.Errorf(
Expand Down Expand Up @@ -133,6 +139,17 @@ func createProvider(modelString string) (llm.Provider, error) {
}
return openai.NewProvider(apiKey, openaiBaseURL, model), nil

case "google":
apiKey := googleAPIKey
if apiKey == "" {
apiKey = os.Getenv("GOOGLE_API_KEY")
}
if apiKey == "" {
// The project structure is provider specific, but Google calls this GEMINI_API_KEY in e.g. AI Studio. Support both.
apiKey = os.Getenv("GEMINI_API_KEY")
}
return google.NewProvider(ctx, apiKey, model)

default:
return nil, fmt.Errorf("unsupported provider: %s", provider)
}
Expand Down Expand Up @@ -219,6 +236,7 @@ func updateRenderer() error {

// Method implementations for simpleMessage
func runPrompt(
ctx context.Context,
provider llm.Provider,
mcpClients map[string]*mcpclient.StdioMCPClient,
tools []llm.Tool,
Expand Down Expand Up @@ -255,7 +273,7 @@ func runPrompt(
for {
action := func() {
message, err = provider.CreateMessage(
context.Background(),
ctx,
prompt,
llmMessages,
tools,
Expand Down Expand Up @@ -294,7 +312,7 @@ func runPrompt(
var messageContent []history.ContentBlock

// Handle the message response
if str, err := renderer.Render("\nAssistant: "); err == nil {
if str, err := renderer.Render("\nAssistant: "); message.GetContent() != "" && err == nil {
fmt.Print(str)
}

Expand Down Expand Up @@ -440,14 +458,14 @@ func runPrompt(
Content: toolResults,
})
// Make another call to get Claude's response to the tool results
return runPrompt(provider, mcpClients, tools, "", messages)
return runPrompt(ctx, provider, mcpClients, tools, "", messages)
}

fmt.Println() // Add spacing
return nil
}

func runMCPHost() error {
func runMCPHost(ctx context.Context) error {
// Set up logging based on debug flag
if debugMode {
log.SetLevel(log.DebugLevel)
Expand All @@ -459,7 +477,7 @@ func runMCPHost() error {
}

// Create the provider based on the model flag
provider, err := createProvider(modelFlag)
provider, err := createProvider(ctx, modelFlag)
if err != nil {
return fmt.Errorf("error creating provider: %v", err)
}
Expand Down Expand Up @@ -497,7 +515,7 @@ func runMCPHost() error {

var allTools []llm.Tool
for serverName, mcpClient := range mcpClients {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
toolsResult, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
cancel()

Expand Down Expand Up @@ -531,28 +549,23 @@ func runMCPHost() error {

// 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).WithTheme(huh.ThemeCharm())

err := form.Run()
err := huh.NewForm(huh.NewGroup(huh.NewText().
Title("Enter your prompt (Type /help for commands, Ctrl+C to quit)").
Value(&prompt)),
).WithWidth(getTerminalWidth()).
WithTheme(huh.ThemeCharm()).
Run()

if err != nil {
// Check if it's a user abort (Ctrl+C)
if err.Error() == "user aborted" {
if errors.Is(err, huh.ErrUserAborted) {
fmt.Println("\nGoodbye!")
return nil // Exit cleanly
}
return err // Return other errors normally
}

prompt = form.GetString("prompt")
if prompt == "" {
continue
}
Expand All @@ -574,7 +587,7 @@ func runMCPHost() error {
if len(messages) > 0 {
messages = pruneMessages(messages)
}
err = runPrompt(provider, mcpClients, allTools, prompt, &messages)
err = runPrompt(ctx, provider, mcpClients, allTools, prompt, &messages)
if err != nil {
return err
}
Expand Down
40 changes: 34 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
module github.com/mark3labs/mcphost

go 1.23
go 1.23.0

require (
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/google/generative-ai-go v0.19.0
github.com/mark3labs/mcp-go v0.8.2
github.com/ollama/ollama v0.5.1
github.com/spf13/cobra v1.8.1
golang.org/x/term v0.22.0
golang.org/x/term v0.30.0
google.golang.org/api v0.228.0
)

require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/ai v0.8.0 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
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/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // 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
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
)

require (
Expand All @@ -47,7 +75,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.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
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
)
Loading