🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

mirror-github.paniser.workers.dev/github/github-mcp-server

Package Overview
Dependencies
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mirror-github.paniser.workers.dev/github/github-mcp-server - go Package Compare versions

Comparing version
v1.1.0
to
v1.1.1
+36
pkg/github/tools_static_validation_test.go
package github
import (
"os"
"testing"
"github.com/github/github-mcp-server/pkg/toolvalidation"
"github.com/stretchr/testify/require"
)
// TestAllToolRegistrationsExplicitlySetReadOnlyHint statically scans every
// non-test Go source file in this package and asserts that every mcp.Tool
// composite literal explicitly sets Annotations.ReadOnlyHint.
//
// The AST scan itself lives in pkg/toolvalidation so downstream packages
// (e.g. github/github-mcp-server-remote) can apply the same guardrail to
// their own tool registrations without duplicating the parser logic.
//
// This complements TestAllToolsHaveRequiredMetadata, which can only check
// that Annotations is non-nil at runtime: Go cannot distinguish an unset
// bool field from one explicitly set to false. Source-level validation
// closes that gap and prevents future tool registrations from silently
// defaulting ReadOnlyHint to false (which has caused downstream agents to
// prompt for human approval on read-intent tools).
//
// Related issue: github/github-mcp-server#2483
func TestAllToolRegistrationsExplicitlySetReadOnlyHint(t *testing.T) {
pkgDir, err := os.Getwd()
require.NoError(t, err, "must be able to resolve package directory")
violations, err := toolvalidation.ScanReadOnlyHint(pkgDir)
require.NoError(t, err)
if len(violations) > 0 {
t.Fatal(toolvalidation.FormatReadOnlyHintViolations(violations))
}
}
package github
import (
"context"
"testing"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestRegisterUIResources_ReadableViaClient verifies that each UI resource URI
// advertised by an MCP App-enabled tool (e.g. issue_write, create_pull_request,
// get_me) actually resolves to a registered resource on the server.
//
// Regression test for the "Error loading MCP App: MPC -32002: Resource not
// found" bug reported in issue #2467, where the HTTP/remote server returned a
// resource URI in the tool's _meta.ui block but never registered the matching
// resource — so the follow-up resources/read call from the client failed.
func TestRegisterUIResources_ReadableViaClient(t *testing.T) {
t.Parallel()
if !UIAssetsAvailable() {
t.Skip("UI assets not built; run script/build-ui to enable this test")
}
srv := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.0.1"}, nil)
RegisterUIResources(srv)
// Connect an in-memory client/server pair and read each advertised URI.
st, ct := mcp.NewInMemoryTransports()
type clientResult struct {
session *mcp.ClientSession
err error
}
clientCh := make(chan clientResult, 1)
go func() {
client := mcp.NewClient(&mcp.Implementation{Name: "test-client"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
clientCh <- clientResult{session: cs, err: err}
}()
ss, err := srv.Connect(context.Background(), st, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = ss.Close() })
got := <-clientCh
require.NoError(t, got.err)
t.Cleanup(func() { _ = got.session.Close() })
uris := []string{
GetMeUIResourceURI,
IssueWriteUIResourceURI,
PullRequestWriteUIResourceURI,
}
for _, uri := range uris {
t.Run(uri, func(t *testing.T) {
res, err := got.session.ReadResource(context.Background(), &mcp.ReadResourceParams{URI: uri})
require.NoError(t, err, "resource %s should be registered (got -32002 means it isn't)", uri)
require.NotNil(t, res)
require.NotEmpty(t, res.Contents)
assert.Equal(t, uri, res.Contents[0].URI)
assert.Equal(t, MCPAppMIMEType, res.Contents[0].MIMEType)
assert.NotEmpty(t, res.Contents[0].Text, "UI resource should return HTML body")
})
}
}
// TestNewMCPServer_RegistersUIResources verifies that NewMCPServer — the
// shared constructor used by both the stdio and HTTP entry points — registers
// the UI resources when UI assets are embedded. Previously this registration
// only happened in the stdio bootstrap, so remote/HTTP clients hit -32002.
func TestNewMCPServer_RegistersUIResources(t *testing.T) {
t.Parallel()
if !UIAssetsAvailable() {
t.Skip("UI assets not built; run script/build-ui to enable this test")
}
srv, err := NewMCPServer(context.Background(), &MCPServerConfig{
Version: "test",
Translator: stubTranslator,
}, stubDeps{t: stubTranslator}, mustEmptyInventory(t))
require.NoError(t, err)
st, ct := mcp.NewInMemoryTransports()
type clientResult struct {
session *mcp.ClientSession
err error
}
clientCh := make(chan clientResult, 1)
go func() {
client := mcp.NewClient(&mcp.Implementation{Name: "test-client"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
clientCh <- clientResult{session: cs, err: err}
}()
ss, err := srv.Connect(context.Background(), st, nil)
require.NoError(t, err)
t.Cleanup(func() { _ = ss.Close() })
got := <-clientCh
require.NoError(t, got.err)
t.Cleanup(func() { _ = got.session.Close() })
res, err := got.session.ReadResource(context.Background(), &mcp.ReadResourceParams{URI: IssueWriteUIResourceURI})
require.NoError(t, err)
require.NotNil(t, res)
require.NotEmpty(t, res.Contents)
assert.Equal(t, MCPAppMIMEType, res.Contents[0].MIMEType)
}
// mustEmptyInventory builds an empty inventory for tests that only care about
// resources/prompts registered outside the inventory (such as the UI resources).
func mustEmptyInventory(t *testing.T) *inventory.Inventory {
t.Helper()
inv, err := NewInventory(stubTranslator).WithToolsets([]string{}).Build()
require.NoError(t, err)
return inv
}
package toolvalidation_test
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/github/github-mcp-server/pkg/toolvalidation"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// writePackage writes a single Go source file into a fresh temp directory and
// returns that directory, suitable for passing to ScanReadOnlyHint.
func writePackage(t *testing.T, filename, source string) string {
t.Helper()
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, filename), []byte(source), 0o600))
return dir
}
func TestScanReadOnlyHint(t *testing.T) {
t.Parallel()
const compliant = `package fixture
import "github.com/modelcontextprotocol/go-sdk/mcp"
var Tool = mcp.Tool{
Name: "compliant_tool",
Annotations: &mcp.ToolAnnotations{
ReadOnlyHint: true,
},
}
`
const missingHint = `package fixture
import "github.com/modelcontextprotocol/go-sdk/mcp"
var Tool = mcp.Tool{
Name: "missing_hint",
Annotations: &mcp.ToolAnnotations{
Title: "no hint",
},
}
`
const missingAnnotations = `package fixture
import "github.com/modelcontextprotocol/go-sdk/mcp"
var Tool = mcp.Tool{
Name: "missing_annotations",
}
`
const nonLiteralAnnotations = `package fixture
import "github.com/modelcontextprotocol/go-sdk/mcp"
func annotations() *mcp.ToolAnnotations { return &mcp.ToolAnnotations{ReadOnlyHint: true} }
var Tool = mcp.Tool{
Name: "non_literal",
Annotations: annotations(),
}
`
const unkeyedTool = `package fixture
import "github.com/modelcontextprotocol/go-sdk/mcp"
var Tool = mcp.Tool{"unkeyed", "desc", nil, nil, nil, nil}
`
const aliasedImport = `package fixture
import sdk "github.com/modelcontextprotocol/go-sdk/mcp"
var Tool = sdk.Tool{
Name: "aliased",
Annotations: &sdk.ToolAnnotations{
ReadOnlyHint: false,
},
}
`
const noMCPImport = `package fixture
import "fmt"
var _ = fmt.Sprintln("nothing to scan here")
`
cases := []struct {
name string
source string
expectCount int
expectReason string
expectToolName string
}{
{name: "compliant literal passes", source: compliant, expectCount: 0},
{name: "aliased import is detected", source: aliasedImport, expectCount: 0},
{name: "file without mcp import is skipped", source: noMCPImport, expectCount: 0},
{
name: "missing ReadOnlyHint is flagged",
source: missingHint,
expectCount: 1,
expectReason: "does not explicitly set ReadOnlyHint",
expectToolName: "missing_hint",
},
{
name: "missing Annotations is flagged",
source: missingAnnotations,
expectCount: 1,
expectReason: "missing an Annotations field",
expectToolName: "missing_annotations",
},
{
name: "non-literal Annotations is flagged",
source: nonLiteralAnnotations,
expectCount: 1,
expectReason: "not an &mcp.ToolAnnotations{...} literal",
expectToolName: "non_literal",
},
{
name: "positional Tool fields are flagged",
source: unkeyedTool,
expectCount: 1,
expectReason: "positional (unkeyed) fields",
expectToolName: "<unknown>",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
dir := writePackage(t, "fixture.go", tc.source)
violations, err := toolvalidation.ScanReadOnlyHint(dir)
require.NoError(t, err)
require.Len(t, violations, tc.expectCount)
if tc.expectCount == 0 {
return
}
v := violations[0]
assert.Equal(t, "fixture.go", v.File)
assert.Greater(t, v.Line, 0)
assert.Equal(t, tc.expectToolName, v.ToolName)
assert.Contains(t, v.Reason, tc.expectReason)
})
}
}
func TestFormatReadOnlyHintViolations(t *testing.T) {
t.Parallel()
assert.Empty(t, toolvalidation.FormatReadOnlyHintViolations(nil))
msg := toolvalidation.FormatReadOnlyHintViolations([]toolvalidation.ReadOnlyHintViolation{{
File: "issues.go",
Line: 42,
ToolName: "issue_read",
Reason: "ToolAnnotations literal does not explicitly set ReadOnlyHint",
}})
assert.True(t, strings.HasPrefix(msg, "Found tool registrations that do not explicitly set ReadOnlyHint:"))
assert.Contains(t, msg, "issues.go:42 tool=issue_read")
assert.Contains(t, msg, "true for read-only tools, false for tools with side effects")
}
func TestScanReadOnlyHint_ReturnsErrorForMissingDirectory(t *testing.T) {
t.Parallel()
_, err := toolvalidation.ScanReadOnlyHint(filepath.Join(t.TempDir(), "does-not-exist"))
require.Error(t, err)
}
// Package toolvalidation provides source-level (AST) validators for MCP tool
// registrations. It is intended to be consumed from _test.go files in any
// package that registers mcp.Tool literals (including downstream repositories
// such as github-mcp-server-remote) so the same guardrails apply everywhere
// without duplicating the parsing logic.
package toolvalidation
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strconv"
"strings"
)
// MCPImportPath is the canonical module path of the MCP go-sdk. Source files
// that import this path under any alias (including the default `mcp`) are
// candidates for tool-literal validation.
const MCPImportPath = "github.com/modelcontextprotocol/go-sdk/mcp"
// ReadOnlyHintViolation describes a single mcp.Tool composite literal that
// failed the ReadOnlyHint check.
type ReadOnlyHintViolation struct {
// File is the path to the offending source file, made relative to the
// scan directory when possible.
File string
// Line is the 1-indexed line number of the offending literal.
Line int
// ToolName is the value of the Name field on the mcp.Tool literal, or
// "<unknown>" when it cannot be statically extracted.
ToolName string
// Reason is a human-readable explanation of why the literal failed.
Reason string
}
// String renders a violation in the format used by FormatReadOnlyHintViolations:
// "<file>:<line> tool=<name>: <reason>".
func (v ReadOnlyHintViolation) String() string {
return fmt.Sprintf("%s:%d tool=%s: %s", v.File, v.Line, v.ToolName, v.Reason)
}
// ScanReadOnlyHint parses every non-test .go file in dir (a single package
// directory) and returns a violation for each mcp.Tool composite literal that
// does not explicitly set Annotations.ReadOnlyHint.
//
// The Go runtime cannot distinguish an unset bool field from one explicitly
// set to false, so this AST-level check exists to prevent future tool
// registrations from silently defaulting ReadOnlyHint to false — which has
// triggered downstream agents to prompt for human approval on safe read
// operations.
//
// Callers typically invoke this from a _test.go file:
//
// dir, _ := os.Getwd()
// violations, err := toolvalidation.ScanReadOnlyHint(dir)
func ScanReadOnlyHint(dir string) ([]ReadOnlyHintViolation, error) {
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
// Skip test files: they are allowed to construct mcp.Tool literals
// for fixtures or mocks where ReadOnlyHint is not meaningful.
return !strings.HasSuffix(info.Name(), "_test.go")
}, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("parse package directory %q: %w", dir, err)
}
var violations []ReadOnlyHintViolation
for _, pkg := range pkgs {
for filename, file := range pkg.Files {
aliases := mcpAliasesFor(file)
if len(aliases) == 0 {
continue
}
rel, relErr := filepath.Rel(dir, filename)
if relErr != nil || rel == "" {
rel = filepath.Base(filename)
}
ast.Inspect(file, func(n ast.Node) bool {
cl, ok := n.(*ast.CompositeLit)
if !ok {
return true
}
if !isQualifiedType(cl.Type, aliases, "Tool") {
return true
}
violations = append(violations, checkToolLiteral(cl, aliases, rel, fset.Position(cl.Pos()).Line)...)
return true
})
}
}
return violations, nil
}
// FormatReadOnlyHintViolations renders a single multi-line error message
// suitable for passing to t.Fatal. Returns "" when violations is empty.
func FormatReadOnlyHintViolations(violations []ReadOnlyHintViolation) string {
if len(violations) == 0 {
return ""
}
var msg strings.Builder
msg.WriteString("Found tool registrations that do not explicitly set ReadOnlyHint:\n")
for _, v := range violations {
msg.WriteString(" - ")
msg.WriteString(v.String())
msg.WriteByte('\n')
}
msg.WriteString("\nEvery mcp.Tool registration must declare Annotations.ReadOnlyHint explicitly ")
msg.WriteString("(true for read-only tools, false for tools with side effects). ")
msg.WriteString("See pkg/toolvalidation.ScanReadOnlyHint.")
return msg.String()
}
func checkToolLiteral(cl *ast.CompositeLit, aliases map[string]struct{}, file string, line int) []ReadOnlyHintViolation {
toolName := extractToolName(cl)
if toolName == "" {
toolName = "<unknown>"
}
mk := func(reason string) ReadOnlyHintViolation {
return ReadOnlyHintViolation{File: file, Line: line, ToolName: toolName, Reason: reason}
}
if hasUnkeyedFields(cl) {
return []ReadOnlyHintViolation{mk("mcp.Tool literal uses positional (unkeyed) fields; this check requires keyed fields so Annotations.ReadOnlyHint can be verified")}
}
annotations := findFieldValue(cl, "Annotations")
if annotations == nil {
return []ReadOnlyHintViolation{mk("mcp.Tool literal is missing an Annotations field")}
}
annoLit := unwrapAnnotationsLiteral(annotations, aliases)
if annoLit == nil {
return []ReadOnlyHintViolation{mk("Annotations is not an &mcp.ToolAnnotations{...} literal; ReadOnlyHint cannot be statically verified")}
}
if hasUnkeyedFields(annoLit) {
return []ReadOnlyHintViolation{mk("mcp.ToolAnnotations literal uses positional (unkeyed) fields; use keyed fields so ReadOnlyHint can be verified")}
}
if findFieldValue(annoLit, "ReadOnlyHint") == nil {
return []ReadOnlyHintViolation{mk("ToolAnnotations literal does not explicitly set ReadOnlyHint")}
}
return nil
}
// mcpAliasesFor returns the set of local identifiers under which the given
// file imports the MCP go-sdk (MCPImportPath). The default unaliased import
// resolves to the package name "mcp". Blank (`_`) and dot (`.`) imports are
// skipped because tool literals cannot meaningfully be qualified through them.
func mcpAliasesFor(file *ast.File) map[string]struct{} {
aliases := map[string]struct{}{}
for _, imp := range file.Imports {
path, err := strconv.Unquote(imp.Path.Value)
if err != nil || path != MCPImportPath {
continue
}
if imp.Name != nil {
if imp.Name.Name == "_" || imp.Name.Name == "." {
continue
}
aliases[imp.Name.Name] = struct{}{}
continue
}
aliases["mcp"] = struct{}{}
}
return aliases
}
// isQualifiedType reports whether expr is a SelectorExpr of the form
// <alias>.<typeName> where alias is in the provided alias set.
func isQualifiedType(expr ast.Expr, aliases map[string]struct{}, typeName string) bool {
sel, ok := expr.(*ast.SelectorExpr)
if !ok {
return false
}
ident, ok := sel.X.(*ast.Ident)
if !ok {
return false
}
if _, ok := aliases[ident.Name]; !ok {
return false
}
return sel.Sel != nil && sel.Sel.Name == typeName
}
// hasUnkeyedFields reports whether the composite literal has any positional
// (non-key/value) elements. The static check cannot reliably map positional
// fields without full type information, so such literals are rejected with a
// dedicated diagnostic rather than producing false "missing field" violations.
func hasUnkeyedFields(cl *ast.CompositeLit) bool {
for _, elt := range cl.Elts {
if _, ok := elt.(*ast.KeyValueExpr); !ok {
return true
}
}
return false
}
// findFieldValue returns the value expression for the named keyed field of a
// composite literal, or nil if the field is absent.
func findFieldValue(cl *ast.CompositeLit, name string) ast.Expr {
for _, elt := range cl.Elts {
kv, ok := elt.(*ast.KeyValueExpr)
if !ok {
continue
}
key, ok := kv.Key.(*ast.Ident)
if !ok {
continue
}
if key.Name == name {
return kv.Value
}
}
return nil
}
// unwrapAnnotationsLiteral attempts to extract the *ast.CompositeLit for
// &mcp.ToolAnnotations{...} or mcp.ToolAnnotations{...} from an expression,
// resolving the MCP package's local alias per file.
func unwrapAnnotationsLiteral(expr ast.Expr, aliases map[string]struct{}) *ast.CompositeLit {
if u, ok := expr.(*ast.UnaryExpr); ok && u.Op == token.AND {
expr = u.X
}
cl, ok := expr.(*ast.CompositeLit)
if !ok {
return nil
}
if !isQualifiedType(cl.Type, aliases, "ToolAnnotations") {
return nil
}
return cl
}
// extractToolName returns the literal value of the Name field of an mcp.Tool
// composite literal, or empty string if the value is not a basic string literal.
// Interpreted ("...") and raw (`...`) string literals are handled via
// strconv.Unquote so embedded escapes are decoded correctly; the raw
// literal value is returned as a best-effort fallback if unquoting fails.
func extractToolName(cl *ast.CompositeLit) string {
v := findFieldValue(cl, "Name")
if v == nil {
return ""
}
bl, ok := v.(*ast.BasicLit)
if !ok || bl.Kind != token.STRING {
return ""
}
if unq, err := strconv.Unquote(bl.Value); err == nil {
return unq
}
return bl.Value
}
+1
-1

@@ -13,3 +13,3 @@ module github.com/github/github-mcp-server

github.com/microcosm-cc/bluemonday v1.0.27
github.com/modelcontextprotocol/go-sdk v1.6.0
github.com/modelcontextprotocol/go-sdk v1.6.1
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021

@@ -16,0 +16,0 @@ github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7

+2
-2

@@ -42,4 +42,4 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=

github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/modelcontextprotocol/go-sdk v1.6.0 h1:PPLS3kn7WtOEnR+Af4X5H96SG0qSab8R/ZQT/HkhPkY=
github.com/modelcontextprotocol/go-sdk v1.6.0/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU=
github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ=
github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g=

@@ -46,0 +46,0 @@ github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc=

@@ -176,11 +176,2 @@ package ghmcp

// Register MCP App UI resources if the remote_mcp_ui_apps feature flag is enabled
// and UI assets are available (requires running script/build-ui).
// We check availability to allow the feature flag to be enabled without
// requiring a UI build (graceful degradation).
mcpAppsEnabled, _ := featureChecker(context.Background(), github.MCPAppsFeatureFlag)
if mcpAppsEnabled && github.UIAssetsAvailable() {
github.RegisterUIResources(ghServer)
}
ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.restUATransp, clients.gqlHTTP))

@@ -187,0 +178,0 @@

@@ -151,3 +151,3 @@ package github

expectedFlags: nil,
unexpectedFlags: []string{MCPAppsFeatureFlag, FeatureFlagIFCLabels},
unexpectedFlags: []string{MCPAppsFeatureFlag},
},

@@ -172,6 +172,5 @@ {

{
name: "internal-only flags are not directly enabled",
name: "ifc_labels can be directly enabled",
enabledFeatures: []string{FeatureFlagIFCLabels},
expectedFlags: nil,
unexpectedFlags: []string{FeatureFlagIFCLabels},
expectedFlags: []string{FeatureFlagIFCLabels},
},

@@ -178,0 +177,0 @@ {

@@ -26,2 +26,3 @@ package github

FeatureFlagCSVOutput,
FeatureFlagIFCLabels,
FeatureFlagIssueFields,

@@ -28,0 +29,0 @@ FeatureFlagIssuesGranular,

@@ -105,2 +105,14 @@ package github

// Register MCP App UI resources whenever the embedded UI assets are
// available. The resources are static HTML and are only referenced by
// tools when the remote_mcp_ui_apps feature flag is enabled for the
// request (the inventory strips the _meta.ui block otherwise via
// stripMCPAppsMetadata), so registering them unconditionally is safe.
// Registering here — rather than in the stdio bootstrap — ensures the
// remote/HTTP server also serves them, fixing the "-32002 Resource not
// found" error clients hit after the tool returns a ui:// URI.
if UIAssetsAvailable() {
RegisterUIResources(ghServer)
}
return ghServer, nil

@@ -107,0 +119,0 @@ }

@@ -226,12 +226,11 @@ package http

// Bypass cross-origin protection: this server uses bearer tokens (not
// cookies), so Sec-Fetch-Site CSRF checks are unnecessary. See PR #2359.
crossOriginProtection := http.NewCrossOriginProtection()
crossOriginProtection.AddInsecureBypassPattern("/")
// Cross-origin protection is intentionally left unset: this server
// authenticates via bearer tokens (not cookies), so Sec-Fetch-Site CSRF
// checks are unnecessary and would block browser-based MCP clients. As of
// go-sdk v1.6.0 a nil CrossOriginProtection disables the check by default;
// see also PR #2359.
mcpHandler := mcp.NewStreamableHTTPHandler(func(_ *http.Request) *mcp.Server {
return ghServer
}, &mcp.StreamableHTTPOptions{
Stateless: true,
CrossOriginProtection: crossOriginProtection,
Stateless: true,
})

@@ -238,0 +237,0 @@

@@ -91,8 +91,2 @@ package http

{
name: "internal-only flag in header is ignored",
flagName: github.FeatureFlagIFCLabels,
headerFeatures: []string{github.FeatureFlagIFCLabels},
wantEnabled: false,
},
{
name: "static insiders enables insiders flags without route context",

@@ -99,0 +93,0 @@ staticInsiders: true,

@@ -27,4 +27,4 @@ # GitHub MCP Server dependencies

- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))

@@ -31,0 +31,0 @@ - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))

@@ -27,4 +27,4 @@ # GitHub MCP Server dependencies

- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))

@@ -31,0 +31,0 @@ - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))

@@ -28,4 +28,4 @@ # GitHub MCP Server dependencies

- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.0/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([Apache-2.0](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.6.1/LICENSE))
- [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt))

@@ -32,0 +32,0 @@ - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE))