Skip to content

Commit 4b6d886

Browse files
committed
feat(inventory): project owner/repo to Mcp-Param-* headers (SEP-2243)
Annotates owner/repo tool params with x-mcp-header so the SDK projects them to Mcp-Param-owner/Mcp-Param-repo request headers. A remote proxy can route and filter on owner/repo from headers instead of re-parsing the JSON-RPC body (headers are SDK-validated against the body). No-op for tools without these params; old-protocol traffic unaffected. Refs: github/copilot-mcp-core#1709, #1828 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 874c25d commit 4b6d886

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

pkg/inventory/server_tool.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77

88
"github.com/github/github-mcp-server/pkg/octicons"
9+
"github.com/google/jsonschema-go/jsonschema"
910
"github.com/modelcontextprotocol/go-sdk/mcp"
1011
)
1112

@@ -116,9 +117,38 @@ func (st *ServerTool) RegisterFunc(s *mcp.Server, deps any) {
116117
if len(toolCopy.Icons) == 0 {
117118
toolCopy.Icons = st.Toolset.Icons()
118119
}
120+
// Project routing-relevant params to standard MCP-Param-* headers (SEP-2243)
121+
// so a remote proxy can read owner/repo from headers instead of re-parsing the
122+
// JSON-RPC body. No-op for tools without these params.
123+
annotateHeaderParams(&toolCopy)
119124
s.AddTool(&toolCopy, handler)
120125
}
121126

127+
// headerParams maps tool input properties to the MCP-Param-* header name a
128+
// header-aware proxy reads, avoiding a second parse of the request body.
129+
var headerParams = map[string]string{"owner": "owner", "repo": "repo"}
130+
131+
// annotateHeaderParams adds an "x-mcp-header" annotation to matching top-level
132+
// input properties, which the SDK projects onto Mcp-Param-{name} request headers.
133+
func annotateHeaderParams(tool *mcp.Tool) {
134+
schema, ok := tool.InputSchema.(*jsonschema.Schema)
135+
if !ok || schema == nil {
136+
return
137+
}
138+
for prop, header := range headerParams {
139+
ps := schema.Properties[prop]
140+
if ps == nil {
141+
continue
142+
}
143+
if ps.Extra == nil {
144+
ps.Extra = map[string]any{}
145+
}
146+
if _, exists := ps.Extra["x-mcp-header"]; !exists {
147+
ps.Extra["x-mcp-header"] = header
148+
}
149+
}
150+
}
151+
122152
// NewServerToolWithContextHandler creates a ServerTool with a handler that receives deps via context.
123153
// This is the preferred approach for tools because it doesn't create closures at registration time,
124154
// which is critical for performance in servers that create a new instance per request.

pkg/inventory/server_tool_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"testing"
77

8+
"github.com/google/jsonschema-go/jsonschema"
89
"github.com/modelcontextprotocol/go-sdk/mcp"
910
"github.com/stretchr/testify/assert"
1011
"github.com/stretchr/testify/require"
@@ -78,3 +79,23 @@ func TestNewServerToolWithContextHandler_ValidArguments_Succeeds(t *testing.T) {
7879
require.True(t, ok)
7980
assert.Equal(t, "success: octocat/hello-world", textContent.Text)
8081
}
82+
83+
func TestAnnotateHeaderParams(t *testing.T) {
84+
tool := &mcp.Tool{InputSchema: &jsonschema.Schema{
85+
Type: "object",
86+
Properties: map[string]*jsonschema.Schema{
87+
"owner": {Type: "string"},
88+
"repo": {Type: "string"},
89+
"detail": {Type: "string"},
90+
},
91+
}}
92+
annotateHeaderParams(tool)
93+
schema := tool.InputSchema.(*jsonschema.Schema)
94+
assert.Equal(t, "owner", schema.Properties["owner"].Extra["x-mcp-header"])
95+
assert.Equal(t, "repo", schema.Properties["repo"].Extra["x-mcp-header"])
96+
assert.Nil(t, schema.Properties["detail"].Extra)
97+
98+
// No-op for tools without owner/repo and when InputSchema is not a *jsonschema.Schema
99+
annotateHeaderParams(&mcp.Tool{InputSchema: &jsonschema.Schema{Properties: map[string]*jsonschema.Schema{"x": {}}}})
100+
annotateHeaderParams(&mcp.Tool{InputSchema: json.RawMessage(`{}`)})
101+
}

0 commit comments

Comments
 (0)