deepseek-web-retrieval 0.9.0 → 0.10.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.
- package/CHANGELOG.md +9 -1
- package/README.md +25 -16
- package/dist/cli.js +33 -14
- package/docs/adr/0001-refuse-drift-between-plan-and-apply.md +7 -3
- package/docs/adr/0002-shared-config-base-layer.md +5 -1
- package/docs/adr/0003-need-content-per-call-boolean.md +49 -0
- package/docs/agents/claude-code.md +8 -4
- package/docs/agents/codex.md +7 -7
- package/docs/agents/domain.md +2 -2
- package/docs/agents/issue-tracker.md +5 -5
- package/docs/agents/opencode-v2.md +21 -15
- package/docs/agents/pi.md +35 -11
- package/docs/configuration.md +19 -9
- package/docs/design/config-layering-design.md +10 -5
- package/docs/design/installer-design.md +53 -21
- package/docs/design/search-content-design.md +133 -0
- package/docs/integration.md +19 -16
- package/docs/research/control-matrix.md +39 -34
- package/docs/research/mcp-compatibility.md +3 -1
- package/docs/research/ori-question-sourcing.md +19 -19
- package/docs/research/result-quality-passthrough.md +9 -7
- package/docs/research/search-api-defaults.md +4 -2
- package/docs/research/search-inline-content.md +49 -9
- package/package.json +1 -1
- package/src/core/client.ts +9 -2
- package/src/core/config-summary.ts +2 -0
- package/src/core/constants.ts +13 -0
- package/src/core/descriptions.ts +7 -1
- package/src/core/env.ts +4 -0
- package/src/core/format.ts +10 -1
- package/src/core/options.ts +9 -0
- package/src/core/search.ts +12 -4
- package/src/core/types.ts +12 -0
- package/src/mcp/server.ts +4 -1
- package/src/pi/extension.ts +3 -0
|
@@ -113,30 +113,31 @@ scopes; keys locked to a single scope are enumerated in the
|
|
|
113
113
|
`web_search = "disabled"` in `~/.codex/config.toml`. Trap: the `[tools] web_search =
|
|
114
114
|
<bool>` form parses but discards the value at 0.163.0 — a silent no-op
|
|
115
115
|
([config source at rust-v0.163.0](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/config/src/config_toml.rs)).
|
|
116
|
-
- **MCP registration** — `~/.codex/config.toml` `[mcp_servers.<name>]`:
|
|
117
|
-
`command`/`args`/`env`/`env_vars`;
|
|
118
|
-
(300 s) defaults —
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
116
|
+
- **MCP registration** — `~/.codex/config.toml` `[mcp_servers.<name>]`:
|
|
117
|
+
- **Entry keys:** stdio `command`/`args`/`env`/`env_vars`; per-tool approval modes.
|
|
118
|
+
- **Timeouts:** `startup_timeout_sec` (30 s) and `tool_timeout_sec` (300 s) defaults —
|
|
119
|
+
the `DEFAULT_STARTUP_TIMEOUT`/`DEFAULT_TOOL_TIMEOUT` constants
|
|
120
|
+
([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)).
|
|
121
|
+
- **Tool filtering:** `enabled_tools`/`disabled_tools` — verbatim raw tool-name match,
|
|
122
|
+
filtered before registration so the model never sees a disabled tool; an empty
|
|
123
|
+
`enabled_tools` allow-list exposes nothing.
|
|
124
|
+
- **Output limits:** MCP tool output can be capped per tool via `output_token_limit`
|
|
125
|
+
("Token budget for one MCP tool's output, before the standard 20% serialization
|
|
126
|
+
allowance"; the default cap is not published there)
|
|
127
|
+
([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)).
|
|
128
|
+
- **Environment:** stdio children do not inherit the parent environment (only `HOME`,
|
|
129
|
+
`PATH`, and explicit `env`), so `DEEPSEEK_API_KEY` must be passed explicitly.
|
|
130
|
+
- **Result shape and identity:** when a tool result has `structuredContent`, Codex
|
|
131
|
+
replaces the content blocks with the serialized JSON. No SSE. Codex identifies itself
|
|
132
|
+
in the initialize handshake as clientInfo name `codex-mcp-client` with title `Codex`
|
|
133
|
+
([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)).
|
|
134
|
+
- **Docs drift:** the official docs moved to learn.chatgpt.com
|
|
135
|
+
([MCP](https://learn.chatgpt.com/docs/extend/mcp),
|
|
136
|
+
[config reference](https://learn.chatgpt.com/docs/config-file/config-reference); the
|
|
137
|
+
old developers.openai.com URLs 308-redirect there) and now document every key above —
|
|
138
|
+
but state 10 s/60 s timeout defaults, which describe an older release line; the
|
|
139
|
+
30 s/300 s constants above are the facts at the locked 0.163.0 (docs discrepancy
|
|
140
|
+
re-verified 2026-10-21).
|
|
140
141
|
- **Rules** — `~/.codex/AGENTS.md` (with `AGENTS.override.md` preferred) at user scope;
|
|
141
142
|
every `AGENTS.md` from the project root down to the cwd, concatenated root→cwd.
|
|
142
143
|
Extensible via `project_doc_fallback_filenames` and `developer_instructions`
|
|
@@ -184,8 +185,10 @@ scopes; keys locked to a single scope are enumerated in the
|
|
|
184
185
|
channel: the runtime config surface is the `DEEPSEEK_*` environment (the same pipeline as
|
|
185
186
|
the MCP server), the shared config file
|
|
186
187
|
(`~/.config/deepseek-web-retrieval/config.json`, layered under the environment), plus a
|
|
187
|
-
`deepseekWebRetrieval` settings namespace the installer writes for
|
|
188
|
-
|
|
188
|
+
`deepseekWebRetrieval` settings namespace the installer writes for the `off` tier (a
|
|
189
|
+
capability switched off — the [tier-to-artifact
|
|
190
|
+
mapping](../design/installer-design.md#tier-to-artifact-mapping) defines it) — Pi
|
|
191
|
+
preserves unknown settings keys on read and on its own rewrites
|
|
189
192
|
(`pi install`), verified at 1.3.0. Action methods such as `getSettings()` cannot be
|
|
190
193
|
called during extension loading, so the adapter reads the settings files itself.
|
|
191
194
|
- **MCP registration** — `~/.pi/agent/mcp.json` or `.pi/mcp.json`: stdio
|
|
@@ -320,11 +323,13 @@ scopes; keys locked to a single scope are enumerated in the
|
|
|
320
323
|
`toolPolicies`). Codex's docs location and Copilot's `GEMINI.md` dating were re-verified
|
|
321
324
|
2026-10-21 and are settled in their sections above. Protocol-revision evidence for every
|
|
322
325
|
host lives in [mcp-compatibility.md](mcp-compatibility.md).
|
|
323
|
-
- **Claude Code forward-compat anchors (checked at the 2.1.303 lock)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
326
|
+
- **Claude Code forward-compat anchors (checked at the 2.1.303 lock)** — three facts a
|
|
327
|
+
baseline bump should re-verify against the then-current docs:
|
|
328
|
+
1. The headless docs pre-announce `--bare` becoming the default for `-p`, and bare mode
|
|
329
|
+
never reads `.mcp.json` — the billed `claude-code` e2e scenario in this repo's test
|
|
330
|
+
suite and the project-scope install story both need re-checking when that lands.
|
|
331
|
+
2. `${VAR}` expansion in the `env` block of a user-scope `~/.claude.json` entry is
|
|
332
|
+
supported but only indirectly documented (the docs spell out `.mcp.json` and
|
|
333
|
+
`command`/`args`).
|
|
334
|
+
3. The same e2e scenario's `hasCompletedOnboarding` flag and the redundant
|
|
335
|
+
`enableAllProjectMcpServers` belt-and-suspenders are undocumented.
|
|
@@ -127,7 +127,9 @@ differences:
|
|
|
127
127
|
spec says clients SHOULD support it whenever possible. Legacy SSE is never required —
|
|
128
128
|
Codex and Pi reject it anyway.
|
|
129
129
|
3. **Build on the official SDK v2 dual-era serving.** One build then serves the full range:
|
|
130
|
-
the `2024-11-05` floor set by Cline (v2 legacy support spans `2024-10-07`–`2025-11-25
|
|
130
|
+
the `2024-11-05` floor set by Cline (v2 legacy support spans `2024-10-07`–`2025-11-25`;
|
|
131
|
+
`2024-10-07` is the pre-1.0 draft version string the SDK accepts for legacy
|
|
132
|
+
compatibility — it predates the spec's initial `2024-11-05` revision)
|
|
131
133
|
up to the `2026-07-28` negotiators (Claude Code 2.1.274+, Devin 3000.11.92, Copilot CLI
|
|
132
134
|
1.0.81+). A v1.x-built server still works today — modern clients fall back to the legacy
|
|
133
135
|
handshake — but it can never offer the stateless era; v2 serves both from one entry
|
|
@@ -54,7 +54,7 @@ MCP never carries conversation context to a server. The facts, per revision:
|
|
|
54
54
|
human-reviewed
|
|
55
55
|
([sampling](https://modelcontextprotocol.io/specification/2025-11-25/client/sampling)). A server
|
|
56
56
|
could only ask a model to *guess* intent — not obtain the user's question.
|
|
57
|
-
- **
|
|
57
|
+
- **Elicitation is interactive, not derivation.** `elicitation/create` asks the user a form/choice
|
|
58
58
|
question and waits ([2025-06-18+](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation));
|
|
59
59
|
it requires client capability (only Codex in this matrix has it, per
|
|
60
60
|
[mcp-compatibility.md](mcp-compatibility.md)) and an explicit round trip.
|
|
@@ -184,21 +184,11 @@ the declared schema; raw JSON Schema forwarded verbatim) and B feasible
|
|
|
184
184
|
(`ctx.session.context`). Websearch provider path (the search replacement this repo ships): neither
|
|
185
185
|
— the input schema is host-fixed to `query`, and the provider context has no session handle.**
|
|
186
186
|
|
|
187
|
-
### Cline 0.0.13
|
|
188
|
-
|
|
189
|
-
Closed source; the platform binary
|
|
190
|
-
([@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13), ~119 MB)
|
|
191
|
-
yielded the protocol facts already recorded — custom client negotiating `2024-11-05`,
|
|
192
|
-
text-content-only results ([mcp-compatibility.md](mcp-compatibility.md)). No primary source
|
|
193
|
-
documents whether the client validates tool arguments against `inputSchema` or mangles schemas;
|
|
194
|
-
strings cannot prove a runtime behavior either way, so this is **undeterminable at the lock**. The
|
|
195
|
-
2024-11-05 revision itself puts no validation obligation on clients and defines `arguments` as a
|
|
196
|
-
free-form object, so there is no protocol-level obstacle to channel A — merely no evidence for or
|
|
197
|
-
against host behavior. Channel B: none (MCP only, `paths` in hosts.jsonc).
|
|
198
|
-
|
|
199
187
|
## Proprietary hosts
|
|
200
188
|
|
|
201
|
-
|
|
189
|
+
Closed-source hosts, from binary analysis (Cline) and official docs/changelogs (the
|
|
190
|
+
rest). No proprietary host documents a context
|
|
191
|
+
channel; the question is whether anything documents
|
|
202
192
|
input-schema handling that would break an optional parameter.
|
|
203
193
|
|
|
204
194
|
- **Claude Code 2.1.303.** The [MCP docs](https://code.claude.com/docs/en/mcp) say nothing about
|
|
@@ -212,6 +202,16 @@ input-schema handling that would break an optional parameter.
|
|
|
212
202
|
preventing calling MCP tools that have nested references in their input schemas". Together these
|
|
213
203
|
show the server's `inputSchema` is what the model provider sees and that arguments follow it;
|
|
214
204
|
no host-side validation is ever mentioned. **A feasible by this evidence; B none.**
|
|
205
|
+
- **Cline 0.0.13.** Closed source; the platform binary
|
|
206
|
+
([@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13), ~119 MB)
|
|
207
|
+
yielded the protocol facts already recorded — custom client negotiating `2024-11-05`,
|
|
208
|
+
text-content-only results ([mcp-compatibility.md](mcp-compatibility.md)). No primary
|
|
209
|
+
source documents whether the client validates tool arguments against `inputSchema` or
|
|
210
|
+
mangles schemas; strings cannot prove a runtime behavior either way, so this is
|
|
211
|
+
**undeterminable at the lock**. The 2024-11-05 revision itself puts no validation
|
|
212
|
+
obligation on clients and defines `arguments` as a free-form object, so there is no
|
|
213
|
+
protocol-level obstacle to channel A — merely no evidence for or against host behavior.
|
|
214
|
+
Channel B: none (MCP only, `paths` in hosts.jsonc).
|
|
215
215
|
- **Cursor 2026.10.08.** The [MCP docs](https://cursor.com/docs/mcp) describe tools only as
|
|
216
216
|
"Functions for the AI model to execute" and say nothing about input schemas, validation, or
|
|
217
217
|
context — and the protocol revision itself is unpinnable from primary sources
|
|
@@ -293,7 +293,7 @@ negotiated revision in the matrix.
|
|
|
293
293
|
[2025-11-25 tasks (experimental)](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks)
|
|
294
294
|
- [2026-07-28 overview (`_meta`, statelessness)](https://modelcontextprotocol.io/specification/2026-07-28/basic/index)
|
|
295
295
|
|
|
296
|
-
### Open-source
|
|
296
|
+
### Open-source host sources
|
|
297
297
|
|
|
298
298
|
- Codex: [rust-v0.163.0 tag](https://github.com/openai/codex/tree/rust-v0.163.0) —
|
|
299
299
|
[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),
|
|
@@ -324,15 +324,15 @@ negotiated revision in the matrix.
|
|
|
324
324
|
(Tool.Context, WebSearch.ProviderInput),
|
|
325
325
|
[@opencode/client](https://registry.npmjs.org/@opencode/client/2.0.32)
|
|
326
326
|
(session.context → SessionMessageInfo[], SessionMessageUser.text)
|
|
327
|
-
- Cline: [@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13)
|
|
328
|
-
(manifest only; behavior undeterminable) and prior binary analysis recorded in
|
|
329
|
-
[mcp-compatibility.md](mcp-compatibility.md)
|
|
330
327
|
|
|
331
|
-
### Proprietary
|
|
328
|
+
### Proprietary host sources
|
|
332
329
|
|
|
333
330
|
- Claude Code: [changelog](https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md)
|
|
334
331
|
(entries 2.0.50, 2.1.30, 2.1.246, 2.1.300 — all ≤ the 2.1.303 lock),
|
|
335
332
|
[MCP docs](https://code.claude.com/docs/en/mcp)
|
|
333
|
+
- Cline: [@cline/cli-linux-x64 0.0.13](https://registry.npmjs.org/@cline/cli-linux-x64/0.0.13)
|
|
334
|
+
(manifest only; behavior undeterminable) and prior binary analysis recorded in
|
|
335
|
+
[mcp-compatibility.md](mcp-compatibility.md)
|
|
336
336
|
- Cursor: [MCP docs](https://cursor.com/docs/mcp) (silent on the question)
|
|
337
337
|
- Devin: [MCP overview](https://docs.devin.ai/cli/extensibility/mcp/overview),
|
|
338
338
|
[changelog](https://docs.devin.ai/cli/changelog) (silent on the question)
|
|
@@ -5,8 +5,8 @@ This note answers one question: through which channel can DeepSeek's per-result
|
|
|
5
5
|
(string|null) — reach the **model** (not the human UI) on each host at its locked version? The
|
|
6
6
|
repo filters on the first two in `src/core/search.ts` (thresholds default to off); at the
|
|
7
7
|
research baseline it dropped all three before returning — the adapters now pass them through
|
|
8
|
-
as a text marker plus a structured mirror (
|
|
9
|
-
`formatQualityMarker`).
|
|
8
|
+
as a text marker plus a structured mirror (the text marker of *What this means for this
|
|
9
|
+
repo*, item 1, is implemented as `formatQualityMarker`).
|
|
10
10
|
Related: [ori-question-sourcing.md](ori-question-sourcing.md) (input side),
|
|
11
11
|
[search-inline-content.md](search-inline-content.md) (content volume).
|
|
12
12
|
|
|
@@ -47,7 +47,8 @@ The server-side validator (`@modelcontextprotocol/server` 2.3.1,
|
|
|
47
47
|
`Output validation error: Tool <name> has an output schema but no structured content was
|
|
48
48
|
provided` (JSON-RPC `InvalidParams`). Text-only results are not allowed for schema'd tools.
|
|
49
49
|
- `structuredContent` is then validated against the schema; a mismatch is rejected the same way.
|
|
50
|
-
- The v2 codec's SEP-2106 §4.3 auto-append (
|
|
50
|
+
- The v2 codec's SEP-2106 §4.3 auto-append (so named in the installed SDK's own source;
|
|
51
|
+
implemented as `appendTextFallbackForNonObject`) adds a
|
|
51
52
|
`JSON.stringify(structuredContent)` TextContent block **only when `structuredContent` is a
|
|
52
53
|
non-object** (primitive/array/null) and no text block exists. Object-shaped
|
|
53
54
|
`structuredContent` gets **no** automatic text mirror — the server must mirror it itself to
|
|
@@ -108,7 +109,8 @@ From the official repo at tag `v2.0.32` (github.com/anomalyco/opencode, where `@
|
|
|
108
109
|
- Every tool result then passes through `ToolOutput.truncate`:
|
|
109
110
|
**`MAX_LINES = 2000`, `MAX_BYTES = 50 KiB`** (full output retained to a `tool-output/`
|
|
110
111
|
file for 7 days) — [`packages/core/src/tool-output.ts`](https://github.com/anomalyco/opencode/blob/v2.0.32/packages/core/src/tool-output.ts).
|
|
111
|
-
These caps
|
|
112
|
+
These caps are also recorded in [control-matrix.md](control-matrix.md#opencode)'s
|
|
113
|
+
OpenCode section.
|
|
112
114
|
|
|
113
115
|
## What this means for this repo
|
|
114
116
|
|
|
@@ -128,9 +130,9 @@ From the official repo at tag `v2.0.32` (github.com/anomalyco/opencode, where `@
|
|
|
128
130
|
The Pi adapter's extension `outputSchema` is safe as-is — Pi re-declares but does not
|
|
129
131
|
validate it, and its `structuredContent` is event data.
|
|
130
132
|
4. **New cap facts for the design margin:** OpenCode plugin results truncate at 2000 lines /
|
|
131
|
-
50 KiB
|
|
132
|
-
|
|
133
|
-
bind at search-result volumes; they matter if [inline content](search-inline-content.md) is
|
|
133
|
+
50 KiB, Pi extension tools bypass the MCP 20 KB truncation, Claude Code caps output at
|
|
134
|
+
25k tokens, and Codex applies its per-tool `output_token_limit` (default unpublished).
|
|
135
|
+
None of these bind at search-result volumes; they matter if [inline content](search-inline-content.md) is
|
|
134
136
|
ever enabled.
|
|
135
137
|
|
|
136
138
|
## Sources
|
|
@@ -159,7 +159,8 @@ the current official docs** (checked 2026-10-21), nor in the November 2025 snaps
|
|
|
159
159
|
page on the Internet Archive; the published truncation mechanism is the token-based
|
|
160
160
|
`max_content_tokens` parameter above, with no published default.
|
|
161
161
|
|
|
162
|
-
SDK layer (official repos, default branch
|
|
162
|
+
SDK layer (official repos, default branch at the 2026-10-21 check — moving heads, unlike
|
|
163
|
+
the pinned firecrawl 4.50.0 above; re-pin to a tag before citing these constants later):
|
|
163
164
|
|
|
164
165
|
| SDK | Default timeout | Default max retries | Source |
|
|
165
166
|
| --- | --- | --- | --- |
|
|
@@ -180,7 +181,8 @@ From the official guide
|
|
|
180
181
|
|
|
181
182
|
No relevance/authority score or filter is exposed on any web-search surface.
|
|
182
183
|
|
|
183
|
-
SDK layer (official repos, default branch
|
|
184
|
+
SDK layer (official repos, default branch at the 2026-10-21 check — same moving-head
|
|
185
|
+
caveat as the Anthropic SDK table above):
|
|
184
186
|
|
|
185
187
|
| SDK | Default timeout | Default max retries | Source |
|
|
186
188
|
| --- | --- | --- | --- |
|
|
@@ -17,6 +17,12 @@ source at the repo's locked tag. Secondhand blogs are excluded. Where no primary
|
|
|
17
17
|
a fact — most importantly the size of `content` — that is stated as such: an undocumented
|
|
18
18
|
size is itself a decision-relevant fact.
|
|
19
19
|
|
|
20
|
+
**Short answer:** DeepSeek documents **no size bound** for `need_content` content and no
|
|
21
|
+
per-result budget knob; of the eight architectures surveyed below, only Jina ships
|
|
22
|
+
content-bearing search as the default; and the binding host cap is Pi's 20 KB MCP-path
|
|
23
|
+
truncation — the volume math below puts a safe per-result budget in the ~1k–2k char
|
|
24
|
+
range.
|
|
25
|
+
|
|
20
26
|
## What `need_content=true` provably returns
|
|
21
27
|
|
|
22
28
|
From the official Search & Fetch guide, the parameter and its response field, verbatim:
|
|
@@ -101,15 +107,36 @@ search response without setting any parameter.
|
|
|
101
107
|
|
|
102
108
|
| Vendor | Content inline by default? | Opt-in mechanism | Per-result content budget knob | Separate fetch tool? |
|
|
103
109
|
| --- | --- | --- | --- | --- |
|
|
104
|
-
| **Tavily** | Partial —
|
|
110
|
+
| **Tavily** | Partial — per-result `content` of relevance chunks by default | Full body via `include_raw_content` (default **false**) | Chunk count only (`chunks_per_source` 1–3); no knob limits `raw_content` size in `/search` | Yes — `/extract` (up to 20 URLs) |
|
|
105
111
|
| **Exa** | No — `contents.text` defaults **false** | `contents.text: true` in the **same** `POST /search` body | **`maxCharacters`** (1–1,000,000, no published default) plus `verbosity` `compact`/`standard`/`full` | Yes — `/contents` endpoint by result ID/URL |
|
|
106
112
|
| **You.com** | No — “Omit `extraction` for a plain search that returns snippets only” | `extraction.full_page` (“crawls each result and returns the page content”) | **None for `full_page`** (only `crawl_timeout`); the `highlights` mode has a response-wide `total_max_tokens` budget of **8192** (1,024–8,192) | No dedicated fetch endpoint documented on `/v1/search` |
|
|
107
|
-
| **Perplexity** | No — Search API results are `title`/`url`/`snippet`/`date` only | Token budgets
|
|
108
|
-
| **Anthropic** | Not caller-visible —
|
|
109
|
-
| **Firecrawl** | No — search returns `title`/`description`/`url` (+ `highlights`, default true) | `scrapeOptions` (default `{}`):
|
|
110
|
-
| **Jina** (`s.jina.ai`) | **Yes — the only default-content architecture surveyed
|
|
113
|
+
| **Perplexity** | No — Search API results are `title`/`url`/`snippet`/`date` only | Token budgets on both the Search API and the Agent API `web_search` tool (detail below) | **`max_tokens_per_page`** (min 1, max 1,000,000, no default) | Yes — Agent API “Fetch URL Content” tool (“Fetch full content from known URLs”) |
|
|
114
|
+
| **Anthropic** | Not caller-visible — content is model-only (`encrypted_content`) | None — `response_inclusion` is not a content-size knob | **None** (no caller-set budget) | Yes — `web_fetch` with token-based `max_content_tokens` (no published default) |
|
|
115
|
+
| **Firecrawl** | No — search returns `title`/`description`/`url` (+ `highlights`, default true) | `scrapeOptions` (default `{}`): “Include `scrapeOptions` with `formats: [{“type”: “markdown”}]` to get complete markdown content for each search result” | **None** for content size (`onlyMainContent` default true trims boilerplate, not length) | Collapsed into search via `scrapeOptions`; standalone `/scrape` endpoint also exists |
|
|
116
|
+
| **Jina** (`s.jina.ai`) | **Yes — the only default-content architecture surveyed**; result count is fixed at 5, not a parameter | None needed (content is the product) | **`x-token-budget`** header, no default (parsed only when sent) — a global budget, not per-result | Same product (`r.jina.ai`) serves plain fetch |
|
|
111
117
|
| **DeepSeek (subject of this note)** | No — `need_content` defaults **false** | `need_content: true` | **None documented** (see above) | Yes — Fetch API, Markdown, no documented size cap |
|
|
112
118
|
|
|
119
|
+
Detail for the trimmed rows above:
|
|
120
|
+
|
|
121
|
+
- **Tavily:** the default per-result `content` carries **relevance chunks** —
|
|
122
|
+
`chunks_per_source` **3** chunks of ~500 chars (~1,500 chars/result); `include_raw_content`
|
|
123
|
+
accepts `markdown`/`text`/`true`; the chunk sizes apply to `content`, **not**
|
|
124
|
+
`raw_content`; `/extract` takes up to 20 URLs with `extract_depth` basic/advanced, no
|
|
125
|
+
per-URL cap, ~800-char chunks only when `query` is given.
|
|
126
|
+
- **Perplexity:** `max_tokens` (“Maximum total webpage content tokens to return across
|
|
127
|
+
all search results”) and **`max_tokens_per_page`** (“Maximum webpage content tokens to
|
|
128
|
+
extract from each result page”), **no default published** for either (“the default
|
|
129
|
+
budgets apply”); on the Search API the extracted text surfaces through `snippet`.
|
|
130
|
+
- **Anthropic:** `web_search` results carry `url`/`title`/`page_age` plus
|
|
131
|
+
`encrypted_content` (“Encrypted content that you must pass back in multi-turn
|
|
132
|
+
conversations”) — the model reads content in its context, the caller cannot.
|
|
133
|
+
`response_inclusion` (`”full”` default / `”excluded”`) only controls whether consumed
|
|
134
|
+
search blocks are echoed in the response; dynamic filtering on `web_search_20260209+`
|
|
135
|
+
has Claude write code to filter results before they reach context, but no caller-set
|
|
136
|
+
budget exists.
|
|
137
|
+
- **Jina:** “Reader searches the web, fetches the top 5 results, visits each URL, and
|
|
138
|
+
applies `r.jina.ai` to it.”
|
|
139
|
+
|
|
113
140
|
The split, stated plainly: of the eight architectures surveyed, exactly one (Jina
|
|
114
141
|
`s.jina.ai`) ships content-bearing search as the default, and it pins the result count at 5.
|
|
115
142
|
Every other vendor makes inline content opt-in (Tavily `raw_content`, Exa `contents.text`,
|
|
@@ -119,6 +146,17 @@ exist they are per-result character caps (Exa `maxCharacters`) or per-result tok
|
|
|
119
146
|
(Perplexity `max_tokens_per_page`); Tavily and You.com ship full-content modes with **no**
|
|
120
147
|
per-result size knob at all — the two closest analogues to DeepSeek's `need_content`.
|
|
121
148
|
|
|
149
|
+
One further architecture reading (added 2026-10-24, same sources): **no surveyed vendor
|
|
150
|
+
narrows another parameter's value range when inline content is requested.** Every budget
|
|
151
|
+
knob in the table is static-range, conditional-effect — Exa's `maxCharacters` and
|
|
152
|
+
Perplexity's `max_tokens_per_page` keep their 1–1,000,000 ranges whether or not
|
|
153
|
+
`contents.text`/extraction is engaged, Tavily's `chunks_per_source` and Anthropic's
|
|
154
|
+
`max_content_tokens` are likewise unconditional, and none shrinks `count`/`numResults`
|
|
155
|
+
or any other parameter's domain when content flows (Jina's fixed 5 is its architecture,
|
|
156
|
+
not a conditional rule). The only conditional behavior anywhere in the table is which
|
|
157
|
+
budget *applies*; a content flag that reshaped other parameters' allowed values appears
|
|
158
|
+
in none of the eight.
|
|
159
|
+
|
|
122
160
|
Sources: [Tavily /search](https://docs.tavily.com/documentation/api-reference/endpoint/search) ·
|
|
123
161
|
[Tavily /extract](https://docs.tavily.com/documentation/api-reference/endpoint/extract) ·
|
|
124
162
|
[Exa /search](https://exa.ai/docs/reference/search) ·
|
|
@@ -173,8 +211,9 @@ With `count=10` (the repo's default, and DeepSeek's) and `need_content: true`:
|
|
|
173
211
|
[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)).
|
|
174
212
|
[control-matrix.md](control-matrix.md) records Codex's 300 s MCP tool timeout — latency,
|
|
175
213
|
not size, is that host's documented ceiling.
|
|
176
|
-
- **The binding constraint is Pi's 20 KB**, roughly
|
|
177
|
-
|
|
214
|
+
- **The binding constraint is Pi's 20 KB**, roughly 5× tighter than Claude Code's
|
|
215
|
+
25k-token default (≈100k English chars by the conversions above): a per-result
|
|
216
|
+
content budget in the **~1k–2k char**
|
|
178
217
|
range keeps count=10 under every published cap; reusing the repo's fetch budget (16,000
|
|
179
218
|
chars) *per search result* would reach 160k chars per response — 8× Pi's cap and ~40k
|
|
180
219
|
tokens on Claude Code.
|
|
@@ -195,8 +234,9 @@ maintainer decides.
|
|
|
195
234
|
- **Today's pipeline would silently drop the content.** `DeepSeekSearchResult`
|
|
196
235
|
([types.ts](../../src/core/types.ts)) has no `content` field, and `processResults`
|
|
197
236
|
([search.ts](../../src/core/search.ts)) maps `SearchResult.content` from
|
|
198
|
-
`summary ?? snippet` only — a `need_content` response's page bodies would be received
|
|
199
|
-
discarded
|
|
237
|
+
`summary ?? snippet` only — a `need_content` response's page bodies would be received
|
|
238
|
+
and discarded: they cost nothing extra (billing is per request), but they are also
|
|
239
|
+
never rendered.
|
|
200
240
|
- **The only per-result clamp in the search path is `maxSummaryChars` (1,200)** — a
|
|
201
241
|
summary/snippet budget. `fetchMaxChars` (16,000) and `formatFetchedContent`'s
|
|
202
242
|
truncation-marker logic apply exclusively to the fetch tool path. There is **no per-result
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "deepseek-web-retrieval",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"description": "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
package/src/core/client.ts
CHANGED
|
@@ -92,6 +92,12 @@ export interface SearchRequest extends RequestOptions {
|
|
|
92
92
|
* ori_question to sharpen intent; adapters fill it from tool input.
|
|
93
93
|
*/
|
|
94
94
|
readonly oriQuestion?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Per-call only. The tool input's need_content flag: true asks for each
|
|
97
|
+
* result's page body, sent as the API's need_content and timed against
|
|
98
|
+
* contentTimeoutMs instead of timeoutMs.
|
|
99
|
+
*/
|
|
100
|
+
readonly needContent?: boolean;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
/**
|
|
@@ -130,14 +136,15 @@ export async function searchWeb(query: string, options: SearchOptions, request:
|
|
|
130
136
|
const composed = composeSearchQuery(trimmed, options);
|
|
131
137
|
const builtQuery = composed.query;
|
|
132
138
|
|
|
133
|
-
const
|
|
139
|
+
const needContent = request.needContent === true;
|
|
140
|
+
const timeout = effectiveTimeout(needContent ? options.contentTimeoutMs : options.timeoutMs, request.timeoutMs);
|
|
134
141
|
const oriQuestion = oriQuestionValue(request.oriQuestion);
|
|
135
142
|
const payload = await postJson<SearchResponse>(
|
|
136
143
|
apiUrl(request.baseUrl, SEARCH_ENDPOINT_PATH),
|
|
137
144
|
{
|
|
138
145
|
query: builtQuery,
|
|
139
146
|
count: options.count,
|
|
140
|
-
need_content:
|
|
147
|
+
need_content: needContent,
|
|
141
148
|
...(oriQuestion === undefined ? {} : { ori_question: oriQuestion }),
|
|
142
149
|
...(options.localization === undefined ? {} : { localization: options.localization }),
|
|
143
150
|
},
|
|
@@ -19,6 +19,8 @@ const VOCABULARY = [
|
|
|
19
19
|
{ key: "language", read: (o: ResolvedOptions) => o.search.language },
|
|
20
20
|
{ key: "searchLocalization", read: (o: ResolvedOptions) => o.search.localization },
|
|
21
21
|
{ key: "maxSummaryChars", read: (o: ResolvedOptions) => o.search.maxSummaryChars },
|
|
22
|
+
{ key: "searchContentMaxChars", read: (o: ResolvedOptions) => o.search.contentMaxChars },
|
|
23
|
+
{ key: "searchContentTimeoutMs", read: (o: ResolvedOptions) => o.search.contentTimeoutMs },
|
|
22
24
|
{ key: "searchTimeoutMs", read: (o: ResolvedOptions) => o.search.timeoutMs },
|
|
23
25
|
{ key: "fetchMaxChars", read: (o: ResolvedOptions) => o.fetch.maxChars },
|
|
24
26
|
{ key: "fetchAllowedDomains", read: (o: ResolvedOptions) => o.fetch.allowedDomains },
|
package/src/core/constants.ts
CHANGED
|
@@ -27,6 +27,19 @@ export const DEFAULT_MAX_RESULTS = 10;
|
|
|
27
27
|
export const DEFAULT_MIN_RERANK = 0;
|
|
28
28
|
export const DEFAULT_MIN_AUTHORITY = 0;
|
|
29
29
|
export const DEFAULT_MAX_SUMMARY_CHARS = 1200;
|
|
30
|
+
/**
|
|
31
|
+
* The per-result budget a need_content page body is clamped to. 1,500 keeps a
|
|
32
|
+
* default count=10 response (bodies plus metadata ≈ 16 KB) under the tightest
|
|
33
|
+
* host output cap, Pi's 20 KB MCP-path truncation — see
|
|
34
|
+
* docs/research/search-inline-content.md.
|
|
35
|
+
*/
|
|
36
|
+
export const DEFAULT_SEARCH_CONTENT_MAX_CHARS = 1500;
|
|
37
|
+
/**
|
|
38
|
+
* The timeout need_content calls use in place of searchTimeoutMs: DeepSeek
|
|
39
|
+
* fetches every result page server-side before responding, which the 30 s
|
|
40
|
+
* search default does not budget for.
|
|
41
|
+
*/
|
|
42
|
+
export const DEFAULT_SEARCH_CONTENT_TIMEOUT_MS = 60000;
|
|
30
43
|
|
|
31
44
|
export const DEFAULT_FETCH_MAX_CHARS = 16000;
|
|
32
45
|
/** The ceiling for a per-call fetch budget, matching the configured knob's range. */
|
package/src/core/descriptions.ts
CHANGED
|
@@ -30,7 +30,8 @@ export const SEARCH_TOOL_DESCRIPTION =
|
|
|
30
30
|
`${SEARCH_RANKED_FILTERED} ` +
|
|
31
31
|
`${SEARCH_EMPTY_IS_NORMAL} ` +
|
|
32
32
|
`${SEARCH_QUERY_OPERATORS} ` +
|
|
33
|
-
"To read
|
|
33
|
+
"To read one full page in depth, pass a result URL to the deepseek_fetch tool; to carry each result's " +
|
|
34
|
+
"page body in this response, set need_content to true.";
|
|
34
35
|
|
|
35
36
|
export const FETCH_TOOL_DESCRIPTION =
|
|
36
37
|
"Fetch one web page as clean Markdown through DeepSeek's Fetch API. " +
|
|
@@ -54,3 +55,8 @@ export const FETCH_MAX_CHARS_PARAMETER =
|
|
|
54
55
|
export const SEARCH_COUNT_PARAMETER =
|
|
55
56
|
"Optional number of results for this call, 1-50. Overrides the configured count and result cap for this call; " +
|
|
56
57
|
"billing is per request, not per result.";
|
|
58
|
+
|
|
59
|
+
export const SEARCH_NEED_CONTENT_PARAMETER =
|
|
60
|
+
"Optional; set true to attach each result's page body as Markdown — an excerpt clamped to a configured per-result " +
|
|
61
|
+
"budget, not a full page — in place of its short summary. Slower per call (DeepSeek fetches the result pages) and " +
|
|
62
|
+
"priced the same per request; for a full page, call deepseek_fetch.";
|
package/src/core/env.ts
CHANGED
|
@@ -14,6 +14,8 @@ const NUMBER_ENV = {
|
|
|
14
14
|
minAuthority: "DEEPSEEK_SEARCH_MIN_AUTHORITY",
|
|
15
15
|
maxSummaryChars: "DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS",
|
|
16
16
|
searchTimeoutMs: "DEEPSEEK_SEARCH_TIMEOUT_MS",
|
|
17
|
+
searchContentMaxChars: "DEEPSEEK_SEARCH_CONTENT_MAX_CHARS",
|
|
18
|
+
searchContentTimeoutMs: "DEEPSEEK_SEARCH_CONTENT_TIMEOUT_MS",
|
|
17
19
|
fetchMaxChars: "DEEPSEEK_FETCH_MAX_CHARS",
|
|
18
20
|
fetchTimeoutMs: "DEEPSEEK_FETCH_TIMEOUT_MS",
|
|
19
21
|
maxRetries: "DEEPSEEK_MAX_RETRIES",
|
|
@@ -97,6 +99,8 @@ export function envOptionsReport(env: Env = process.env): EnvOptionsReport {
|
|
|
97
99
|
putText(options, "searchLocalization", env);
|
|
98
100
|
putNumber(options, "maxSummaryChars", env, warnings);
|
|
99
101
|
putNumber(options, "searchTimeoutMs", env, warnings);
|
|
102
|
+
putNumber(options, "searchContentMaxChars", env, warnings);
|
|
103
|
+
putNumber(options, "searchContentTimeoutMs", env, warnings);
|
|
100
104
|
|
|
101
105
|
putNumber(options, "fetchMaxChars", env, warnings);
|
|
102
106
|
putDomains(options, "fetchAllowedDomains", env, warnings);
|
package/src/core/format.ts
CHANGED
|
@@ -118,7 +118,16 @@ export function formatSearchResults(results: readonly SearchResult[], context?:
|
|
|
118
118
|
const marker = formatQualityMarker(result);
|
|
119
119
|
const lines = [`${index + 1}. [${escapeBrackets(title)}](${result.url})${marker ? ` ${marker}` : ""}`];
|
|
120
120
|
if (result.content) {
|
|
121
|
-
|
|
121
|
+
// Page bodies keep their Markdown line structure, indented into the
|
|
122
|
+
// list item; digests compact to one line, as summaries always did.
|
|
123
|
+
lines.push(
|
|
124
|
+
result.contentKind === "page"
|
|
125
|
+
? result.content
|
|
126
|
+
.split("\n")
|
|
127
|
+
.map((line) => ` ${line}`)
|
|
128
|
+
.join("\n")
|
|
129
|
+
: ` ${result.content.replaceAll("\n", " ")}`,
|
|
130
|
+
);
|
|
122
131
|
}
|
|
123
132
|
if (result.published !== undefined) {
|
|
124
133
|
lines.push(` Published: ${formatPublished(result.published)}`);
|
package/src/core/options.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
DEFAULT_MIN_AUTHORITY,
|
|
10
10
|
DEFAULT_MIN_RERANK,
|
|
11
11
|
DEFAULT_RETRY_BASE_DELAY_MS,
|
|
12
|
+
DEFAULT_SEARCH_CONTENT_MAX_CHARS,
|
|
13
|
+
DEFAULT_SEARCH_CONTENT_TIMEOUT_MS,
|
|
12
14
|
DEFAULT_SEARCH_COUNT,
|
|
13
15
|
DEFAULT_TIMEOUT_MS,
|
|
14
16
|
MAX_RETRIES,
|
|
@@ -67,6 +69,13 @@ export function resolveSearchOptions(input?: PluginOptions): SearchOptions {
|
|
|
67
69
|
language: optionalText(input?.language),
|
|
68
70
|
localization: localizationValue(input?.searchLocalization),
|
|
69
71
|
maxSummaryChars: clampInt(input?.maxSummaryChars, DEFAULT_MAX_SUMMARY_CHARS, 0, 1_000_000),
|
|
72
|
+
contentMaxChars: clampInt(input?.searchContentMaxChars, DEFAULT_SEARCH_CONTENT_MAX_CHARS, 0, 1_000_000),
|
|
73
|
+
contentTimeoutMs: clampInt(
|
|
74
|
+
input?.searchContentTimeoutMs,
|
|
75
|
+
DEFAULT_SEARCH_CONTENT_TIMEOUT_MS,
|
|
76
|
+
MIN_TIMEOUT_MS,
|
|
77
|
+
MAX_TIMEOUT_MS,
|
|
78
|
+
),
|
|
70
79
|
timeoutMs: clampInt(input?.searchTimeoutMs, DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS),
|
|
71
80
|
enabled: input?.searchEnabled !== false,
|
|
72
81
|
};
|
package/src/core/search.ts
CHANGED
|
@@ -142,12 +142,20 @@ export function processResults(raw: readonly DeepSeekSearchResult[], options: Se
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
seen.add(url);
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
// A need_content page body outranks the digest — the summary is only ever
|
|
146
|
+
// a proxy for the body it compresses. A zero budget drops the body so the
|
|
147
|
+
// digest renders instead.
|
|
148
|
+
const body = optionalText(item.content);
|
|
149
|
+
const digest = optionalText(item.summary) ?? optionalText(item.snippet) ?? "";
|
|
150
|
+
const fromPage = body !== undefined && options.contentMaxChars > 0;
|
|
151
|
+
const rawContent: string = fromPage && body !== undefined ? body : digest;
|
|
152
|
+
const contentBudget = fromPage ? options.contentMaxChars : options.maxSummaryChars;
|
|
153
|
+
const cutContent = truncate(rawContent, contentBudget);
|
|
147
154
|
// The ellipsis marks a summary the cap actually shortened; official
|
|
148
155
|
// summaries are typically 600-800 characters, so the 1,200 default
|
|
149
156
|
// rarely triggers it.
|
|
150
|
-
const content = cutContent && charCount(rawContent) >
|
|
157
|
+
const content = cutContent && charCount(rawContent) > contentBudget ? `${cutContent}…` : cutContent;
|
|
158
|
+
const contentKind = fromPage ? "page" : "digest";
|
|
151
159
|
const published = parsePublished(item.date);
|
|
152
160
|
const title = optionalText(item.title);
|
|
153
161
|
const rerankScore = typeof rerank === "number" && Number.isFinite(rerank) ? rerank : undefined;
|
|
@@ -156,7 +164,7 @@ export function processResults(raw: readonly DeepSeekSearchResult[], options: Se
|
|
|
156
164
|
results.push({
|
|
157
165
|
url,
|
|
158
166
|
...(title ? { title } : {}),
|
|
159
|
-
...(content ? { content } : {}),
|
|
167
|
+
...(content ? { content, contentKind } : {}),
|
|
160
168
|
...(published !== undefined ? { published } : {}),
|
|
161
169
|
...(rerankScore !== undefined ? { rerankScore } : {}),
|
|
162
170
|
...(authorityScore !== undefined ? { authority: authorityScore } : {}),
|
package/src/core/types.ts
CHANGED
|
@@ -20,6 +20,14 @@ export interface SearchOptions {
|
|
|
20
20
|
*/
|
|
21
21
|
readonly localization?: string;
|
|
22
22
|
readonly maxSummaryChars: number;
|
|
23
|
+
/**
|
|
24
|
+
* Deployment-level only — never a per-call tool parameter. The budget a
|
|
25
|
+
* need_content page body is clamped to per result; the per-call boolean is
|
|
26
|
+
* `SearchRequest.needContent`.
|
|
27
|
+
*/
|
|
28
|
+
readonly contentMaxChars: number;
|
|
29
|
+
/** Deployment-level only; the timeout a need_content call uses instead of `timeoutMs`. */
|
|
30
|
+
readonly contentTimeoutMs: number;
|
|
23
31
|
readonly timeoutMs: number;
|
|
24
32
|
readonly enabled: boolean;
|
|
25
33
|
}
|
|
@@ -63,6 +71,8 @@ export interface DeepSeekSearchResult {
|
|
|
63
71
|
readonly url?: string | null;
|
|
64
72
|
readonly snippet?: string | null;
|
|
65
73
|
readonly summary?: string | null;
|
|
74
|
+
/** The need_content page body; an empty string when need_content is false or the content is unavailable. */
|
|
75
|
+
readonly content?: string | null;
|
|
66
76
|
readonly date?: string | null;
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -71,6 +81,8 @@ export interface SearchResult {
|
|
|
71
81
|
readonly url: string;
|
|
72
82
|
readonly title?: string;
|
|
73
83
|
readonly content?: string;
|
|
84
|
+
/** Which wire field `content` came from: a need_content page body or the summary/snippet digest. */
|
|
85
|
+
readonly contentKind?: "page" | "digest";
|
|
74
86
|
readonly published?: number;
|
|
75
87
|
readonly rerankScore?: number;
|
|
76
88
|
/** Faithful to the wire: -1 means unknown authority. */
|
package/src/mcp/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
FETCH_TOOL_DESCRIPTION,
|
|
12
12
|
FETCH_URL_PARAMETER,
|
|
13
13
|
SEARCH_COUNT_PARAMETER,
|
|
14
|
+
SEARCH_NEED_CONTENT_PARAMETER,
|
|
14
15
|
SEARCH_ORI_QUESTION_PARAMETER,
|
|
15
16
|
SEARCH_QUERY_PARAMETER,
|
|
16
17
|
SEARCH_TOOL_DESCRIPTION,
|
|
@@ -27,6 +28,7 @@ const SEARCH_INPUT = z.object({
|
|
|
27
28
|
query: z.string().min(1).describe(SEARCH_QUERY_PARAMETER),
|
|
28
29
|
original_question: z.string().optional().describe(SEARCH_ORI_QUESTION_PARAMETER),
|
|
29
30
|
count: z.number().int().min(1).max(MAX_SEARCH_COUNT).optional().describe(SEARCH_COUNT_PARAMETER),
|
|
31
|
+
need_content: z.boolean().optional().describe(SEARCH_NEED_CONTENT_PARAMETER),
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
const FETCH_INPUT = z.object({
|
|
@@ -54,12 +56,13 @@ export function createDeepSeekServer(
|
|
|
54
56
|
inputSchema: SEARCH_INPUT,
|
|
55
57
|
annotations: { readOnlyHint: true },
|
|
56
58
|
},
|
|
57
|
-
async ({ query, original_question, count }, ctx) => {
|
|
59
|
+
async ({ query, original_question, count, need_content }, ctx) => {
|
|
58
60
|
try {
|
|
59
61
|
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal);
|
|
60
62
|
const outcome = await client.searchWeb(query, withPerCallCount(config.search, count), {
|
|
61
63
|
...request,
|
|
62
64
|
oriQuestion: original_question,
|
|
65
|
+
needContent: need_content === true,
|
|
63
66
|
});
|
|
64
67
|
return callResult(server, ctx, formatSearchResults(outcome.results, outcome), {
|
|
65
68
|
results: outcome.results.map(toStructuredResult),
|