mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-14 03:30:26 +00:00
streamable HTTP
This commit is contained in:
@@ -151,6 +151,40 @@ Each SSE entry requires:
|
||||
- `url`: The URL where the MCP server is accessible.
|
||||
- `headers`: (Optional) Array of headers that will be attached to the requests
|
||||
|
||||
### Streamable HTTP
|
||||
|
||||
For Streamable HTTP transport, use the following configuration:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"websearch": {
|
||||
"transport": "streamable",
|
||||
"url": "https://api.example.com/mcp",
|
||||
"headers": [
|
||||
"Authorization: Bearer your-api-token",
|
||||
"Content-Type: application/json"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each Streamable HTTP entry requires:
|
||||
- `transport`: Must be set to `"streamable"`
|
||||
- `url`: The URL where the MCP server is accessible
|
||||
- `headers`: (Optional) Array of headers that will be attached to the requests
|
||||
|
||||
### Transport Types
|
||||
|
||||
MCPHost supports three transport types:
|
||||
- **`stdio`** (default): Launches a local process and communicates via stdin/stdout
|
||||
- **`sse`**: Connects to a server using Server-Sent Events
|
||||
- **`streamable`**: Connects to a server using Streamable HTTP protocol
|
||||
|
||||
If no `transport` field is specified, MCPHost will automatically detect the transport type:
|
||||
- If `command` is present → `stdio`
|
||||
- If `url` is present → `sse`
|
||||
|
||||
### System Prompt
|
||||
|
||||
You can specify a custom system prompt using the `--system-prompt` flag. You can either:
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ func initConfig() {
|
||||
if configFile != "" {
|
||||
// Use config file from the flag
|
||||
viper.SetConfigFile(configFile)
|
||||
|
||||
|
||||
// Try to read the specified config file
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading config file '%s': %v\n", configFile, err)
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/cloudwego/eino-ext/components/model/ollama v0.0.0-20250609074000-b7f307dffa18
|
||||
github.com/cloudwego/eino-ext/components/model/openai v0.0.0-20250609074000-b7f307dffa18
|
||||
github.com/getkin/kin-openapi v0.118.0
|
||||
github.com/mark3labs/mcp-go v0.31.0
|
||||
github.com/mark3labs/mcp-go v0.32.0
|
||||
github.com/ollama/ollama v0.5.12
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
|
||||
@@ -196,8 +196,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
|
||||
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
|
||||
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
// MCPServerConfig represents configuration for an MCP server
|
||||
type MCPServerConfig struct {
|
||||
Transport string `json:"transport,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Env map[string]any `json:"env,omitempty"`
|
||||
@@ -38,12 +39,41 @@ type Config struct {
|
||||
StopSequences []string `json:"stop-sequences,omitempty" yaml:"stop-sequences,omitempty"`
|
||||
}
|
||||
|
||||
// GetTransportType returns the transport type for the server config
|
||||
func (s *MCPServerConfig) GetTransportType() string {
|
||||
if s.Transport != "" {
|
||||
return s.Transport
|
||||
}
|
||||
// Backward compatibility: infer transport type
|
||||
if s.Command != "" {
|
||||
return "stdio"
|
||||
}
|
||||
if s.URL != "" {
|
||||
return "sse"
|
||||
}
|
||||
return "stdio" // default
|
||||
}
|
||||
|
||||
// Validate validates the configuration
|
||||
func (c *Config) Validate() error {
|
||||
for serverName, serverConfig := range c.MCPServers {
|
||||
if len(serverConfig.AllowedTools) > 0 && len(serverConfig.ExcludedTools) > 0 {
|
||||
return fmt.Errorf("server %s: allowedTools and excludedTools are mutually exclusive", serverName)
|
||||
}
|
||||
|
||||
transport := serverConfig.GetTransportType()
|
||||
switch transport {
|
||||
case "stdio":
|
||||
if serverConfig.Command == "" {
|
||||
return fmt.Errorf("server %s: command is required for stdio transport", serverName)
|
||||
}
|
||||
case "sse", "streamable":
|
||||
if serverConfig.URL == "" {
|
||||
return fmt.Errorf("server %s: url is required for %s transport", serverName, transport)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("server %s: unsupported transport type '%s'. Supported types: stdio, sse, streamable", serverName, transport)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -110,14 +140,27 @@ func createDefaultConfig(homeDir string) error {
|
||||
|
||||
# MCP Servers configuration
|
||||
# Add your MCP servers here
|
||||
# Example:
|
||||
# Examples for different transport types:
|
||||
# mcpServers:
|
||||
# # STDIO transport (default) - launches local processes
|
||||
# filesystem:
|
||||
# command: npx
|
||||
# args: ["@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
|
||||
# sqlite:
|
||||
# command: uvx
|
||||
# args: ["mcp-server-sqlite", "--db-path", "/tmp/example.db"]
|
||||
#
|
||||
# # SSE transport - connects to remote servers via Server-Sent Events
|
||||
# remote-sse:
|
||||
# transport: sse
|
||||
# url: "https://api.example.com/sse"
|
||||
# headers: ["Authorization: Bearer your-token"]
|
||||
#
|
||||
# # Streamable HTTP transport - connects via Streamable HTTP protocol
|
||||
# websearch:
|
||||
# transport: streamable
|
||||
# url: "https://api.example.com/mcp"
|
||||
# headers: ["Authorization: Bearer your-api-token"]
|
||||
|
||||
mcpServers:
|
||||
|
||||
|
||||
+434
-434
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
package tokens
|
||||
package tokens
|
||||
|
||||
@@ -4,4 +4,4 @@ package tokens
|
||||
func EstimateTokens(text string) int {
|
||||
// Rough approximation: ~4 characters per token for most models
|
||||
return len(text) / 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,4 @@ func InitializeTokenCounters() {
|
||||
// InitializeTokenCountersWithKeys registers token counters with provided API keys
|
||||
func InitializeTokenCountersWithKeys() {
|
||||
// Future provider-specific counters can be registered here
|
||||
}
|
||||
}
|
||||
|
||||
+43
-4
@@ -4,12 +4,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/cloudwego/eino/components/tool"
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/mark3labs/mcp-go/client"
|
||||
"github.com/mark3labs/mcp-go/client/transport"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcphost/internal/config"
|
||||
)
|
||||
@@ -192,7 +194,10 @@ func (m *MCPToolManager) shouldExcludeTool(toolName string, serverConfig config.
|
||||
}
|
||||
|
||||
func (m *MCPToolManager) createMCPClient(ctx context.Context, serverName string, serverConfig config.MCPServerConfig) (client.MCPClient, error) {
|
||||
if serverConfig.Command != "" {
|
||||
transportType := serverConfig.GetTransportType()
|
||||
|
||||
switch transportType {
|
||||
case "stdio":
|
||||
// STDIO client
|
||||
env := make([]string, 0, len(serverConfig.Env))
|
||||
for k, v := range serverConfig.Env {
|
||||
@@ -200,7 +205,8 @@ func (m *MCPToolManager) createMCPClient(ctx context.Context, serverName string,
|
||||
}
|
||||
|
||||
return client.NewStdioMCPClient(serverConfig.Command, env, serverConfig.Args...)
|
||||
} else if serverConfig.URL != "" {
|
||||
|
||||
case "sse":
|
||||
// SSE client
|
||||
sseClient, err := client.NewSSEMCPClient(serverConfig.URL)
|
||||
if err != nil {
|
||||
@@ -213,9 +219,42 @@ func (m *MCPToolManager) createMCPClient(ctx context.Context, serverName string,
|
||||
}
|
||||
|
||||
return sseClient, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid server configuration for %s: must specify either command or url", serverName)
|
||||
case "streamable":
|
||||
// Streamable HTTP client
|
||||
var options []transport.StreamableHTTPCOption
|
||||
|
||||
// Add headers if specified
|
||||
if len(serverConfig.Headers) > 0 {
|
||||
headers := make(map[string]string)
|
||||
for _, header := range serverConfig.Headers {
|
||||
parts := strings.SplitN(header, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
headers[key] = value
|
||||
}
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
options = append(options, transport.WithHTTPHeaders(headers))
|
||||
}
|
||||
}
|
||||
|
||||
streamableClient, err := client.NewStreamableHttpClient(serverConfig.URL, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start the streamable HTTP client
|
||||
if err := streamableClient.Start(ctx); err != nil {
|
||||
return nil, fmt.Errorf("failed to start streamable HTTP client: %v", err)
|
||||
}
|
||||
|
||||
return streamableClient, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported transport type '%s' for server %s", transportType, serverName)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MCPToolManager) initializeClient(ctx context.Context, client client.MCPClient) error {
|
||||
|
||||
+3
-5
@@ -322,8 +322,6 @@ func (c *CLI) UpdateUsage(inputText, outputText string) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// UpdateUsageFromResponse updates the usage tracker using token usage from response metadata
|
||||
func (c *CLI) UpdateUsageFromResponse(response *schema.Message, inputText string) {
|
||||
if c.usageTracker == nil {
|
||||
@@ -333,15 +331,15 @@ func (c *CLI) UpdateUsageFromResponse(response *schema.Message, inputText string
|
||||
// Try to extract token usage from response metadata
|
||||
if response.ResponseMeta != nil && response.ResponseMeta.Usage != nil {
|
||||
usage := response.ResponseMeta.Usage
|
||||
|
||||
|
||||
// Use actual token counts from the response
|
||||
inputTokens := int(usage.PromptTokens)
|
||||
outputTokens := int(usage.CompletionTokens)
|
||||
|
||||
|
||||
// Handle cache tokens if available (some providers support this)
|
||||
cacheReadTokens := 0
|
||||
cacheWriteTokens := 0
|
||||
|
||||
|
||||
c.usageTracker.UpdateUsage(inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens)
|
||||
} else {
|
||||
// Fallback to estimation if no metadata is available
|
||||
|
||||
Reference in New Issue
Block a user