Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package github-mcp-server for 
openSUSE:Factory checked in at 2026-10-09 21:45:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/github-mcp-server (Old)
 and      /work/SRC/openSUSE:Factory/.github-mcp-server.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "github-mcp-server"

Fri Oct  9 21:45:26 2026 rev:13 rq:1383687 version:2.0.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/github-mcp-server/github-mcp-server.changes      
2026-10-08 13:28:10.238757705 +0200
+++ 
/work/SRC/openSUSE:Factory/.github-mcp-server.new.3698736/github-mcp-server.changes
 2026-10-09 21:45:55.530686732 +0200
@@ -1,0 +2,17 @@
+Fri Oct  9 07:12:08 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 2.0.2 (bugfix):
+  * Restore typed tool HTTP header validation (upstream PR 3454):
+    Streamable HTTP rejects header/body mismatches before the
+    receiving middleware, with full body validation and defaults
+    unchanged behind tool guards and advertised schemas unchanged
+  * No tool changes: identical tool surface as 2.0.1, verified via
+    schema on locally built binaries of both versions (91 tools each)
+- Vendored Go modules are unchanged (go.mod/go.sum identical to
+  2.0.1), so the licence audit stands: only MIT, BSD-3-Clause and
+  Apache-2.0, no copyleft; the crossed range and release notes
+  name no CVE
+- go.mod still requires go 1.26.8, so the golang(API) >= 1.26.8
+  floor stands
+
+-------------------------------------------------------------------

Old:
----
  github-mcp-server-2.0.1.tar.zst

New:
----
  github-mcp-server-2.0.2.tar.zst

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ github-mcp-server.spec ++++++
--- /var/tmp/diff_new_pack.YVzu3Q/_old  2026-10-09 21:45:57.006748843 +0200
+++ /var/tmp/diff_new_pack.YVzu3Q/_new  2026-10-09 21:45:57.008748928 +0200
@@ -23,7 +23,7 @@
 #   -modcacherw     leave the module cache writable so cleanup cannot fail
 %define goflags "-buildmode=pie -mod=vendor -modcacherw"
 Name:           github-mcp-server
-Version:        2.0.1
+Version:        2.0.2
 Release:        0
 Summary:        Model Context Protocol server for GitHub
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.YVzu3Q/_old  2026-10-09 21:45:57.050750695 +0200
+++ /var/tmp/diff_new_pack.YVzu3Q/_new  2026-10-09 21:45:57.054750863 +0200
@@ -3,8 +3,8 @@
     <param name="url">https://github.com/github/github-mcp-server</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v2.0.1</param>
-    <param name="version">2.0.1</param>
+    <param name="revision">v2.0.2</param>
+    <param name="version">2.0.2</param>
     <param name="changesgenerate">disable</param>
   </service>
   <service name="recompress" mode="manual">
@@ -13,7 +13,7 @@
   </service>
   <service name="go_modules" mode="manual">
     <param name="compression">zst</param>
-      <param name="archive">github-mcp-server-2.0.1.tar.zst</param>
+      <param name="archive">github-mcp-server-2.0.2.tar.zst</param>
   </service>
 </services>
 

++++++ github-mcp-server-2.0.1.tar.zst -> github-mcp-server-2.0.2.tar.zst ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/github-mcp-server-2.0.1/docs/streamable-http.md 
new/github-mcp-server-2.0.2/docs/streamable-http.md
--- old/github-mcp-server-2.0.1/docs/streamable-http.md 2026-10-07 
10:09:18.000000000 +0200
+++ new/github-mcp-server-2.0.2/docs/streamable-http.md 2026-10-08 
13:25:50.000000000 +0200
@@ -12,6 +12,22 @@
 
 ## Running the Server
 
+### MCP parameter headers
+
+For protocol version `2026-07-28` and later, the SDK validates standard
+`Mcp-Method`, `Mcp-Name`, and schema-annotated `Mcp-Param-*` headers at the
+Streamable HTTP boundary. Tool schemas annotate `owner` and `repo` with
+`x-mcp-header`; when an annotated argument is present and non-null, its header
+must be present and match the body value. A header for an absent or null
+argument is rejected. Header names are case-insensitive; string values and
+JSON property names are case-sensitive.
+
+Mismatches return HTTP `400` with JSON-RPC error code `-32020` before MCP
+receiving middleware or tool handlers run. This applies to typed and untyped
+tools without changing their advertised schemas. Full typed body validation
+and defaults still run after tool authorization guards. Older protocol versions
+do not require or validate these standard parameter headers.
+
 ### Basic HTTP Server
 
 Start the server on the default port (8082):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/github-mcp-server-2.0.1/internal/ghmcp/oauth_typed_test.go 
new/github-mcp-server-2.0.2/internal/ghmcp/oauth_typed_test.go
--- old/github-mcp-server-2.0.1/internal/ghmcp/oauth_typed_test.go      
2026-10-07 10:09:18.000000000 +0200
+++ new/github-mcp-server-2.0.2/internal/ghmcp/oauth_typed_test.go      
2026-10-08 13:25:50.000000000 +0200
@@ -1,8 +1,12 @@
 package ghmcp
 
 import (
+       "bytes"
        "context"
        "encoding/json"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
        "testing"
 
        "github.com/github/github-mcp-server/v2/internal/oauth"
@@ -236,6 +240,143 @@
                })
        }
 }
+
+func TestOAuthTypedHTTPHeaderBoundary(t *testing.T) {
+       for _, tc := range []struct {
+               name      string
+               arguments string
+               owner     string
+               repo      string
+               mismatch  bool
+               valid     bool
+       }{
+               {"owner-mismatch", 
`{"owner":"octo","repo":"hello","mode":"valid"}`, "other", "hello", true, true},
+               {"repo-mismatch", 
`{"owner":"octo","repo":"hello","mode":"valid"}`, "octo", "other", true, true},
+               {"missing-header", 
`{"owner":"octo","repo":"hello","mode":"valid"}`, "", "hello", true, true},
+               {"matching", `{"owner":"octo","repo":"hello","mode":"valid"}`, 
"octo", "hello", false, true},
+               {"missing-required", `{}`, "", "", false, false},
+               {"invalid-enum", 
`{"owner":"octo","repo":"hello","mode":"invalid"}`, "octo", "hello", false, 
false},
+               {"invalid-owner-type", 
`{"owner":42,"repo":"hello","mode":"valid"}`, "42", "hello", false, false},
+               {"null-owner", `{"owner":null,"repo":"hello","mode":"valid"}`, 
"", "hello", false, false},
+               {"non-object", `[]`, "", "", false, false},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       fake := oauthPendingAuthenticator()
+                       handlerCalls, receivingCalls, preflightCalls, 
normalizerCalls := 0, 0, 0, 0
+                       type input struct {
+                               Owner string `json:"owner"`
+                               Repo  string `json:"repo"`
+                               Mode  string `json:"mode"`
+                       }
+                       tool := 
inventory.NewServerToolWithContextHandlerAndSchemaOptions(
+                               mcp.Tool{
+                                       Name: probeToolName,
+                                       InputSchema: &jsonschema.Schema{
+                                               Type: "object",
+                                               Properties: 
map[string]*jsonschema.Schema{
+                                                       "owner": {Type: 
"string"},
+                                                       "repo":  {Type: 
"string"},
+                                                       "mode":  {Type: 
"string", Enum: []any{"valid"}},
+                                               },
+                                               Required: []string{"owner", 
"repo", "mode"},
+                                       },
+                               },
+                               inventory.ToolsetMetadata{ID: "test"},
+                               func(context.Context, *mcp.CallToolRequest, 
input) (*mcp.CallToolResult, oauthProbeOutput, error) {
+                                       handlerCalls++
+                                       return nil, oauthProbeOutput{Status: 
"tool-ran"}, nil
+                               },
+                               inventory.TypedSchemaOptions{
+                                       Preflight: func(ctx context.Context, _ 
*mcp.CallToolRequest) (context.Context, *mcp.CallToolResult, error) {
+                                               preflightCalls++
+                                               return ctx, nil, nil
+                                       },
+                               },
+                               func(raw json.RawMessage) (json.RawMessage, 
error) {
+                                       normalizerCalls++
+                                       return raw, nil
+                               },
+                       )
+                       server := mcp.NewServer(&mcp.Implementation{Name: 
"oauth-http", Version: "test"}, nil)
+                       tool.RegisterFunc(server, nil, 
createOAuthToolMiddleware(fake, discardLogger()))
+                       server.AddReceivingMiddleware(func(next 
mcp.MethodHandler) mcp.MethodHandler {
+                               return func(ctx context.Context, method string, 
req mcp.Request) (mcp.Result, error) {
+                                       if method == 
inventory.MCPMethodToolsCall {
+                                               receivingCalls++
+                                       }
+                                       return next(ctx, method, req)
+                               }
+                       })
+                       handler := 
mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
+                               return server
+                       }, &mcp.StreamableHTTPOptions{Stateless: true, 
JSONResponse: true})
+                       call := func(accept bool) *httptest.ResponseRecorder {
+                               t.Helper()
+                               params := map[string]any{
+                                       "name": probeToolName, "arguments": 
json.RawMessage(tc.arguments),
+                                       "_meta": mcp.Meta{
+                                               mcp.MetaKeyProtocolVersion:    
inventory.ProtocolVersionMultiRoundTrip,
+                                               mcp.MetaKeyClientInfo:         
&mcp.Implementation{Name: "test", Version: "test"},
+                                               mcp.MetaKeyClientCapabilities: 
&mcp.ClientCapabilities{Elicitation: &mcp.ElicitationCapabilities{URL: 
&mcp.URLElicitationCapabilities{}}},
+                                       },
+                               }
+                               if accept {
+                                       params["inputResponses"] = 
mcp.InputResponseMap{
+                                               oauthElicitIDPrefix + "flow-1": 
&mcp.ElicitResult{Action: "accept"},
+                                       }
+                               }
+                               body, err := json.Marshal(params)
+                               require.NoError(t, err)
+                               req := httptest.NewRequest(http.MethodPost, 
"/", bytes.NewBufferString(fmt.Sprintf(
+                                       
`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":%s}`, body)))
+                               req.Header.Set("Content-Type", 
"application/json")
+                               req.Header.Set("Accept", "application/json, 
text/event-stream")
+                               req.Header.Set("Mcp-Protocol-Version", 
inventory.ProtocolVersionMultiRoundTrip)
+                               req.Header.Set("Mcp-Method", "tools/call")
+                               req.Header.Set("Mcp-Name", probeToolName)
+                               req.Header.Set("Mcp-Param-owner", tc.owner)
+                               req.Header.Set("Mcp-Param-repo", tc.repo)
+                               rec := httptest.NewRecorder()
+                               handler.ServeHTTP(rec, req)
+                               return rec
+                       }
+                       rec := call(false)
+                       if tc.mismatch {
+                               require.Equal(t, http.StatusBadRequest, 
rec.Code, rec.Body.String())
+                               assert.Contains(t, rec.Body.String(), "header 
mismatch")
+                               assert.Zero(t, receivingCalls)
+                               assert.Zero(t, fake.authCalls)
+                       } else {
+                               require.Equal(t, http.StatusOK, rec.Code, 
rec.Body.String())
+                               assert.Contains(t, rec.Body.String(), 
`"resultType":"input_required"`)
+                               assert.NotContains(t, rec.Body.String(), 
"structuredContent")
+                               assert.Equal(t, 1, receivingCalls)
+                               assert.Equal(t, 1, fake.authCalls)
+                       }
+                       assert.Zero(t, handlerCalls)
+                       assert.Zero(t, preflightCalls)
+                       assert.Zero(t, normalizerCalls)
+                       if tc.mismatch {
+                               return
+                       }
+                       rec = call(true)
+                       require.Equal(t, http.StatusOK, rec.Code, 
rec.Body.String())
+                       assert.Contains(t, rec.Body.String(), 
`"resultType":"complete"`)
+                       assert.NotContains(t, rec.Body.String(), 
`"resultType":"input_required"`)
+                       assert.Equal(t, 1, fake.awaitCalls)
+                       assert.Equal(t, 1, preflightCalls)
+                       assert.Equal(t, 1, normalizerCalls)
+                       if tc.valid {
+                               assert.Equal(t, 1, handlerCalls)
+                               assert.Contains(t, rec.Body.String(), 
`"structuredContent":{"status":"tool-ran"}`)
+                       } else {
+                               assert.Zero(t, handlerCalls)
+                               assert.Contains(t, rec.Body.String(), 
`"isError":true`)
+                               assert.NotContains(t, rec.Body.String(), 
"structuredContent")
+                       }
+               })
+       }
+}
 
 func TestOAuthTypedRegistrationGuardsInvalidArguments(t *testing.T) {
        type input struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/github-mcp-server-2.0.1/pkg/inventory/header_params_test.go 
new/github-mcp-server-2.0.2/pkg/inventory/header_params_test.go
--- old/github-mcp-server-2.0.1/pkg/inventory/header_params_test.go     
1970-01-01 01:00:00.000000000 +0100
+++ new/github-mcp-server-2.0.2/pkg/inventory/header_params_test.go     
2026-10-08 13:25:50.000000000 +0200
@@ -0,0 +1,266 @@
+package inventory
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "net/http/httptest"
+       "testing"
+
+       "github.com/google/jsonschema-go/jsonschema"
+       "github.com/modelcontextprotocol/go-sdk/jsonrpc"
+       "github.com/modelcontextprotocol/go-sdk/mcp"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+func TestCachedHeaderInputSchema(t *testing.T) {
+       original := &jsonschema.Schema{
+               Type: "object",
+               Properties: map[string]*jsonschema.Schema{
+                       "owner": {
+                               Type: "string", MinLength: new(1), Enum: 
[]any{"octo"},
+                               Default: json.RawMessage(`"octo"`),
+                               Extra:   map[string]any{"x-mcp-header": 
"owner", "unrelated": "metadata"},
+                       },
+                       "query": {Type: "string", Default: 
json.RawMessage(`"default"`)},
+                       "nested": {Type: "object", Required: []string{"count"}, 
Properties: map[string]*jsonschema.Schema{
+                               "count": {Type: "integer", Minimum: 
new(float64(1)), Extra: map[string]any{"x-mcp-header": "count"}},
+                       }},
+               },
+               Required:             []string{"owner", "query", "nested"},
+               AdditionalProperties: &jsonschema.Schema{Not: 
&jsonschema.Schema{}},
+       }
+       before := mustMarshalJSON(t, original)
+       raw := json.RawMessage(before)
+       var mapped map[string]any
+       require.NoError(t, json.Unmarshal(raw, &mapped))
+       var first *jsonschema.Schema
+       for _, value := range []any{original, CloneSchema(original), raw, 
mapped} {
+               projected, err := cachedHeaderInputSchema(value)
+               require.NoError(t, err)
+               if first == nil {
+                       first = projected
+               } else {
+                       assert.Same(t, first, projected)
+               }
+               assert.JSONEq(t, `{
+                               "type":"object",
+                               "properties":{
+                                       
"owner":{"type":"string","x-mcp-header":"owner"},
+                                       
"nested":{"properties":{"count":{"type":"integer","x-mcp-header":"count"}}}
+                               }
+                       }`, mustMarshalJSON(t, projected))
+               resolved, err := projected.Resolve(nil)
+               require.NoError(t, err)
+               arguments := map[string]any{"query": 42}
+               require.NoError(t, resolved.ApplyDefaults(&arguments))
+               require.NoError(t, resolved.Validate(arguments))
+               assert.Equal(t, map[string]any{"query": 42}, arguments, "no 
required fields, defaults, or unannotated body constraints")
+       }
+       assert.JSONEq(t, before, mustMarshalJSON(t, original), "projection must 
not mutate the advertised schema")
+       empty, err := cachedHeaderInputSchema(&jsonschema.Schema{Type: 
"object", Properties: map[string]*jsonschema.Schema{"query": {Type: "string"}}})
+       require.NoError(t, err)
+       object, err := cachedObjectInputSchema()
+       require.NoError(t, err)
+       assert.Same(t, object, empty)
+}
+
+func TestTypedHeaderParamsHTTP(t *testing.T) {
+       type input struct {
+               Owner  string `json:"owner"`
+               Repo   string `json:"repo"`
+               Query  string `json:"query,omitempty"`
+               Active bool   `json:"active,omitempty"`
+               Count  int    `json:"count,omitempty"`
+               Nested struct {
+                       Route string `json:"route"`
+               } `json:"nested,omitzero"`
+       }
+       schema := &jsonschema.Schema{
+               Type: "object",
+               Properties: map[string]*jsonschema.Schema{
+                       "owner": {Type: "string", MinLength: new(1)},
+                       "repo":  {Type: "string"},
+                       "query": {Type: "string", Default: 
json.RawMessage(`"default-query"`), Enum: []any{"default-query"}},
+                       "active": {Type: "boolean", Extra: map[string]any{
+                               "x-mcp-header": "Active",
+                       }},
+                       "count": {Type: "integer", Extra: map[string]any{
+                               "x-mcp-header": "count",
+                       }},
+                       "nested": {Type: "object", Properties: 
map[string]*jsonschema.Schema{
+                               "route": {Type: "string", Extra: 
map[string]any{"x-mcp-header": "route"}},
+                       }},
+               },
+               Required: []string{"owner", "repo"},
+       }
+       for _, typed := range []bool{false, true} {
+               for _, registration := range []string{"single", "inventory", 
"modern", "legacy", "validation-override", "inferred"} {
+                       t.Run(fmt.Sprintf("typed=%t/%s", typed, registration), 
func(t *testing.T) {
+                               for _, protocol := range 
[]string{ProtocolVersionMultiRoundTrip, "2025-11-25"} {
+                                       t.Run(protocol, func(t *testing.T) {
+                                               toolSchema := schema
+                                               if registration == "inferred" {
+                                                       var err error
+                                                       toolSchema, err = 
CachedInputSchemaFor[input](nil)
+                                                       require.NoError(t, err)
+                                               }
+                                               options := TypedSchemaOptions{}
+                                               if registration == 
"validation-override" {
+                                                       
options.ValidationInputSchema = CloneSchema(toolSchema)
+                                                       // Runtime validation 
need not carry the advertised bindings.
+                                                       for _, property := 
range options.ValidationInputSchema.Properties {
+                                                               property.Extra 
= nil
+                                                       }
+                                                       
options.ValidationInputSchema.Properties["nested"].Properties["route"].Extra = 
nil
+                                               }
+                                               handlerCalls, receivingCalls, 
guardCalls := 0, 0, 0
+                                               gotQuery := ""
+                                               definition := mcp.Tool{Name: 
"header_probe", InputSchema: toolSchema}
+                                               tool := 
NewServerToolWithContextHandlerAndSchemaOptions(
+                                                       definition, 
testToolsetMetadata("test"),
+                                                       func(_ context.Context, 
_ *mcp.CallToolRequest, args input) (*mcp.CallToolResult, typedTestOutput, 
error) {
+                                                               handlerCalls++
+                                                               gotQuery = 
args.Query
+                                                               return nil, 
typedTestOutput{Query: args.Query}, nil
+                                                       }, options,
+                                               )
+                                               if !typed {
+                                                       tool = 
NewServerToolWithContextHandler(
+                                                               definition, 
testToolsetMetadata("test"),
+                                                               
func(context.Context, *mcp.CallToolRequest, input) (*mcp.CallToolResult, any, 
error) {
+                                                                       
handlerCalls++
+                                                                       return 
&mcp.CallToolResult{Content: []mcp.Content{&mcp.TextContent{Text: "ok"}}}, nil, 
nil
+                                                               },
+                                                       )
+                                               } else if registration == 
"inferred" {
+                                                       tool.Tool.InputSchema = 
nil
+                                               }
+                                               server := 
mcp.NewServer(&mcp.Implementation{Name: "header-test", Version: "test"}, nil)
+                                               guard := func(next 
mcp.ToolHandler) mcp.ToolHandler {
+                                                       return func(ctx 
context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+                                                               guardCalls++
+                                                               return 
next(ctx, req)
+                                                       }
+                                               }
+                                               switch registration {
+                                               case "inventory":
+                                                       inv, err := 
NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"test"}).Build()
+                                                       require.NoError(t, err)
+                                                       
inv.RegisterTools(context.Background(), server, nil, guard)
+                                               case "modern":
+                                                       
tool.RegisterFuncForProtocolEra(server, nil, ProtocolEraModern, guard)
+                                               case "legacy":
+                                                       
tool.RegisterFuncForProtocolEra(server, nil, ProtocolEraLegacy, guard)
+                                               default:
+                                                       
tool.RegisterFunc(server, nil, guard)
+                                               }
+                                               // Model a scanner installed as 
receiving middleware. Header failures
+                                               // must be rejected by the HTTP 
transport before reaching it.
+                                               
server.AddReceivingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler {
+                                                       return func(ctx 
context.Context, method string, req mcp.Request) (mcp.Result, error) {
+                                                               if method == 
MCPMethodToolsCall {
+                                                                       
receivingCalls++
+                                                               }
+                                                               return 
next(ctx, method, req)
+                                                       }
+                                               })
+                                               handler := 
mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
+                                                       return server
+                                               }, 
&mcp.StreamableHTTPOptions{Stateless: true, JSONResponse: true})
+                                               request := func(method, 
arguments string, headers map[string]string) *httptest.ResponseRecorder {
+                                                       t.Helper()
+                                                       params := 
fmt.Sprintf(`{"name":"header_probe","arguments":%s,"_meta":{"io.modelcontextprotocol/protocolVersion":%q,"io.modelcontextprotocol/clientInfo":{"name":"test","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}}`,
 arguments, protocol)
+                                                       req := 
httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString(fmt.Sprintf(
+                                                               
`{"jsonrpc":"2.0","id":1,"method":%q,"params":%s}`, method, params)))
+                                                       
req.Header.Set("Content-Type", "application/json")
+                                                       
req.Header.Set("Accept", "application/json, text/event-stream")
+                                                       
req.Header.Set("Mcp-Protocol-Version", protocol)
+                                                       
req.Header.Set("Mcp-Method", method)
+                                                       
req.Header.Set("Mcp-Name", "header_probe")
+                                                       for key, value := range 
headers {
+                                                               
req.Header.Set(key, value)
+                                                       }
+                                                       rec := 
httptest.NewRecorder()
+                                                       handler.ServeHTTP(rec, 
req)
+                                                       return rec
+                                               }
+                                               list := 
request(MCPMethodToolsList, `{}`, nil)
+                                               require.Equal(t, http.StatusOK, 
list.Code, list.Body.String())
+                                               var listed struct {
+                                                       Result 
mcp.ListToolsResult `json:"result"`
+                                               }
+                                               require.NoError(t, 
json.Unmarshal(list.Body.Bytes(), &listed))
+                                               require.Len(t, 
listed.Result.Tools, 1)
+                                               want := definition
+                                               want.InputSchema = toolSchema
+                                               AnnotateHeaderParams(&want)
+                                               assert.JSONEq(t, 
mustMarshalJSON(t, want.InputSchema), mustMarshalJSON(t, 
listed.Result.Tools[0].InputSchema))
+
+                                               type headerCase struct {
+                                                       name      string
+                                                       arguments string
+                                                       headers   
map[string]string
+                                                       reject    bool
+                                               }
+                                               cases := []headerCase{
+                                                       {"matching", 
`{"owner":"octo","repo":"hello"}`, map[string]string{"Mcp-Param-owner": "octo", 
"Mcp-Param-repo": "hello"}, false},
+                                                       {"owner-mismatch", 
`{"owner":"octo","repo":"hello"}`, map[string]string{"Mcp-Param-owner": 
"other", "Mcp-Param-repo": "hello"}, true},
+                                                       {"repo-mismatch", 
`{"owner":"octo","repo":"hello"}`, map[string]string{"Mcp-Param-owner": "octo", 
"Mcp-Param-repo": "other"}, true},
+                                                       {"missing-headers", 
`{"owner":"octo","repo":"hello"}`, nil, true},
+                                                       {"absent-body", `{}`, 
map[string]string{"Mcp-Param-owner": "octo"}, true},
+                                                       {"null-body", 
`{"owner":null}`, map[string]string{"Mcp-Param-owner": "octo"}, true},
+                                                       
{"case-insensitive-header", `{"owner":"octo","repo":"hello"}`, 
map[string]string{"mcp-param-OWNER": "octo", "MCP-PARAM-REPO": "hello"}, false},
+                                                       
{"case-sensitive-value", `{"owner":"Octo","repo":"hello"}`, 
map[string]string{"Mcp-Param-owner": "octo", "Mcp-Param-repo": "hello"}, true},
+                                                       
{"case-sensitive-property", `{"Owner":"octo"}`, 
map[string]string{"Mcp-Param-owner": "octo"}, true},
+                                                       {"base64", 
`{"owner":"octo","repo":"hello"}`, map[string]string{"Mcp-Param-owner": 
"=?base64?b2N0bw==?=", "Mcp-Param-repo": "hello"}, false},
+                                                       {"invalid-base64", 
`{"owner":"octo","repo":"hello"}`, map[string]string{"Mcp-Param-owner": 
"=?base64?!!!?=", "Mcp-Param-repo": "hello"}, true},
+                                               }
+                                               if registration != "inferred" {
+                                                       cases = append(cases,
+                                                               
headerCase{"optional-and-nested", 
`{"owner":"octo","repo":"hello","active":false,"count":42,"nested":{"route":"here"}}`,
+                                                                       
map[string]string{"Mcp-Param-owner": "octo", "Mcp-Param-repo": "hello", 
"Mcp-Param-active": "false", "Mcp-Param-count": "42", "Mcp-Param-route": 
"here"}, false},
+                                                               
headerCase{"nested-mismatch", 
`{"owner":"octo","repo":"hello","nested":{"route":"here"}}`,
+                                                                       
map[string]string{"Mcp-Param-owner": "octo", "Mcp-Param-repo": "hello", 
"Mcp-Param-route": "elsewhere"}, true},
+                                                               
headerCase{"optional-absent", `{"owner":"octo","repo":"hello"}`,
+                                                                       
map[string]string{"Mcp-Param-owner": "octo", "Mcp-Param-repo": "hello", 
"Mcp-Param-active": "false"}, true},
+                                                       )
+                                               }
+                                               for _, tc := range cases {
+                                                       t.Run(tc.name, func(t 
*testing.T) {
+                                                               handlerCalls, 
receivingCalls, guardCalls = 0, 0, 0
+                                                               rec := 
request(MCPMethodToolsCall, tc.arguments, tc.headers)
+                                                               if tc.reject && 
protocol == ProtocolVersionMultiRoundTrip {
+                                                                       
require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String())
+                                                                       var 
wire struct {
+                                                                               
Error *jsonrpc.Error `json:"error"`
+                                                                       }
+                                                                       
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wire))
+                                                                       
require.NotNil(t, wire.Error)
+                                                                       
assert.EqualValues(t, mcp.CodeHeaderMismatch, wire.Error.Code)
+                                                                       
assert.Contains(t, wire.Error.Message, "header mismatch")
+                                                                       
assert.Zero(t, handlerCalls)
+                                                                       
assert.Zero(t, receivingCalls)
+                                                                       
assert.Zero(t, guardCalls)
+                                                               } else {
+                                                                       
require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
+                                                                       
assert.Equal(t, 1, receivingCalls)
+                                                                       
assert.Equal(t, 1, guardCalls)
+                                                                       if 
!tc.reject {
+                                                                               
assert.Equal(t, 1, handlerCalls, rec.Body.String())
+                                                                               
if typed && registration != "inferred" {
+                                                                               
        assert.Equal(t, "default-query", gotQuery)
+                                                                               
}
+                                                                       }
+                                                               }
+                                                       })
+                                               }
+                                       })
+                               }
+                       })
+               }
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/github-mcp-server-2.0.1/pkg/inventory/server_tool.go 
new/github-mcp-server-2.0.2/pkg/inventory/server_tool.go
--- old/github-mcp-server-2.0.1/pkg/inventory/server_tool.go    2026-10-07 
10:09:18.000000000 +0200
+++ new/github-mcp-server-2.0.2/pkg/inventory/server_tool.go    2026-10-08 
13:25:50.000000000 +0200
@@ -472,8 +472,9 @@
                                panic(fmt.Sprintf("failed to resolve input 
schema for tool %q: %v", tool.Name, err))
                        }
                        // Input validation is deferred until guards have 
allowed the call.
-                       // The advertised and validation schemas remain the 
original schemas.
-                       tool.InputSchema, err = cachedObjectInputSchema()
+                       // Retain the advertised header bindings for checks at 
the original
+                       // HTTP boundary, even when the body validation schema 
is overridden.
+                       tool.InputSchema, err = 
cachedHeaderInputSchema(registration.modernTool.InputSchema)
                        if err != nil {
                                panic(fmt.Sprintf("failed to prepare input 
schema for tool %q: %v", tool.Name, err))
                        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/github-mcp-server-2.0.1/pkg/inventory/typed_input_test.go 
new/github-mcp-server-2.0.2/pkg/inventory/typed_input_test.go
--- old/github-mcp-server-2.0.1/pkg/inventory/typed_input_test.go       
2026-10-07 10:09:18.000000000 +0200
+++ new/github-mcp-server-2.0.2/pkg/inventory/typed_input_test.go       
2026-10-08 13:25:50.000000000 +0200
@@ -41,6 +41,8 @@
        for _, raw := range []string{
                `{"mode":"valid"}`, `{"mode":"valid","count":9}`, 
`{"mode":"valid","Count":9}`,
                `{"mode":"invalid"}`, `{"mode":42}`, `{}`, 
`{"mode":"valid","count":"bad"}`,
+               `{"mode":"valid","owner":"octo"}`, 
`{"mode":"valid","owner":"invalid"}`,
+               `{"mode":"valid","owner":42}`, `{"mode":"valid","owner":null}`,
                `[]`, `"not an object"`,
        } {
                t.Run(raw, func(t *testing.T) {
@@ -83,6 +85,18 @@
                runtime.Properties["count"].Default = json.RawMessage(`3`)
                compareTypedInputWithSDK[typedValidationInput](t, schema, 
runtime)
        })
+       t.Run("header-annotations", func(t *testing.T) {
+               type input struct {
+                       Owner string `json:"owner"`
+                       typedValidationInput
+               }
+               advertised := CloneSchema(schema)
+               advertised.Properties["owner"] = &jsonschema.Schema{
+                       Type: "string", Enum: []any{"octo"}, Default: 
json.RawMessage(`"octo"`),
+                       Extra: map[string]any{"x-mcp-header": "owner"},
+               }
+               compareTypedInputWithSDK[input](t, advertised, advertised)
+       })
 }
 
 func TestResolvedTypedInputSchemaCache(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/github-mcp-server-2.0.1/pkg/inventory/typed_schema.go 
new/github-mcp-server-2.0.2/pkg/inventory/typed_schema.go
--- old/github-mcp-server-2.0.1/pkg/inventory/typed_schema.go   2026-10-07 
10:09:18.000000000 +0200
+++ new/github-mcp-server-2.0.2/pkg/inventory/typed_schema.go   2026-10-08 
13:25:50.000000000 +0200
@@ -111,6 +111,7 @@
 var explicitSchemaCache sync.Map
 var ownedSchemaPointers sync.Map
 var annotatedSchemaCache sync.Map
+var headerInputSchemaCache sync.Map
 var defaultObjectInputSchemaEntry inferredSchemaEntry
 
 type resolvedInputSchemaEntry struct {
@@ -121,7 +122,7 @@
 
 var resolvedInputSchemaCache sync.Map
 
-func cachedResolvedInputSchema(value any) (*jsonschema.Resolved, error) {
+func cachedInputSchema(value any) (*jsonschema.Schema, error) {
        schema, ok := value.(*jsonschema.Schema)
        if !ok {
                encoded, err := json.Marshal(value)
@@ -140,6 +141,14 @@
        if schema.Type != "object" {
                return nil, fmt.Errorf("input schema must have type \"object\"")
        }
+       return schema, nil
+}
+
+func cachedResolvedInputSchema(value any) (*jsonschema.Resolved, error) {
+       schema, err := cachedInputSchema(value)
+       if err != nil {
+               return nil, err
+       }
        entryValue, _ := resolvedInputSchemaCache.LoadOrStore(schema, 
&resolvedInputSchemaEntry{})
        entry := entryValue.(*resolvedInputSchemaEntry)
        entry.once.Do(func() {
@@ -155,6 +164,46 @@
        return defaultObjectInputSchemaEntry.schema, 
defaultObjectInputSchemaEntry.err
 }
 
+// Keep header bindings visible to the SDK's HTTP transport without restoring
+// required fields, defaults, or other body constraints ahead of tool guards.
+func cachedHeaderInputSchema(value any) (*jsonschema.Schema, error) {
+       schema, err := cachedInputSchema(value)
+       if err != nil {
+               return nil, err
+       }
+       entryValue, _ := headerInputSchemaCache.LoadOrStore(schema, 
&inferredSchemaEntry{})
+       entry := entryValue.(*inferredSchemaEntry)
+       entry.once.Do(func() {
+               headerSchema := headerPropertySchema(schema)
+               headerSchema.Type = "object"
+               entry.schema, entry.err = CachedSchema(headerSchema)
+       })
+       return entry.schema, entry.err
+}
+
+func headerPropertySchema(schema *jsonschema.Schema) *jsonschema.Schema {
+       result := &jsonschema.Schema{}
+       if annotation, ok := schema.Extra["x-mcp-header"]; ok {
+               // The SDK requires a primitive type when validating header 
annotations.
+               result.Type = schema.Type
+               result.Extra = map[string]any{"x-mcp-header": annotation}
+       }
+       for name, property := range schema.Properties {
+               if property == nil {
+                       continue
+               }
+               child := headerPropertySchema(property)
+               if len(child.Extra) == 0 && len(child.Properties) == 0 {
+                       continue
+               }
+               if result.Properties == nil {
+                       result.Properties = make(map[string]*jsonschema.Schema)
+               }
+               result.Properties[name] = child
+       }
+       return result
+}
+
 // CloneSchemaWithoutDefaults returns a deep schema copy with default keywords
 // removed. The MCP SDK applies input-schema defaults before decoding 
arguments,
 // so use this for runtime validation schemas when omitted arguments must stay

++++++ vendor.tar.zst ++++++

Reply via email to