From a2a609783e751ec31fcbfb4d71fd57ba4c041f00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Oct 2026 05:07:40 +0000 Subject: [PATCH 1/2] Initial plan From b9c516b4c65f076d0773d1c3083696b563144d63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Oct 2026 05:10:57 +0000 Subject: [PATCH 2/2] fix(http): preserve typed owner and repo extraction on latest main Co-authored-by: Daigrin <198701947+Daigrin@users.noreply.github.com> --- pkg/context/mcp_info.go | 4 ++ pkg/http/middleware/mcp_parse.go | 11 ++++ pkg/http/middleware/mcp_parse_test.go | 78 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/pkg/context/mcp_info.go b/pkg/context/mcp_info.go index fc09e6b139..ccb7bab3e1 100644 --- a/pkg/context/mcp_info.go +++ b/pkg/context/mcp_info.go @@ -22,6 +22,10 @@ type MCPMethodInfo struct { // ItemName is the name of the specific item being accessed (tool name, resource URI, prompt name) // Only populated for call/get methods (tools/call, prompts/get, resources/read) ItemName string + // Owner is the repository owner from tool call arguments, if present. + Owner string + // Repo is the repository name from tool call arguments, if present. + Repo string // RawArguments contains the unmaterialized tool arguments for tools/call requests. RawArguments json.RawMessage // ProtocolVersion and ClientCapabilities describe the requesting MCP client diff --git a/pkg/http/middleware/mcp_parse.go b/pkg/http/middleware/mcp_parse.go index bcc44f01f5..ec585fccae 100644 --- a/pkg/http/middleware/mcp_parse.go +++ b/pkg/http/middleware/mcp_parse.go @@ -115,6 +115,17 @@ func parseMCPMethodInfo(body []byte) (*ghcontext.MCPMethodInfo, error) { case "tools/call": methodInfo.ItemName = mcpReq.Params.Name methodInfo.RawArguments = mcpReq.Params.Arguments + if len(methodInfo.RawArguments) > 0 { + var args struct { + Owner json.RawMessage `json:"owner"` + Repo json.RawMessage `json:"repo"` + } + if err := json.Unmarshal(methodInfo.RawArguments, &args); err == nil { + // Decode independently so an invalid type does not discard the other field. + _ = json.Unmarshal(args.Owner, &methodInfo.Owner) + _ = json.Unmarshal(args.Repo, &methodInfo.Repo) + } + } case "prompts/get": methodInfo.ItemName = mcpReq.Params.Name case "resources/read": diff --git a/pkg/http/middleware/mcp_parse_test.go b/pkg/http/middleware/mcp_parse_test.go index 8034dc29a6..60dc125e0c 100644 --- a/pkg/http/middleware/mcp_parse_test.go +++ b/pkg/http/middleware/mcp_parse_test.go @@ -23,6 +23,8 @@ func TestWithMCPParse(t *testing.T) { expectInfo bool expectedMethod string expectedItem string + expectedOwner string + expectedRepo string expectedRaw string expectedArgs map[string]any expectedProtocol string @@ -109,6 +111,8 @@ func TestWithMCPParse(t *testing.T) { expectInfo: true, expectedMethod: "tools/call", expectedItem: "get_file_contents", + expectedOwner: "github", + expectedRepo: "github-mcp-server", expectedRaw: `{"owner":"github","repo":"github-mcp-server","path":"README.md"}`, expectedArgs: map[string]any{"owner": "github", "repo": "github-mcp-server", "path": "README.md"}, }, @@ -174,6 +178,8 @@ func TestWithMCPParse(t *testing.T) { require.NotNil(t, capturedInfo) assert.Equal(t, tt.expectedMethod, capturedInfo.Method) assert.Equal(t, tt.expectedItem, capturedInfo.ItemName) + assert.Equal(t, tt.expectedOwner, capturedInfo.Owner) + assert.Equal(t, tt.expectedRepo, capturedInfo.Repo) assert.Equal(t, tt.expectedProtocol, capturedInfo.ProtocolVersion) if tt.expectedForm { require.NotNil(t, capturedInfo.ClientCapabilities) @@ -199,6 +205,78 @@ func TestWithMCPParse(t *testing.T) { } } +func TestWithMCPParseOwnerRepo(t *testing.T) { + tests := []struct { + name string + arguments string + owner string + repo string + }{ + { + name: "owner survives numeric repo", + arguments: `{"owner":"github","repo":123}`, + owner: "github", + }, + { + name: "repo survives object owner", + arguments: `{"owner":{"name":"github"},"repo":"github-mcp-server"}`, + repo: "github-mcp-server", + }, + { + name: "owner survives array repo", + arguments: `{"owner":"github","repo":["github-mcp-server"]}`, + owner: "github", + }, + { + name: "repo survives boolean owner", + arguments: `{"owner":false,"repo":"github-mcp-server"}`, + repo: "github-mcp-server", + }, + { + name: "null owner", + arguments: `{"owner":null,"repo":"github-mcp-server"}`, + repo: "github-mcp-server", + }, + { + name: "nested arguments are ignored", + arguments: `{"owner":"github","repo":"github-mcp-server","extra":{"owner":"other","items":[1,{"repo":"other"}]}}`, + owner: "github", + repo: "github-mcp-server", + }, + { + name: "duplicate field uses last value", + arguments: `{"owner":"github","owner":null,"repo":"github-mcp-server"}`, + repo: "github-mcp-server", + }, + {name: "empty object", arguments: `{}`}, + {name: "null arguments", arguments: `null`}, + {name: "array arguments", arguments: `["github","github-mcp-server"]`}, + {name: "string arguments", arguments: `"github"`}, + {name: "numeric arguments", arguments: `123`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body := `{"jsonrpc":"2.0","method":"tools/call","params":{"name":"test_tool","arguments":` + tt.arguments + `}}` + next := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + info, ok := ghcontext.MCPMethod(r.Context()) + require.True(t, ok) + require.NotNil(t, info) + assert.Equal(t, "tools/call", info.Method) + assert.Equal(t, "test_tool", info.ItemName) + assert.Equal(t, tt.owner, info.Owner) + assert.Equal(t, tt.repo, info.Repo) + assert.JSONEq(t, tt.arguments, string(info.RawArguments)) + restoredBody, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.Equal(t, body, string(restoredBody)) + }) + request := httptest.NewRequest(http.MethodPost, "/mcp", strings.NewReader(body)) + WithMCPParse()(next).ServeHTTP(httptest.NewRecorder(), request) + }) + } +} + func TestWithMCPParseRetainsLargeArgumentsWithoutMaterializingThem(t *testing.T) { nested := map[string]any{ "items": []any{