Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions internal/daemon/daemon_tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package daemon
import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -171,7 +170,7 @@ func TestToolSchemaPayloadUsesNativeToolName(t *testing.T) {
}
}

func TestListServersHidesCodexAppsAndShowsVirtualServers(t *testing.T) {
func TestListServersHidesCodexAppsWithoutDiscovery(t *testing.T) {
cfg := &config.Config{
Servers: map[string]config.ServerConfig{
"github": {},
Expand All @@ -186,16 +185,11 @@ func TestListServersHidesCodexAppsAndShowsVirtualServers(t *testing.T) {
ka := NewKeepalive(nil)
defer ka.Stop()

calls := 0
deps := runtimeDefaultDeps()
deps.poolListTools = func(_ context.Context, _ *mcppool.Pool, server string) ([]mcppool.ToolInfo, error) {
if server != codexAppsServerName {
t.Fatalf("poolListTools server = %q, want %q", server, codexAppsServerName)
}
return []mcppool.ToolInfo{
{Name: "linear_get_profile"},
{Name: "zillow_get_zestimate"},
{Name: "google calendar_search"},
}, nil
deps.poolListTools = func(_ context.Context, _ *mcppool.Pool, _ string) ([]mcppool.ToolInfo, error) {
calls++
return nil, nil
}

resp := listServersWithDeps(context.Background(), cfg, nil, ka, false, deps)
Expand All @@ -204,31 +198,27 @@ func TestListServersHidesCodexAppsAndShowsVirtualServers(t *testing.T) {
}

got := decodeServerLines(resp.Content)
want := []string{"github", "google_calendar", "linear", "supermemory", "zillow"}
want := []string{"github", "supermemory"}
if !reflect.DeepEqual(got, want) {
t.Fatalf("server list = %#v, want %#v", got, want)
}
entries := decodeServerEntries(resp.Content)
for _, entry := range entries {
switch entry.Name {
case "github", "supermemory":
if entry.Origin.Kind != config.ServerOriginKindMCPXConfig {
t.Fatalf("server %q origin kind = %q, want %q", entry.Name, entry.Origin.Kind, config.ServerOriginKindMCPXConfig)
}
case "google_calendar", "linear", "zillow":
if entry.Origin.Kind != config.ServerOriginKindCodexApps {
t.Fatalf("server %q origin kind = %q, want %q", entry.Name, entry.Origin.Kind, config.ServerOriginKindCodexApps)
}
if entry.Origin.Kind != config.ServerOriginKindMCPXConfig {
t.Fatalf("server %q origin kind = %q, want %q", entry.Name, entry.Origin.Kind, config.ServerOriginKindMCPXConfig)
}
}
if calls != 0 {
t.Fatalf("codex list-tools calls = %d, want 0", calls)
}
for _, name := range got {
if name == codexAppsServerName {
t.Fatalf("server list = %#v, want %q omitted", got, codexAppsServerName)
}
}
}

func TestListServersKeepsConfiguredServersWhenCodexAppsDiscoveryFails(t *testing.T) {
func TestListServersKeepsConfiguredServersWhenCodexAppsConfigured(t *testing.T) {
cfg := &config.Config{
Servers: map[string]config.ServerConfig{
"github": {},
Expand All @@ -239,9 +229,11 @@ func TestListServersKeepsConfiguredServersWhenCodexAppsDiscoveryFails(t *testing
ka := NewKeepalive(nil)
defer ka.Stop()

calls := 0
deps := runtimeDefaultDeps()
deps.poolListTools = func(_ context.Context, _ *mcppool.Pool, _ string) ([]mcppool.ToolInfo, error) {
return nil, errors.New("token expired")
calls++
return nil, nil
}

resp := listServersWithDeps(context.Background(), cfg, nil, ka, false, deps)
Expand All @@ -260,8 +252,11 @@ func TestListServersKeepsConfiguredServersWhenCodexAppsDiscoveryFails(t *testing
t.Fatalf("server %q origin kind = %q, want %q", entry.Name, entry.Origin.Kind, config.ServerOriginKindMCPXConfig)
}
}
if !strings.Contains(resp.Stderr, "failed to enumerate codex apps") {
t.Fatalf("listServers() stderr = %q, want codex-apps warning", resp.Stderr)
if resp.Stderr != "" {
t.Fatalf("listServers() stderr = %q, want empty", resp.Stderr)
}
if calls != 0 {
t.Fatalf("codex list-tools calls = %d, want 0", calls)
}
}

Expand Down
20 changes: 1 addition & 19 deletions internal/servercatalog/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func New(cfg *config.Config, listTools ListToolsFunc) *Catalog {
}

func (c *Catalog) ServerNames(ctx context.Context) ([]string, error) {
_ = ctx
if c == nil || c.cfg == nil {
return nil, nil
}
Expand All @@ -50,25 +51,6 @@ func (c *Catalog) ServerNames(ctx context.Context) ([]string, error) {
names[name] = struct{}{}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include discovered Codex app aliases in server listing

For users relying on Codex app fallback/discovery, aliases such as linear and zillow only exist after listTools feeds codexVirtualServerMap; this implementation now returns only the literal cfg.Servers keys (minus codex_apps), so list_servers becomes empty or incomplete. That regresses multiple CLI paths that consume list_servers: the root mcpx listing can incorrectly print “No MCP servers configured” (internal/cli/root.go), mcpx <alias> --help rejects valid virtual servers because it preflights against that list, and completion / mcpx shim install <alias> no longer discover those servers (internal/cli/completion_runtime.go, internal/cli/shim.go). The docs still advertise these virtual per-app servers in docs/usage.md.

Useful? React with 👍 / 👎.

}

if c.hasCodexApps() {
if c.listTools == nil {
return nil, fmt.Errorf("codex apps discovery requires list tools callback")
}
tools, err := c.listTools(ctx, CodexAppsServerName)
if err != nil {
return nil, err
}
for name := range codexVirtualServerMap(tools) {
if strings.TrimSpace(name) == "" {
continue
}
if _, exists := c.cfg.Servers[name]; exists {
continue
}
names[name] = struct{}{}
}
}

out := make([]string, 0, len(names))
for name := range names {
out = append(out, name)
Expand Down
20 changes: 9 additions & 11 deletions internal/servercatalog/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/lydakis/mcpx/internal/mcppool"
)

func TestServerNamesHidesCodexAppsAndAddsVirtualApps(t *testing.T) {
func TestServerNamesHidesCodexAppsWithoutDiscovery(t *testing.T) {
cfg := &config.Config{
Servers: map[string]config.ServerConfig{
"playwright": {},
Expand All @@ -18,26 +18,24 @@ func TestServerNamesHidesCodexAppsAndAddsVirtualApps(t *testing.T) {
},
}

catalog := New(cfg, func(_ context.Context, server string) ([]mcppool.ToolInfo, error) {
if server != CodexAppsServerName {
t.Fatalf("listTools server = %q, want %q", server, CodexAppsServerName)
}
return []mcppool.ToolInfo{
{Name: "linear_get_profile"},
{Name: "zillow_get_zestimate"},
{Name: "google calendar_search"},
}, nil
calls := 0
catalog := New(cfg, func(_ context.Context, _ string) ([]mcppool.ToolInfo, error) {
calls++
return nil, nil
})

names, err := catalog.ServerNames(context.Background())
if err != nil {
t.Fatalf("ServerNames() error = %v", err)
}

want := []string{"google_calendar", "linear", "playwright", "supermemory", "zillow"}
want := []string{"playwright", "supermemory"}
if !reflect.DeepEqual(names, want) {
t.Fatalf("ServerNames() = %#v, want %#v", names, want)
}
if calls != 0 {
t.Fatalf("codex list-tools calls = %d, want 0", calls)
}
}

func TestResolveReturnsConfiguredRouteWithoutCodexAppsProbe(t *testing.T) {
Expand Down
Loading