Repository navigation
[v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7, GitMCP, DeepWiki, Ref.tools) #87
Description
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 apply agent-ready before 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)
- One adapter per competitor in
benchmarks/adapters/, each recording: pinned version/package/endpoint/commit, invocation transcript, latency, failure category (tool_failure/timeout/mcp_protocol_crash). - Eligibility screening is encoded: an adapter whose competitor cannot be pinned or reproducibly invoked reports a structured exclusion (methodology honesty rule: "Do not drop failed systems silently"), never a silent skip.
- All unit tests are mock-transport only — zero network calls in CI. Live invocation sits behind the same
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). - Competitor terms-of-use check is recorded per adapter in the manifest metadata (methodology eligibility rule).
- No README/benchmark claims;
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
uv run ruff check src/ tests/
uv run ruff check benchmarks/
uv run pyright src/
uv run pyright benchmarks/
uv run pytest --tb=short -q
uv run pytest tests/benchmarks -q
uv run python-docs-mcp-server doctorRecovery
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.
Activity
ayhammouda commentedon Jul 9, 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.
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."not-a-registered-provider", test_adapters.py:151-152 — registering new providers does not touch it).validate_manifest_against_matrixalready skips competitor entries that don't declareprovider+model(model_matrix.py:227-233) — competitor entries pass through untouched._ADAPTER_DISPATCHentry per adapter (runner.py:434-444 states "Adding a new dispatchable adapter means adding one entry here").1. Per-competitor dossier summary and design decisions
1.1 Context7 (Upstash)
@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.mcp.client.stdio; remote streamable-HTTPhttps://mcp.context7.com/mcpexists as fallback.CONTEXT7_API_KEY(free tier 1,000 calls/mo with account; keyless = lower unspecified limits).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_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
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.mcp.client.streamable_http.streamablehttp_clienthandles the session-id echo and SSE-framed responses.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.1.3 DeepWiki (Cognition)
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./mcp;/sseofficially deprecated). No auth.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.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
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).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_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—RefToolsAdapterCommon contract (all verified against python_docs_mcp_adapter.py):
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).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.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)._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).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.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: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 carriespin: {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?: [...]}.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.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).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
benchmarks/adapters/context7_adapter.pybenchmarks/adapters/gitmcp_adapter.pybenchmarks/adapters/deepwiki_adapter.pybenchmarks/adapters/ref_tools_adapter.pybenchmarks/adapters/eligibility.pyBenchmarkValidationErrorbenchmarks/adapters/guard.pyPROVIDER_API_KEY_ENVentries; +require_live_competitor()two-latch gate (additive)benchmarks/adapters/__init__.pybenchmarks/runner.py_ADAPTER_DISPATCHentries (documented seam); + onevalidate_manifest_eligibilitycall in_load_manifest(refusal idiom, §2.4)docs/benchmarks/competitor-manifest.template.ymltests/benchmarks/test_competitor_adapters.pyNothing 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):_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).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)._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.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._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)
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)
@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.c487a29) accepted with disclosure; include. The verified doc-tool weakness is reported as a finding, not an exclusion. Confirm.answer_generation: "llm"disclosure + competitive-clause note — or exclude. Recommend include-with-disclosure. Confirm/choose.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.require_live_competitor()(global flag +BENCHMARK_LIVE_COMPETITORSallowlist) for keyless competitors. Confirm.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.ayhammouda commentedon Jul 9, 2026
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)
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.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
tests/benchmarks/test_competitor_adapters.py. Plan §4 is the authoritative test list.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._transport_factory(plussubprocess/socket.socketfor Context7's stdio path) to raiseAssertionError("network reached")in every guard-refusal test.4. Known pitfalls
git-mcpon npm is an UNRELATED project; third-party "deepwiki" npm packages are NOT Cognition's server. Never reference either."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.## Why this triggered supervisor reviewsection.Refs #63, neverCloses #63;Closes #87only if every acceptance criterion is met. Branch:agent/87-competitor-adapters.provider+model— competitor entries need no matrix changes; do not touch model_matrix.py.5. Decision log (worker fills in)
agent: fix — wire plan §2.4 defense-in-depth eligibility call into di…
[v0.5.0] benchmark adapters — competitor MCP tool adapters (Context7,…