Skip to content

Commit f607202

Browse files
committed
feat: add tests for MCP file handling and validation in server commands
1 parent a9637f6 commit f607202

File tree

5 files changed

+285
-0
lines changed

5 files changed

+285
-0
lines changed

cmd/server/server_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,3 +799,55 @@ func TestServerCmd_AllowedOrigins(t *testing.T) {
799799
})
800800
}
801801
}
802+
803+
func TestServerCmd_MCPFileFlag(t *testing.T) {
804+
t.Run("mcp-file default is empty", func(t *testing.T) {
805+
isolateViper(t)
806+
807+
serverCmd := CreateServerCmd()
808+
setupCommandOutput(t, serverCmd)
809+
serverCmd.SetArgs([]string{"--exit", "dummy-command"})
810+
err := serverCmd.Execute()
811+
require.NoError(t, err)
812+
813+
assert.Equal(t, "", viper.GetString(FlagMCPFile))
814+
})
815+
816+
t.Run("mcp-file can be set via CLI flag", func(t *testing.T) {
817+
isolateViper(t)
818+
819+
serverCmd := CreateServerCmd()
820+
setupCommandOutput(t, serverCmd)
821+
serverCmd.SetArgs([]string{"--mcp-file", "/path/to/mcp.json", "--exit", "dummy-command"})
822+
err := serverCmd.Execute()
823+
require.NoError(t, err)
824+
825+
assert.Equal(t, "/path/to/mcp.json", viper.GetString(FlagMCPFile))
826+
})
827+
828+
t.Run("mcp-file can be set via environment variable", func(t *testing.T) {
829+
isolateViper(t)
830+
t.Setenv("AGENTAPI_MCP_FILE", "/env/path/to/mcp.json")
831+
832+
serverCmd := CreateServerCmd()
833+
setupCommandOutput(t, serverCmd)
834+
serverCmd.SetArgs([]string{"--exit", "dummy-command"})
835+
err := serverCmd.Execute()
836+
require.NoError(t, err)
837+
838+
assert.Equal(t, "/env/path/to/mcp.json", viper.GetString(FlagMCPFile))
839+
})
840+
841+
t.Run("CLI flag overrides environment variable", func(t *testing.T) {
842+
isolateViper(t)
843+
t.Setenv("AGENTAPI_MCP_FILE", "/env/path/to/mcp.json")
844+
845+
serverCmd := CreateServerCmd()
846+
setupCommandOutput(t, serverCmd)
847+
serverCmd.SetArgs([]string{"--mcp-file", "/cli/path/to/mcp.json", "--exit", "dummy-command"})
848+
err := serverCmd.Execute()
849+
require.NoError(t, err)
850+
851+
assert.Equal(t, "/cli/path/to/mcp.json", viper.GetString(FlagMCPFile))
852+
})
853+
}

e2e/echo_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,3 +556,41 @@ func getFreePort() (int, error) {
556556

557557
return l.Addr().(*net.TCPAddr).Port, nil
558558
}
559+
560+
func TestServerFlagValidation(t *testing.T) {
561+
if testing.Short() {
562+
t.Skip("Skipping integration test in short mode")
563+
}
564+
565+
t.Run("mcp-file requires experimental-acp", func(t *testing.T) {
566+
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
567+
defer cancel()
568+
569+
binaryPath := os.Getenv("AGENTAPI_BINARY_PATH")
570+
if binaryPath == "" {
571+
cwd, err := os.Getwd()
572+
require.NoError(t, err, "Failed to get current working directory")
573+
binaryPath = filepath.Join(cwd, "..", "out", "agentapi")
574+
t.Logf("Building binary at %s", binaryPath)
575+
buildCmd := exec.CommandContext(ctx, "go", "build", "-o", binaryPath, ".")
576+
buildCmd.Dir = filepath.Join(cwd, "..")
577+
require.NoError(t, buildCmd.Run(), "Failed to build binary")
578+
}
579+
580+
// Create a temporary MCP file
581+
tmpDir := t.TempDir()
582+
mcpFile := filepath.Join(tmpDir, "mcp.json")
583+
err := os.WriteFile(mcpFile, []byte(`{"mcpServers": []}`), 0o644)
584+
require.NoError(t, err, "Failed to create temp MCP file")
585+
586+
// Run the server with --mcp-file but WITHOUT --experimental-acp
587+
cmd := exec.CommandContext(ctx, binaryPath, "server",
588+
"--mcp-file", mcpFile,
589+
"--", "echo", "test")
590+
591+
output, err := cmd.CombinedOutput()
592+
require.Error(t, err, "Expected server to fail when --mcp-file is used without --experimental-acp")
593+
require.Contains(t, string(output), "--mcp-file requires --experimental-acp",
594+
"Expected error message about --mcp-file requiring --experimental-acp, got: %s", string(output))
595+
})
596+
}

x/acpio/acpio.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ func NewWithPipes(ctx context.Context, toAgent io.Writer, fromAgent io.Reader, l
188188
}
189189

190190
func getSupportedMCPConfig(mcpFilePath string, logger *slog.Logger, initResp *acp.InitializeResponse) ([]acp.McpServer, error) {
191+
if mcpFilePath == "" {
192+
return []acp.McpServer{}, nil
193+
}
194+
191195
mcpFile, err := os.Open(mcpFilePath)
192196
if err != nil {
193197
return nil, xerrors.Errorf("Failed to open mcp file: %v", err)

x/acpio/acpio_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func newTestPair(t *testing.T, agent *testAgent) *acpio.ACPAgentIO {
7070
clientToAgentW, agentToClientR,
7171
nil,
7272
func() (string, error) { return os.TempDir(), nil },
73+
"", // no MCP file
7374
)
7475
require.NoError(t, err)
7576

x/acpio/mcp_internal_test.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package acpio
2+
3+
import (
4+
"log/slog"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
acp "github.com/coder/acp-go-sdk"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestGetSupportedMCPConfig(t *testing.T) {
15+
logger := slog.Default()
16+
17+
t.Run("empty file path returns empty slice", func(t *testing.T) {
18+
initResp := &acp.InitializeResponse{}
19+
result, err := getSupportedMCPConfig("", logger, initResp)
20+
require.NoError(t, err)
21+
assert.Empty(t, result)
22+
})
23+
24+
t.Run("file not found returns error", func(t *testing.T) {
25+
initResp := &acp.InitializeResponse{}
26+
_, err := getSupportedMCPConfig("/nonexistent/path/mcp.json", logger, initResp)
27+
require.Error(t, err)
28+
assert.Contains(t, err.Error(), "Failed to open mcp file")
29+
})
30+
31+
t.Run("invalid JSON returns error", func(t *testing.T) {
32+
tmpDir := t.TempDir()
33+
mcpFile := filepath.Join(tmpDir, "invalid.json")
34+
err := os.WriteFile(mcpFile, []byte("not valid json"), 0o644)
35+
require.NoError(t, err)
36+
37+
initResp := &acp.InitializeResponse{}
38+
_, err = getSupportedMCPConfig(mcpFile, logger, initResp)
39+
require.Error(t, err)
40+
assert.Contains(t, err.Error(), "Failed to decode mcp file")
41+
})
42+
43+
t.Run("stdio servers always included", func(t *testing.T) {
44+
tmpDir := t.TempDir()
45+
mcpFile := filepath.Join(tmpDir, "mcp.json")
46+
mcpContent := `{
47+
"mcpServers": [
48+
{
49+
"name": "test-stdio",
50+
"command": "/usr/bin/test",
51+
"args": ["--stdio"],
52+
"env": []
53+
}
54+
]
55+
}`
56+
err := os.WriteFile(mcpFile, []byte(mcpContent), 0o644)
57+
require.NoError(t, err)
58+
59+
initResp := &acp.InitializeResponse{
60+
AgentCapabilities: acp.AgentCapabilities{
61+
McpCapabilities: acp.McpCapabilities{
62+
Http: false,
63+
Sse: false,
64+
},
65+
},
66+
}
67+
result, err := getSupportedMCPConfig(mcpFile, logger, initResp)
68+
require.NoError(t, err)
69+
assert.Len(t, result, 1)
70+
assert.NotNil(t, result[0].Stdio)
71+
assert.Equal(t, "test-stdio", result[0].Stdio.Name)
72+
})
73+
74+
t.Run("http servers filtered when capability is false", func(t *testing.T) {
75+
tmpDir := t.TempDir()
76+
mcpFile := filepath.Join(tmpDir, "mcp.json")
77+
mcpContent := `{
78+
"mcpServers": [
79+
{
80+
"type": "http",
81+
"name": "test-http",
82+
"url": "https://example.com/mcp",
83+
"headers": []
84+
}
85+
]
86+
}`
87+
err := os.WriteFile(mcpFile, []byte(mcpContent), 0o644)
88+
require.NoError(t, err)
89+
90+
initResp := &acp.InitializeResponse{
91+
AgentCapabilities: acp.AgentCapabilities{
92+
McpCapabilities: acp.McpCapabilities{
93+
Http: false,
94+
Sse: false,
95+
},
96+
},
97+
}
98+
result, err := getSupportedMCPConfig(mcpFile, logger, initResp)
99+
require.NoError(t, err)
100+
assert.Empty(t, result)
101+
})
102+
103+
t.Run("http servers included when capability is true", func(t *testing.T) {
104+
tmpDir := t.TempDir()
105+
mcpFile := filepath.Join(tmpDir, "mcp.json")
106+
mcpContent := `{
107+
"mcpServers": [
108+
{
109+
"type": "http",
110+
"name": "test-http",
111+
"url": "https://example.com/mcp",
112+
"headers": []
113+
}
114+
]
115+
}`
116+
err := os.WriteFile(mcpFile, []byte(mcpContent), 0o644)
117+
require.NoError(t, err)
118+
119+
initResp := &acp.InitializeResponse{
120+
AgentCapabilities: acp.AgentCapabilities{
121+
McpCapabilities: acp.McpCapabilities{
122+
Http: true,
123+
Sse: false,
124+
},
125+
},
126+
}
127+
result, err := getSupportedMCPConfig(mcpFile, logger, initResp)
128+
require.NoError(t, err)
129+
assert.Len(t, result, 1)
130+
assert.NotNil(t, result[0].Http)
131+
assert.Equal(t, "test-http", result[0].Http.Name)
132+
})
133+
134+
t.Run("mixed servers filtered correctly", func(t *testing.T) {
135+
tmpDir := t.TempDir()
136+
mcpFile := filepath.Join(tmpDir, "mcp.json")
137+
mcpContent := `{
138+
"mcpServers": [
139+
{
140+
"name": "stdio-server",
141+
"command": "/usr/bin/stdio-mcp",
142+
"args": [],
143+
"env": []
144+
},
145+
{
146+
"type": "http",
147+
"name": "http-server",
148+
"url": "https://example.com/mcp",
149+
"headers": []
150+
}
151+
]
152+
}`
153+
err := os.WriteFile(mcpFile, []byte(mcpContent), 0o644)
154+
require.NoError(t, err)
155+
156+
// With HTTP capability disabled, only stdio should be included
157+
initResp := &acp.InitializeResponse{
158+
AgentCapabilities: acp.AgentCapabilities{
159+
McpCapabilities: acp.McpCapabilities{
160+
Http: false,
161+
Sse: false,
162+
},
163+
},
164+
}
165+
result, err := getSupportedMCPConfig(mcpFile, logger, initResp)
166+
require.NoError(t, err)
167+
assert.Len(t, result, 1)
168+
assert.NotNil(t, result[0].Stdio)
169+
assert.Equal(t, "stdio-server", result[0].Stdio.Name)
170+
171+
// With HTTP capability enabled, both should be included
172+
initResp.AgentCapabilities.McpCapabilities.Http = true
173+
result, err = getSupportedMCPConfig(mcpFile, logger, initResp)
174+
require.NoError(t, err)
175+
assert.Len(t, result, 2)
176+
})
177+
178+
t.Run("empty mcpServers array returns empty slice", func(t *testing.T) {
179+
tmpDir := t.TempDir()
180+
mcpFile := filepath.Join(tmpDir, "mcp.json")
181+
mcpContent := `{"mcpServers": []}`
182+
err := os.WriteFile(mcpFile, []byte(mcpContent), 0o644)
183+
require.NoError(t, err)
184+
185+
initResp := &acp.InitializeResponse{}
186+
result, err := getSupportedMCPConfig(mcpFile, logger, initResp)
187+
require.NoError(t, err)
188+
assert.Empty(t, result)
189+
})
190+
}

0 commit comments

Comments
 (0)