added auth to remote mcps
This commit is contained in:
@@ -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,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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user