fix: run MCP servers from project directory
Fixes #68 Co-Authored-By: Claude <noreply@anthropic.com>
- commit
- fb35dff1f4dd556a9a545e84ab2453aed0ae5081
- author
- zerone0x · · 2026-07-07 20:06 UTC
- parent
- 2bf9508
- signature
- unsigned
5 files · +50 -15
M examples/extensions/mcp-bridge/bridge.go +4 -2
| @@ -33,6 +33,7 @@ type toolMapping struct { | |||
| 33 | 33 | // bridge connects MCP servers to zot's extension protocol. | |
| 34 | 34 | type bridge struct { | |
| 35 | 35 | e *ext.Extension | |
| 36 | + | cwd string | |
| 36 | 37 | // servers is written once in loadServers before any goroutine | |
| 37 | 38 | // starts, and read-only afterwards — that invariant is what makes | |
| 38 | 39 | // the lock-free reads in handleToolCall and the idle reaper safe. | |
| @@ -45,9 +46,10 @@ type bridge struct { | |||
| 45 | 46 | } | |
| 46 | 47 | ||
| 47 | 48 | // newBridge creates a new MCP→zot bridge. | |
| 48 | - | func newBridge(e *ext.Extension, logger *log.Logger) *bridge { | |
| 49 | + | func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge { | |
| 49 | 50 | return &bridge{ | |
| 50 | 51 | e: e, | |
| 52 | + | cwd: cwd, | |
| 51 | 53 | servers: make(map[string]*managedServer), | |
| 52 | 54 | mapping: make(map[string]toolMapping), | |
| 53 | 55 | logger: logger, | |
| @@ -78,7 +80,7 @@ func toolName(serverName, mcpToolName string) string { | |||
| 78 | 80 | // loadServers reads the config and creates managed servers. | |
| 79 | 81 | func (b *bridge) loadServers(cfg Config) { | |
| 80 | 82 | for name, srvCfg := range cfg.MCPServers { | |
| 81 | - | srv := newManagedServer(name, srvCfg, b.logger) | |
| 83 | + | srv := newManagedServer(name, srvCfg, b.cwd, b.logger) | |
| 82 | 84 | b.servers[name] = srv | |
| 83 | 85 | } | |
| 84 | 86 | } | |
| @@ -33,6 +33,7 @@ type toolMapping struct { | |||||
| 33 | // bridge connects MCP servers to zot's extension protocol. | 33 | // bridge connects MCP servers to zot's extension protocol. | ||
| 34 | type bridge struct { | 34 | type bridge struct { | ||
| 35 | e *ext.Extension | 35 | e *ext.Extension | ||
| 36 | + | cwd string | |||
| 36 | // servers is written once in loadServers before any goroutine | 37 | // servers is written once in loadServers before any goroutine | ||
| 37 | // starts, and read-only afterwards — that invariant is what makes | 38 | // starts, and read-only afterwards — that invariant is what makes | ||
| 38 | // the lock-free reads in handleToolCall and the idle reaper safe. | 39 | // the lock-free reads in handleToolCall and the idle reaper safe. | ||
| @@ -45,9 +46,10 @@ type bridge struct { | |||||
| 45 | } | 46 | } | ||
| 46 | 47 | ||||
| 47 | // newBridge creates a new MCP→zot bridge. | 48 | // newBridge creates a new MCP→zot bridge. | ||
| 48 | - | func newBridge(e *ext.Extension, logger *log.Logger) *bridge { | 49 | + | func newBridge(e *ext.Extension, cwd string, logger *log.Logger) *bridge { |
| 49 | return &bridge{ | 50 | return &bridge{ | ||
| 50 | e: e, | 51 | e: e, | ||
| 52 | + | cwd: cwd, | |||
| 51 | servers: make(map[string]*managedServer), | 53 | servers: make(map[string]*managedServer), | ||
| 52 | mapping: make(map[string]toolMapping), | 54 | mapping: make(map[string]toolMapping), | ||
| 53 | logger: logger, | 55 | logger: logger, | ||
| @@ -78,7 +80,7 @@ func toolName(serverName, mcpToolName string) string { | |||||
| 78 | // loadServers reads the config and creates managed servers. | 80 | // loadServers reads the config and creates managed servers. | ||
| 79 | func (b *bridge) loadServers(cfg Config) { | 81 | func (b *bridge) loadServers(cfg Config) { | ||
| 80 | for name, srvCfg := range cfg.MCPServers { | 82 | for name, srvCfg := range cfg.MCPServers { | ||
| 81 | - | srv := newManagedServer(name, srvCfg, b.logger) | 83 | + | srv := newManagedServer(name, srvCfg, b.cwd, b.logger) |
| 82 | b.servers[name] = srv | 84 | b.servers[name] = srv | ||
| 83 | } | 85 | } | ||
| 84 | } | 86 | } | ||
M examples/extensions/mcp-bridge/bridge_test.go +12 -0
| @@ -12,6 +12,7 @@ import ( | |||
| 12 | 12 | ||
| 13 | 13 | func testBridge() *bridge { | |
| 14 | 14 | return &bridge{ | |
| 15 | + | cwd: "/project", | |
| 15 | 16 | servers: map[string]*managedServer{}, | |
| 16 | 17 | mapping: map[string]toolMapping{}, | |
| 17 | 18 | logger: log.New(io.Discard, "", 0), | |
| @@ -30,6 +31,7 @@ func TestHandleToolCallStartFailureIsFriendlyError(t *testing.T) { | |||
| 30 | 31 | b := testBridge() | |
| 31 | 32 | b.servers["bogus"] = newManagedServer("bogus", | |
| 32 | 33 | ServerConfig{Transport: "no-such-transport", ConnectTimeout: 1, RequestTimeout: 1}, | |
| 34 | + | "/project", | |
| 33 | 35 | log.New(io.Discard, "", 0)) | |
| 34 | 36 | b.mapping["mcp__bogus__t"] = toolMapping{serverName: "bogus", mcpTool: "t"} | |
| 35 | 37 | ||
| @@ -39,6 +41,16 @@ func TestHandleToolCallStartFailureIsFriendlyError(t *testing.T) { | |||
| 39 | 41 | } | |
| 40 | 42 | } | |
| 41 | 43 | ||
| 44 | + | func TestLoadServersPassesProjectCWD(t *testing.T) { | |
| 45 | + | b := testBridge() | |
| 46 | + | b.loadServers(Config{MCPServers: map[string]ServerConfig{ | |
| 47 | + | "fs": {Transport: "stdio", Command: "dummy"}, | |
| 48 | + | }}) | |
| 49 | + | if got := b.servers["fs"].cwd; got != "/project" { | |
| 50 | + | t.Fatalf("managed server cwd = %q, want /project", got) | |
| 51 | + | } | |
| 52 | + | } | |
| 53 | + | ||
| 42 | 54 | func TestClassifyTimeoutUsesErrorsIs(t *testing.T) { | |
| 43 | 55 | wrapped := errors.Join(errors.New("call failed"), context.DeadlineExceeded) | |
| 44 | 56 | if !errors.Is(wrapped, context.DeadlineExceeded) { | |
| @@ -12,6 +12,7 @@ import ( | |||||
| 12 | 12 | ||||
| 13 | func testBridge() *bridge { | 13 | func testBridge() *bridge { | ||
| 14 | return &bridge{ | 14 | return &bridge{ | ||
| 15 | + | cwd: "/project", | |||
| 15 | servers: map[string]*managedServer{}, | 16 | servers: map[string]*managedServer{}, | ||
| 16 | mapping: map[string]toolMapping{}, | 17 | mapping: map[string]toolMapping{}, | ||
| 17 | logger: log.New(io.Discard, "", 0), | 18 | logger: log.New(io.Discard, "", 0), | ||
| @@ -30,6 +31,7 @@ func TestHandleToolCallStartFailureIsFriendlyError(t *testing.T) { | |||||
| 30 | b := testBridge() | 31 | b := testBridge() | ||
| 31 | b.servers["bogus"] = newManagedServer("bogus", | 32 | b.servers["bogus"] = newManagedServer("bogus", | ||
| 32 | ServerConfig{Transport: "no-such-transport", ConnectTimeout: 1, RequestTimeout: 1}, | 33 | ServerConfig{Transport: "no-such-transport", ConnectTimeout: 1, RequestTimeout: 1}, | ||
| 34 | + | "/project", | |||
| 33 | log.New(io.Discard, "", 0)) | 35 | log.New(io.Discard, "", 0)) | ||
| 34 | b.mapping["mcp__bogus__t"] = toolMapping{serverName: "bogus", mcpTool: "t"} | 36 | b.mapping["mcp__bogus__t"] = toolMapping{serverName: "bogus", mcpTool: "t"} | ||
| 35 | 37 | ||||
| @@ -39,6 +41,16 @@ func TestHandleToolCallStartFailureIsFriendlyError(t *testing.T) { | |||||
| 39 | } | 41 | } | ||
| 40 | } | 42 | } | ||
| 41 | 43 | ||||
| 44 | + | func TestLoadServersPassesProjectCWD(t *testing.T) { | |||
| 45 | + | b := testBridge() | |||
| 46 | + | b.loadServers(Config{MCPServers: map[string]ServerConfig{ | |||
| 47 | + | "fs": {Transport: "stdio", Command: "dummy"}, | |||
| 48 | + | }}) | |||
| 49 | + | if got := b.servers["fs"].cwd; got != "/project" { | |||
| 50 | + | t.Fatalf("managed server cwd = %q, want /project", got) | |||
| 51 | + | } | |||
| 52 | + | } | |||
| 53 | + | ||||
| 42 | func TestClassifyTimeoutUsesErrorsIs(t *testing.T) { | 54 | func TestClassifyTimeoutUsesErrorsIs(t *testing.T) { | ||
| 43 | wrapped := errors.Join(errors.New("call failed"), context.DeadlineExceeded) | 55 | wrapped := errors.Join(errors.New("call failed"), context.DeadlineExceeded) | ||
| 44 | if !errors.Is(wrapped, context.DeadlineExceeded) { | 56 | if !errors.Is(wrapped, context.DeadlineExceeded) { | ||
M examples/extensions/mcp-bridge/main.go +1 -1
| @@ -76,7 +76,7 @@ func main() { | |||
| 76 | 76 | ||
| 77 | 77 | logger.Printf("found %d MCP server(s)", len(cfg.MCPServers)) | |
| 78 | 78 | ||
| 79 | - | b = newBridge(e, logger) | |
| 79 | + | b = newBridge(e, cwd, logger) | |
| 80 | 80 | b.loadServers(cfg) | |
| 81 | 81 | ||
| 82 | 82 | cachePath := toolCachePath() | |
| @@ -76,7 +76,7 @@ func main() { | |||||
| 76 | 76 | ||||
| 77 | logger.Printf("found %d MCP server(s)", len(cfg.MCPServers)) | 77 | logger.Printf("found %d MCP server(s)", len(cfg.MCPServers)) | ||
| 78 | 78 | ||||
| 79 | - | b = newBridge(e, logger) | 79 | + | b = newBridge(e, cwd, logger) |
| 80 | b.loadServers(cfg) | 80 | b.loadServers(cfg) | ||
| 81 | 81 | ||||
| 82 | cachePath := toolCachePath() | 82 | cachePath := toolCachePath() | ||
M examples/extensions/mcp-bridge/server.go +24 -3
| @@ -16,6 +16,8 @@ import ( | |||
| 16 | 16 | "fmt" | |
| 17 | 17 | "io" | |
| 18 | 18 | "log" | |
| 19 | + | "os" | |
| 20 | + | "os/exec" | |
| 19 | 21 | "strings" | |
| 20 | 22 | "sync" | |
| 21 | 23 | "time" | |
| @@ -54,6 +56,7 @@ func (s serverState) String() string { | |||
| 54 | 56 | type managedServer struct { | |
| 55 | 57 | name string | |
| 56 | 58 | config ServerConfig | |
| 59 | + | cwd string | |
| 57 | 60 | logger *log.Logger | |
| 58 | 61 | ||
| 59 | 62 | mu sync.Mutex | |
| @@ -66,10 +69,11 @@ type managedServer struct { | |||
| 66 | 69 | } | |
| 67 | 70 | ||
| 68 | 71 | // newManagedServer creates a new server wrapper. | |
| 69 | - | func newManagedServer(name string, cfg ServerConfig, logger *log.Logger) *managedServer { | |
| 72 | + | func newManagedServer(name string, cfg ServerConfig, cwd string, logger *log.Logger) *managedServer { | |
| 70 | 73 | return &managedServer{ | |
| 71 | 74 | name: name, | |
| 72 | 75 | config: cfg, | |
| 76 | + | cwd: cwd, | |
| 73 | 77 | logger: logger, | |
| 74 | 78 | state: stateStopped, | |
| 75 | 79 | lastUsed: time.Now(), | |
| @@ -186,8 +190,25 @@ func (s *managedServer) startStdio() (*client.Client, error) { | |||
| 186 | 190 | env = append(env, fmt.Sprintf("%s=%s", k, v)) | |
| 187 | 191 | } | |
| 188 | 192 | ||
| 189 | - | // Create stdio client (this spawns the subprocess) | |
| 190 | - | c, err := client.NewStdioMCPClient(s.config.Command, env, s.config.Args...) | |
| 193 | + | // Create stdio client (this spawns the subprocess). Run stdio MCP | |
| 194 | + | // servers from the zot session's project directory, not the installed | |
| 195 | + | // extension directory. Many MCP servers use their process cwd as their | |
| 196 | + | // project root when answering relative-path or directory-listing | |
| 197 | + | // requests, so inheriting mcp-bridge's cwd makes them operate on the | |
| 198 | + | // extension install instead of the user's project. | |
| 199 | + | c, err := client.NewStdioMCPClientWithOptions( | |
| 200 | + | s.config.Command, | |
| 201 | + | env, | |
| 202 | + | s.config.Args, | |
| 203 | + | transport.WithCommandFunc(func(ctx context.Context, command string, env []string, args []string) (*exec.Cmd, error) { | |
| 204 | + | cmd := exec.CommandContext(ctx, command, args...) | |
| 205 | + | cmd.Env = append(os.Environ(), env...) | |
| 206 | + | if s.cwd != "" { | |
| 207 | + | cmd.Dir = s.cwd | |
| 208 | + | } | |
| 209 | + | return cmd, nil | |
| 210 | + | }), | |
| 211 | + | ) | |
| 191 | 212 | if err != nil { | |
| 192 | 213 | return nil, fmt.Errorf("spawn %s: %w", s.config.Command, err) | |
| 193 | 214 | } | |
| @@ -16,6 +16,8 @@ import ( | |||||
| 16 | "fmt" | 16 | "fmt" | ||
| 17 | "io" | 17 | "io" | ||
| 18 | "log" | 18 | "log" | ||
| 19 | + | "os" | |||
| 20 | + | "os/exec" | |||
| 19 | "strings" | 21 | "strings" | ||
| 20 | "sync" | 22 | "sync" | ||
| 21 | "time" | 23 | "time" | ||
| @@ -54,6 +56,7 @@ func (s serverState) String() string { | |||||
| 54 | type managedServer struct { | 56 | type managedServer struct { | ||
| 55 | name string | 57 | name string | ||
| 56 | config ServerConfig | 58 | config ServerConfig | ||
| 59 | + | cwd string | |||
| 57 | logger *log.Logger | 60 | logger *log.Logger | ||
| 58 | 61 | ||||
| 59 | mu sync.Mutex | 62 | mu sync.Mutex | ||
| @@ -66,10 +69,11 @@ type managedServer struct { | |||||
| 66 | } | 69 | } | ||
| 67 | 70 | ||||
| 68 | // newManagedServer creates a new server wrapper. | 71 | // newManagedServer creates a new server wrapper. | ||
| 69 | - | func newManagedServer(name string, cfg ServerConfig, logger *log.Logger) *managedServer { | 72 | + | func newManagedServer(name string, cfg ServerConfig, cwd string, logger *log.Logger) *managedServer { |
| 70 | return &managedServer{ | 73 | return &managedServer{ | ||
| 71 | name: name, | 74 | name: name, | ||
| 72 | config: cfg, | 75 | config: cfg, | ||
| 76 | + | cwd: cwd, | |||
| 73 | logger: logger, | 77 | logger: logger, | ||
| 74 | state: stateStopped, | 78 | state: stateStopped, | ||
| 75 | lastUsed: time.Now(), | 79 | lastUsed: time.Now(), | ||
| @@ -186,8 +190,25 @@ func (s *managedServer) startStdio() (*client.Client, error) { | |||||
| 186 | env = append(env, fmt.Sprintf("%s=%s", k, v)) | 190 | env = append(env, fmt.Sprintf("%s=%s", k, v)) | ||
| 187 | } | 191 | } | ||
| 188 | 192 | ||||
| 189 | - | // Create stdio client (this spawns the subprocess) | 193 | + | // Create stdio client (this spawns the subprocess). Run stdio MCP |
| 190 | - | c, err := client.NewStdioMCPClient(s.config.Command, env, s.config.Args...) | 194 | + | // servers from the zot session's project directory, not the installed |
| 195 | + | // extension directory. Many MCP servers use their process cwd as their | |||
| 196 | + | // project root when answering relative-path or directory-listing | |||
| 197 | + | // requests, so inheriting mcp-bridge's cwd makes them operate on the | |||
| 198 | + | // extension install instead of the user's project. | |||
| 199 | + | c, err := client.NewStdioMCPClientWithOptions( | |||
| 200 | + | s.config.Command, | |||
| 201 | + | env, | |||
| 202 | + | s.config.Args, | |||
| 203 | + | transport.WithCommandFunc(func(ctx context.Context, command string, env []string, args []string) (*exec.Cmd, error) { | |||
| 204 | + | cmd := exec.CommandContext(ctx, command, args...) | |||
| 205 | + | cmd.Env = append(os.Environ(), env...) | |||
| 206 | + | if s.cwd != "" { | |||
| 207 | + | cmd.Dir = s.cwd | |||
| 208 | + | } | |||
| 209 | + | return cmd, nil | |||
| 210 | + | }), | |||
| 211 | + | ) | |||
| 191 | if err != nil { | 212 | if err != nil { | ||
| 192 | return nil, fmt.Errorf("spawn %s: %w", s.config.Command, err) | 213 | return nil, fmt.Errorf("spawn %s: %w", s.config.Command, err) | ||
| 193 | } | 214 | } | ||
M examples/extensions/mcp-bridge/status_test.go +9 -9
| @@ -16,7 +16,7 @@ import ( | |||
| 16 | 16 | ) | |
| 17 | 17 | ||
| 18 | 18 | func TestManagedServerStatusCompact(t *testing.T) { | |
| 19 | - | s := newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 19 | + | s := newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 20 | 20 | ||
| 21 | 21 | s.state = stateReady | |
| 22 | 22 | s.tools = []mcp.Tool{{Name: "searchGitHub"}} | |
| @@ -38,10 +38,10 @@ func TestManagedServerStatusCompact(t *testing.T) { | |||
| 38 | 38 | ||
| 39 | 39 | func TestFormatStatusSummaryCompact(t *testing.T) { | |
| 40 | 40 | b := &bridge{servers: map[string]*managedServer{}} | |
| 41 | - | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 41 | + | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 42 | 42 | b.servers["grep"].state = stateReady | |
| 43 | 43 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | |
| 44 | - | b.servers["n8n-mcp"] = newManagedServer("n8n-mcp", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 44 | + | b.servers["n8n-mcp"] = newManagedServer("n8n-mcp", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 45 | 45 | b.servers["n8n-mcp"].state = stateReady | |
| 46 | 46 | b.servers["n8n-mcp"].tools = make([]mcp.Tool, 26) | |
| 47 | 47 | ||
| @@ -54,7 +54,7 @@ func TestFormatStatusSummaryCompact(t *testing.T) { | |||
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | 56 | func TestManagedServerStopBeforeStartDoesNotPanic(t *testing.T) { | |
| 57 | - | s := newManagedServer("never-started", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 57 | + | s := newManagedServer("never-started", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 58 | 58 | s.stop() | |
| 59 | 59 | if got, want := s.state, stateStopped; got != want { | |
| 60 | 60 | t.Fatalf("state = %s, want %s", got, want) | |
| @@ -98,10 +98,10 @@ func TestCompactErrShortensConnectionFailures(t *testing.T) { | |||
| 98 | 98 | ||
| 99 | 99 | func TestNotifyLevelWarnOnPartialFailure(t *testing.T) { | |
| 100 | 100 | b := &bridge{servers: map[string]*managedServer{}} | |
| 101 | - | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 101 | + | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 102 | 102 | b.servers["grep"].state = stateReady | |
| 103 | 103 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | |
| 104 | - | b.servers["broken"] = newManagedServer("broken", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 104 | + | b.servers["broken"] = newManagedServer("broken", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 105 | 105 | b.servers["broken"].state = stateError | |
| 106 | 106 | b.servers["broken"].startErr = errors.New("authorization required\nmore detail") | |
| 107 | 107 | ||
| @@ -114,7 +114,7 @@ func TestNotifyLevelWarnOnPartialFailure(t *testing.T) { | |||
| 114 | 114 | } | |
| 115 | 115 | ||
| 116 | 116 | func TestStopDuringStartDiscardsStaleResult(t *testing.T) { | |
| 117 | - | s := newManagedServer("racy", ServerConfig{}, log.New(io.Discard, "", 0)) | |
| 117 | + | s := newManagedServer("racy", ServerConfig{}, "", log.New(io.Discard, "", 0)) | |
| 118 | 118 | ||
| 119 | 119 | // Simulate the window inside start(): state is starting, gen recorded. | |
| 120 | 120 | s.mu.Lock() | |
| @@ -142,7 +142,7 @@ func TestStopDuringStartDiscardsStaleResult(t *testing.T) { | |||
| 142 | 142 | } | |
| 143 | 143 | ||
| 144 | 144 | func TestCallToolNilClientReturnsErrorNotPanic(t *testing.T) { | |
| 145 | - | s := newManagedServer("bogus", ServerConfig{Transport: "no-such-transport", RequestTimeout: 1, ConnectTimeout: 1}, log.New(io.Discard, "", 0)) | |
| 145 | + | s := newManagedServer("bogus", ServerConfig{Transport: "no-such-transport", RequestTimeout: 1, ConnectTimeout: 1}, "", log.New(io.Discard, "", 0)) | |
| 146 | 146 | _, err := s.callTool(context.Background(), "anything", nil) | |
| 147 | 147 | if err == nil { | |
| 148 | 148 | t.Fatal("expected error from unknown transport") | |
| @@ -151,7 +151,7 @@ func TestCallToolNilClientReturnsErrorNotPanic(t *testing.T) { | |||
| 151 | 151 | ||
| 152 | 152 | func TestPipeStderrKeepsLinesIntactAcrossChunks(t *testing.T) { | |
| 153 | 153 | var buf bytes.Buffer | |
| 154 | - | s := newManagedServer("srv", ServerConfig{}, log.New(&buf, "", 0)) | |
| 154 | + | s := newManagedServer("srv", ServerConfig{}, "", log.New(&buf, "", 0)) | |
| 155 | 155 | ||
| 156 | 156 | // iotest.OneByteReader forces 1-byte reads: the old 4096-byte | |
| 157 | 157 | // chunking code would log every byte as its own "line". | |
| @@ -16,7 +16,7 @@ import ( | |||||
| 16 | ) | 16 | ) | ||
| 17 | 17 | ||||
| 18 | func TestManagedServerStatusCompact(t *testing.T) { | 18 | func TestManagedServerStatusCompact(t *testing.T) { | ||
| 19 | - | s := newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | 19 | + | s := newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 20 | 20 | ||||
| 21 | s.state = stateReady | 21 | s.state = stateReady | ||
| 22 | s.tools = []mcp.Tool{{Name: "searchGitHub"}} | 22 | s.tools = []mcp.Tool{{Name: "searchGitHub"}} | ||
| @@ -38,10 +38,10 @@ func TestManagedServerStatusCompact(t *testing.T) { | |||||
| 38 | 38 | ||||
| 39 | func TestFormatStatusSummaryCompact(t *testing.T) { | 39 | func TestFormatStatusSummaryCompact(t *testing.T) { | ||
| 40 | b := &bridge{servers: map[string]*managedServer{}} | 40 | b := &bridge{servers: map[string]*managedServer{}} | ||
| 41 | - | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | 41 | + | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 42 | b.servers["grep"].state = stateReady | 42 | b.servers["grep"].state = stateReady | ||
| 43 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | 43 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | ||
| 44 | - | b.servers["n8n-mcp"] = newManagedServer("n8n-mcp", ServerConfig{}, log.New(io.Discard, "", 0)) | 44 | + | b.servers["n8n-mcp"] = newManagedServer("n8n-mcp", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 45 | b.servers["n8n-mcp"].state = stateReady | 45 | b.servers["n8n-mcp"].state = stateReady | ||
| 46 | b.servers["n8n-mcp"].tools = make([]mcp.Tool, 26) | 46 | b.servers["n8n-mcp"].tools = make([]mcp.Tool, 26) | ||
| 47 | 47 | ||||
| @@ -54,7 +54,7 @@ func TestFormatStatusSummaryCompact(t *testing.T) { | |||||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | func TestManagedServerStopBeforeStartDoesNotPanic(t *testing.T) { | 56 | func TestManagedServerStopBeforeStartDoesNotPanic(t *testing.T) { | ||
| 57 | - | s := newManagedServer("never-started", ServerConfig{}, log.New(io.Discard, "", 0)) | 57 | + | s := newManagedServer("never-started", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 58 | s.stop() | 58 | s.stop() | ||
| 59 | if got, want := s.state, stateStopped; got != want { | 59 | if got, want := s.state, stateStopped; got != want { | ||
| 60 | t.Fatalf("state = %s, want %s", got, want) | 60 | t.Fatalf("state = %s, want %s", got, want) | ||
| @@ -98,10 +98,10 @@ func TestCompactErrShortensConnectionFailures(t *testing.T) { | |||||
| 98 | 98 | ||||
| 99 | func TestNotifyLevelWarnOnPartialFailure(t *testing.T) { | 99 | func TestNotifyLevelWarnOnPartialFailure(t *testing.T) { | ||
| 100 | b := &bridge{servers: map[string]*managedServer{}} | 100 | b := &bridge{servers: map[string]*managedServer{}} | ||
| 101 | - | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, log.New(io.Discard, "", 0)) | 101 | + | b.servers["grep"] = newManagedServer("grep", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 102 | b.servers["grep"].state = stateReady | 102 | b.servers["grep"].state = stateReady | ||
| 103 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | 103 | b.servers["grep"].tools = []mcp.Tool{{Name: "searchGitHub"}} | ||
| 104 | - | b.servers["broken"] = newManagedServer("broken", ServerConfig{}, log.New(io.Discard, "", 0)) | 104 | + | b.servers["broken"] = newManagedServer("broken", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 105 | b.servers["broken"].state = stateError | 105 | b.servers["broken"].state = stateError | ||
| 106 | b.servers["broken"].startErr = errors.New("authorization required\nmore detail") | 106 | b.servers["broken"].startErr = errors.New("authorization required\nmore detail") | ||
| 107 | 107 | ||||
| @@ -114,7 +114,7 @@ func TestNotifyLevelWarnOnPartialFailure(t *testing.T) { | |||||
| 114 | } | 114 | } | ||
| 115 | 115 | ||||
| 116 | func TestStopDuringStartDiscardsStaleResult(t *testing.T) { | 116 | func TestStopDuringStartDiscardsStaleResult(t *testing.T) { | ||
| 117 | - | s := newManagedServer("racy", ServerConfig{}, log.New(io.Discard, "", 0)) | 117 | + | s := newManagedServer("racy", ServerConfig{}, "", log.New(io.Discard, "", 0)) |
| 118 | 118 | ||||
| 119 | // Simulate the window inside start(): state is starting, gen recorded. | 119 | // Simulate the window inside start(): state is starting, gen recorded. | ||
| 120 | s.mu.Lock() | 120 | s.mu.Lock() | ||
| @@ -142,7 +142,7 @@ func TestStopDuringStartDiscardsStaleResult(t *testing.T) { | |||||
| 142 | } | 142 | } | ||
| 143 | 143 | ||||
| 144 | func TestCallToolNilClientReturnsErrorNotPanic(t *testing.T) { | 144 | func TestCallToolNilClientReturnsErrorNotPanic(t *testing.T) { | ||
| 145 | - | s := newManagedServer("bogus", ServerConfig{Transport: "no-such-transport", RequestTimeout: 1, ConnectTimeout: 1}, log.New(io.Discard, "", 0)) | 145 | + | s := newManagedServer("bogus", ServerConfig{Transport: "no-such-transport", RequestTimeout: 1, ConnectTimeout: 1}, "", log.New(io.Discard, "", 0)) |
| 146 | _, err := s.callTool(context.Background(), "anything", nil) | 146 | _, err := s.callTool(context.Background(), "anything", nil) | ||
| 147 | if err == nil { | 147 | if err == nil { | ||
| 148 | t.Fatal("expected error from unknown transport") | 148 | t.Fatal("expected error from unknown transport") | ||
| @@ -151,7 +151,7 @@ func TestCallToolNilClientReturnsErrorNotPanic(t *testing.T) { | |||||
| 151 | 151 | ||||
| 152 | func TestPipeStderrKeepsLinesIntactAcrossChunks(t *testing.T) { | 152 | func TestPipeStderrKeepsLinesIntactAcrossChunks(t *testing.T) { | ||
| 153 | var buf bytes.Buffer | 153 | var buf bytes.Buffer | ||
| 154 | - | s := newManagedServer("srv", ServerConfig{}, log.New(&buf, "", 0)) | 154 | + | s := newManagedServer("srv", ServerConfig{}, "", log.New(&buf, "", 0)) |
| 155 | 155 | ||||
| 156 | // iotest.OneByteReader forces 1-byte reads: the old 4096-byte | 156 | // iotest.OneByteReader forces 1-byte reads: the old 4096-byte | ||
| 157 | // chunking code would log every byte as its own "line". | 157 | // chunking code would log every byte as its own "line". | ||