Fix MCP tools/list rejection: wrap list_repos slice in object

The go-sdk infers each tool's outputSchema from its handler result
type, and MCP structured output must be type "object". list_repos
returned []repos.State, yielding outputSchema.type "array", which
Claude Desktop rejects at tools/list — taking the whole server down.

Wrap the slice in listReposOutput{Repos: ...} so the schema is an
object, update the round-trip test, and record the struct-result rule
in AGENT.md §8.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 08:00:30 -04:00
parent e3336efc25
commit d3abd4416e
3 changed files with 19 additions and 4 deletions
+9 -2
View File
@@ -21,6 +21,13 @@ type getRepoInput struct {
Path string `json:"path" jsonschema:"absolute filesystem path of the repository, exactly as returned by list_repos"`
}
// listReposOutput wraps the repository list. MCP structured output must be a JSON
// object, so the SDK-inferred outputSchema has to be type "object" — returning a
// bare slice yields type "array", which Claude Desktop rejects at tools/list.
type listReposOutput struct {
Repos []repos.State `json:"repos" jsonschema:"the discovered repositories"`
}
// NewServer builds the MCP server and registers the (currently read-only) tools.
func NewServer(svc *service.Service, version string) *mcpsdk.Server {
s := mcpsdk.NewServer(&mcpsdk.Implementation{
@@ -34,8 +41,8 @@ func NewServer(svc *service.Service, version string) *mcpsdk.Server {
mcpsdk.AddTool(s, &mcpsdk.Tool{
Name: "list_repos",
Description: "List every Git repository GitManager has discovered, each with its current branch, dirty/clean state, ahead/behind counts, and remote names.",
}, func(_ context.Context, _ *mcpsdk.CallToolRequest, _ struct{}) (*mcpsdk.CallToolResult, []repos.State, error) {
return nil, svc.ListRepos(), nil
}, func(_ context.Context, _ *mcpsdk.CallToolRequest, _ struct{}) (*mcpsdk.CallToolResult, listReposOutput, error) {
return nil, listReposOutput{Repos: svc.ListRepos()}, nil
})
// get_repo — details for one already-discovered repository.
+3 -2
View File
@@ -65,8 +65,9 @@ func TestMCPRoundTrip(t *testing.T) {
if err != nil {
t.Fatalf("list_repos: %v", err)
}
var states []repos.State
decodeResult(t, res, &states)
var listOut listReposOutput
decodeResult(t, res, &listOut)
states := listOut.Repos
if len(states) != 1 {
t.Fatalf("expected 1 repo, got %d: %+v", len(states), states)
}