Repository navigation
[v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools) #87
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:P2Medium priorityMedium priority
on Jul 8, 2026 Approved implementation plan + Stage 3 gate record (2026-07-09)
Auditable mirror of
PLAN-87-competitor-adapters.md(local, untracked). Codex adversarial review: round 1 no-sign-off (1 blocking, triaged + fixed), round 2 sign-off. Maintainer confirmed A1–A6 as recommended (see the plan's Stage 3 gate record below).
PLAN-87 — Competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools)
Produced: 2026-07-09 (plan-review-ship pipeline; Stage 1 output). Planned against
main@5a518de.
Issue: #87[v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools)(Refs #63).
Status: awaiting Stage 2 (Codex adversarial review) and the Stage 3 gate (maintainer confirms assumptions A1–A6 = the pre-flight pin/terms confirmation required by the run rules; then context refresh +agent-ready).
Research basis: 5-agent fan-out on 2026-07-09 — four competitor dossiers (live-probed endpoints, npm/registry metadata, ToS text read in full) + repo-seam recon against merged #86/#98/#101 code. Every load-bearing claim below carries its source.
0. Scope and hard boundaries
Deliver exactly #87: four adapter modules + guard extensions + dispatch registration + manifest metadata convention + mock-transport tests + a committed template manifest. Nothing else.
- No live network calls anywhere in this change or its tests. Live invocation stays behind the guard; CI never passes it.
- No new dependencies.
mcp1.28.1 is an installed project dependency (uv.lock:297-302) whose client API covers stdio + streamable-HTTP + SSE (verified against.venv:mcp.client.stdio,mcp.client.streamable_http.streamablehttp_client,mcp.client.sse.sse_client; httpx 0.28.1 installed transitively).pyproject.toml/uv.lockmust be absent from the diff. Websocket transport is NOT available (extra not installed) — no competitor needs it. - No merged-test edits. No D6-style sanction exists for this task and none is needed (verified: the guard's unknown-provider test now asserts on
"not-a-registered-provider", test_adapters.py:151-152 — registering new providers does not touch it). - No report.py / model_matrix.py / scoring.py changes.
validate_manifest_against_matrixalready skips competitor entries that don't declareprovider+model(model_matrix.py:227-233) — competitor entries pass through untouched. - runner.py changes limited to the documented extension seam: one lazily-importing dispatch function + one
_ADAPTER_DISPATCHentry per adapter (runner.py:434-444 states "Adding a new dispatchable adapter means adding one entry here"). - Worker prohibitions from PLAN.md (WORKER-PROHIBITIONS block) apply verbatim.
1. Per-competitor dossier summary and design decisions
1.1 Context7 (Upstash)
- Pin (strongest of the four): npm
@upstash/context7-mcp@3.2.3(published 2026-07-06), run as stdio subprocessnpx -y @upstash/context7-mcp@3.2.3(Node ≥18 — live-phase env requirement only, never in tests). Client pins exactly; doc index is hosted and unpinnable → effective pin =3.2.3+ run date. Dockermcp/context7is 13 months stale — do not use. - Transport: stdio (default) via
mcp.client.stdio; remote streamable-HTTPhttps://mcp.context7.com/mcpexists as fallback. - Auth: optional
CONTEXT7_API_KEY(free tier 1,000 calls/mo with account; keyless = lower unspecified limits). - Tool flow (verified live 2026-07-09): two tools,
resolve-library-id+query-docs. Python stdlib coverage confirmed:/python/cpython(48k snippets, versions incl. v3.13.9). Adapter flow:resolve-library-id(libraryName="Python", query=<question>)→query-docs(libraryId=<top id>, query=<question>). Two calls/question, exercising the tool's own routing (methodology "same question to every system"). Library ID hardcoding is a fallback knob, not the default (that would be per-competitor tuning). - Terms (read in full): client MIT. Upstash ToS §C.8 (April 2025): "You must not perform any benchmark tests or analyses relating to Upstash's Website or Services without express permission of Upstash." The Context7 Addendum (Aug 2025) licenses "evaluation purposes" and sanctions automated MCP access but contains no benchmark clause — so §C.8 stands. Publishing comparative results is forbidden without express permission.
- Decision: build the adapter fully (mock-tested; the client is MIT and evaluation use is licensed). Manifest metadata records
terms_check: {verdict: "forbidden-without-permission", clause: "Upstash ToS §C.8", permission: "not-obtained"}. Whether Context7 enters the live-run manifest is the maintainer's call at T13 after seeking permission (support@upstash.com) — assumption A1.
1.2 GitMCP
- Pin (weak): hosted
https://gitmcp.io/python/cpython+ access date +serverInfo.version(1.1.0 on 2026-07-09), recorded per run. No npm (the npmgit-mcppackage is an unrelated name-collision project — never reference it), no tags/releases/Docker; self-host needs a Cloudflare account (not clean-clone reproducible). Code reference:idosal/git-mcp@mainSHAc487a29. - Transport: streamable HTTP (verified live; legacy GET/SSE returns 405).
mcp.client.streamable_http.streamablehttp_clienthandles the session-id echo and SSE-framed responses. - Auth: none.
- Tool flow (verified live):
search_cpython_documentationreturns no results for stdlib queries (indexes only the repo's README fallback — nollms.txtin cpython); the working path issearch_cpython_code(query)→fetch_generic_url_content(url)on the foundDoc/**/*.rst(or docs.python.org) URL. Adapter flow: doc-search first (honest: it is the advertised path), then code-search → fetch. This weak-doc-tool finding is a legitimate benchmark result, not an exclusion. - Terms: Apache-2.0 code; hosted service has no ToS document; README explicitly invites programmatic agent access. Benchmarking permitted. Keep concurrency low (shared GitHub API quota behind it).
- Decision: include, with the weak pin disclosed in manifest metadata — assumption A2.
1.3 DeepWiki (Cognition)
- Pin (weakest): hosted
https://mcp.deepwiki.com/mcp+ access date +serverInfo.version(2.14.3 on 2026-07-09) + target-wiki last-indexed date (python/cpython: 2026-06-24). No package/image/self-host exists. Third-party npm "deepwiki" packages are NOT Cognition's server — never reference them. - Transport: streamable HTTP (
/mcp;/sseofficially deprecated). No auth. - Tool flow (verified live):
ask_question(repoName="python/cpython", question=<verbatim>)— returns an LLM-generated answer (~13.5 s, nondeterministic across identical calls — verified).read_wiki_contentsis an all-or-nothing ~1.18 MB blob of a Devin-generated wiki, not official docs. Adapter flow: singleask_questioncall; 60 s timeout. - Terms: no DeepWiki-specific ToS; Cognition Platform ToS + AUP (June 2026) contain no benchmark clause; gray zone: AUP competitive-use language, given the publisher is a competing docs tool.
- Decision: include-with-disclosure is the recommendation: manifest metadata flags
answer_generation: "llm"(an answer-synthesis system scored inside a retrieval benchmark — the report's per-competitor framing must not hide this) and the competitive-clause note. Maintainer may instead exclude — assumption A3.
1.4 Ref.tools
- Pin: client
ref-tools-mcp@3.0.3(npm, MIT) or Dockermcp/ref-tools-mcpdigestsha256:8b5fcfe3...; backendapi.ref.toolsunpinnable → effective pin = client 3.0.3 + run date. Remote streamable-HTTPhttps://api.ref.tools/mcpwithx-ref-api-keyheader is vendor-recommended; stdionpx ref-tools-mcp@3.0.3(envREF_API_KEY) is the legacy path. Adapter default: remote streamable HTTP (no Node needed). - Auth: API key REQUIRED (signup; free tier = 200 one-time credits; 1 credit per tool call → a 50-question run at 2 calls/question ≈ 100 credits; a full matrix run needs a paid month ~$19).
- Tool flow:
ref_search_documentation(query=...)→ref_read_url(url=<top docs.python.org hit>). Verified capability: vendor's own canonical example is a Python-stdlib query. Session-dependent behavior (dedupes repeats within a session; read trimming keyed on session history) → fresh MCP session per question (adapters already instantiate per cell — enforced by design). - Terms (extracted from SPA bundle, 48k chars, grep-verified): no benchmark/evaluation clause; but §3 "no automated or non-human access", §7 no systematic retrieval "without written permission", subjective disparagement clause, "personal, non-commercial use only" license. Unclear, leaning risky for publication. The systematic-retrieval clause itself names the cure: written permission.
- Decision: build fully (mock-tested); manifest metadata records
terms_check: {verdict: "unclear-permission-recommended", ...}; live use needs maintainer key + credits; publication needs the maintainer's permission decision — assumption A4.
2. Shared design
2.1 Adapter shape (mirror of the merged #86 product adapter — the proven template)
Each adapter is a plain class (NOT a
ProviderAdaptersubclass — that contract models an LLM call; these model retrieval, same asPythonDocsMcpAdapter, python_docs_mcp_adapter.py:9-16):benchmarks/adapters/context7_adapter.py—Context7Adapterbenchmarks/adapters/gitmcp_adapter.py—GitMcpAdapterbenchmarks/adapters/deepwiki_adapter.py—DeepWikiAdapterbenchmarks/adapters/ref_tools_adapter.py—RefToolsAdapter
Common contract (all verified against python_docs_mcp_adapter.py):
- Ctor takes endpoint/command overrides +
timeout_seconds(30.0 default; 60.0 for DeepWiki) + flow knobs; instantiated fresh per cell by the dispatch fn (gives Ref its fresh-session-per-question requirement for free). run(prompt) -> <Adapter>Result(answer: str, tool_calls: list[ToolCallRecord])—answer= the retrieved content (concatenated tool payloads / ask_question text),tool_calls= full raw request/response records for the transcript (sameToolCallRecordshape: tool, arguments, result, is_error).- Lazy
mcpimports inside the async runner (module import must never require mcp client submodules — established pattern, python_docs_mcp_adapter.py:246-259). asyncio.run(asyncio.wait_for(...)); exception mapping exactly:BenchmarkCellFailurere-raised;TimeoutError→"timeout"; otherException→"mcp_protocol_crash"; toolisError=True→"tool_failure". Known inherited limitation (Codex round-1 finding 2, not fixed here): on failure the runner recordstool_calls: None(runner.py:239-247) — the failing call's request/response is carried only in the failure message, not the transcript; changing that is a runner-contract change out of [v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools) #87's scope. Adapters mirror the merged contract exactly.- Guard-first: the FIRST statement of
run()calls the applicable guard (§2.2) — before any subprocess spawn or transport construction. Structural offline proof mirrors test_claude_tokens.py:135-181 (fail-closed stubs). - Retrieval flows are pure orchestration over a duck-typed session Protocol (same
_CallToolSessiontrick) so tests drive them with scripted doubles, no transport.
2.2 Guard extensions (
benchmarks/adapters/guard.py, additive only)PROVIDER_API_KEY_ENV+={"ref": "REF_API_KEY", "context7": "CONTEXT7_API_KEY"}(registration point per guard.py:41-45; the unknown-provider merged test is unaffected).- NEW
require_live_competitor(name: str) -> None— two non-secret latches (Codex round-1 finding 3: flag-only gating would weaken the guard posture): (1)BENCHMARK_LIVE_PROVIDERS_ENABLEDtruthy (same set, sameLiveProviderDisabledError), AND (2) new envBENCHMARK_LIVE_COMPETITORS— a comma-separated allowlist that must containname(e.g.BENCHMARK_LIVE_COMPETITORS=gitmcp,deepwiki). Keyless competitors thus keep the same two-latch shape as keyed providers (global flag + per-target opt-in), with the allowlist standing in for the key. Additive function; existing guard contract and merged tests untouched. - Mapping: Context7 →
require_live_environment("context7")when the manifest entry declares key-mode, elserequire_live_competitor("context7")(keyless mode is real but rate-limited); GitMCP →require_live_competitor("gitmcp"); DeepWiki →require_live_competitor("deepwiki"); Ref →require_live_environment("ref").
2.3 Dispatch registration (
benchmarks/runner.py, the documented seam)Four lazily-importing dispatch fns (pattern:
_python_docs_mcp_stdio_answer, runner.py:410-431) + four_ADAPTER_DISPATCHentries:
"context7","gitmcp","deepwiki","ref-tools". Each reads its config fromcell.competitor.raw(endpoint override, key-mode, timeout) and convertsToolCallRecords to transcript dicts exactly as the product adapter does.2.4 Eligibility screening + structured exclusion (issue criterion 2 — REDESIGNED per Codex round-1 finding 1)
The rejected first design (per-cell
BenchmarkCellFailure("tool_failure", "structured-exclusion: ...")) would score excluded competitors as failures: the runner turns everyBenchmarkCellFailureintostatus: failed(runner.py:245), scoring fixes failed cells at 0.0 inside the denominator, and report error-rate stats count them — contaminating exactly the metrics the methodology says exclusions must stay out of ("report the failure reason and exclude it from scored comparisons"). Exclusion is therefore a manifest-load-time refusal, never a cell outcome:- Manifest metadata convention (
Competitor.rawpasses every YAML key through untouched, runner.py:73-79; the manifest is snapshotted byte-for-byte into run artifacts): each ACTIVE competitor entry carries
pin: {kind: npm-version|endpoint-date, value, server_info_version?, code_sha?, access_date},
terms_check: {verdict: permitted|forbidden-without-permission|unclear-permission-recommended, clause?, checked_on, source_url},
eligibility: {status: eligible|conditional, conditions?: [...]}. - Excluded systems live in a top-level
exclusions:block (sibling ofcompetitors:), each withid,reason,terms_check,decided_on. The runner's execution path ignores unknown top-level keys, but the byte-for-byte snapshot preserves the block in every run's artifacts — exclusions are documented in the same auditable file, never silently dropped. - Encoded screening:
benchmarks/adapters/eligibility.pyexposesvalidate_competitor_eligibility(raw)(per-entry: pin + terms_check present and well-formed;status∈ {eligible, conditional}) andvalidate_manifest_eligibility(manifest_data). The runner's_load_manifestcalls the latter: any competitor entry witheligibility.status: excluded(or an entry that belongs inexclusions:) → cleanBenchmarkValidationError(exit 2, before any cell executes, message naming the competitor and pointing at theexclusions:block). This is a small additive runner change beyond the dispatch entries, in the runner's established refusal idiom (same class as the non-empty--outrefusal) — justified here explicitly so the worker doesn't treat it as scope creep. Adapters' dispatch fns also callvalidate_competitor_eligibility(defense in depth; unreachable-for-excluded by construction). - Zero cells ever execute for an excluded competitor → zero contamination of correctness/error-rate/latency metrics. Final include/exclude for the live run stays the maintainer's call when authoring the live manifest (T13); the template (§2.5) prefills the
exclusions:block per A1–A4.
2.5 Committed template manifest
docs/benchmarks/competitor-manifest.template.yml— the four competitors with researched pins + terms_check verbatim (clauses quoted),eligibilityprefilled per A1–A4, header comment: "TEMPLATE — not a benchmark result; live-run manifests are maintainer-authored copies; per methodology, exclusions must be reported, not hidden." Satisfies the methodology's "Competitor manifest with pinned versions" reproducibility artifact and the issue's terms-recording criterion. NOT atdocs/benchmarks/results/(that path is claim-bearing).3. File-by-file work list
File Change benchmarks/adapters/context7_adapter.pyNEW (~180 lines): stdio (npx pin) + streamable-HTTP modes; resolve→query flow benchmarks/adapters/gitmcp_adapter.pyNEW (~180): streamable HTTP; doc-search → code-search → fetch-url flow benchmarks/adapters/deepwiki_adapter.pyNEW (~140): streamable HTTP; single ask_question; 60 s timeout benchmarks/adapters/ref_tools_adapter.pyNEW (~170): streamable HTTP + x-ref-api-key; search → read_url flow benchmarks/adapters/eligibility.pyNEW (~80): per-entry + manifest-level eligibility validation (pin/terms/status); exclusion → BenchmarkValidationErrorbenchmarks/adapters/guard.py+2 PROVIDER_API_KEY_ENVentries; +require_live_competitor()two-latch gate (additive)benchmarks/adapters/__init__.pyexport new names (additive) benchmarks/runner.py+4 lazily-importing dispatch fns, +4 _ADAPTER_DISPATCHentries (documented seam); + onevalidate_manifest_eligibilitycall in_load_manifest(refusal idiom, §2.4)docs/benchmarks/competitor-manifest.template.ymlNEW template manifest (§2.5) tests/benchmarks/test_competitor_adapters.pyNEW (~550): see §4 Nothing else. Explicitly untouched: report.py, model_matrix.py, scoring.py, corpus.py, all merged tests, pyproject.toml, uv.lock, src/, .github/.
4. Test plan (mock-transport only; zero network in CI)
All in
tests/benchmarks/test_competitor_adapters.py(pinned name):- Flow tests per adapter via scripted-session doubles (pattern:
_ScriptedSession, test_python_docs_mcp_adapter.py:42-58): correct tool sequence + arguments, answer assembly,tool_callsrecords complete (including the GitMCP empty-doc-search fallback chain and Ref's search→read chaining of the top URL). - Failure-category mapping per adapter:
isError→ tool_failure; timeout → timeout; transport crash → mcp_protocol_crash (monkeypatch the adapter's_run_async, dummy paths — pattern test_python_docs_mcp_adapter.py:240-277). - Guard refusals fail closed: for each adapter, every disabled env permutation refuses BEFORE any transport construction. Each adapter routes ALL transport creation through one adapter-local
_transport_factoryseam (Codex round-1 finding 4: the SDK ships bothstreamable_http_clientand the deprecatedstreamablehttp_clientalias — tests must patch the seam actually used, so make it a single owned symbol); tests monkeypatch that factory (andsubprocess/stdio +socket.socketfor Context7) to raiseAssertionError("network reached")— pattern test_claude_tokens.py:135-181; env names imported from guard constants, never hardcoded. require_live_competitor: refuses without the flag; refuses with flag but name absent fromBENCHMARK_LIVE_COMPETITORS; passes only with both latches; key-independent; allowlist parsing tolerant of spaces/case.- Eligibility/exclusion: missing pin/terms metadata → per-entry validation error; a manifest whose
competitors:list containseligibility.status: excluded→ runner refuses at load withBenchmarkValidationErrornaming the competitor, exit 2 via the CLI, zero artifacts written (reuse_write_yaml/tmp-dir + CLI-subprocess patterns from test_runner.py); anexclusions:block passes validation and survives byte-for-byte intosnapshots/competitor-manifest.yml; the committed template itself parses and validates. - Registry integration: manifest entries with the four adapter strings dispatch to the right fns (mocked
_run_async), and the template manifest file itself parses + passes eligibility validation.
5. Validation commands (the run's gate; paste full output in PR body)
uv run ruff check src/ tests/ uv run ruff check benchmarks/ # additive until #84 lands in ci.yml uv run pyright src/ uv run pyright benchmarks/ # additive until #84 lands uv run pytest --tb=short -q uv run pytest tests/benchmarks -q uv run pytest tests/benchmarks/test_competitor_adapters.py -q uv run python-docs-mcp-server doctor
Plus PR-body proofs:
git diff --stat main...HEADshowing pyproject.toml/uv.lock absent; supervisor-review label (expected: total diff >500 lines — precedent #97/#98/#100);Refs #63,Closes #87only if all criteria met.6. Labelled assumptions — Stage 3 gate items (maintainer must confirm; these ARE the pre-flight pin/terms confirmations)
- A1 (Context7): pin
@upstash/context7-mcp@3.2.3stdio; terms verdict "forbidden-without-permission" (ToS §C.8) recorded; adapter built now; live inclusion deferred to T13 pending permission request to Upstash. Confirm/override. - A2 (GitMCP): weak pin (endpoint + date + serverInfo 1.1.0 + code SHA
c487a29) accepted with disclosure; include. The verified doc-tool weakness is reported as a finding, not an exclusion. Confirm. - A3 (DeepWiki): weak pin (endpoint + date + serverInfo 2.14.3 + wiki index date) accepted; include WITH
answer_generation: "llm"disclosure + competitive-clause note — or exclude. Recommend include-with-disclosure. Confirm/choose. - A4 (Ref.tools): pin client
3.0.3+ date; terms "unclear-permission-recommended" recorded; adapter built now; live use needs maintainer account/key/credits; publication needs the permission decision at T13. Confirm. - A5 (guard design): two new key-env registrations + additive two-latch
require_live_competitor()(global flag +BENCHMARK_LIVE_COMPETITORSallowlist) for keyless competitors. Confirm. - A6 (exclusion semantics): exclusion is a manifest-load-time refusal (runner
BenchmarkValidationError, zero cells executed, zero metric contamination) + a documented top-levelexclusions:block preserved in run snapshots; live-manifest inclusion is the T13 maintainer decision. Confirm.
7. Out of scope (restated)
Corpus questions; live benchmark calls of any kind; README/PyPI/launch claims; report/scorer/matrix changes; #33/#34; pyproject/uv.lock; obtaining vendor permissions or API keys (maintainer-only, T13); any second competitor repo target beyond
python/cpython.Stage 3 gate record (2026-07-09)
Codex sign-off round 2 (zero new findings; merged-test compatibility of the
runner-refusal design explicitly confirmed). Maintainer (Aymen) confirmed all
six assumptions in-session via AskUserQuestion, each as recommended:
A1 Context7 build-now/permission-before-publishing; A2 GitMCP include with
disclosed weak pin; A3 DeepWiki include with LLM-answer disclosure; A4
Ref.tools build-now/permission+credits at T13; A5 two-latch guard design; A6
manifest-load-time exclusion semantics. These confirmations constitute the
pre-flight pin/terms decision required by PLAN.md before #87 dispatch.
Mirrored as a comment on #87.Residual review notes
Codex round 1 (no sign-off) — triage: finding 1 (BLOCKING, exclusion-as-cell-failure contaminates scored metrics) accepted → §2.4 redesigned to manifest-load-time refusal +
exclusions:block; finding 2 (WRONG-IN-PLAN, failure-transcript claim) accepted → §2.1 corrected, limitation inherited not fixed; finding 3 (NICE-TO-HAVE, flag-only gate) accepted → two-latchrequire_live_competitor(); finding 4 (NICE-TO-HAVE, transport-symbol patching) accepted → adapter-local_transport_factoryseam. Codex confirmations kept: no-dependency claim PASS (mcp 1.28.1 + httpx locked); terms-handling split PASS; scope PASS. Test-plan note honored: mocks do NOT claim to cover live-only behaviors (GitMCP session-id echo, Ref session dedupe, DeepWiki nondeterminism) — those are documented run-time risks in the dossiers, exercised only in the maintainer-run live phase.Agent context — #87 [v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools)
Status: FINAL for dispatch (refreshed against main @
5a518deper the T1 timing rule — supersedes any pre-#86 assumptions; #86's registry merged @548acd1, guard state includes #101 @53749fc)
Auditable copy: this content is mirrored as a comment on issue #87 (.planning/is gitignored — the issue comment is the copy pre-flight verifies and the dispatch packet links).1. Roadmap excerpt (why this exists)
- Roadmap §4 v0.5.0: public benchmark harness — "all eight target docs MCPs + no-MCP baseline"; methodology "Systems Under Test" names Context7, GitMCP, DeepWiki, Ref.tools. Decision 5.17: zero comparative claims in public copy until reproducible data exists.
- Governing plan:
PLAN-87-competitor-adapters.md(Codex-signed round 2; full text mirrored as a comment on [v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools) #87). Maintainer gate decisions A1–A6 (2026-07-09, also mirrored on [v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools) #87) are BINDING: A1 Context7 built now, publication requires Upstash permission (ToS §C.8); A2 GitMCP in with disclosed weak pin; A3 DeepWiki in withanswer_generation: llmdisclosure; A4 Ref.tools built now, key/credits/permission at live phase; A5 two-latch guard; A6 manifest-load-time exclusion.
2. Code touch-points (verify against merged main before starting)
benchmarks/runner.py:439-444—_ADAPTER_DISPATCH: dict[str, Callable[[BenchmarkCell], _CellDispatchResult]]; add four entries:"context7","gitmcp","deepwiki","ref-tools", each mapping to a lazily-importing dispatch fn (pattern:_python_docs_mcp_stdio_answer, runner.py:410-431 — adapter modules importBenchmarkCellFailurefrombenchmarks.runner, so dispatch fns MUST lazy-import inside the function body)._CellDispatchResultat runner.py:384-395 (answer: str,tool_calls: list[dict] | None).benchmarks/runner.py:200-218—_load_manifest: add one call tovalidate_manifest_eligibility(...)(new, §2.4 of the plan): acompetitors:entry witheligibility.status: excluded→BenchmarkValidationErrorbefore any cell executes; a top-levelexclusions:block passes untouched (execution ignores it; the byte-for-byte snapshot at runner.py:568-578 preserves it).benchmarks/adapters/python_docs_mcp_adapter.py— THE template: plain class (NOT ProviderAdapter, docstring lines 9-16), lazymcpimports in_run_async(:246-259),asyncio.run(asyncio.wait_for(...))(:234), exception mapping BenchmarkCellFailure/TimeoutError→timeout/other→mcp_protocol_crash (:235-244), duck-typed_CallToolSessionProtocol (:91-98),ToolCallRecord(:60-67),_tool_result_payloadprefers structuredContent (:119-130). On failure the runner recordstool_calls: None(runner.py:233-247) — inherited contract, do NOT change it.benchmarks/adapters/guard.py:41-45—PROVIDER_API_KEY_ENV+={"ref": "REF_API_KEY", "context7": "CONTEXT7_API_KEY"}. NEW additiverequire_live_competitor(name): latch 1 =BENCHMARK_LIVE_PROVIDERS_ENABLEDtruthy ({"1","true","yes"}, guard.py:47); latch 2 =name∈ comma-separatedBENCHMARK_LIVE_COMPETITORS. SameLiveProviderDisabledError. Guard call is the FIRST statement of every adapter'srun().mcp1.28.1 is an installed project dependency (uv.lock:297-302):mcp.client.stdio,mcp.client.streamable_http.streamablehttp_client(+ preferredstreamable_http_clientat :601),mcp.client.sse.sse_client; httpx 0.28.1 installed. Route ALL transport creation through one adapter-local_transport_factoryseam per adapter (tests patch that seam). websocket client is NOT usable (extra absent) — not needed.- New modules:
context7_adapter.py(stdionpx -y @upstash/context7-mcp@3.2.3, flow resolve-library-id→query-docs),gitmcp_adapter.py(streamable HTTPhttps://gitmcp.io/python/cpython, flow search_cpython_documentation→search_cpython_code→fetch_generic_url_content),deepwiki_adapter.py(streamable HTTPhttps://mcp.deepwiki.com/mcp, single ask_question, timeout 60 s),ref_tools_adapter.py(streamable HTTPhttps://api.ref.tools/mcp, headerx-ref-api-key, flow ref_search_documentation→ref_read_url),eligibility.py(per-entry + manifest validation). Endpoints/pins/flows/timeouts: exactly perPLAN-87-competitor-adapters.md§1, no improvisation. docs/benchmarks/competitor-manifest.template.yml— NEW: four competitors with pins + terms_check verbatim from the plan §1,exclusions:block prefilled per A1–A4, TEMPLATE header comment (not results; live manifests are maintainer-authored).benchmarks/adapters/__init__.py— additive exports.
3. Test patterns to follow
- Focused file pinned:
tests/benchmarks/test_competitor_adapters.py. Plan §4 is the authoritative test list. - Scripted-session double:
tests/benchmarks/test_python_docs_mcp_adapter.py:42-58; failure-category mapping via_run_asyncmonkeypatch (:240-277); fail-closed env/network patterntests/benchmarks/test_claude_tokens.py:135-181(env names imported from guard constants — e.g.PROVIDER_API_KEY_ENV["ref"]— never hardcoded); CLI-as-subprocess +_write_yamlmanifest/tmp-dir patternstests/benchmarks/test_runner.py:37-50, 353-378. - Zero network in CI: patch each adapter's
_transport_factory(plussubprocess/socket.socketfor Context7's stdio path) to raiseAssertionError("network reached")in every guard-refusal test.
4. Known pitfalls
- npm name traps:
git-mcpon npm is an UNRELATED project; third-party "deepwiki" npm packages are NOT Cognition's server. Never reference either. - Merged tests additive-only; NO sanction exists for this issue (none needed — verified: unknown-provider guard test uses
"not-a-registered-provider"). pyproject.toml/uv.lockmust be absent from the diff (mcp SDK + httpx already installed); PR body includesgit diff --stat main...HEADproof.- No live/network calls ever — not in tests, not "just to check". Node/npx is a live-phase environment note in the template manifest, never a test dependency.
- Supervisor-review expected: total diff >500 lines (precedent [v0.5.0] benchmark corpus — mechanical slice: schema, validator, placeholder fixture (split from #71) #97/[v0.5.0] benchmark adapter — python-docs-mcp-server tool runner (offline stdio) #98/[v0.5.0] benchmark scoring — correctness scorer with manual-adjudication hooks #100) → apply the label +
## Why this triggered supervisor reviewsection. Refs #63, neverCloses #63;Closes #87only if every acceptance criterion is met. Branch:agent/87-competitor-adapters.- validate_manifest_against_matrix (model_matrix.py:227-233) skips entries without
provider+model— competitor entries need no matrix changes; do not touch model_matrix.py.
5. Decision log (worker fills in)
- …
- addedagent-readyIssue passed AGENT-EXECUTION-PIPELINE.md §10 pre-flight; scoped for an autonomous agentIssue passed AGENT-EXECUTION-PIPELINE.md §10 pre-flight; scoped for an autonomous agent
on Jul 9, 2026
Context
Parent: #63. Methodology:
docs/benchmarks/PUBLIC-BENCHMARK-METHODOLOGY.md(work package 4, real-adapter half — #72 delivered the manifest format; #85 delivered LLM-provider adapters, which are a different axis). Eligibility rules and candidate set (Context7, GitMCP, DeepWiki, Ref.tools) live in the methodology's "Systems Under Test" section.Status: NOT agent-ready. Filed by the orchestrator per PLAN.md T6(b); Vision must review, pin the competitor versions/endpoints, create the
.planning/agent-context/file (decision 5.14, mirrored as an issue comment), and applyagent-readybefore any dispatch.Goal
Add competitor MCP tool adapters — one per eligible docs MCP — with pinned versions, reproducible invocation, and failure capture, so the maintainer-run live phase can execute every eligible tool cell.
Acceptance criteria (draft — Vision edits before agent-ready)
benchmarks/adapters/, each recording: pinned version/package/endpoint/commit, invocation transcript, latency, failure category (tool_failure/timeout/mcp_protocol_crash).require_live_environment()guard as [v0.5.0] benchmark adapters — define OpenAI/Google model matrix #85's provider adapters (network at benchmark-runtime is maintainer-controlled live-phase only; the server's own offline-first principle 2.2 is untouched).Refs #63, neverCloses #63.Scope boundaries
In scope: adapter code, pinning metadata, mocked tests, exclusion reporting. Out of scope: running the live benchmark, scoring, reporting, corpus, README.
Forbidden-territory reminder
Standard §2 list applies; merged tests additive-only; any new third-party runtime dependency is a supervisor-review trigger (justify vs stdlib/httpx).
Validation commands
Recovery
If a competitor cannot be made reproducible (no pinnable version, terms forbid benchmarking), stop and comment here with the evidence — exclusion is a maintainer call.
Effort estimate
6–10 hours (four adapters).
Refs #63.