deepseek-web-retrieval 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +113 -53
  3. package/dist/cli.js +2427 -1813
  4. package/docs/adr/0002-shared-config-base-layer.md +43 -0
  5. package/docs/agents/claude-code.md +273 -0
  6. package/docs/agents/codex.md +50 -27
  7. package/docs/agents/opencode-v2.md +59 -33
  8. package/docs/agents/pi.md +50 -27
  9. package/docs/configuration.md +109 -0
  10. package/docs/design/config-layering-design.md +180 -0
  11. package/docs/design/installer-design.md +24 -13
  12. package/docs/hosts.jsonc +5 -1
  13. package/docs/integration.md +15 -9
  14. package/docs/research/control-matrix.md +49 -11
  15. package/docs/research/mcp-compatibility.md +6 -3
  16. package/docs/research/ori-question-sourcing.md +336 -0
  17. package/docs/research/result-quality-passthrough.md +159 -0
  18. package/docs/research/search-api-defaults.md +18 -2
  19. package/docs/research/search-inline-content.md +244 -0
  20. package/examples/opencode.global.jsonc +3 -2
  21. package/package.json +2 -1
  22. package/src/core/auth.ts +3 -0
  23. package/src/core/client.ts +26 -4
  24. package/src/core/config-file.ts +168 -0
  25. package/src/core/config-summary.ts +82 -0
  26. package/src/core/constants.ts +15 -1
  27. package/src/core/descriptions.ts +6 -2
  28. package/src/core/env.ts +102 -32
  29. package/src/core/format.ts +19 -1
  30. package/src/core/options.ts +22 -2
  31. package/src/core/search.ts +6 -0
  32. package/src/core/types.ts +11 -0
  33. package/src/installer/cli.ts +50 -16
  34. package/src/installer/config-command.ts +116 -0
  35. package/src/installer/hosts/claude-code.ts +325 -0
  36. package/src/installer/hosts/codex.ts +9 -4
  37. package/src/installer/hosts/index.ts +3 -0
  38. package/src/installer/hosts/opencode.ts +9 -5
  39. package/src/installer/hosts/pi.ts +34 -9
  40. package/src/installer/ownership.ts +54 -1
  41. package/src/installer/paths.ts +7 -1
  42. package/src/installer/state.ts +73 -20
  43. package/src/installer/types.ts +11 -1
  44. package/src/installer/uninstall.ts +4 -1
  45. package/src/mcp/auth.ts +2 -3
  46. package/src/mcp/cli.ts +4 -3
  47. package/src/mcp/serve.ts +10 -1
  48. package/src/mcp/server.ts +7 -2
  49. package/src/opencode/plugin.ts +22 -2
  50. package/src/pi/extension.ts +78 -9
@@ -71,8 +71,9 @@ Reading notes:
71
71
  One section per host, in hosts.jsonc order. Each carries what an install needs beyond the
72
72
  matrix: the built-in competitors, their kill switches, how the MCP server is registered,
73
73
  and which rules files steer preference. The `init` CLI implements these tiers for
74
- OpenCode, Pi, and Codex (see [installer design](../design/installer-design.md)). Config
75
- paths imply the available scopes; keys locked to a single scope are enumerated in the
74
+ OpenCode, Pi, Codex, and Claude Code (see
75
+ [installer design](../design/installer-design.md)). Config paths imply the available
76
+ scopes; keys locked to a single scope are enumerated in the
76
77
  [installer design](../design/installer-design.md#scope).
77
78
 
78
79
  ### Cline
@@ -118,7 +119,11 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
118
119
  ([rmcp_client.rs at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/codex-mcp/src/rmcp_client.rs));
119
120
  `enabled_tools`/`disabled_tools` (verbatim raw tool-name match, filtered before
120
121
  registration so the model never sees a disabled tool; an empty `enabled_tools` allow-list
121
- exposes nothing), per-tool approval modes, and output limits.
122
+ exposes nothing), per-tool approval modes, and output limits: MCP tool output can be
123
+ capped per tool via `output_token_limit` ("Token budget for one MCP tool's output,
124
+ before the standard 20% serialization allowance"; the default cap is not published
125
+ there)
126
+ ([mcp_types.rs at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/mcp_types.rs)).
122
127
  stdio children do not inherit the parent environment (only `HOME`, `PATH`, and explicit
123
128
  `env`), so `DEEPSEEK_API_KEY` must be passed explicitly. When a tool result has
124
129
  `structuredContent`, Codex replaces the content blocks with the serialized JSON. No SSE.
@@ -146,6 +151,13 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
146
151
  tool from model requests entirely; `webfetch` has no disable config key at 2.0.32 — the
147
152
  deterministic paths are a permissions deny (`action: "webfetch"`) or a plugin
148
153
  tool-transform removal, the latter being what `fetchOverrideWebfetch` does.
154
+ - **Tool output caps** — plugin tool results truncate at 2,000 lines / 50 KiB: every
155
+ tool result passes through `ToolOutput.truncate` (`MAX_LINES = 2000`,
156
+ `MAX_BYTES = 50 KiB`), with the full output retained to a `tool-output/` file for
157
+ 7 days
158
+ ([tool-output.ts at v2.0.32](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool-output.ts));
159
+ caps and model-channel behavior recorded in
160
+ [result-quality-passthrough.md](result-quality-passthrough.md).
149
161
  - **MCP registration** — out of scope for this host: the shipped path is the plugin
150
162
  adapter, and OpenCode's MCP support is not part of it.
151
163
  - **Rules** — `AGENTS.md` only (no CLAUDE.md fallback in v2): global
@@ -170,7 +182,9 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
170
182
  `process.env` (no forwarding), and results bypass the MCP layer entirely, so neither the
171
183
  20 KB truncation nor the 60 s request timeout applies. There is no per-extension options
172
184
  channel: the runtime config surface is the `DEEPSEEK_*` environment (the same pipeline as
173
- the MCP server) plus a `deepseekWebRetrieval` settings namespace the installer writes for
185
+ the MCP server), the shared config file
186
+ (`~/.config/deepseek-web-retrieval/config.json`, layered under the environment), plus a
187
+ `deepseekWebRetrieval` settings namespace the installer writes for
174
188
  the off tier — Pi preserves unknown settings keys on read and on its own rewrites
175
189
  (`pi install`), verified at 1.3.0. Action methods such as `getSettings()` cannot be
176
190
  called during extension loading, so the adapter reads the settings files itself.
@@ -196,15 +210,31 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
196
210
  - **Built-in web tools** — `WebSearch` and `WebFetch`. Kill switch: bare deny rules —
197
211
  `"permissions": {"deny": ["WebSearch", "WebFetch"]}` — remove the tools from the model's
198
212
  context entirely; deny outranks allow at every settings level and cannot be carved out.
199
- `--disallowedTools` is the flag form. `CLAUDE_CODE_DISABLE_WEB_FETCH=1` (2.1.285+) kills
200
- WebFetch only; there is no env equivalent for WebSearch
213
+ Permission rules live in the settings file of each level: managed
214
+ `managed-settings.json` (Linux `/etc/claude-code/`, macOS
215
+ `/Library/Application Support/ClaudeCode/`, Windows
216
+ `C:\Program Files\ClaudeCode\` — the legacy `C:\ProgramData\ClaudeCode\` path
217
+ is not read) →
218
+ user `~/.claude/settings.json` → project `.claude/settings.json` → local
219
+ `.claude/settings.local.json`; the installer writes the chosen scope's settings file
220
+ and reports an existing managed layer. A bare `mcp__<server>` rule denies every tool
221
+ of one MCP server — the per-server kill switch the installer uses for web-capable
222
+ competitors under the forced tier. `--disallowedTools` is the flag form.
223
+ `CLAUDE_CODE_DISABLE_WEB_FETCH=1` (2.1.285+) kills WebFetch only; there is no env
224
+ equivalent for WebSearch
201
225
  ([tools](https://code.claude.com/docs/en/tools),
202
- [permissions](https://code.claude.com/docs/en/permissions)).
203
- - **MCP registration** — `.mcp.json` (`mcpServers` with `command`/`args`/`env`, or
204
- `type: "http"` + `url`); `${VAR}` expansion in `command`/`args`/`env`/`url`/`headers`.
205
- Tool permission rules use `mcp__<server>__<tool>`. Tool descriptions are capped at 4,096
226
+ [permissions](https://code.claude.com/docs/en/permissions),
227
+ [settings](https://code.claude.com/docs/en/settings)).
228
+ - **MCP registration** — three scopes across two files: project `.mcp.json` at the
229
+ project root (team-shared, first-use approval prompt per project), local scope under
230
+ the project's entry in `~/.claude.json` (private, the `claude mcp add` default), and
231
+ user scope at the top-level `mcpServers` key of `~/.claude.json` (`--scope user`).
232
+ Entries take `mcpServers` with `command`/`args`/`env`, or `type: "http"` + `url`;
233
+ `${VAR}` expansion in `command`/`args`/`env`/`url`/`headers`. Tool permission rules
234
+ use `mcp__<server>__<tool>`. Tool descriptions are capped at 4,096
206
235
  chars (2.1.296+) and tool output at 25k tokens by default (`MAX_MCP_OUTPUT_TOKENS`),
207
- with oversized text saved to a file; 16 MiB message cap on HTTP transports.
236
+ with oversized text saved to a file; 16 MiB message cap on HTTP transports
237
+ ([MCP quickstart](https://code.claude.com/docs/en/mcp-quickstart)).
208
238
  - **Rules** — `CLAUDE.md` hierarchy (managed → user `~/.claude/CLAUDE.md` → project →
209
239
  `CLAUDE.local.md`) with `@import`; `AGENTS.md` is read instead only when no CLAUDE.md
210
240
  exists anywhere above the cwd (since 2.1.277)
@@ -290,3 +320,11 @@ paths imply the available scopes; keys locked to a single scope are enumerated i
290
320
  `toolPolicies`). Codex's docs location and Copilot's `GEMINI.md` dating were re-verified
291
321
  2026-10-21 and are settled in their sections above. Protocol-revision evidence for every
292
322
  host lives in [mcp-compatibility.md](mcp-compatibility.md).
323
+ - **Claude Code forward-compat anchors (checked at the 2.1.303 lock):** the headless docs
324
+ pre-announce `--bare` becoming the default for `-p`, and bare mode never reads
325
+ `.mcp.json` — the billed e2e scenario and the project-scope install story both need
326
+ re-checking when that lands. `${VAR}` expansion in the `env` block of a user-scope
327
+ `~/.claude.json` entry is supported but only indirectly documented (the docs spell out
328
+ `.mcp.json` and `command`/`args`; the e2e's `hasCompletedOnboarding` flag and the
329
+ redundant `enableAllProjectMcpServers` belt-and-suspenders are undocumented), so a
330
+ baseline bump should re-verify all three against the then-current docs.
@@ -82,7 +82,7 @@ host facts; each row below was verified against that lock on the research baseli
82
82
  | Cline | `2024-11-05` | stdio, Streamable HTTP, SSE | Custom client (no official SDK); `protocolVersion "2024-11-05"` extracted from the platform binary; source repo not public |
83
83
  | Codex | `2025-06-18`; `mcp_2026_07_28` feature flag opts into `2026-07-28` | stdio, Streamable HTTP (no SSE) | Git tag `rust-v0.163.0`: rmcp 3.5.1 pinned; initialize sends `V_2025_06_18` |
84
84
  | Pi | `2025-11-25` | stdio, Streamable HTTP (SSE rejected) | Git tag: own `@earendil-works/pi-mcp` client, `LATEST_PROTOCOL_VERSION "2025-11-25"` |
85
- | Claude Code | `2026-07-28` by default (`MCP_PROTOCOL_NEGOTIATION=legacy` opt-out) | stdio, Streamable HTTP, SSE, ws | Changelog 2.1.296+: "negotiate MCP protocol version 2026-07-28 by default"; docs: TS SDK 2.0 runtime |
85
+ | Claude Code | `2026-07-28` by default (`MCP_PROTOCOL_NEGOTIATION=legacy` opt-out) | stdio, Streamable HTTP, SSE, ws | Docs: the v2 runtime (TS SDK 2.0) defaults from 2.1.274+ without flag fetching and "asks stdio servers for that revision by default"; changelog 2.1.299 extended the default to cloud session and Remote Control connectors |
86
86
  | Cursor | Unpinnable from primary sources (docs show ≥ `2025-06-18` features) | stdio, Streamable HTTP, SSE | Docs feature set only; no revision stated in docs or changelog |
87
87
  | Devin | `2026-07-28` | stdio, Streamable HTTP (SSE fallback), SSE | Changelog for the locked release itself: "Support for MCP protocol revision 2026-07-28" |
88
88
  | GitHub Copilot | `2026-07-28` | stdio (`type: "local"`), Streamable HTTP, SSE | Changelog 1.0.81: "Ship MCP 2026-07-28 support to CLI, SDK, IDE, and in-memory clients" |
@@ -117,7 +117,10 @@ differences:
117
117
  content-first, falling back to `structuredContent` only when a result has no content
118
118
  blocks (verified in `tools.ts` at v1.3.0). No `outputSchema` is declared: the SDK
119
119
  rejects text-only results for a tool with a declared schema, and no host in the
120
- matrix validates the schema anyway.
120
+ matrix validates the schema anyway. The per-host model-channel facts behind this —
121
+ which of `content`/`structuredContent` provably reaches the model on each host,
122
+ native adapter paths included — are recorded in
123
+ [result-quality-passthrough.md](result-quality-passthrough.md).
121
124
  2. **stdio is the universal transport.** Every host in the matrix launches stdio servers
122
125
  with the same conceptual JSON (`mcpServers` → `command`/`args`/`env`), differing only in
123
126
  file location and cosmetic field names. stdio needs no OAuth and no URL config, and the
@@ -125,7 +128,7 @@ differences:
125
128
  Codex and Pi reject it anyway.
126
129
  3. **Build on the official SDK v2 dual-era serving.** One build then serves the full range:
127
130
  the `2024-11-05` floor set by Cline (v2 legacy support spans `2024-10-07`–`2025-11-25`)
128
- up to the `2026-07-28` negotiators (Claude Code 2.1.296+, Devin 3000.11.92, Copilot CLI
131
+ up to the `2026-07-28` negotiators (Claude Code 2.1.274+, Devin 3000.11.92, Copilot CLI
129
132
  1.0.81+). A v1.x-built server still works today — modern clients fall back to the legacy
130
133
  handshake — but it can never offer the stateless era; v2 serves both from one entry
131
134
  point.
@@ -0,0 +1,336 @@
1
+ # Sourcing `ori_question` across hosts
2
+
3
+ This note answers one question: through what channel could each host in the
4
+ [support matrix](../integration.md) supply a value for the DeepSeek Search API's optional
5
+ `ori_question` request parameter — "触发搜索的用户原始问题,1~128 个字符,有助于更好地理解搜索意图" (the
6
+ user's original question that triggered the search;
7
+ [API guide](https://api-docs.deepseek.com/guides/search), parameter table reproduced in
8
+ [search-api-defaults.md](search-api-defaults.md))? Two candidate channels are examined per host:
9
+ **(A)** a model-filled optional tool input parameter (e.g. an optional `original_question` string in
10
+ the tool's input schema), and **(B)** the adapter itself reading the surrounding session/task
11
+ context at tool-call time. At the research baseline the repo never sent the parameter; it is
12
+ now sent on the MCP path (channel A) and the Pi path (A plus B as a blank-input fallback),
13
+ while OpenCode's websearch provider path has no channel at 2.0.32 (below).
14
+
15
+ **Research baseline:** 2026-10-23, using the same version locks as
16
+ [hosts.jsonc](../hosts.jsonc) (baseline 2026-10-19): Cline 0.0.13, Codex 0.163.0
17
+ (rust-v0.163.0, rmcp 3.5.1), OpenCode 2.0.32, Pi 1.3.0, Claude Code 2.1.303, Cursor 2026.10.08,
18
+ Devin 3000.11.92, GitHub Copilot 1.0.98. Claims are cited to primary sources only: the MCP
19
+ specification, host source code at exact git tags, npm tarballs at the locked versions, and
20
+ official docs/changelogs. Where no primary source pins a fact, that is stated rather than guessed.
21
+ Protocol-revision facts per host are inherited from
22
+ [mcp-compatibility.md](mcp-compatibility.md) and not re-derived.
23
+
24
+ ## What the protocol offers
25
+
26
+ MCP never carries conversation context to a server. The facts, per revision:
27
+
28
+ - **`tools/call` params are `name` + `arguments` (+ `_meta`).** The 2025-06-18 schema defines the
29
+ params as `name` (required, string) and `arguments` (optional, free-form object with
30
+ `additionalProperties: {}`) — the concrete `CallToolRequest` does not even enumerate `_meta`
31
+ ([schema.json](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.json));
32
+ the same shape holds in 2024-11-05 and 2025-11-25
33
+ ([tools pages](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)). 2025-11-25
34
+ adds an optional `task` field (task augmentation) and 2026-07-28 adds `inputResponses` /
35
+ `requestState` for multi round-trip retries plus required per-request `_meta` protocol fields
36
+ ([2026-07-28 tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools),
37
+ [_meta](https://modelcontextprotocol.io/specification/2026-07-28/basic/index#meta)) — none of
38
+ these carry user or conversation data.
39
+ - **The spec assigns input validation to servers, not clients.** "Servers **MUST**: Validate all
40
+ tool inputs"; the client-side SHOULDs cover validating *results* and showing inputs to the user —
41
+ no revision obliges (or forbids) a client to validate model arguments against `inputSchema`
42
+ ([security considerations](https://modelcontextprotocol.io/specification/2025-06-18/server/tools),
43
+ all revisions). `inputSchema` is "JSON Schema defining expected parameters", defaulting to the
44
+ 2020-12 dialect from 2025-11-25 on. This makes an optional declared property the spec-sanctioned
45
+ way for a model to convey extra intent.
46
+ - **"MCP has no protocol-level session."** The 2026-07-28 spec says it directly: "a server cannot
47
+ rely on implicit per-connection state to relate one tool call to the next"
48
+ ([Stateful Tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#stateful-tools)),
49
+ and the statelessness rules forbid inferring context from prior requests
50
+ ([Statelessness](https://modelcontextprotocol.io/specification/2026-07-28/basic/index#statelessness)).
51
+ - **Sampling cannot be abused to obtain the context.** `sampling/createMessage` is
52
+ server-initiated with server-authored `messages`; `includeContext: "thisServer"/"allServers"`
53
+ (soft-deprecated) includes *MCP-server* context, not the host conversation, and requests are
54
+ human-reviewed
55
+ ([sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling)). A server
56
+ could only ask a model to *guess* intent — not obtain the user's question.
57
+ - **Elicititation is interactive, not derivation.** `elicitation/create` asks the user a form/choice
58
+ question and waits ([2025-06-18+](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation));
59
+ it requires client capability (only Codex in this matrix has it, per
60
+ [mcp-compatibility.md](mcp-compatibility.md)) and an explicit round trip.
61
+ - **Tasks are state machines, not context.** The experimental 2025-11-25 `Task` object is
62
+ `taskId`/`status`/timestamps/`ttl`/`pollInterval` — no user data
63
+ ([tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)).
64
+ - **`_meta` has no context convention.** Reserved keys are enumerated; vendors may add prefixed
65
+ keys, but no host documents sending conversation context there
66
+ ([_meta](https://modelcontextprotocol.io/specification/2026-07-28/basic/index#meta)). Codex is the
67
+ one host observed adding its own key — `openai/entitlementContext`, carrying entitlement status
68
+ for zero-argument read-only plugin tools
69
+ ([trusted_access.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/codex-mcp/src/trusted_access.rs))
70
+ — not user context.
71
+
72
+ Conclusion: **for an MCP-connected host, channel A (the tool's input schema) is the only spec-borne
73
+ channel; channel B does not exist at the protocol layer.** Whether A works in practice depends on
74
+ each host's client, below.
75
+
76
+ ## Open-source hosts, from source at the lock
77
+
78
+ ### Codex 0.163.0 (rust-v0.163.0)
79
+
80
+ - **No client-side argument validation.** The model's `arguments` string is parsed as JSON (empty
81
+ allowed; invalid JSON becomes an error result) and the resulting `serde_json::Value` flows
82
+ unchanged to rmcp's `call_tool`
83
+ ([mcp_tool_call.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/core/src/mcp_tool_call.rs),
84
+ [connection_manager.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/codex-mcp/src/connection_manager.rs)).
85
+ No JSON-Schema validator exists in the tree (only `schemars`, used to *generate* schemas for
86
+ built-in tools; codex-rs/Cargo.toml at the tag).
87
+ - **Input schemas reach the model essentially verbatim.** `mcp_tool_to_responses_api_tool` forwards
88
+ the server's `inputSchema` as the tool's `parameters`
89
+ ([responses_api.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/responses_api.rs));
90
+ the only transformations are inserting an empty `properties` object when missing, sanitizing
91
+ boolean schemas, and a default 5,000-byte budget: over-budget schemas are progressively
92
+ compacted (strip descriptions → drop definitions → collapse deep objects → prune compositions)
93
+ ([mcp_tool.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/mcp_tool.rs),
94
+ [json_schema.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/json_schema.rs),
95
+ [compaction.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/json_schema/compaction.rs)).
96
+ A small schema with one optional string property stays intact.
97
+ - **Channel B: none.** Codex's third-party integration surface at this tag is MCP servers; there is
98
+ no session/context API a server can call.
99
+
100
+ Verdict: **A feasible (nothing validates or strips arguments; schema forwarded), B impossible.**
101
+
102
+ ### Pi 1.3.0 (v1.3.0)
103
+
104
+ - **The agent loop validates arguments against the tool's parameter schema before `execute`.**
105
+ `validateToolArguments` clones the arguments, normalizes `null` for optional properties, coerces
106
+ types (`Value.Convert`, plus JSON-Schema coercion for plain JSON-Schema tools), then runs a
107
+ TypeBox `Check`; failures come back to the model as `Validation failed for tool …` error results
108
+ ([validation.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/ai/src/utils/validation.ts),
109
+ [agent-loop.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/agent/src/agent-loop.ts)).
110
+ A *declared optional* property therefore passes and reaches the tool; undeclared fields pass only
111
+ when the schema itself permits them.
112
+ - **MCP tools: schema forwarded, params passed through.** The adapter spreads the server's
113
+ `inputSchema` into the model-facing parameters (adding only `type: "object"` and an empty
114
+ `properties` when missing) and passes the validated `params` to the client's `callTool`, which
115
+ sends `{ name, arguments }` verbatim (a `_meta.progressToken` is added only when progress
116
+ reporting is on)
117
+ ([tools.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/extensions/mcp/tools.ts),
118
+ [client.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/mcp/src/client.ts)).
119
+ - **Extension tools receive session access as a fifth `execute` argument.** At v1.3.0 the signature
120
+ is `execute(toolCallId, params, signal, onUpdate, ctx: ExtensionToolContext)`
121
+ ([types.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/extensions/types.ts)).
122
+ `ExtensionToolContext` extends `ExtensionContext`, whose `sessionManager` is a
123
+ `ReadonlySessionManager` exposing `buildSessionProjection()` — returning the model-visible
124
+ `messages: AgentMessage[]` (user messages included), plus `getEntries`/`getTree`/`getBranch`
125
+ ([session-manager.ts](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/session-manager.ts)).
126
+ The extension docs at the tag document `ctx` for tools (`ctx.executeTool`, `ctx.sessionManager`)
127
+ ([extensions.md](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/docs/extensions.md)).
128
+ The `ExtensionAPI` event surface (`input`, `message_end`, `tool_execution_*`) additionally lets an
129
+ extension track the user prompt as it arrives.
130
+
131
+ Verdict: **A feasible on both paths (declared optional TypeBox property; validated, then passed
132
+ through). B feasible on the extension path via the fifth `execute` argument — the handler can read
133
+ the session's user messages at call time. B impossible via MCP** (separate process, no session
134
+ API).
135
+
136
+ ### OpenCode 2.0.32
137
+
138
+ Repo provenance first: `@opencode/cli@2.0.32`'s manifest points at
139
+ [github.com/anomalyco/opencode](https://github.com/anomalyco/opencode), which has the `v2.0.32`
140
+ tag (commit `843a6c5`); the `opencode-ai/opencode` repository's tags stop at `v0.0.x` and hold no
141
+ 2.x tag. Facts below are from the tag plus the npm tarballs at 2.0.32
142
+ (`@opencode/plugin`, `@opencode/schema`, `@opencode/client`).
143
+
144
+ - **The tool runtime validates arguments against the tool's declared input schema before
145
+ `execute`.** `Tool.runtime.execute` decodes the model's input first: raw JSON-Schema inputs are
146
+ converted to an Effect codec and validated; failures return `Invalid arguments for tool "…":
147
+ … Update the arguments and call the tool again.`
148
+ ([runtime.ts](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/runtime.ts)).
149
+ A plugin tool's plain JSON Schema `input` is forwarded to the model **as-is** (`inputJsonSchema`
150
+ returns it unchanged), so a declared optional parameter is both model-visible and validated.
151
+ - **`ToolContext` carries IDs only**: `sessionID`, `agent`, `messageID`, call `id`, `signal`,
152
+ `progress` ([tool.d.ts in @opencode/schema 2.0.32](https://registry.npmjs.org/@opencode/schema/2.0.32);
153
+ [tool.d.ts in @opencode/plugin 2.0.32](https://registry.npmjs.org/@opencode/plugin/2.0.32)).
154
+ - **But the plugin `Context.session` domain can read the conversation.** The picked `SessionDomain`
155
+ includes `context({ sessionID }) → SessionMessageInfo[]`, and `SessionMessageUser` has
156
+ `text: string` — the user's prompt text — plus attachments
157
+ ([session.d.ts](https://registry.npmjs.org/@opencode/plugin/2.0.32),
158
+ [client types](https://registry.npmjs.org/@opencode/client/2.0.32)). Session hooks round it out:
159
+ `"prompt"` fires with the prompt itself, `"generate"`/`"context"` with `messages: Array<Message>`
160
+ ([session.d.ts at 2.0.32](https://registry.npmjs.org/@opencode/plugin/2.0.32)). So a tool's
161
+ `execute` can call `ctx.session.context({ sessionID: context.sessionID })` at call time.
162
+ - **The native websearch provider interface carries only the query.**
163
+ `WebSearch.ProviderInput = Pick<Input, "query">`, the provider context is `{ signal }`, and core
164
+ invokes providers as `.execute({ query: input.query })`
165
+ ([websearch.ts](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/websearch.ts),
166
+ [websearch.d.ts](https://registry.npmjs.org/@opencode/schema/2.0.32)). The model-facing
167
+ `websearch` tool's input schema is fixed in host source to `query` alone
168
+ ([tool/plugin/websearch.ts](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/plugin/websearch.ts)).
169
+ A provider gets no sessionID, so session auto-derivation on this path is not directly possible —
170
+ only heuristic correlation via the session hooks (which fire per model request with the
171
+ sessionID, but no API contract binds a provider call to a session).
172
+
173
+ Verdict: **tool path (`deepseek_fetch` / the `webfetch` override): A feasible (validated against
174
+ the declared schema; raw JSON Schema forwarded verbatim) and B feasible
175
+ (`ctx.session.context`). Websearch provider path (the search replacement this repo ships): neither
176
+ — the input schema is host-fixed to `query`, and the provider context has no session handle.**
177
+
178
+ ### Cline 0.0.13
179
+
180
+ Closed source; the platform binary
181
+ ([@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13), ~119 MB)
182
+ yielded the protocol facts already recorded — custom client negotiating `2024-11-05`,
183
+ text-content-only results ([mcp-compatibility.md](mcp-compatibility.md)). No primary source
184
+ documents whether the client validates tool arguments against `inputSchema` or mangles schemas;
185
+ strings cannot prove a runtime behavior either way, so this is **undeterminable at the lock**. The
186
+ 2024-11-05 revision itself puts no validation obligation on clients and defines `arguments` as a
187
+ free-form object, so there is no protocol-level obstacle to channel A — merely no evidence for or
188
+ against host behavior. Channel B: none (MCP only, `paths` in hosts.jsonc).
189
+
190
+ ## Proprietary hosts
191
+
192
+ No proprietary host documents a context channel; the question is whether anything documents
193
+ input-schema handling that would break an optional parameter.
194
+
195
+ - **Claude Code 2.1.303.** The [MCP docs](https://code.claude.com/docs/en/mcp) say nothing about
196
+ input schemas or argument validation. The
197
+ [changelog](https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md) documents
198
+ the mechanics indirectly, all at or before the lock: 2.1.300 — "Fixed a conversation failing on
199
+ every turn with a 400 error when the API rejected an MCP tool's input schema; Claude Code now
200
+ sends that tool without parameters for the rest of the conversation"; 2.1.246 — "Fixed MCP tool
201
+ arguments being sent as JSON strings when the parameter's schema is empty (`{}`)"; 2.1.30 —
202
+ prompt-cache invalidation "when tool descriptions or input schemas changed"; 2.0.50 — "bug
203
+ preventing calling MCP tools that have nested references in their input schemas". Together these
204
+ show the server's `inputSchema` is what the model provider sees and that arguments follow it;
205
+ no host-side validation is ever mentioned. **A feasible by this evidence; B none.**
206
+ - **Cursor 2026.10.08.** The [MCP docs](https://cursor.com/docs/mcp) describe tools only as
207
+ "Functions for the AI model to execute" and say nothing about input schemas, validation, or
208
+ context — and the protocol revision itself is unpinnable from primary sources
209
+ ([mcp-compatibility.md](mcp-compatibility.md)). **No primary source either way for A; B none.**
210
+ - **Devin 3000.11.92.** The [MCP docs](https://docs.devin.ai/cli/extensibility/mcp/overview) and
211
+ [changelog](https://docs.devin.ai/cli/changelog) cover transport, OAuth, permissions, and
212
+ tool-level gating (`disabled_tools`, `mcp__server__tool` patterns) but nothing on input schemas
213
+ or argument validation; the nearest mechanisms (`PostToolUse` hooks, positional prompt arguments)
214
+ are not tool-input channels. **No primary source either way for A; B none.**
215
+ - **GitHub Copilot 1.0.98.** The
216
+ [MCP docs](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers)
217
+ document per-server `tools` filtering only. The
218
+ [changelog](https://raw.githubusercontent.com/github/copilot-cli/main/changelog.md) shows MCP tool
219
+ schemas are forwarded to model providers verbatim enough that *provider-side* schema validation
220
+ fires, at or before the lock: 1.0.89 — "Gemini models no longer fail every request with 400
221
+ invalid request body when an MCP server exposes a tool whose schema puts a type or properties
222
+ beside anyOf"; 1.0.85 — "MCP tools with boolean property or array-item schemas work with Gemini
223
+ instead of causing a 400 error"; 1.0.61 — "Gemini models work correctly with MCP tools that use
224
+ nullable schema types". No host-side argument validation is mentioned. **A feasible by this
225
+ evidence; B none.**
226
+
227
+ ## Verdicts at a glance
228
+
229
+ | Host | (A) model-filled optional param | (B) session auto-derivation | Load-bearing fact |
230
+ | --- | --- | --- | --- |
231
+ | Codex | Yes | No | No argument validation anywhere in the tool-call path; schema forwarded (compaction only above 5 KB) |
232
+ | Pi | Yes (extension and MCP paths) | Yes — extension path only | 5th `execute` arg exposes `sessionManager.buildSessionProjection()`; MCP path validated against the server's own schema |
233
+ | OpenCode | Yes — tool path; **no** — websearch provider | Yes — tool path; not directly — provider | Runtime validates against the declared schema and forwards raw JSON Schemas verbatim; `ctx.session.context` reads user text; provider input is host-fixed to `query` |
234
+ | Cline | Undeterminable (no primary source) | No | 2024-11-05 client; behavior not documented |
235
+ | Claude Code | Yes (changelog evidence) | No | Input schemas demonstrably reach the model API |
236
+ | Cursor | No primary source | No | Docs silent |
237
+ | Devin | No primary source | No | Docs/changelog silent |
238
+ | Copilot | Yes (changelog evidence) | No | Provider-side 400s driven by MCP schema shapes prove forwarding |
239
+
240
+ For every MCP-only host, channel A is the only candidate; where no primary source pins the
241
+ behavior (Cline, Cursor, Devin), an optional declared parameter is still spec-conformant on every
242
+ negotiated revision in the matrix.
243
+
244
+ ## What this means for this repo
245
+
246
+ - **MCP server (`src/mcp/server.ts`).** The tool `inputSchema` is the only channel any MCP host
247
+ offers. The server declares its inputs as zod objects (`z.object({ query, original_question })`,
248
+ `z.object({ url })`) through SDK v2's `registerTool`; zod's default behavior strips undeclared
249
+ keys server-side, so a host-side pre-filled undeclared field would be dropped — whatever a host
250
+ sends must be *declared* in the schema to arrive in the handler. The declared optional string
251
+ surfaces in the JSON Schema every host in this note forwards (Codex and Pi verbatim-small,
252
+ OpenCode verbatim, Claude Code and Copilot per changelog evidence; Cline, Cursor, Devin
253
+ unproven but spec-conformant) — exactly the channel the shipped `original_question` rides.
254
+ - **OpenCode plugin (`src/opencode/plugin.ts`).** The fetch tools' `execute` receives a
255
+ `ToolContext` with `sessionID`, and the plugin `Context` can read
256
+ `ctx.session.context({ sessionID })` — user messages with their `text` — at call time, so
257
+ channel B exists here without touching schemas. The `websearch` provider replacement — the path
258
+ this repo uses for search — has neither channel at 2.0.32: its model-facing input schema is
259
+ fixed in host source to `query`, and its provider context is `{ signal }` only. Note also that
260
+ `FETCH_INPUT` sets `additionalProperties: false`, which matters if a parameter is ever added on
261
+ channel A: an undeclared field would be rejected by OpenCode's runtime validation with a
262
+ retryable error.
263
+ - **Pi extension (`src/pi/extension.ts`).** The search handler reads the fifth `execute`
264
+ argument (`ExtensionToolContext`), whose `sessionManager.buildSessionProjection()` returns the
265
+ model-visible messages, user prompts included — readable synchronously at call time, so channel
266
+ B exists on this path; the shipped adapter uses it as the blank-input fallback. Channel A is
267
+ equally open: the TypeBox `parameters` are what the agent loop validates against, and
268
+ `Type.Optional(Type.String(...))` properties pass validation when the model fills them. The
269
+ extension runs in-process, so B does not pay the MCP detour.
270
+
271
+ ## Sources
272
+
273
+ ### Specification
274
+
275
+ - MCP tools: [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05/server/tools) ·
276
+ [2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) ·
277
+ [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) ·
278
+ [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
279
+ (Stateful Tools, MRTR retry params, `x-mcp-header`)
280
+ - [2025-06-18 schema.json (CallToolRequest)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-06-18/schema.json)
281
+ - [2025-11-25 sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling) ·
282
+ [2025-11-25 tasks (experimental)](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)
283
+ - [2026-07-28 overview (`_meta`, statelessness)](https://modelcontextprotocol.io/specification/2026-07-28/basic/index)
284
+
285
+ ### Open-source hosts
286
+
287
+ - Codex: [rust-v0.163.0 tag](https://github.com/openai/codex/tree/rust-v0.163.0) —
288
+ [mcp_tool_call.rs (argument parsing, no validation)](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/core/src/mcp_tool_call.rs),
289
+ [connection_manager.rs (call_tool passthrough)](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/codex-mcp/src/connection_manager.rs),
290
+ [responses_api.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/responses_api.rs),
291
+ [mcp_tool.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/mcp_tool.rs),
292
+ [json_schema.rs](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/json_schema.rs),
293
+ [compaction.rs (5 KB default budget)](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/tools/src/json_schema/compaction.rs),
294
+ [trusted_access.rs (openai/entitlementContext)](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/codex-mcp/src/trusted_access.rs)
295
+ - Pi: [v1.3.0 tag](https://github.com/earendil-works/pi/tree/v1.3.0) —
296
+ [extensions/mcp/tools.ts (toParameters, execute passthrough)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/extensions/mcp/tools.ts),
297
+ [agent-loop.ts (prepare/validate/execute)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/agent/src/agent-loop.ts),
298
+ [validation.ts (validateToolArguments)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/ai/src/utils/validation.ts),
299
+ [mcp/client.ts (callTool)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/mcp/src/client.ts),
300
+ [extensions/types.ts (ToolDefinition.execute 5th arg, ExtensionAPI)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/extensions/types.ts),
301
+ [session-manager.ts (ReadonlySessionManager)](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/src/core/session-manager.ts),
302
+ [extensions.md at the tag](https://github.com/earendil-works/pi/blob/v1.3.0/packages/coding-agent/docs/extensions.md)
303
+ - OpenCode: [anomalyco/opencode v2.0.32 tag](https://github.com/anomalyco/opencode/tree/v2.0.32)
304
+ (repository URL from
305
+ [@opencode/cli 2.0.32](https://registry.npmjs.org/@opencode/cli/2.0.32)) —
306
+ [tool/runtime.ts (input validation, schema forwarding)](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/runtime.ts),
307
+ [websearch.ts (ProviderInput, execute({query}))](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/websearch.ts),
308
+ [tool/plugin/websearch.ts (fixed query schema)](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool/plugin/websearch.ts);
309
+ npm tarballs at 2.0.32:
310
+ [@opencode/plugin](https://registry.npmjs.org/@opencode/plugin/2.0.32)
311
+ (ToolContext, Context, SessionDomain hooks),
312
+ [@opencode/schema](https://registry.npmjs.org/@opencode/schema/2.0.32)
313
+ (Tool.Context, WebSearch.ProviderInput),
314
+ [@opencode/client](https://registry.npmjs.org/@opencode/client/2.0.32)
315
+ (session.context → SessionMessageInfo[], SessionMessageUser.text)
316
+ - Cline: [@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13)
317
+ (manifest only; behavior undeterminable) and prior binary analysis recorded in
318
+ [mcp-compatibility.md](mcp-compatibility.md)
319
+
320
+ ### Proprietary hosts
321
+
322
+ - Claude Code: [changelog](https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md)
323
+ (entries 2.0.50, 2.1.30, 2.1.246, 2.1.300 — all ≤ the 2.1.303 lock),
324
+ [MCP docs](https://code.claude.com/docs/en/mcp)
325
+ - Cursor: [MCP docs](https://cursor.com/docs/mcp) (silent on the question)
326
+ - Devin: [MCP overview](https://docs.devin.ai/cli/extensibility/mcp/overview),
327
+ [changelog](https://docs.devin.ai/cli/changelog) (silent on the question)
328
+ - GitHub Copilot: [changelog](https://raw.githubusercontent.com/github/copilot-cli/main/changelog.md)
329
+ (entries 1.0.61, 1.0.85, 1.0.89 — all ≤ the 1.0.98 lock),
330
+ [MCP docs](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers)
331
+
332
+ ### DeepSeek API
333
+
334
+ - [Search & Fetch guide](https://api-docs.deepseek.com/guides/search) (`ori_question` optional,
335
+ 1–128 chars; parameter table transcribed in
336
+ [search-api-defaults.md](search-api-defaults.md))