deps: lift the mcp<2 cap — fixtures ported to the 2.x SDK (supersedes #96, DSE-1261) - #105
Merged
Merged
Conversation
…de SDK-neutral (supersedes #96, DSE-1261) The 2.x SDK removed the @server.list_tools() decorators the fixture servers used (why #92 capped mcp<2). tests/fixtures/_sdk_compat.py wires the same declared surfaces against the 1.x decorator API or the 2.x on_* callbacks. Two 2.x changes would have silently altered every committed lock and are absorbed in capture.py rather than re-pinned: * model fields renamed to snake_case (input_schema, mime_type, protocol_version) while wire keys stayed camelCase — a plain model_dump() under 2.x returned no inputSchema at all; * PromptArgument gained title=None, which leaked into arguments_hash. _model_dump now returns the wire view (by_alias=True, exclude_none=True) and the protocol version is read by attribute on either name. Proven: `check` against clean.warden.lock exits 0 and a fresh pin reproduces overall_digest cb20a16c… under mcp 2.1.1; the mutated fixture still drifts; the negative controls (plain dump → 9 drift rows; alias-only → prompt-modified) fail. Locks resolve to mcp==2.1.1 (requirements-dev.lock via the CI-documented uv command, byte-identical on the CI sync re-compile; action/requirements.lock).
…r pages — CSO review of #105 F1: exclude_none had dropped the description/required nulls of a bare prompt argument, changing arguments_hash vs every released warden. The protocol field set {name, description, required} is now always emitted (null when absent) and only keys outside it (2.x title, _meta) are shed when null. No SCHEMA_VERSION bump; pinned by a canon() byte-equality test. F3: tools/resources/prompts list calls now follow nextCursor through the SDK's params= cursor (same call shape on 1.x and 2.x). A failure on any later page is a CaptureError — a partial surface is never pinned — and a chain longer than 256 pages is refused. The first-page swallow for capability-less servers is unchanged and labelled as the deliberate fail-open it is. Docs: mcp 2.x -> httpx2 -> truststore verifies --url TLS against the OS trust store instead of certifi; stated plainly in CHANGELOG and README.
…en note (CSO re-verify of #105) The cross-major digest-parity claim needs both SDK lines exercised: a test-mcp-1x job re-resolves to the newest 1.x and runs the capture, parity, pagination and e2e tests only (CI time stays flat). The first-page fail-open docstring and CHANGELOG now cite DSE-1538 (capability-aware error handling) and DSE-1539 (annotations/outputSchema hashing) instead of "a follow-up".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #96. Lifts
mcp>=1.27.2,<2→mcp>=1.27.2,<3; locks resolve tomcp==2.1.1.What changed
tests/fixtures/_sdk_compat.py(new) — wires a fixture's declared surface against the 1.x decorator API or the 2.xon_*callback API (capability-detected, not version-sniffed).clean_server.py,mutated_server.py,poison_server.pynow route through it with their declared surfaces unchanged.src/mcp_warden/capture.py— two 2.x changes would have silently altered every committed lock; both are absorbed instead of re-pinned:input_schema,mime_type,protocol_version) while the protocol keys stayed camelCase → a plainmodel_dump()returned noinputSchemaat all (9 drift rows against the committed lock).PromptArgumentgainedtitle=None; the server never sent it, but it leaked intoarguments_hash(prompt-modifieddrift)._model_dumpnow returns the wire view (by_alias=True, exclude_none=True); the protocol version is read by attribute on either name.tests/test_capture_model_dump.py(new) — pins the wire view on whichever SDK line is installed.requirements-dev.lockregenerated with the CI-documenteduv pip compilecommand (+--upgrade-package mcp); the CI sync re-compile reproduces it byte-for-byte.action/requirements.lockregenerated the same way so the composite Action's cross-OS tests exercise 2.x.pyproject.tomlcomment and CHANGELOG[Unreleased]updated.Digest-parity evidence (builder, mcp 2.1.1)
check python tests/fixtures/clean_server.py --lock tests/fixtures/clean.warden.lockOK no drift, exit 0pinoverall_digestvs committedsha256:cb20a16ce572cca8f682672c42036e351359d32ca8265e5af3bfc861ab6f9219— identicalcheckmutated fixture vs clean lockmodel_dump()DRIFT DETECTED (9 item(s))by_aliaswithoutexclude_noneDRIFT DETECTED (2 item(s)),prompt-modifiedclean_listchange.warden.lock(hand-crafted guard fixture, zero digests)Verification
COVERAGE_PROCESS_START: 1115 passed, 2 skipped, coverage 88.26% (floor 80) — after the security-review fixes below.ruff check .clean.Security review remediation (CSO RED → fixes → GREEN; head
de2b76a)exclude_none=Truedroppeddescription/requirednulls from prompt arguments —arguments_hashdiverged from every released warden for any prompt with a bare argument_normalize_prompt_argument: the protocol field setname/description/requiredis always emitted (nullwhen absent, byte-identical to the 1.xmodel_dump()shape); only keys outside that set (2.xtitle,_meta) are shed when null; non-null extras kept. NoSCHEMA_VERSIONbump. CHANGELOG sentence corrected to name exactly which keys are shed.test_bare_prompt_argument_hashes_byte_identically_to_every_released_warden—canon([norm]) == b'[{"description":null,"name":"text","required":null}]'andhash_argumentsequality vs the literal;titleabsent.test_non_null_extra_prompt_argument_fields_are_kept.nextCursorpagination — a server that splitstools/listwas pinned from page one_list_alldrains every page via the SDK'sparams=PaginatedRequestParams(cursor=…)(same call shape on 1.x and 2.x;nextCursor/next_cursorboth honoured). Hard capMAX_LIST_PAGES = 256→CaptureError(exit 2). A failure on any page after the first isCaptureError— a partial surface is never pinned. First-page swallow for capability-less servers unchanged, now labelled as the deliberate fail-open with the follow-up ticket referenced in the docstring.tests/test_capture_pagination.py(7): two-page == one-page digest, cursor passed throughparamson every follow-up page, snake_case cursor attr, prompts/resources paginate, never-terminating chain refused, later-page failure fails closed, first-page failure stays fail-open.truststore)mcp 2.1.1 → httpx2 2.12.0 → truststore 0.10.4, referenced inhttpx2/_config.py. Stated plainly in CHANGELOG[Unreleased]and the README runtime-dependencies note.| CSO re-verify fold-ins | New CI job
test-mcp-1x(Capture parity on mcp 1.x): installsmcp>=1.27.2,<2and runs the capture/parity/pagination/e2e tests, so both SDK lines back the digest-parity claim (permissions: contents: readinherited, SHA-pinned actions, no${{ }}in run blocks). Fail-open docstring + CHANGELOG now cite DSE-1538 (capability-aware error handling) and DSE-1539 (annotations/outputSchema hashing). | CI job on this PR |Parity for the null-field prompt argument:
_normalize_prompt_argument(PromptArgument(name="text"))canonicalises to exactly[{"description":null,"name":"text","required":null}]— the pre-PR bytes — andcheck python tests/fixtures/clean_server.py --lock tests/fixtures/clean.warden.lockstill exits 0 (OK no drift) under mcp 2.1.1.🤖 Generated with Claude Code
https://claude.ai/code/session_01Lu4ZLyWWL7TXMGYpNFU5Dc