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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ jobs:
with:
go-version-file: 'go.mod'
- run: go test ./... -race
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Discuss the Project on [Discord](https://discord.gg/RqSS2NQVsY)
- [Requirements](#requirements-)
- [Environment Setup](#environment-setup-)
- [Installation](#installation-)
- [SDK Usage](#sdk-usage-)
- [Configuration](#configuration-)
- [MCP Servers](#mcp-servers)
- [Environment Variable Substitution](#environment-variable-substitution)
Expand Down Expand Up @@ -126,6 +127,52 @@ mcphost --provider-url https://192.168.1.100:443 --tls-skip-verify
go install github.com/mark3labs/mcphost@latest
```

## SDK Usage 🛠️

MCPHost also provides a Go SDK for programmatic access without spawning OS processes. The SDK maintains identical behavior to the CLI, including configuration loading, environment variables, and defaults.

### Quick Example

```go
package main

import (
"context"
"fmt"
"github.com/mark3labs/mcphost/sdk"
)

func main() {
ctx := context.Background()

// Create MCPHost instance with default configuration
host, err := sdk.New(ctx, nil)
if err != nil {
panic(err)
}
defer host.Close()

// Send a prompt and get response
response, err := host.Prompt(ctx, "What is 2+2?")
if err != nil {
panic(err)
}

fmt.Println(response)
}
```

### SDK Features

- ✅ Programmatic access without spawning processes
- ✅ Identical configuration behavior to CLI
- ✅ Session management (save/load/clear)
- ✅ Tool execution callbacks for monitoring
- ✅ Streaming support
- ✅ Full compatibility with all providers and MCP servers

For detailed SDK documentation, examples, and API reference, see the [SDK README](sdk/README.md).

## Configuration ⚙️

### MCP Servers
Expand Down
12 changes: 6 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ func GetRootCommand(v string) *cobra.Command {
return rootCmd
}

func initConfig() {
func InitConfig() {
if configFile != "" {
// Use config file from the flag
if err := loadConfigWithEnvSubstitution(configFile); err != nil {
if err := LoadConfigWithEnvSubstitution(configFile); err != nil {
fmt.Fprintf(os.Stderr, "Error reading config file '%s': %v\n", configFile, err)
os.Exit(1)
}
Expand Down Expand Up @@ -163,7 +163,7 @@ func initConfig() {
if err := viper.ReadInConfig(); err == nil {
// Config file found, now reload it with env substitution
configPath := viper.ConfigFileUsed()
if err := loadConfigWithEnvSubstitution(configPath); err != nil {
if err := LoadConfigWithEnvSubstitution(configPath); err != nil {
// Only exit on environment variable substitution errors
if strings.Contains(err.Error(), "environment variable substitution failed") {
fmt.Fprintf(os.Stderr, "Error reading config file '%s': %v\n", configPath, err)
Expand Down Expand Up @@ -202,8 +202,8 @@ func initConfig() {

}

// loadConfigWithEnvSubstitution loads a config file with environment variable substitution
func loadConfigWithEnvSubstitution(configPath string) error {
// LoadConfigWithEnvSubstitution loads a config file with environment variable substitution
func LoadConfigWithEnvSubstitution(configPath string) error {
// Read raw config file content
rawContent, err := os.ReadFile(configPath)
if err != nil {
Expand Down Expand Up @@ -252,7 +252,7 @@ func configToUiTheme(theme config.Theme) ui.Theme {
}

func init() {
cobra.OnInitialize(initConfig)
cobra.OnInitialize(InitConfig)
var theme config.Theme
err := config.FilepathOr("theme", &theme)
if err == nil && viper.InConfig("theme") {
Expand Down
3 changes: 3 additions & 0 deletions internal/hooks/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"testing"
)

Expand Down Expand Up @@ -137,6 +138,8 @@ hooks:
}
paths = append(paths, path)
}
// Sort paths to ensure deterministic order
sort.Strings(paths)

// Load configuration
got, err := LoadHooksConfig(paths...)
Expand Down
148 changes: 148 additions & 0 deletions sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# MCPHost SDK

The MCPHost SDK allows you to use MCPHost programmatically from Go applications without spawning OS processes.

## Installation

```bash
go get github.com/mark3labs/mcphost
```

## Basic Usage

```go
package main

import (
"context"
"fmt"
"log"

"github.com/mark3labs/mcphost/sdk"
)

func main() {
ctx := context.Background()

// Create MCPHost instance with default configuration
host, err := sdk.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer host.Close()

// Send a prompt
response, err := host.Prompt(ctx, "What is 2+2?")
if err != nil {
log.Fatal(err)
}

fmt.Println(response)
}
```

## Configuration

The SDK behaves identically to the CLI:
- Loads configuration from `~/.mcphost.yml` by default
- Creates default configuration if none exists
- Respects all environment variables (`MCPHOST_*`)
- Uses the same defaults as the CLI

### Options

You can override specific settings:

```go
host, err := sdk.New(ctx, &sdk.Options{
Model: "ollama:llama3", // Override model
SystemPrompt: "You are a helpful bot", // Override system prompt
ConfigFile: "/path/to/config.yml", // Use specific config file
MaxSteps: 10, // Override max steps
Streaming: true, // Enable streaming
Quiet: true, // Suppress debug output
})
```

## Advanced Usage

### With Tool Callbacks

Monitor tool execution in real-time:

```go
response, err := host.PromptWithCallbacks(
ctx,
"List files in the current directory",
func(name, args string) {
fmt.Printf("Calling tool: %s\n", name)
},
func(name, args, result string, isError bool) {
if isError {
fmt.Printf("Tool %s failed: %s\n", name, result)
} else {
fmt.Printf("Tool %s succeeded\n", name)
}
},
func(chunk string) {
fmt.Print(chunk) // Stream output
},
)
```

### Session Management

Maintain conversation context:

```go
// First message
host.Prompt(ctx, "My name is Alice")

// Second message (remembers context)
response, _ := host.Prompt(ctx, "What's my name?")
// Response: "Your name is Alice"

// Save session
host.SaveSession("./session.json")

// Load session later
host.LoadSession("./session.json")

// Clear session
host.ClearSession()
```

## API Reference

### Types

- `MCPHost` - Main SDK type
- `Options` - Configuration options
- `Message` - Conversation message
- `ToolCall` - Tool invocation details

### Methods

- `New(ctx, opts)` - Create new MCPHost instance
- `Prompt(ctx, message)` - Send message and get response
- `PromptWithCallbacks(ctx, message, ...)` - Send message with progress callbacks
- `LoadSession(path)` - Load session from file
- `SaveSession(path)` - Save session to file
- `ClearSession()` - Clear conversation history
- `GetSessionManager()` - Get session manager for advanced usage
- `GetModelString()` - Get current model string
- `Close()` - Clean up resources

## Environment Variables

All CLI environment variables work with the SDK:

- `MCPHOST_MODEL` - Override model
- `ANTHROPIC_API_KEY` - Anthropic API key
- `OPENAI_API_KEY` - OpenAI API key
- `GEMINI_API_KEY` - Google API key
- etc.

## License

Same as MCPHost CLI
100 changes: 100 additions & 0 deletions sdk/examples/basic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"context"
"fmt"
"log"

"github.com/mark3labs/mcphost/sdk"
)

func main() {
ctx := context.Background()

// Example 1: Use all defaults (loads ~/.mcphost.yml)
fmt.Println("=== Example 1: Default configuration ===")
host, err := sdk.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer host.Close()

response, err := host.Prompt(ctx, "What is 2+2?")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n\n", response)

// Example 2: Override model
fmt.Println("=== Example 2: Custom model ===")
host2, err := sdk.New(ctx, &sdk.Options{
Model: "ollama:qwen3:8b",
})
if err != nil {
log.Fatal(err)
}
defer host2.Close()

response, err = host2.Prompt(ctx, "Tell me a short joke")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n\n", response)

// Example 3: With callbacks
fmt.Println("=== Example 3: With tool callbacks ===")
host3, err := sdk.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer host3.Close()

response, err = host3.PromptWithCallbacks(
ctx,
"List files in the current directory",
func(name, args string) {
fmt.Printf("🔧 Calling tool: %s\n", name)
},
func(name, args, result string, isError bool) {
if isError {
fmt.Printf("❌ Tool %s failed\n", name)
} else {
fmt.Printf("✅ Tool %s completed\n", name)
}
},
func(chunk string) {
fmt.Print(chunk) // Stream output
},
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nFinal response: %s\n", response)

// Example 4: Session management
fmt.Println("\n=== Example 4: Session management ===")
host4, err := sdk.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer host4.Close()

// First message
_, err = host4.Prompt(ctx, "Remember that my favorite color is blue")
if err != nil {
log.Fatal(err)
}

// Second message (should remember context)
response, err = host4.Prompt(ctx, "What's my favorite color?")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", response)

// Save session
if err := host4.SaveSession("./session.json"); err != nil {
log.Fatal(err)
}
fmt.Println("Session saved to ./session.json")
}
Loading
Loading