Preserve versioned STDIO API user agents across MCP protocols - #3323
Conversation
Preserve release identities, resolve actual source revisions, and attach upstream client metadata per request without mutating shared transports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6dde41da-ec25-4370-b26c-b036d0a53b3c
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Client metadata can invalidate HTTP headers, and default Docker builds can be incorrectly marked dirty.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds versioned, request-scoped user agents for STDIO GitHub API requests and resolves source-build versions from VCS metadata.
Changes:
- Adds request-scoped user-agent transport handling.
- Resolves release and source revision identifiers.
- Adds protocol, transport, and version tests plus documentation.
| File | Description |
|---|---|
README.md |
Documents source builds and user agents. |
pkg/http/transport/user_agent.go |
Adds request-scoped user-agent overrides. |
pkg/http/transport/user_agent_test.go |
Tests concurrent override isolation. |
internal/ghmcp/server.go |
Propagates MCP client identity to API requests. |
internal/ghmcp/server_test.go |
Adds wire-level protocol coverage. |
docs/installation-guides/README.md |
Corrects the package build command. |
cmd/github-mcp-server/version.go |
Resolves and validates build versions. |
cmd/github-mcp-server/version_test.go |
Tests version resolution sources. |
cmd/github-mcp-server/main.go |
Applies resolved versions to STDIO startup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Encode upstream client comments as valid HTTP header data, keep explicit build revisions independent of embedded dirty state, and reject null source hashes. Cover protocol wire behaviour and document the version-source contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6dde41da-ec25-4370-b26c-b036d0a53b3c
|
Addressed both review findings in 56d11f5.
Before the implementation fix, the new tests reproduced five header-encoding failures, two explicit-revision/dirty-state failures and eight null-revision failures. After the fix, the complete repository race suite passed. A fresh targeted race run and the pinned analyser also passed after the unrelated formatter-only change had been removed. GOTOOLCHAIN=go1.25.12 script/lint
script/test
go test -race -count=1 -timeout 5m ./cmd/github-mcp-server ./internal/ghmcp ./pkg/http/transport
bin/golangci-lint runValidation selected the Go 1.25.12 toolchain root and executable path together, matching the repository's lint setup rather than mixing the image's Go root with a different compiler. Actual compiled STDIO executables successfully sent Its embedded source revision matches the published commit and reports |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation preserves transport isolation and existing headers with comprehensive protocol and version-resolution coverage.
Review effort: Balanced
Findings: None
Resolved since last review (2)
56d11f5 to
1e16b58
Compare
SamMorrowDrums
left a comment
There was a problem hiding this comment.
The user-agent part of this looks good to me. Resolving ClientInfo() per request and passing it through the request context is the right approach for 2026-07-28, where initialize/server/discover are optional and requests have to stand alone. It still works for legacy sessions, it doesn't mutate the shared transport, and the remote server keeps its default behaviour.
One thing needs changing before this merges: resolveServerVersion now makes stdio fail to start when there's no version metadata. On main, version defaults to the literal "version" and gets passed straight through, so a server with no version metadata has always started. I confirmed that a go build -buildvcs=false build now exits with server build has no release or revision. The same would happen with source tarballs or vendored copies that have no .git, and with Docker builds where .git isn't in the build context (git rev-parse HEAD returns nothing and VERSION defaults to dev). Stopping startup over what is only a header label is a regression.
Could you:
- Keep the precedence: release version → explicit
main.commit→ embedded VCS revision (vcs-<sha>[-dirty]). That part is a nice improvement. - Fall back instead of erroring: if nothing resolves, use the previous placeholder (or
dev), optionally log a warning, and don't return an error. - Treat malformed values the same way: a short or invalid SHA should fall back or pass through as-is, not block startup.
- Update the README: remove the "STDIO startup reports an error…" wording.
The user-agent fix doesn't depend on the version-resolution work, so splitting that into its own PR would also be fine if that's easier.
Skip invalid build metadata while retaining release and revision precedence. Fall back to dev with a stderr warning rather than preventing startup, and cover the compatibility contract in tests and documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6dde41da-ec25-4370-b26c-b036d0a53b3c
|
@SamMorrowDrums, addressed the startup regression from #3323 (review). Version resolution now skips missing or malformed candidates while preserving release -> explicit commit -> embedded VCS -> installed module precedence. If none is usable, STDIO starts with Added precedence and fallback regressions. The full race-enabled suite and lint passed in the Codespace, and offline probes of the compiled executable confirmed successful MCP/GraphQL requests for |
SamMorrowDrums
left a comment
There was a problem hiding this comment.
Thanks for the changes!


Summary
Give STDIO GitHub API requests a server/version user agent before any MCP handshake, retaining upstream client metadata per request across legacy and modern MCP protocols. Resolve usable build metadata for version-specific attribution without making missing or malformed metadata prevent startup.
Why
The pinned MCP SDK permits discovery and direct metadata-bearing tool calls without
initialize, so initialisation-only middleware misses those requests. ReadingClientInfo()per request also avoids mutating a shared transport when client identity changes.What changed
vcs-<full-sha>for revisions, applying-dirtyonly to embedded VCS metadata. If nothing is usable, start withdevand warn on stderr, keeping stdout available for MCP.MCP impact
Prompts tested (tool changes only)
Not applicable; tool definitions and schemas are unchanged.
Security / limits
Tool renaming
Lint & tests
script/lintpassed in the Codespace.script/testpassed, including the full race-enabled suite.Offline compiled-binary probes exercised actual MCP calls and outgoing GraphQL headers for metadata-free builds, malformed metadata, release versions, explicit revisions and embedded VCS metadata. Fallback warnings stayed on stderr; authentication and feature headers were preserved. The tagged live-service E2E suite, full container build and production rollout were not run.
Docs