feat(mcp-scan): support modern/legacy protocol auto-negotiation and record protocol version - #579
feat(mcp-scan): support modern/legacy protocol auto-negotiation and record protocol version#579NY1024 wants to merge 2 commits into
Conversation
…ecord protocol version Implements Issue Tencent#574: MCP scan should support new/old protocol auto-negotiation and record the negotiated protocol version. Previously, mcp-scan and agent-scan unconditionally called session.initialize() (legacy handshake) after establishing a connection. This works for pre-2026 MCP servers but fails against servers that only support the modern server/discover protocol introduced in MCP 2026-07-28. Changes: - mcp-scan/mcp_scan/utils/mcp_tools.py: Replace unconditional session.initialize() with _negotiate_protocol() that first probes server/discover (modern), falls back to initialize() (legacy) on failure. Records negotiated_protocol_version and negotiation_type. - agent-scan/agent_scan/utils/mcp_tools.py: Same auto-negotiation logic applied to keep both modules in sync. - mcp-scan/mcp_scan/tools/dispatcher.py: Log the negotiated protocol version and handshake type after successful MCP connection. - mcp-scan/mcp_scan/agent/agent.py: Include mcp_protocol_version, mcp_negotiation_type, and mcp_transport in the dynamic_analysis result_meta for reporting. - mcp-scan/requirements.txt, agent-scan/requirements.txt: Bump mcp dependency range to >=1.23.0,<3.0.0 to allow SDK 2.0 (which provides session.discover() for modern protocol probing). The discover() method is available on ClientSession in both SDK 1.x (recent versions) and 2.x. On SDK 1.x servers that do not implement server/discover, the probe raises an exception and the code falls back to the legacy initialize() handshake, preserving backward compatibility.
|
Thanks @NY1024 for implementing this — protocol auto-negotiation is exactly what AIG needs as a measurement tool that can't know the target server's protocol in advance. The probe- What looks good:
Issues / suggestions:
None of these are blockers for the design, but (1) and (4) are worth addressing before merge. Nice work overall! |
…d code, pin mcp==2.0.0, add negotiation tests Address reviewer (boy-hack) feedback on PR Tencent#579: 1. Dead code: _MODERN_PROTOCOL_VERSIONS was defined but never used. Now used as the source of truth for _DEFAULT_MODERN_VERSION. 2. Magic strings: hardcoded '2026-07-28' and '2025-11-25' default versions replaced with named constants (_DEFAULT_MODERN_VERSION, _DEFAULT_LEGACY_VERSION) derived from _MODERN_PROTOCOL_VERSIONS. 3. SDK version: discover() is only available in mcp SDK 2.0+ (not in 1.23.0 lower bound). Pinned mcp==2.0.0 in both requirements.txt files to ensure discover() is always present. On 1.x the try/except would hit AttributeError every time — harmless but wasteful. 4. Tests: added unit tests for _negotiate_protocol covering: - discover() success with explicit protocolVersion (modern) - discover() success with None protocolVersion (modern + default) - discover() raises exception (legacy fallback) - discover() returns None (legacy fallback) - legacy initialize with None protocolVersion (legacy + default) Applied symmetrically to both mcp-scan and agent-scan modules.
|
Thanks @boy-hack for the detailed review! All four points addressed in the latest push (94791f5): 1. Dead code ( 2. Magic strings consolidated:
All version defaults now live next to 3. Fix: pinned 4. Unit tests added:
Five test cases covering both branches:
All changes applied symmetrically to both |
|
Thanks for addressing all four points, @NY1024 — verified in the latest push:
The symmetric application to |
Summary
Implements #574: MCP scan should support new/old protocol auto-negotiation and record the negotiated protocol version.
Problem
mcp-scanandagent-scanunconditionally callsession.initialize()(the legacy handshake) after establishing a connection. This works for pre-2026 MCP servers but fails against servers that only support the modernserver/discoverprotocol introduced in MCP 2026-07-28.AIG is a measurement tool — the protocol version of the target server cannot be known in advance, so the scanner must auto-negotiate.
Solution
Replace the unconditional
session.initialize()with a_negotiate_protocol()method that:server/discover(modern protocol, MCP 2026-07-28+). If the server responds, adopts the modern protocol version.initialize()(legacy handshake) if the server does not supportserver/discover. This preserves backward compatibility with all pre-2026 servers.modern/legacy), and transport in the scan result for reporting.The
discover()method is available onClientSessionin both SDK 1.x (recent versions) and 2.x. On servers that do not implementserver/discover, the probe raises an exception and the code gracefully falls back to the legacyinitialize()handshake.Changes
mcp-scan/mcp_scan/utils/mcp_tools.pysession.initialize()with_negotiate_protocol()that probesserver/discoverfirst, falls back toinitialize(). Addednegotiated_protocol_versionandnegotiation_typeinstance attributes.agent-scan/agent_scan/utils/mcp_tools.pymcp-scan/mcp_scan/tools/dispatcher.pymcp-scan/mcp_scan/agent/agent.pymcp_protocol_version,mcp_negotiation_type, andmcp_transportin thedynamic_analysisresult for reporting.mcp-scan/requirements.txtmcpto>=1.23.0,<3.0.0to allow SDK 2.0 (providessession.discover()).agent-scan/requirements.txtProtocol Negotiation Flow
Verification
The issue suggests verifying with local Mock servers covering:
server/discoverinitialize()Compatibility
session.discover()may not exist on very old 1.x versions; thetry/excepthandles this gracefully by falling back toinitialize().session.discover()is the first-class modern protocol entry point;session.initialize()still works for legacy fallback.server/discoverreturns an error → falls back toinitialize()→ works as before.server/discoversucceeds → modern protocol adopted → no unnecessary handshake.Related