Skip to content

feat: log which MCP client connected, fix capability-read race - #253

Open
mattpodwysocki wants to merge 2 commits into
mainfrom
feat/client-identification
Open

feat: log which MCP client connected, fix capability-read race#253
mattpodwysocki wants to merge 2 commits into
mainfrom
feat/client-identification

Conversation

@mattpodwysocki

@mattpodwysocki mattpodwysocki commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • server.server.getClientVersion() (populated from the clientInfo sent in the client's initialize request) is now logged on connect, e.g. Client identified as: claude-ai v1.0.0 — useful for support/debugging when behavior differs across Claude Desktop, Cursor, VS Code, etc. Also recorded as mcp.client.name/mcp.client.version on every subsequent tool-execution trace span, so OTel-backed traces can be filtered or grouped by client.
  • While wiring that up, found and fixed a real timing bug: getClientVersion()/getClientCapabilities() are only populated once the client's initialize request has been processed. Reading them synchronously right after await server.connect(transport) races that request, since connect() only waits for the transport to start, not for the handshake to complete. Confirmed live — even with a client that explicitly declared elicitation support, the existing "Client capabilities detected" debug log always printed undefined.
  • Fixed by moving both reads into a server.server.oninitialized callback (registered before connect()), which only fires once the initialize handshake is fully done. This also fixes the same-shaped bug in the existing capability-gated elicitation-tool registration path — currently dormant since ELICITATION_TOOLS is empty, but would have silently never worked for the first tool added there.

Test plan

  • npx vitest run — all 937 tests pass, including new coverage in test/utils/tracing.test.ts for the client-info trace attributes
  • npm run build succeeds
  • Live-verified against the real built server with a real MCP client declaring a custom name/version and elicitation capability: server now logs Client identified as: pretend-cursor-client v9.9.9 and Client capabilities detected: { "elicitation": { "form": {} } } (previously undefined)
  • CHANGELOG.md updated

🤖 Generated with Claude Code

server.server.getClientVersion()/getClientCapabilities() are only
populated once the client's initialize request has been processed --
reading them synchronously right after server.connect() races that
request, since connect() only waits for the transport to start, not for
the handshake to finish. Confirmed live: reading capabilities immediately
after connect() reliably returned undefined even for a client that
declared them.

Moved both reads into a server.server.oninitialized callback, which fires
only once the handshake is fully done. This also fixes the same-shaped
bug in the existing capability-gated elicitation-tool registration
(currently dormant since ELICITATION_TOOLS is empty, but was silently
broken for whenever a tool is added there), and adds client identification
as a byproduct: the client's name/version from its initialize request is
now logged on connect.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mattpodwysocki
mattpodwysocki requested a review from a team as a code owner August 25, 2026 18:47
setClientInfo (tracing.ts) stores the connected client's clientInfo as
module-level state -- there's exactly one client per stdio server process
-- and createToolSpan now reads it into mcp.client.name/mcp.client.version
attributes on every tool span. index.ts calls setClientInfo from the same
oninitialized callback that already logs the client identity, so OTel
traces can be filtered/grouped by which client made each call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants