Web, LSP, MCP, and Skills
Stable tool names, replaceable providers, and progressive capability disclosure
Conclusion: Four Extension Surfaces, Four Different Contracts
DeepSeek Harness does not flatten every external capability into one generic plugin API. Web keeps stable product-owned tool names over replaceable providers; LSP keeps one closed operation vocabulary over extension-routed language servers; MCP imports provider-owned names and schemas dynamically; Skills publish a compact routing catalog and reveal trusted instruction bodies only on demand. The common philosophy is not “everything is a tool,” but “put a stable, typed seam at the narrowest layer that can own the semantics.”
This separation is unusually useful for analysis because each surface answers a different question: who owns the public name, who selects the provider, how much data enters every model request, and where untrusted or privileged content crosses into execution. Treating the four as equivalent would hide their most important safety and cache properties.
1. Capability Topology: Stability Moves to a Different Layer in Each Surface
| Surface | Stable model-facing unit | Replaceable/dynamic unit | Selection point | Default exposure |
|---|---|---|---|---|
| Web | web_search / web_fetch | Search and fetch providers | Execution-time configured ID or exactly one usable provider | Search only |
| LSP | One lsp tool with four operations | Extension-to-language-server routes | Final file extension | Optional examples, not the shipped base |
| MCP | mcp__server__raw per discovered tool | The entire remote tool generation | Server discovery and re-sync | Default-off examples |
| Skill | One skill loader plus a summary catalog | Winning provider candidate and loaded body | Scope layer, then rank within the layer | Shipped in coding presets |
The architecture spends stability where it buys the most. Web and LSP can define a portable product vocabulary, so their schemas stay fixed. MCP exists precisely to accept an external server's vocabulary, so it stabilizes identity and generation swaps instead. Skills stabilize discovery metadata while postponing the expensive and authoritative body.
2. Web: Provider Choice Is Deterministic and Re-evaluated at Every Call
web_search(query)
→ ctx.web.search(request)
├─ configured id exists + available → use it
├─ configured id missing/unavailable → typed failure
├─ no configured id + one usable → auto-select
├─ no configured id + many usable → ambiguous failure
└─ no usable provider → unavailable failure
→ cap sources to maxResults
Search and fetch have separate registries, duplicate IDs fail, and provider availability is checked at execution time. Environment overrides feed the same two configuration fields rather than forming a hidden priority chain. If more than one unconfigured provider is usable, execution fails and asks for an explicit choice. CAP-WEB-SELECTION
The result vocabulary is intentionally small: search returns optional answer text plus citeable sources; fetch returns final URL, status, a closed HTML/text body union, and truncation. A non-2xx response is resource state rather than a transport exception. CAP-WEB-TYPES
3. Stable Tool Visibility Is Decoupled from Provider Health
dsh-tool-web owns schemas, prompt guidance, result caps, and presentation. Its search/fetch booleans decide whether the tools register; concrete providers do not. Consequently, an enabled tool remains in the model catalog even if its configured provider is temporarily absent or unusable, and fails with structured metadata only when called. CAP-WEB-TOOL
This protects tool-schema and KV-prefix stability across credential outages and provider HMR. It also means “the model can see this tool” is not a readiness signal. Operations need a separate provider-health view; otherwise the first user-visible probe is a failed tool call.
4. Shipped Web Is Search-Only Because Fetch Has an Explicit SSRF Hole
The base composition mounts ctx.web, pins search to deepseek-official, mounts the DeepSeek provider, exposes web_search with a 60-second tool budget, and sets fetch:false. It mounts no fetch provider. The configuration comment names the reason: the local fetcher defers SSRF/private-network protection while the model chooses the target. CAP-WEB-SHIPPED
The optional HTTP fetcher still enforces useful transport hygiene: HTTP(S) only, no embedded credentials, bounded URL size, same-origin redirects, supported text content types, declared charset decoding, one timeout, byte and character caps, and no browser cookies or ambient credentials. It explicitly warns that private-network blocking is absent. CAP-WEB-FETCH-POLICYCAP-WEB-FETCH-TRANSPORTCAP-WEB-FETCH-CAPS
5. DeepSeek Search Is an Auxiliary Model Call, Not Reuse of the Main LLM Adapter
web_search query
→ resolve one settings + credential snapshot
→ append secret-free web/deepseek-search-llm-request
→ POST Anthropic-compatible /messages
model: deepseek-v4-flash
tool: web_search_20250305
→ require web_search_tool_result blocks
→ join cited_text by URL, deduplicate, normalize
The provider uses its own native fetch client and Anthropic-compatible request, not ctx.llm. It snapshots endpoint, key source, model, token cap, and search-use cap once per operation; records the exact secret-free auxiliary request before dispatch; rejects redirects; and sends both supported API-key header shapes. CAP-WEB-DEEPSEEK-WIRECAP-WEB-DEEPSEEK-REQUESTCAP-WEB-DEEPSEEK-SETTINGS
A response without native search-result blocks is an error. Result items are deduplicated by URL, while snippets come from separate citation text blocks. Final maxResults truncation remains owned by the Web seam, not this adapter. CAP-WEB-DEEPSEEK-MAP
One visible web_search is an additional metered model request with its own failure and latency path. It is observable in the initiating Session, but it is not routed through the main adapter's retry, model selection, or streaming implementation.
6. LSP Deliberately Refuses to Become Generic JSON-RPC
The LSP service exports exactly goToDefinition, findReferences, goToImplementation, and hover. A provider atomically reserves one ID and an exclusive set of normalized file extensions. The registry validates the whole mapping before mutation; conflicts publish nothing, and disposal releases every route together. Query selection uses the file's final lowercase extension—foo.d.ts routes as .ts—and exposes no raw JSON-RPC escape hatch. CAP-LSP-REGISTRY
The seam can normalize positions, results, caps, errors, cancellation, and read-only retry because it understands every operation. Arbitrary JSON-RPC would maximize protocol coverage but move compatibility and safety back into prompts and provider-specific behavior.
7. The lsp Tool Is a Precision Aid, Not a General Code Browser
One read-only tool accepts a closed operation enum, file path, and one-based UTF-16 line/character. It requires the calling Session's workspace with no fallback, converts into the normalized seam, caps location count and rendered characters, and carries a 60-second timeout. Its prompt explicitly recommends ordinary search/read first and LSP only when textual navigation is ambiguous or a change needs precise symbol relationships. CAP-LSP-TOOL
This is an important restraint: LSP does not replace source reads. It answers semantic navigation questions over the current file text, then returns bounded locations or hover content that the model can verify by reading.
8. The Stdio LSP Backend Shares the Filesystem and Process Execution World
query(file, workspace)
→ ctx.fs canonicalizes workspace and proves containment
→ ctx.fs streams one complete byte-bounded source
→ per-canonical-workspace queue
→ lazy one-server process via ctx.subprocess
→ didOpen → query → didClose
→ on transport failure: replace once and replay read-only query
The plugin validates every server config and resolves every executable before registering any provider. Registry conflicts roll back earlier registrations. Processes start lazily on the first matching query, routes are removed before teardown, and sibling process disposals settle before errors propagate. CAP-LSP-STDIO-LOAD
Each canonical workspace owns one pooled server and one queue spanning source read, document open, query, and close. Source bytes come through ctx.fs; servers launch through ctx.subprocess. A transport failure may replace the selected process and retry once because the operation set is read-only. CAP-LSP-STDIO-POOLCAP-LSP-HOST-FS
9. LSP Is Implemented and Demonstrated as an Optional Capability
The repository demonstrates LSP in an optional Headless/E2B composition, where the operator supplies a concrete server command and extension map. The example pins typescript-language-server and exposes the tool only beside the service and stdio provider. CAP-LSP-OPTIONAL
Package existence is not product availability. A useful UI or capability inventory must distinguish “code exists,” “provider is configured,” “tool is visible to this Agent,” and “the provider is currently ready.”
10. MCP Imports a Server-Owned Tool Generation into the Native Registry
One MCP client instance owns one stdio or Streamable HTTP server. A stable local serverName reserves a namespace, and each discovered raw name becomes mcp__<serverName>__<rawName>. Invalid characters or overlength names receive a deterministic identity-derived hash suffix, so reconnection order never renames a tool and normalization collisions become extremely unlikely. CAP-MCP-CONFIGCAP-MCP-NAMING
Activation waits for the first connection and complete tool discovery. failOnStartupError:true rejects and rolls back the plugin; otherwise activation may complete with no tools while the supervisor retries. Duplicate live server namespaces fail before the connection begins. CAP-MCP-STARTUP
11. Tool Re-sync Is Transactional in Fetch, but a Registration Conflict Drops the Old Generation
Fetch all pages
Build the entire next definition map without touching the registry.
Preserve on fetch failure
A network, schema, or duplicate-list failure leaves the previous generation live.
Swap
Dispose the previous generation, then register every next definition.
Fail closed on collision
If a foreign tool squats on the namespace, roll back the partial new generation to zero.
The fetch phase is last-known-good, while the registration phase is all-new-or-none—not rollback-to-old. Every initial, notification, and reconnect sync is serialized across client generations so swaps cannot interleave. CAP-MCP-SYNCCAP-MCP-RECONNECT
12. MCP Recovery Retains the Last Good Catalog Until the Attempt Budget Is Exhausted
A transport close starts bounded exponential backoff. During the outage the last good registrations remain visible, but calls fail against the unavailable generation. A sufficiently stable connection resets the outage budget; a crash loop eventually unregisters every tool and stops until reload/restart. A generation that cannot prove closure within the bounded barrier stops reconnection rather than overlapping two stdio server processes. Disposal cancels timers, closes the current client, drains the connection attempt and sync chain, then unregisters the final generation. CAP-MCP-RECONNECTCAP-MCP-GENERATION
Like Web, an MCP schema can remain visible while execution is temporarily impossible. Unlike Web's stable schemas, MCP recovery may replace the entire model-facing catalog when the server changes its tool list.
13. Canonical MCP Results Are Rich; Native Model History Is Deliberately Lossy
| Data | Execution-local canonical value | Native model rendering |
|---|---|---|
| Text blocks | Full JSON blocks | Text joined with newlines |
| Image/audio/resource | Full JSON blocks | Short “content discarded” placeholders |
structuredContent | Preserved; advertised supported schema enforced | Not separately rendered into rich content |
isError:true | Converted to a thrown execution failure | ToolRuntime's normalized error result |
| Task-required tool | Rejected | Unsupported execution error |
The bridge validates network values defensively, preserves complete JSON for programmatic/Code Mode callers, and falls back to unconstrained JSON when an advertised output schema uses unsupported vocabulary. Native context intentionally discards non-text payloads. CAP-MCP-RESULT
14. MCP Stdio Does Not Share the Harness Subprocess/Sandbox Spawn Path
The stdio transport reuses the subprocess package's environment-scrubbing definition, then merges explicit config environment. But the MCP SDK owns the actual child spawn; it does not call ctx.subprocess. Therefore it does not automatically inherit the configured execution world, sandbox runner, remote process provider, process-tree contract, or output collector used by Shell and LSP. Streamable HTTP similarly delegates transport to the SDK and accepts configured headers. CAP-MCP-TRANSPORT
This is the most important cross-surface inconsistency in the chapter. LSP deliberately co-locates file reads and server processes behind shared seams; MCP stdio only shares credential-name scrubbing. A deployment that assumes “all child processes are sandboxed” is wrong unless it independently confines the Host or MCP command.
15. MCP Is Broadly Interoperable but Narrower Than the Protocol
The bridge consumes Tools only; MCP Resources, Prompts, and server instructions have no consumer. Connection/discovery timeout is inherited from the SDK rather than exposed as plugin configuration. Streamable HTTP request failures do not necessarily trigger the stdio-style supervisor path. Native non-text projection is lossy, task-required execution is unsupported, and unsupported output-schema vocabulary loses validation. CAP-MCP-LIMITS
No MCP server ships in the default composition. The repository's memory integrations are explicitly default-off reference overlays; the operator installs and configures the third-party server and owns its storage, identity, model, embedding, and licensing behavior. CAP-MCP-DEFAULT-OFF
16. Skills Are a Two-Stage Prompt Capability: Route Cheaply, Load Authority Deliberately
provider list() → SkillCandidate summaries
→ layered merge and cache
→ durable <available_skills> names + descriptions
→ model calls skill(name) OR user explicitly invokes /name
→ provider get(winning candidate)
→ canonical <skill_content> body + resource base
A summary separates routing data from the full definition and carries two independent permissions: modelInvocable and userInvocable. Resource bases may be directories, URLs, or opaque provider instructions. Full bodies are asynchronous provider loads, not stored in every catalog entry. CAP-SKILL-TYPES
The wrapper escapes framing metadata but embeds the instruction body verbatim because Skills are defined as trusted local content. The resource hint tells the model to load referenced assets only when needed. CAP-SKILL-RENDER
17. Scope Beats Rank; Rank Only Breaks Ties Inside One Layer
| Filesystem source | Rank | Meaning within one layer |
|---|---|---|
Project .dsh/skills | 100 | Highest local priority |
Project .agents/skills | 200 | Below project-native skills |
| Runtime registration | 250 | Between project and custom/user roots |
| Custom roots | 300 | Operator-supplied roots |
User .dsh/skills | 400 | User-native fallback |
User .agents/skills | 500 | Shared-agent fallback |
| Bundled | 600 | Lowest filesystem priority |
The registry merges global, ancestor, then exact-Agent layers; a nearer layer replaces a same-name entry outright. Only candidates inside one layer are sorted by rank and registration order. Complete observations are cached by cwd, scope chain, and revision; incomplete discovery is never cached, and one revision race receives a bounded retry. CAP-SKILL-LAYERSCAP-SKILL-COLLECT
18. The Filesystem Provider Uses the Execution-World FS for Reads, but Host Watchers for Change Detection
Workspace-sensitive discovery builds project, custom, user, and bundled roots with the ranks above. When ctx.fs exists, ordinary roots are listed and read through it; a trusted bundled host root may use Node filesystem access directly. Model-facing write/edit observations synchronously invalidate matching roots. Host-local watchers are bounded by a maximum retained project count and coalesce invalidations. CAP-SKILL-FS-ROOTSCAP-SKILL-FS-TRUST
Discovery content can follow a remote/sandbox filesystem while watcher mechanics remain Host-local. The provider explicitly represents watcher failure as an incomplete observation, allowing the model catalog to retain its last good version and retry later instead of publishing a transient empty list.
19. Model Invocation and User Invocation Take Different, Revalidated Paths
| Path | Discovery gate | Load gate | Durable/model-visible result |
|---|---|---|---|
Model calls skill(name) | Summary must still exist and be model-invocable | Loaded definition is rechecked | Ordinary tool call/result containing the body |
User message invokes /name | Only direct source.kind=user text is scanned | Loaded definition must be user-invocable | Instructions-form user context appended last |
| Unknown/user-disabled gesture | No recognized authority | No injection | Remains ordinary prose |
The model tool lists, then loads, and checks modelInvocable at both boundaries. The explicit-user path loads directly and checks userInvocable, making it the only route to a model-disabled/user-enabled Skill. The same canonical renderer is used on both paths. CAP-SKILL-TOOL-LOADCAP-SKILL-USER-INVOKE
20. The Skill Catalog Is Durable Replacement State, Not a Rebuilt System-Prompt Blob
At every eligible pre-step, the plugin publishes only sorted model-invocable names and bounded descriptions—and only when its exact skill registration is visible. The durable source stores those entries directly. A digest change appends a complete replacement catalog; an empty replacement explicitly retires older names; incomplete discovery publishes nothing; compaction hiding the visible catalog causes a later complete observation to re-establish it. CAP-SKILL-CATALOG
Adding a Skill does not inject every body into every request. Catalog changes are append-only suffix facts, while a body appears only in the chosen tool result or explicit invocation. The price is historical duplication: whole-list replacements cost tokens, body-only edits do not change the summary digest, and an older loaded body remains a historical Session fact.
The base owns the layered Skill registry, while coding presets mount filesystem discovery and the loader into their Agent scope. This allows global repository contributions and preset-local shadowing to coexist. CAP-SKILL-BASECAP-SKILL-SHIPPED
21. Trust Matrix: “External” Does Not Mean the Same Thing Across These Surfaces
| Boundary | Data authority | Execution authority | Primary hazard |
|---|---|---|---|
| Web search result | Remote, untrusted content | No local process by itself | Prompt injection and citation quality |
| Web fetch URL | Model-chosen remote target | Host network reachability | SSRF/private-network access |
| LSP server | Local source plus server response | Process in shared execution world | Operator-chosen executable and workspace content |
| MCP server | Remote schemas and result blocks | External actions or an SDK-spawned child | Dynamic capability, auth, and spawn confinement |
| Skill body | Trusted instructions by design | Indirect authority over later tool choices | Writable skill roots become prompt-authority roots |
The strongest reusable lesson is to name prompt authority explicitly. Escaping XML-like framing cannot make a Skill body untrusted because following the body is its purpose. Conversely, a Web page must never acquire that authority merely because it arrived through a first-party search tool. Path policy, provider authentication, and prompt provenance solve different problems.
22. Focused Codex Comparison: Similar Progressive Disclosure, Broader Documented MCP Policy
This comparison uses only OpenAI's public Web search, Model Context Protocol, and Customization documentation, retrieved on 2026-08-13. DeepSeek Harness is analyzed at pinned source; Codex is described only at the public-documentation level. No claim is made about private services or undocumented implementation.
| Dimension | DeepSeek Harness pinned source | Codex public documentation |
|---|---|---|
| Web | Provider registry; shipped DeepSeek auxiliary search; fetch disabled for missing SSRF defense | First-party search; cached by default locally, live/disabled modes; results explicitly treated as untrusted input |
| MCP transports | Stdio and Streamable HTTP; Tools only | Stdio and Streamable HTTP; documented server instructions and richer auth |
| MCP controls | Per-server startup strictness, call timeout, reconnect; no tool allow/deny or per-tool approval in this bridge | Documented startup/tool timeouts, required server, tool allow/deny, server/per-tool approval modes, OAuth and bearer options |
| Skills | Summary catalog, on-demand body, separate model/user invocation, durable replacement facts | Metadata first, SKILL.md on selection, references/scripts as needed; implicit or explicit $ invocation |
| LSP | Closed four-operation seam with optional stdio backend | The retrieved official manual documents no standalone LSP tool surface; no parity claim is made |
DeepSeek Harness's source makes provider selection, catalog durability, result projection, and process seams exceptionally auditable. Codex's public MCP surface documents a more complete operator policy plane—authentication, filtering, required startup, server guidance, and per-tool approval. Both publicly describe progressive Skill disclosure; the DeepSeek source additionally exposes exactly how replacement catalogs become Session facts.
23. Transferable Design Rules
Stabilize the narrowest honest vocabulary
Use fixed tools for product semantics; import dynamic schemas only where interoperability requires it.
Separate visibility from readiness
Stable catalogs help caching, but provider health must be observable independently.
Make generation swaps explicit
Define separately what happens on discovery failure, registration conflict, transport loss, and exhausted recovery.
Treat instruction stores as authority stores
A writable Skill root is not merely documentation storage; it can steer subsequent privileged calls.
The chapter's deepest pattern is progressive capability disclosure with typed ownership. Web hides provider diversity behind stable product semantics; LSP narrows a huge protocol into four reliable questions; MCP accepts dynamism but wraps it in deterministic identity and lifecycle generations; Skills keep routing cheap and instruction authority explicit. The remaining debt appears exactly where a surface crosses a seam it does not own: fetch without network policy, MCP spawn outside the subprocess world, and trusted Skill bodies rooted in mutable files.
All DeepSeek claims above are tied to the pinned baseline's production source, shipped configuration, examples, and package documentation. This delivery also ran 16 focused Vitest files across Web, LSP, MCP, and Skills under an isolated temporary root: all 454 tests passed. The Codex comparison is separately bounded to the three linked official pages and their retrieval date. Runtime validation does not turn an optional composition into a shipped default, and passing package tests does not prove real-network safety or third-party server correctness.
My Learning Notes
Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.