added auth to remote mcps

This commit is contained in:
Luxferre
2026-03-21 16:29:28 +02:00
parent d24150d2e9
commit c2bc527b30
3 changed files with 107 additions and 46 deletions
+54 -30
View File
@@ -54,8 +54,6 @@ This file defines your models, MCP servers, and agent profiles.
tool_response_threshold = 10000 # Bytes after which tool results are buffered to files tool_response_threshold = 10000 # Bytes after which tool results are buffered to files
[models.default] [models.default]
```
model_id = "env:DEFAULT_MODEL_ID" model_id = "env:DEFAULT_MODEL_ID"
endpoint = "env:DEFAULT_MODEL_ENDPOINT" endpoint = "env:DEFAULT_MODEL_ENDPOINT"
api_key = "env:DEFAULT_MODEL_API_KEY" api_key = "env:DEFAULT_MODEL_API_KEY"
@@ -67,6 +65,12 @@ transport = "stdio"
command = "npx" command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
[mcp_servers.remote_tools]
transport = "sse"
url = "https://api.example.com/mcp"
auth_token = "env:MCP_API_KEY" # Optional Bearer token
headers = { "X-Custom-Header" = "value" } # Optional custom headers
[agents.coordinator] [agents.coordinator]
role = "COORDINATOR" role = "COORDINATOR"
model_id = "default" model_id = "default"
@@ -105,6 +109,26 @@ user's request and delegate tasks accordingly.
./bin/sidekick-tui ./bin/sidekick-tui
``` ```
### Running the Sidekick MCP Server
Sidekick can also act as an MCP server, allowing other LLM-powered applications to leverage its agentic capabilities.
1. Configure the `mcp_listener` in `config.toml`:
```toml
[mcp_listener]
transport = "sse" # "stdio" or "sse"/"http"
port = 8080 # for sse/http
```
2. Run the MCP server:
```bash
./bin/sidekick-mcp
```
The server provides a `query` tool that routes requests to your coordinator agent, maintaining conversation history if provided.
* **Input:** Type your messages at the bottom prompt. * **Input:** Type your messages at the bottom prompt.
* **Send:** Press `Enter` to send a message. * **Send:** Press `Enter` to send a message.
* **Quit:** Press `ctrl+c` to exit. * **Quit:** Press `ctrl+c` to exit.
@@ -116,43 +140,43 @@ user's request and delegate tasks accordingly.
package main package main
import ( import (
"context" "context"
"fmt" "fmt"
"log" "log"
"github.com/yourusername/sidekick-ng" "github.com/yourusername/sidekick-ng"
) )
func main() { func main() {
// 1. Load Configuration // 1. Load Configuration
cfg, err := sidekick.LoadConfig("config.toml") cfg, err := sidekick.LoadConfig("config.toml")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// 2. Load and Apply Prompts (Optional) // 2. Load and Apply Prompts (Optional)
tmpl, _ := sidekick.LoadPrompts("prompts.toml") tmpl, _ := sidekick.LoadPrompts("prompts.toml")
// 3. Initialize Agent // 3. Initialize Agent
agentCfg := cfg.Agents["coordinator"] agentCfg := cfg.Agents["coordinator"]
// Apply template override if exists // Apply template override if exists
if tmpl != nil && tmpl.Lookup("coordinator") != nil { if tmpl != nil && tmpl.Lookup("coordinator") != nil {
if rendered, err := sidekick.RenderPrompt(tmpl, "coordinator"); err == nil { if rendered, err := sidekick.RenderPrompt(tmpl, "coordinator"); err == nil {
agentCfg.SystemPrompt = rendered agentCfg.SystemPrompt = rendered
} }
} }
agent := sidekick.NewAgent("coordinator", agentCfg, cfg) agent := sidekick.NewAgent("coordinator", agentCfg, cfg)
// 4. Run the Agent Loop // 4. Run the Agent Loop
ctx := context.Background() ctx := context.Background()
result, err := agent.Run(ctx, "What files are in the /tmp directory?") result, err := agent.Run(ctx, "What files are in the /tmp directory?")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Println(result) fmt.Println(result)
} }
``` ```
+25 -11
View File
@@ -25,22 +25,24 @@ type MCPListenerConfig struct {
// ModelConfig holds configuration for the LLM // ModelConfig holds configuration for the LLM
type ModelConfig struct { type ModelConfig struct {
ModelID string `toml:"model_id"` ModelID string `toml:"model_id"`
Endpoint string `toml:"endpoint"` Endpoint string `toml:"endpoint"`
APIKey string `toml:"api_key"` APIKey string `toml:"api_key"`
Temperature float32 `toml:"temperature"` Temperature float32 `toml:"temperature"`
MaxTokens int `toml:"max_tokens"` MaxTokens int `toml:"max_tokens"`
TimeoutSecs int `toml:"timeout_secs"` TimeoutSecs int `toml:"timeout_secs"`
Timeout time.Duration `toml:"-"` Timeout time.Duration `toml:"-"`
} }
// MCPServerConfig configures a single MCP server connection // MCPServerConfig configures a single MCP server connection
type MCPServerConfig struct { type MCPServerConfig struct {
Transport string `toml:"transport"` // "stdio", "sse", "http", "websocket" Transport string `toml:"transport"` // "stdio", "sse", "http", "websocket"
Command string `toml:"command"` // for stdio Command string `toml:"command"` // for stdio
Args []string `toml:"args"` // for stdio Args []string `toml:"args"` // for stdio
Env []string `toml:"env"` // for stdio, format "KEY=VALUE" Env []string `toml:"env"` // for stdio, format "KEY=VALUE"
URL string `toml:"url"` // for HTTP/SSE/WebSocket URL string `toml:"url"` // for HTTP/SSE/WebSocket
AuthToken string `toml:"auth_token"` // for HTTP Bearer authentication
Headers map[string]string `toml:"headers"` // for custom headers
} }
// AgentConfig holds configuration for a specific agent instance // AgentConfig holds configuration for a specific agent instance
@@ -66,6 +68,14 @@ func resolveEnv(val string) string {
return val return val
} }
func (s *MCPServerConfig) Resolve() {
s.URL = resolveEnv(s.URL)
s.AuthToken = resolveEnv(s.AuthToken)
for k, v := range s.Headers {
s.Headers[k] = resolveEnv(v)
}
}
// Resolve applies environment variable resolution to relevant fields // Resolve applies environment variable resolution to relevant fields
func (m *ModelConfig) Resolve() { func (m *ModelConfig) Resolve() {
m.ModelID = resolveEnv(m.ModelID) m.ModelID = resolveEnv(m.ModelID)
@@ -92,6 +102,10 @@ func LoadConfig(path string) (*ProjectConfig, error) {
m.Resolve() m.Resolve()
cfg.Models[k] = m cfg.Models[k] = m
} }
for k, s := range cfg.MCPServers {
s.Resolve()
cfg.MCPServers[k] = s
}
for k, a := range cfg.Agents { for k, a := range cfg.Agents {
a.ID = k a.ID = k
a.ToolResponseThreshold = cfg.ToolResponseThreshold a.ToolResponseThreshold = cfg.ToolResponseThreshold
+28 -5
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/client/transport"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
) )
@@ -22,12 +23,12 @@ func (w *mcpGoClientWrapper) CallTool(ctx context.Context, name string, args map
req := mcp.CallToolRequest{} req := mcp.CallToolRequest{}
req.Params.Name = name req.Params.Name = name
req.Params.Arguments = args req.Params.Arguments = args
res, err := w.client.CallTool(ctx, req) res, err := w.client.CallTool(ctx, req)
if err != nil { if err != nil {
return "", err return "", err
} }
if res.IsError { if res.IsError {
return "", fmt.Errorf("tool returned error") return "", fmt.Errorf("tool returned error")
} }
@@ -71,9 +72,31 @@ var defaultMCPFactory MCPClientFactory = func(toolset string) (MCPClientInterfac
case "stdio": case "stdio":
c, err = client.NewStdioMCPClient(cfg.Command, cfg.Env, cfg.Args...) c, err = client.NewStdioMCPClient(cfg.Command, cfg.Env, cfg.Args...)
case "sse": case "sse":
c, err = client.NewSSEMCPClient(cfg.URL) var opts []transport.ClientOption
headers := make(map[string]string)
if cfg.AuthToken != "" {
headers["Authorization"] = "Bearer " + cfg.AuthToken
}
for k, v := range cfg.Headers {
headers[k] = v
}
if len(headers) > 0 {
opts = append(opts, transport.WithHeaders(headers))
}
c, err = client.NewSSEMCPClient(cfg.URL, opts...)
case "http": case "http":
c, err = client.NewStreamableHttpClient(cfg.URL) var opts []transport.StreamableHTTPCOption
headers := make(map[string]string)
if cfg.AuthToken != "" {
headers["Authorization"] = "Bearer " + cfg.AuthToken
}
for k, v := range cfg.Headers {
headers[k] = v
}
if len(headers) > 0 {
opts = append(opts, transport.WithHTTPHeaders(headers))
}
c, err = client.NewStreamableHttpClient(cfg.URL, opts...)
default: default:
return nil, fmt.Errorf("unsupported MCP transport: %s", cfg.Transport) return nil, fmt.Errorf("unsupported MCP transport: %s", cfg.Transport)
} }
@@ -89,7 +112,7 @@ var defaultMCPFactory MCPClientFactory = func(toolset string) (MCPClientInterfac
Name: "Sidekick", Name: "Sidekick",
Version: "1.0.0", Version: "1.0.0",
} }
_, err = c.Initialize(context.Background(), initReq) _, err = c.Initialize(context.Background(), initReq)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to initialize MCP client: %w", err) return nil, fmt.Errorf("failed to initialize MCP client: %w", err)