feat(tools): web_search MCP tool + mcp_servers harness passthrough#543
feat(tools): web_search MCP tool + mcp_servers harness passthrough#543AbirAbbas wants to merge 4 commits into
Conversation
Adds agentfield.tools.search — a unified async interface over Jina, Tavily, Firecrawl, and Serper. Each provider implements the SearchProvider ABC and returns the same SearchResponse shape. Auto-detection picks the first provider whose API key is configured (priority: jina > tavily > firecrawl > serper); SEARCH_PROVIDER env var overrides. Vendored from github.com/Agent-Field/af-deep-research/skills/search; ported from aiohttp to httpx so tests can use the existing pytest-httpx fixtures. Tests: 28 unit tests covering happy path, missing-key, HTTP error, and registry/auto-detect logic per provider, plus a live Jina smoke test gated on JINA_API_KEY. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an mcp_servers parameter to Agent.harness() (and its router-delegated form). When the claude-code provider is selected, the value is forwarded verbatim to ClaudeAgentOptions so reasoners can attach in-process MCP servers (e.g. those from claude_agent_sdk.create_sdk_mcp_server) for the agent to invoke. Other providers silently ignore mcp_servers, matching the existing behavior for the tools allow-list. The runner's _resolve_options gains "mcp_servers" as a recognized field so HarnessConfig defaults can carry it too. Tests: 4 cases covering passthrough to ClaudeAgentOptions, omission when absent, real web_search server-config wiring, and silent-ignore on a non-claude provider. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… agents Adds agentfield.tools.web_search.get_web_search_server() which returns an McpSdkServerConfig backed by the multi-provider search package, plus the fully-namespaced tool names to drop into the harness allow-list. Reasoners running against the claude-code provider can now grant a Claude harness the ability to look up external context (library docs, error messages, deprecation status) by passing the returned server + tool names through the new mcp_servers / tools parameters on router.harness(). Use sparingly — the tool description tells the model to reach for it only when the answer cannot come from the codebase. This avoids the loop-risk of agents searching instead of coding. Tests: 11 unit tests covering input validation, no-provider fallback, formatted-result shape, exception handling in the tool handler, and the get_web_search_server contract. Plus an e2e test (harness_live marker; gated on ANTHROPIC_API_KEY + JINA_API_KEY) that drives a real Claude harness call and asserts the model actually invoked web_search. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Performance
✓ No regressions detected |
… is absent
CI installs the SDK as ``.[dev]``, which doesn't pull claude-agent-sdk
(it lives under the ``harness-claude`` / ``harness`` extras). Tests that
exercise ``@tool`` and ``create_sdk_mcp_server`` need the real SDK and
can't run in that env.
Adds ``pytest.importorskip("claude_agent_sdk")`` to the call sites that
require it:
- _get_handler() in tests/tools/test_web_search.py — covers the @tool
handler tests
- test_get_web_search_server_returns_config_and_namespaced_tool_names
- test_harness_with_real_web_search_server_config in test_harness_mcp.py
Pure markdown-formatting tests (no SDK call) and the harness-passthrough
tests (which use a fake claude_agent_sdk module via monkeypatch) continue
to run in every env.
Verified: with the SDK blocked from import, the gated tests skip cleanly
and the rest still pass; with the SDK installed locally, all 41 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
|
Closing in favor of a simpler approach. After research, both providers have native first-party web search built in:
So the MCP wrapper, the multi-provider search package, and the Filed as a separate concern: the opencode provider in agentfield silently drops No production code from this branch was running anywhere — safe to close without follow-up. |
Summary
Adds a way for harnessed Claude Code agents to invoke web search during a run, so reasoners can look up library docs, error messages, deprecation status, and other context that doesn't live in the codebase.
Three layers, three commits:
agentfield.tools.search— vendored multi-provider search package (Jina, Tavily, Firecrawl, Serper) with a unifiedSearchProviderABC, env-driven auto-detection, and a priority order overridable viaSEARCH_PROVIDER. Ported fromAgent-Field/af-deep-research'sskills/searchfromaiohttptohttpxso it lines up with this repo's existingpytest-httpxtest infra.mcp_serversparameter onAgent.harness— new keyword arg (also reachable via therouter.harnessdelegation path) that forwards an MCP-server mapping straight toClaudeAgentOptions.mcp_servers. Honored only by theclaude-codeprovider;opencode/codex/geminiignore it the same way they already ignoretools=[...].agentfield.tools.web_search.get_web_search_server()— returns(McpSdkServerConfig, [tool_names])for an in-process MCP server (built viaclaude_agent_sdk.create_sdk_mcp_server+@tool) that wraps the search package. Reasoners drop the server under a key inmcp_servers={...}and merge the namespacedmcp__af_search__web_searchname into theirtools=[...]allow-list. Tool description tells the model to reach for it only when the answer can't come from the codebase, mitigating loop risk in coding agents.Why this shape
Considered building it as a per-call Python pre-step (search before the prompt, inject results) and as a custom-tool registry across providers. Pre-step loses the agent's ability to decide when to search; cross-provider registries would have required modifying three external CLI binaries. The in-process MCP server is the canonical extension point in
claude-agent-sdkand matches the way Claude Code itself ships built-in tools — minimal new surface area, no per-call wrapper code in reasoners.Test plan
tests/tools/,tests/test_harness_mcp.py)SEARCH_PROVIDERoverride, fallback when preferred is unavailable, custom provider registrationweb_searchwrapper: input validation, no-provider error path, exception-in-search handler, markdown formatting (snippet truncation, missing fields, empty results)ClaudeAgentOptions, omission when not set, real-server-config wiring, silent ignore on non-claude providertests/tools/test_search_live_jina.py— skipped withoutJINA_API_KEY, otherwise hit the real Jina API and assert structured resultstests/test_harness_web_search_e2e.py—harness_livemarker (deselected by defaultaddopts), gated on bothANTHROPIC_API_KEYandJINA_API_KEY. Drives a real Claude harness call, asks the model to useweb_searchto look up pytest's docs URL, asserts the model actually invoked the toolExisting harness suite (130 tests under
-k harness): all green, no regressions.Follow-up
Sister PR coming on
Agent-Field/SWE-AFthat wiresget_web_search_server()into 6 reasoners —run_architect,run_ci_fixer,run_pr_resolveras Tier 1 (web access is high-leverage), andrun_coder,run_retry_advisor,run_product_manageras Tier 2 (with prompt guardrails on the coder to scope use). Per-reasoner cost analysis already done; the deterministic / pure-synthesis / short-budget reasoners are deliberately excluded.🤖 Generated with Claude Code