Research and incidental findings
created: 2026-11-01T01:30:00-05:00
last-updated: 2026-11-01T01:30:00-05:00
increment: 1
status: draft
tags: area:docs, area:compendium, topic:research
description: what was learned while building mcpx, and while doing anything
else in this repository, collected by topic and tagged with its
provenance so it is not lost in session transcripts. First slice
of #1871: the section outline, the seed findings that already
exist in the repository, and the harvesting process.
This chapter is the compendium’s research chapter (#1871). It holds research and incidental findings — things learned while building mcpx, and while doing anything else in this repository — filed by topic so they survive the sessions that produced them. Findings that live inside feature entries stay there as the source of truth; this chapter excerpts them with a citation (the answer to open question 5 on #1871, applied: excerpt, never copy-and-fork).
Every finding here carries a provenance tag, per the quality bar below:
- measured — a number or behaviour produced by running something, with the environment named in the source.
- read-from-source — read in code, docs, or a specification in this repository.
- reported — someone else’s claim, cited: an external article, another project’s documentation, or a statement in an issue. Reported findings are not verified here and must not read like measurements.
Sections with no seed yet are outline only: the heading exists so the next harvest has somewhere to put things, and says so rather than padding.
AI and LLMs
- The cost model of zero-context discovery. Listing servers costs ~171
tokens, a search ~61, and
types675–4,402 per namespace — the numbers behind progressive discovery: a client should not pay for every tool definition up front. (measured —docs/story.md, “Zero-context discovery”) - Switch to progressive discovery at 1–5% of the context window, and do
not mutate the
toolsarray mid-conversation: it invalidates the provider’s prompt cache. (reported — modelcontextprotocol.io, MCP client best practices, as cited on #1871) - Models write code better than they call tools, Cloudflare argues, because tool-call tokens are synthetic training data while code is what models were trained on. This is the argument behind Code Mode. (reported — blog.cloudflare.com/code-mode, as cited on #1871)
Harnesses
- What is opencode’s and what is general.
docs/opencode-plugin.mdseparates the opencode plugin surface (v1 vs v2) from the parts that apply to any harness;OPENCODE-V2.mdcorrects the record where the first draft blurred them — opencode v1 ships code mode behindOPENCODE_EXPERIMENTAL_CODE_MODE. (read-from-source —docs/opencode-plugin.md,OPENCODE-V2.md) - The harness knows what the agent does not. Session state, statistics,
and affordances live in the harness; an agent that assumes them instead of
asking gets them wrong. The opencode session statistics are the worked
example. (read-from-source —
docs/story.md, “The harness knows what the agent does not” and “Statistics from opencode”) AGENTS.mdis the cross-harness instruction file: the same file is read by the harnesses this repository is used from, which is why fleet rules live there and not in one tool’s config. (read-from-source —AGENTS.mdat the repository root and inpkgs/mcpx)- A harness-free core is a design constraint, not an accident (#48):
codex, claude code, pi, and manus must all be able to drive the same core.
(read-from-source —
docs/story.md; issue #48)
Plugins
- A plugin can connect without the binary. The connection ladder and
GET /v1/resolvelet the opencode plugin find or start a daemon instead of assuming one is installed (#44, fixed in #53). (read-from-source —docs/plugin-discovery.md) - The plugin logs over the socket (#36), so plugin diagnostics land
where the daemon’s do instead of in a file nobody opens.
(read-from-source —
docs/plugin-discovery.md; issue #36) - Plugin install is a README, not a package step. (read-from-source —
plugin/opencode/README.md)
Token providers
The outline assumes both readings of the term (open question 1 on #1871): model providers, and the credential/OAuth providers that hand out tokens.
- Model providers are maintained as patches. This repository patches
opencode for provider behaviour (Copilot business support, Copilot
compaction, OpenAI response-id caching) and keeps skills for maintaining
those patches across opencode updates. (read-from-source — #1871 seed
list. Harvest note, 2026-11-01: the three patch files the issue names are
no longer at the repository root on
main; the patches’ current home needs naming here by whoever next touches the opencode patch set.) - **Credential placeholders keep secrets out of config files and descriptions, but
stdio servers receive them through the environment. Generated scripts inherit
that environment unless variables are unset or the run uses
-i; do not treat this as isolation from code you execute. (read-from-source —docs/story.md, “Credentials”;internal/runner/dispatch.go:693–705)
Cloudflare
- Code Mode mechanics. The Worker Loader API runs generated code in
isolates;
fetch()/connect()throw inside it; bindings hide API keys from the generated code. Shared with #153 — the design treatment lives with the design, this is the finding. (read-from-source —docs/story.md; issue #153)
The TypeScript ecosystem
- Node runs TypeScript by stripping types, and only erasable syntax
survives. Node v24.20.0 type stripping rejects a file with an
enum; constructor parameter properties failed too, which is why the generated client uses plain fields. (read-from-source —ASSESSMENT.mdbug 3;docs/story.md, “Runtime portability”) - Runtime detection order is deno, then bun, then node (
script.runtime,--runtime,MCPX_SCRIPT_RUNTIME). Versions at the time of the seed: Deno 2.9.6, Bun 1.3.11. (read-from-source —docs/story.md, “Runtime portability”; the runtime comparison itself belongs to #101 anddocs/runtimes.md) --typecheckisdeno check, Deno only: ~80 ms on repeat, ~400 ms for a new program (#79). (measured — as recorded on #1871;docs/story.md)- Constructing a TypeScript compiler per request was lootbox’s memory
problem — the
ts-morphlesson behind the current design. (read-from-source —ASSESSMENT.md)
SSE
- Server-sent events, not WebSockets. SSE is plain HTTP,
EventSourcereconnects on its own, and WebSocket upgrades are mishandled by middleboxes — the reasoning behind mcpx’s remote transport. (read-from-source —docs/story.md, “Server-sent events, not WebSockets”) - Lossless reconnection is a protocol property, not a client hope: the
stream carries what a reconnecting client needs to resume without a gap.
(read-from-source —
docs/story.md, “Lossless reconnection”)
Sockets and TCP
- What each transport costs. Spawning
mcpx statustook 23.12 ms; a unix-socket keep-alive round trip 0.17 ms (135× cheaper); TCP loopback keep-alive 0.65 ms. The daemon-and-socket design is priced by these numbers. (measured —docs/story.md, “What each transport costs”) - macOS
sun_pathis 104 bytes. Deep state directories produced socket paths that failed withbind: invalid argument. (read-from-source —ASSESSMENT.mdbug 4) localhostis not127.0.0.1. It resolved to IPv6 only while the compiled build bound127.0.0.1, so the client dialled an address nothing listened on. (read-from-source —ASSESSMENT.md)
RPC and WebSockets
- JSON-RPC framing needs no dependency — it is standard library, which
is why it appears in the dependency audit as a deliberate non-dependency.
(read-from-source —
docs/dependencies.md) - Lootbox’s WebSocket. The predecessor system used a WebSocket RPC
channel (
lootbox.config.json:serverUrl: ws://127.0.0.1:9420/ws) — the contrast case for the SSE choice above. (read-from-source — #1871 seed list)
MCP
- Two protocol eras. The protocol has a before and an after, and a
client has to speak to both; “What 2026-07-28 actually asks of a client”
is the checklist. (read-from-source —
docs/story.md, “Two protocol eras”) - The revision matrix — every revision in both directions, plus what is
still missing — lives in
docs/protocol.mdand is excerpted, not copied, here. (read-from-source —docs/protocol.md) - The sampling bug: it did not work, and nothing said so. Capabilities
are declared in the handshake, but the handler was installed after it,
and every test called the passthrough directly — so sampling was
advertised, broken, and green in CI at the same time.
(read-from-source —
docs/story.md, “Sampling”) - The elicitation bug came first — the same shape, earlier.
(read-from-source —
docs/story.md, “Elicitation”;docs/elicitation.md) mcpx doctorfound a bug immediately — a diagnostics command earns its keep the first time it runs against a real installation. (read-from-source —docs/story.md, “mcpx doctor”)- Remote servers without a shim: Streamable HTTP makes
mcp-remoteunnecessary for remote servers. (read-from-source —docs/story.md, “Remote servers without a shim”) - Which registry. The official registry publishes an OpenAPI spec;
HAPI is not open source and returns 404. Registry choice is a licensing
and availability decision before it is a technical one.
(read-from-source —
docs/story.md, “Which registry”)
OpenAPI and REST
- Specifications as tools: two things easy to get wrong. A relative
server URL must resolve against the document’s location, and only read
methods are exposed by default. (read-from-source —
docs/story.md, “Specifications as tools”) - The specification described the wrong thing: the first spec described
mcpx’s own ten tools, not the hundreds it fronts. (read-from-source —
docs/story.md, “Specifications as tools”) - One ops table generates the routes, the MCP tools, and the OpenAPI
document (
internal/api/ops*.go), so the three cannot drift apart. (read-from-source —internal/api/)
OAuth
- OAuth for remote servers is
status: proposed, not built; the credential story that exists today is in “Credentials”. (read-from-source —docs/story.md, “OAuth for remote servers” and “Credentials”)
Command line
- Command-line programs as servers: a CLI with a daemon behind it is a
server with a command-line client, and should be designed like one.
(read-from-source —
docs/story.md, “Command-line programs as servers”) - Derived flag names surprise people:
daemon.leaseTTLbecomes--daemon-lease-ttl. (read-from-source —docs/cli.md:142–146) - Accepted-and-ignored flags are bugs, even when accepting them is deliberate-looking (#74, fixed by #75). (read-from-source — issues #74, #75)
- Help coverage once drifted from the dispatch table (historical):
man,completion,serve,openapi,adapter,registry, andapiwere once dispatched bycmd/mcpx/main.gobut undeclared ininternal/cli/manual.go, so help, the man page, and completions did not list them. All seven are declared now, andTestEveryDispatchedCommandIsDeclaredholds the declaration and the dispatch table equal in both directions, so the gap cannot reopen silently. (read-from-source —docs/cli.md, “once dispatched but undeclared”;internal/cli/manual.go)
Containers and CI
Outline only in this slice: the container and CI seeds (static Go builds,
image-size anatomy, actions/setup-go in a monorepo, stopping test
daemons) are harvested with the Nix slice below, because several were
recorded alongside it (#182) and splitting them twice would fork them.
The seeds themselves are listed on #1871 and in docs/container.md
sections 4–5.
Nix
Later slice, named rather than dropped. The standalone Nix section —
a section that does not need to mention mcpx — is the second slice of
#1871, not part of this one. Its seeds are already recorded on #1871:
vendorHash repair in the mcpx workflow, the sandbox-test package list in
package.nix, pure-Go SQLite, wrapProgram PATH suffixes, generated man
pages and completions, the flake layout and patch maintenance for this
repository, and the macOS findings. Open question 2 on #1871 (does this
section live in this book, or in a book for the whole repository that this
one links to?) is still open and should be answered before the slice is
written, not after.
Harvesting: how findings get here
Approach C from #1871, in force for this chapter:
- Intake. Anything learned that is not about the change at hand — a
verified surprise, a measured cost, a behaviour read in someone else’s
source — is filed as an issue (or a comment with a link) under the
researchlabel, not buried in a session transcript or a PR body. A finding stays an issue until it is written up here, so nothing is lost in between.docs/ideas.mdstates the same discipline for design questions: an answer that only exists in a conversation is lost. - Entry format. A finding written up here is an excerpt plus a
citation, in the entry style the runbook uses (created / last-updated /
status / tags / sources / checked), with the finding-specific statuses
verified,measured,read-in-source,reported,hearsay, andsuperseded. The tags in this file’s key block are the chapter’s; a finding’s own topics go in its section placement and its citation. - Harvest. A per-release step moves labelled items into this chapter as entries and closes the issues with a link to the section that took them. Excerpt, do not move: a finding inside a feature entry keeps that entry as its source of truth and is cited from here.
- Supersede, never silently edit. When a finding turns out wrong, it
is struck through or marked
supersededwith a link to what replaced it. The history is the audit trail (#150).
Quality bar for an entry
From #1871, applied to every seed above:
- Sources. Every claim has at least one: a file with a section or line, a commit, an issue, or an external URL with the date it was read.
- Measurements. A measurement carries its environment (machine, OS, runtime and tool versions) and the command that produced it — re-run the command, paste the result.
- Provenance. Say how it is known: reproduced, read in source, read in docs, or someone said so. Never let hearsay read like a measurement.
- Dates. Every entry is dated; a re-check updates the date. The seed
harvest above was checked against
mainon 2026-11-01. - Standalone. Where possible an entry is readable without mcpx context. That is the point of the Nix section.