mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-13 19:20:06 +00:00
feat: add --tls-skip-verify flag for self-signed certificates (#115)
* feat: add --tls-skip-verify flag for self-signed certificates Adds support for skipping TLS certificate verification when connecting to providers with self-signed certificates. This is particularly useful for local Ollama instances secured with HTTPS. - Add --tls-skip-verify command-line flag with security warnings - Update ProviderConfig to include TLSSkipVerify field - Modify HTTP client creation for all providers (Ollama, OpenAI, Anthropic, Google, Azure) - Create helper functions for TLS-aware HTTP client creation - Add comprehensive unit tests for TLS skip verify functionality - Update documentation with usage examples and security warnings Fixes #113 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai> * feat: add TLS skip verify support to script mode - Add TLSSkipVerify field to Config struct for script frontmatter - Update script parsing to handle tls-skip-verify in YAML frontmatter - Pass TLS configuration to model creation in script mode - Add example script demonstrating TLS skip verify usage - Update script examples documentation This allows scripts to specify tls-skip-verify: true in their frontmatter to connect to providers with self-signed certificates. 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai> --------- Co-authored-by: opencode <noreply@opencode.ai>
This commit is contained in:
@@ -113,6 +113,13 @@ export GOOGLE_API_KEY='your-api-key'
|
||||
- Get your API server base URL, API key and model name
|
||||
- Use `--provider-url` and `--provider-api-key` flags or set environment variables
|
||||
|
||||
5. Self-Signed Certificates (TLS):
|
||||
If your provider uses self-signed certificates (e.g., local Ollama with HTTPS), you can skip certificate verification:
|
||||
```bash
|
||||
mcphost --provider-url https://192.168.1.100:443 --tls-skip-verify
|
||||
```
|
||||
⚠️ **WARNING**: Only use `--tls-skip-verify` for development or when connecting to trusted servers with self-signed certificates. This disables TLS certificate verification and is insecure for production use.
|
||||
|
||||
## Installation 📦
|
||||
|
||||
```bash
|
||||
@@ -741,6 +748,7 @@ mcphost -p "Generate a random UUID" --quiet | tr '[:lower:]' '[:upper:]'
|
||||
### Flags
|
||||
- `--provider-url string`: Base URL for the provider API (applies to OpenAI, Anthropic, Ollama, and Google)
|
||||
- `--provider-api-key string`: API key for the provider (applies to OpenAI, Anthropic, and Google)
|
||||
- `--tls-skip-verify`: Skip TLS certificate verification (WARNING: insecure, use only for self-signed certificates)
|
||||
- `--config string`: Config file location (default is $HOME/.mcphost.yml)
|
||||
- `--system-prompt string`: system-prompt file location
|
||||
- `--debug`: Enable debug logging
|
||||
@@ -808,6 +816,7 @@ stream: false # Disable streaming (default: true)
|
||||
# API Configuration
|
||||
provider-api-key: "your-api-key" # For OpenAI, Anthropic, or Google
|
||||
provider-url: "https://api.openai.com/v1" # Custom base URL
|
||||
tls-skip-verify: false # Skip TLS certificate verification (default: false)
|
||||
```
|
||||
|
||||
**Note**: Command-line flags take precedence over config file values.
|
||||
|
||||
+11
@@ -55,6 +55,9 @@ var (
|
||||
|
||||
// Hooks control
|
||||
noHooks bool
|
||||
|
||||
// TLS configuration
|
||||
tlsSkipVerify bool
|
||||
)
|
||||
|
||||
// agentUIAdapter adapts agent.Agent to ui.AgentInterface
|
||||
@@ -260,6 +263,7 @@ func init() {
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.StringVar(&providerURL, "provider-url", "", "base URL for the provider API (applies to OpenAI, Anthropic, Ollama, and Google)")
|
||||
flags.StringVar(&providerAPIKey, "provider-api-key", "", "API key for the provider (applies to OpenAI, Anthropic, and Google)")
|
||||
flags.BoolVar(&tlsSkipVerify, "tls-skip-verify", false, "skip TLS certificate verification (WARNING: insecure, use only for self-signed certificates)")
|
||||
|
||||
// Model generation parameters
|
||||
flags.IntVar(&maxTokens, "max-tokens", 4096, "maximum number of tokens in the response")
|
||||
@@ -291,6 +295,7 @@ func init() {
|
||||
viper.BindPFlag("stop-sequences", rootCmd.PersistentFlags().Lookup("stop-sequences"))
|
||||
viper.BindPFlag("num-gpu-layers", rootCmd.PersistentFlags().Lookup("num-gpu-layers"))
|
||||
viper.BindPFlag("main-gpu", rootCmd.PersistentFlags().Lookup("main-gpu"))
|
||||
viper.BindPFlag("tls-skip-verify", rootCmd.PersistentFlags().Lookup("tls-skip-verify"))
|
||||
|
||||
// Defaults are already set in flag definitions, no need to duplicate in viper
|
||||
|
||||
@@ -364,6 +369,7 @@ func runNormalMode(ctx context.Context) error {
|
||||
StopSequences: viper.GetStringSlice("stop-sequences"),
|
||||
NumGPU: &numGPU,
|
||||
MainGPU: &mainGPU,
|
||||
TLSSkipVerify: viper.GetBool("tls-skip-verify"),
|
||||
}
|
||||
|
||||
// Create spinner function for agent creation
|
||||
@@ -448,6 +454,11 @@ func runNormalMode(ctx context.Context) error {
|
||||
"system-prompt": viper.GetString("system-prompt"),
|
||||
}
|
||||
|
||||
// Add TLS skip verify if enabled
|
||||
if viper.GetBool("tls-skip-verify") {
|
||||
debugConfig["tls-skip-verify"] = true
|
||||
}
|
||||
|
||||
// Add Ollama-specific parameters if using Ollama
|
||||
if strings.HasPrefix(viper.GetString("model"), "ollama:") {
|
||||
debugConfig["num-gpu-layers"] = viper.GetInt("num-gpu-layers")
|
||||
|
||||
@@ -134,6 +134,9 @@ func overrideConfigWithFrontmatter(scriptFile string, variables map[string]strin
|
||||
if scriptConfig.Stream != nil && !flagChanged("stream") {
|
||||
viper.Set("stream", *scriptConfig.Stream)
|
||||
}
|
||||
if scriptConfig.TLSSkipVerify && !flagChanged("tls-skip-verify") {
|
||||
viper.Set("tls-skip-verify", scriptConfig.TLSSkipVerify)
|
||||
}
|
||||
}
|
||||
|
||||
// parseCustomVariables extracts custom variables from command line arguments
|
||||
@@ -392,6 +395,9 @@ func parseScriptContent(content string, variables map[string]string) (*config.Co
|
||||
if noExit := frontmatterViper.GetBool("no-exit"); noExit {
|
||||
scriptConfig.NoExit = noExit
|
||||
}
|
||||
if tlsSkipVerify := frontmatterViper.GetBool("tls-skip-verify"); tlsSkipVerify {
|
||||
scriptConfig.TLSSkipVerify = tlsSkipVerify
|
||||
}
|
||||
}
|
||||
|
||||
// Set prompt from content after frontmatter
|
||||
@@ -586,6 +592,7 @@ func runScriptMode(ctx context.Context, mcpConfig *config.Config, prompt string,
|
||||
TopP: &finalTopP,
|
||||
TopK: &finalTopK,
|
||||
StopSequences: finalStopSequences,
|
||||
TLSSkipVerify: viper.GetBool("tls-skip-verify"),
|
||||
}
|
||||
|
||||
// Create the agent using the factory (scripts don't need spinners)
|
||||
|
||||
@@ -28,6 +28,28 @@ mcphost script default-values-demo.sh \
|
||||
--args:format "json"
|
||||
```
|
||||
|
||||
### `tls-test-script.sh`
|
||||
Demonstrates TLS skip verify for connecting to providers with self-signed certificates.
|
||||
|
||||
**Features showcased:**
|
||||
- `tls-skip-verify` configuration in script frontmatter
|
||||
- Connecting to HTTPS endpoints with self-signed certificates
|
||||
- Security considerations for development environments
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Run with TLS skip verify enabled (configured in script)
|
||||
mcphost script tls-test-script.sh
|
||||
|
||||
# Override the provider URL
|
||||
mcphost script tls-test-script.sh --provider-url https://192.168.1.100:443
|
||||
|
||||
# Disable TLS skip verify via command line (overrides script config)
|
||||
mcphost script tls-test-script.sh --tls-skip-verify=false
|
||||
```
|
||||
|
||||
⚠️ **WARNING**: Only use `tls-skip-verify` for development or when connecting to trusted servers with self-signed certificates.
|
||||
|
||||
## Variable Syntax Reference
|
||||
|
||||
MCPHost scripts support two types of variables:
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S mcphost script
|
||||
---
|
||||
# Example script demonstrating TLS skip verify for self-signed certificates
|
||||
model: "ollama:llama3.2"
|
||||
provider-url: "https://localhost:8443"
|
||||
tls-skip-verify: true
|
||||
max-tokens: 1000
|
||||
---
|
||||
Hello! Can you tell me about TLS certificates and why someone might need to skip certificate verification in development environments?
|
||||
@@ -117,6 +117,9 @@ type Config struct {
|
||||
TopP *float32 `json:"top-p,omitempty" yaml:"top-p,omitempty"`
|
||||
TopK *int32 `json:"top-k,omitempty" yaml:"top-k,omitempty"`
|
||||
StopSequences []string `json:"stop-sequences,omitempty" yaml:"stop-sequences,omitempty"`
|
||||
|
||||
// TLS configuration
|
||||
TLSSkipVerify bool `json:"tls-skip-verify,omitempty" yaml:"tls-skip-verify,omitempty"`
|
||||
}
|
||||
|
||||
// GetTransportType returns the transport type for the server config
|
||||
|
||||
@@ -3,6 +3,7 @@ package models
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -78,6 +79,9 @@ type ProviderConfig struct {
|
||||
// Ollama-specific parameters
|
||||
NumGPU *int32
|
||||
MainGPU *int32
|
||||
|
||||
// TLS configuration
|
||||
TLSSkipVerify bool // Skip TLS certificate verification (insecure)
|
||||
}
|
||||
|
||||
// ProviderResult contains the result of provider creation
|
||||
@@ -216,6 +220,11 @@ func createAzureOpenAIProvider(ctx context.Context, config *ProviderConfig, mode
|
||||
azureConfig.Stop = config.StopSequences
|
||||
}
|
||||
|
||||
// Set HTTP client with TLS config if needed
|
||||
if config.TLSSkipVerify {
|
||||
azureConfig.HTTPClient = createHTTPClientWithTLSConfig(true)
|
||||
}
|
||||
|
||||
return openai.NewCustomChatModel(ctx, azureConfig)
|
||||
}
|
||||
|
||||
@@ -246,12 +255,16 @@ func createAnthropicProvider(ctx context.Context, config *ProviderConfig, modelN
|
||||
if strings.HasPrefix(source, "stored OAuth") {
|
||||
// For OAuth tokens, we need to use Authorization: Bearer header
|
||||
// Create a custom HTTP client that adds the proper headers
|
||||
claudeConfig.HTTPClient = createOAuthHTTPClient(apiKey)
|
||||
claudeConfig.HTTPClient = createOAuthHTTPClient(apiKey, config.TLSSkipVerify)
|
||||
// Set a dummy API key to prevent the library from failing validation
|
||||
claudeConfig.APIKey = "oauth-placeholder"
|
||||
} else {
|
||||
// For API keys, use the standard x-api-key header
|
||||
claudeConfig.APIKey = apiKey
|
||||
// Set HTTP client with TLS config if needed
|
||||
if config.TLSSkipVerify {
|
||||
claudeConfig.HTTPClient = createHTTPClientWithTLSConfig(true)
|
||||
}
|
||||
}
|
||||
|
||||
if config.ProviderURL != "" {
|
||||
@@ -295,6 +308,11 @@ func createOpenAIProvider(ctx context.Context, config *ProviderConfig, modelName
|
||||
openaiConfig.BaseURL = config.ProviderURL
|
||||
}
|
||||
|
||||
// Set HTTP client with TLS config if needed
|
||||
if config.TLSSkipVerify {
|
||||
openaiConfig.HTTPClient = createHTTPClientWithTLSConfig(true)
|
||||
}
|
||||
|
||||
// Check if this is a reasoning model to handle beta limitations (skip validation if using custom URL)
|
||||
registry := GetGlobalRegistry()
|
||||
isReasoningModel := false
|
||||
@@ -350,10 +368,17 @@ func createGoogleProvider(ctx context.Context, config *ProviderConfig, modelName
|
||||
return nil, fmt.Errorf("Google API key not provided. Use --provider-api-key flag or GOOGLE_API_KEY/GEMINI_API_KEY/GOOGLE_GENERATIVE_AI_API_KEY environment variable")
|
||||
}
|
||||
|
||||
client, err := genai.NewClient(ctx, &genai.ClientConfig{
|
||||
clientConfig := &genai.ClientConfig{
|
||||
APIKey: apiKey,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
})
|
||||
}
|
||||
|
||||
// Set HTTP client with TLS config if needed
|
||||
if config.TLSSkipVerify {
|
||||
clientConfig.HTTPClient = createHTTPClientWithTLSConfig(true)
|
||||
}
|
||||
|
||||
client, err := genai.NewClient(ctx, clientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Google client: %v", err)
|
||||
}
|
||||
@@ -389,8 +414,8 @@ type OllamaLoadingResult struct {
|
||||
}
|
||||
|
||||
// loadOllamaModelWithFallback loads an Ollama model with GPU settings and automatic CPU fallback
|
||||
func loadOllamaModelWithFallback(ctx context.Context, baseURL, modelName string, options *api.Options) (*OllamaLoadingResult, error) {
|
||||
client := &http.Client{}
|
||||
func loadOllamaModelWithFallback(ctx context.Context, baseURL, modelName string, options *api.Options, tlsSkipVerify bool) (*OllamaLoadingResult, error) {
|
||||
client := createHTTPClientWithTLSConfig(tlsSkipVerify)
|
||||
|
||||
// Phase 1: Check if model exists locally
|
||||
if err := checkOllamaModelExists(client, baseURL, modelName); err != nil {
|
||||
@@ -601,7 +626,7 @@ func createOllamaProviderWithResult(ctx context.Context, config *ProviderConfig,
|
||||
|
||||
// Try to pre-load the model with GPU settings and automatic CPU fallback
|
||||
// If this fails, fall back to the original behavior
|
||||
loadingResult, err := loadOllamaModelWithFallback(ctx, baseURL, modelName, options)
|
||||
loadingResult, err := loadOllamaModelWithFallback(ctx, baseURL, modelName, options, config.TLSSkipVerify)
|
||||
var loadingMessage string
|
||||
|
||||
if err != nil {
|
||||
@@ -620,6 +645,11 @@ func createOllamaProviderWithResult(ctx context.Context, config *ProviderConfig,
|
||||
Options: finalOptions,
|
||||
}
|
||||
|
||||
// Set HTTP client with TLS config if needed
|
||||
if config.TLSSkipVerify {
|
||||
ollamaConfig.HTTPClient = createHTTPClientWithTLSConfig(true)
|
||||
}
|
||||
|
||||
chatModel, err := ollama.NewChatModel(ctx, ollamaConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -631,12 +661,38 @@ func createOllamaProviderWithResult(ctx context.Context, config *ProviderConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createHTTPClientWithTLSConfig creates an HTTP client with optional TLS skip verify
|
||||
func createHTTPClientWithTLSConfig(skipVerify bool) *http.Client {
|
||||
if !skipVerify {
|
||||
return &http.Client{}
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
// createOAuthHTTPClient creates an HTTP client that adds OAuth headers for Anthropic API
|
||||
func createOAuthHTTPClient(accessToken string) *http.Client {
|
||||
func createOAuthHTTPClient(accessToken string, skipVerify bool) *http.Client {
|
||||
var base http.RoundTripper = http.DefaultTransport
|
||||
if skipVerify {
|
||||
base = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &oauthTransport{
|
||||
accessToken: accessToken,
|
||||
base: http.DefaultTransport,
|
||||
base: base,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateHTTPClientWithTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
skipVerify bool
|
||||
wantInsecure bool
|
||||
}{
|
||||
{
|
||||
name: "skip verify disabled",
|
||||
skipVerify: false,
|
||||
wantInsecure: false,
|
||||
},
|
||||
{
|
||||
name: "skip verify enabled",
|
||||
skipVerify: true,
|
||||
wantInsecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := createHTTPClientWithTLSConfig(tt.skipVerify)
|
||||
|
||||
if client == nil {
|
||||
t.Fatal("expected non-nil client")
|
||||
}
|
||||
|
||||
// Check if the client has a custom transport when skipVerify is true
|
||||
if tt.skipVerify {
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatal("expected *http.Transport when skipVerify is true")
|
||||
}
|
||||
|
||||
if transport.TLSClientConfig == nil {
|
||||
t.Fatal("expected non-nil TLSClientConfig when skipVerify is true")
|
||||
}
|
||||
|
||||
if transport.TLSClientConfig.InsecureSkipVerify != tt.wantInsecure {
|
||||
t.Errorf("InsecureSkipVerify = %v, want %v",
|
||||
transport.TLSClientConfig.InsecureSkipVerify, tt.wantInsecure)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOAuthHTTPClient(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
accessToken string
|
||||
skipVerify bool
|
||||
wantInsecure bool
|
||||
}{
|
||||
{
|
||||
name: "oauth with skip verify disabled",
|
||||
accessToken: "test-token",
|
||||
skipVerify: false,
|
||||
wantInsecure: false,
|
||||
},
|
||||
{
|
||||
name: "oauth with skip verify enabled",
|
||||
accessToken: "test-token",
|
||||
skipVerify: true,
|
||||
wantInsecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := createOAuthHTTPClient(tt.accessToken, tt.skipVerify)
|
||||
|
||||
if client == nil {
|
||||
t.Fatal("expected non-nil client")
|
||||
}
|
||||
|
||||
// Check that the transport is an oauthTransport
|
||||
oauthTransport, ok := client.Transport.(*oauthTransport)
|
||||
if !ok {
|
||||
t.Fatal("expected *oauthTransport")
|
||||
}
|
||||
|
||||
if oauthTransport.accessToken != tt.accessToken {
|
||||
t.Errorf("accessToken = %v, want %v", oauthTransport.accessToken, tt.accessToken)
|
||||
}
|
||||
|
||||
// Check the base transport when skipVerify is true
|
||||
if tt.skipVerify {
|
||||
baseTransport, ok := oauthTransport.base.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatal("expected base transport to be *http.Transport when skipVerify is true")
|
||||
}
|
||||
|
||||
if baseTransport.TLSClientConfig == nil {
|
||||
t.Fatal("expected non-nil TLSClientConfig when skipVerify is true")
|
||||
}
|
||||
|
||||
if baseTransport.TLSClientConfig.InsecureSkipVerify != tt.wantInsecure {
|
||||
t.Errorf("InsecureSkipVerify = %v, want %v",
|
||||
baseTransport.TLSClientConfig.InsecureSkipVerify, tt.wantInsecure)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderConfigTLSSkipVerify(t *testing.T) {
|
||||
// Test that ProviderConfig properly stores TLSSkipVerify
|
||||
config := &ProviderConfig{
|
||||
ModelString: "test:model",
|
||||
TLSSkipVerify: true,
|
||||
}
|
||||
|
||||
if !config.TLSSkipVerify {
|
||||
t.Error("expected TLSSkipVerify to be true")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user