Repository navigation
Description
Relocated from dezren39/darwin-nix#137 on 2026-10-28 (placement audit): mcpx's single source lives in developing-today/code (pkgs/mcpx); re-home tracked at #309. Original body below; issue references inside it are historical (dezren39/nix numbering).
Parent: #97
Summary
Define and label the subset of integration tests that must work end to end with real software: real MCP servers, real runtimes, and real network where the server needs it. Pin the versions they run against in one place, the flake. #134 recommends building the test image from the flake so there is one version set; #182's user-facing pkgs/mcpx/Containerfile pins its own (Go via golang:1.26-bookworm, bun via oven/bun:1-debian), so today there are two, and they already move separately (Renovate #184). The protocol half of the pin is the binary's own report, stamped into both (below). Run the subset nightly and on release, not per PR, because real servers bring network access and flakiness (#65). Fold the manual scripts/stress.sh into the subset.
Original notes
- testing
- a general goal is to ensure all features have one or more integration tests
- the mcp servers can be specialized mocks but the app entrypoint or binary is used and config is passed in or config path set to appropriate test
- for both binary && app entrypoint (consider binary a superset)
- all tests should pass for nix flake & container
- a specific subset should completely work with all real software
- these should be found/labeled whenever possible
- the supported mcp version is the one in the flake/containerfile
Current state (main d0b7e01)
-
All e2e tests use fakes (199 at d0b7e01, 209 at 05c78b2).
fakemcp/askmcp(internal/testsupport/) and a stub registry (MCPX_REGISTRY_URL,internal/e2e/e2e_test.go:52-57, since Update Rust crate axum to 0.6.20 #65). Nothing in CI runs a real MCP server. -
The runtimes are real but unpinned in CI.
mcpx.ymlinstallssetup-denov2.x,setup-bunlatest and node 24.package.nixwraps the nixpkgs versions instead. The runtime version CI tests is not the one users of the package get (Update Rust crate rand to v0.9.3 [SECURITY] #134). -
scripts/stress.shchecks discovery latency under load (<200 ms permcpx lsafter 30 concurrent), concurrent isolation on a stateful server (tools.chrome_devtools.new_page/list_pages, lines 36-40), and leaks. It needsMCPX=<binary>and runs against whatever.mcpx.jsonthe caller's config chain resolves, so the servers and their versions are the user's, not pinned. It is manual, and so isscripts/bench.sh. It usespython3for arithmetic. -
The MCP protocol version lives in Go source, not the flake:
- the client speaks
2025-11-25(internal/mcpclient/client.go:22); - the server side serves
2025-03-26(internal/mcpserver/revisions.go:14,Oldest) up to the newest, with per-feature floors (revisions.go:85-87,conn.go:192,198); - the full served list is
Supported = {"2026-07-28", "2025-11-25", "2025-06-18", "2025-03-26"}(internal/mcpserver/server.go:778); - the mocks already span two revisions:
fakemcpannounces2025-06-18(internal/testsupport/fakemcp/main.go:112),askmcp2025-11-25(askmcp/main.go:145) -- keep that; it gives the negotiation some coverage for free (from Phase 5: alias display rules #164); - the protocol matrix is served at
/v1/protocoland tested byTestTheProtocolMatrixIsServedFromTheTableTheCodeReads(internal/e2e/proto_test.go:567).
So "the supported mcp version" has two parts. The protocol revisions are fixed by the mcpx source at a commit. The real server implementations and their versions are what the flake can pin.
How to pin the first part, from Phase 5: alias display rules #164: there is no single "supported mcp version" to type into the flake or a Containerfile -- it is a list plus a client constant, and
/v1/protocolalready reports both. So (1) in the gating mock suite, assert/v1/protocolagainst a checked-in golden file, so any change toSupportedormcpclient.ProtocolVersionis a reviewed diff; (2) stamp the same list onto the artifacts generated from the binary at build time, the waypostInstallgenerates the man page -- e.g. apassthruattribute on the nix package and an OCI label on the image -- never transcribed by hand; (3) in the real suite, assert each pinned server negotiated a revision inSupportedand record which. The image label would live in Phase 5: tag event bodies must travel with the event #182's Containerfile, which the GIT architect owns; this is input for Update Rust crate rand to v0.9.3 [SECURITY] #134, not a design of that file. The golden file is the same "generated reference that cannot drift" problem as Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/import #96/⚡ Bolt: Optimize cursor color generation and position grouping #145 -- one mechanism for all of them. - the client speaks
-
Real servers available in the pinned nixpkgs (
c7def04,pkgs/by-name):- the reference servers
mcp-server-fetch2026.8.31,mcp-server-filesystem2026.8.18,mcp-server-git2026.8.31,mcp-server-memory2026.8.18,mcp-server-time2026.8.31 andmcp-server-sequential-thinking; playwright-mcp0.0.76,github-mcp-server1.11.0,context7-mcp4.0.3,firefox-devtools-mcp,mcp-proxy, and others.
Not in nixpkgs:
@modelcontextprotocol/server-everything, the reference server that exercises every protocol feature (prompts, resources, templates, sampling, elicitation, notifications, long-running progress). Also absent: chrome-devtools-mcp, whichstress.shuses. - the reference servers
What the subset must cover
"completely work with all real software" is too broad to be a test, so here it becomes: every feature in the #132 inventory, exercised once against a real server that implements it, plus the runtimes at pinned versions.
| Feature | Real server (pinned in the flake) |
|---|---|
stdio tools, schemas to TS client, exec on deno, bun and node |
mcp-server-time, mcp-server-memory (no network) |
| filesystem-scoped tools, workdirs | mcp-server-filesystem |
git scope (repo/worktree) |
mcp-server-git + git |
prompts, resources, templates, completion, sampling, elicitation, progress, list_changed |
server-everything (to package in pkgs/) |
| stateful leasing, concurrent isolation (stress.sh) | playwright-mcp (in nixpkgs), replacing chrome-devtools-mcp, which is not |
| network server, HTTP transport | mcp-server-fetch behind a local HTTP fixture (no internet); a streamable-HTTP server: mcp-proxy wrapping a stdio server |
| registry search/add | the real registry, the only internet-dependent test, isolated in its own job (#65) |
Approaches -- how the subset is labelled
A -- a test-name prefix, TestReal... (least recommended)
Selectable with go test -run '^TestReal', and visible in output. Nothing stops a real-server test from going unnamed, and nothing stops a TestReal test from quietly using the fake. On a machine without the server, the test must skip itself, and the skip is invisible. That is the #35 lesson again. (testing.Short() with CI passing -short is worse still: it is opt-out, so the obvious go test ./... on a laptop runs the real servers -- the property that produced #65 -- and -short already means "skip slow", not "skip network"; from #164.)
B -- a manifest file
internal/e2e/realsoftware.json lists test names with the server and version each needs. A guard fails if a listed test does not exist or an unlisted test starts a non-fake server. It is explicit and reviewable, and gives a place to record versions. It is also a second list to keep in sync, and the guard needs to know what counts as "a non-fake server". #164's variant lists servers instead of tests: a checked-in .mcpx.json with every server at an exact version (npx -y @modelcontextprotocol/server-filesystem@1.2.3, never @latest), bumped by Renovate as a reviewable diff. Simpler than a nix derivation, but it needs the network at test time; C's flake-built servers do not.
C -- a build tag realsoftware plus a harness helper (recommended)
- The real-software tests live in
internal/e2e/real_*_test.gobehind//go:build realsoftware. They do not compile into the per-PR run, so they cannot slow it down or flake it. - Each test calls
testsupport.RealServer(t, "mcp-server-time"), which resolves the server fromMCPX_REAL_SERVERS_DIR(a nix-built directory of pinned servers:nix build .#mcpx-real-servers). UnderMCPX_TEST_REQUIRE(Update Rust crate rand to v0.9.3 [SECURITY] #134) a missing server fails; without it, it skips with the name. RealServerrecords the name and version into the Update dependency @chenglou/pretext to ^0.0.9 [SECURITY] #132 ledger. The ledger then serves as the "found/labeled" list, generated, not maintained by hand.- "these should be found/labeled whenever possible": a guard in the default build greps
real_*_test.gofor any test that does not callRealServer(it would be a fake test hiding in the tag), and the default-build tests forRealServercalls outside the tag.
Recommended because the tag keeps the per-PR budget clean (#95), and the helper makes the label a fact about what the test does, not a naming convention. Pinning comes from the flake, so "the supported version" is nix flake metadata plus the mcpx-real-servers derivation.
A compile-time tag has a cost: the tagged files are type-checked only when the tag is built. So the nightly job must run go vet -tags realsoftware ./internal/e2e, and it is cheap to also do so in tier 1.
Where it runs
-
Nightly and on release (tier 2, Update dependency happy-dom to v21 [SECURITY] #136), in the flake image (Update Rust crate rand to v0.9.3 [SECURITY] #134) with
mcpx-real-serversadded. No internet except the registry job. -
stress.sh folded in:
- its latency check becomes a
realsoftwaretest with a generous threshold (the 200 ms figure is a laptop number, so record it and alarm on regression rather than failing on an absolute value); - its isolation rounds become a test using
playwright-mcp; - its leak check becomes an assertion on Update dependency htmx.org to v1.9.12 #138's isolated state dir.
Keep the script as a thin wrapper for manual runs against your own config, which is still useful and is the only place "the user's servers" belong.
- its latency check becomes a
-
On a flake update: because the servers are pinned, a
nix flake updatethat bumps a server is exactly when the subset must run. Trigger it onflake.lockchanges (Bump golang.org/x/crypto from 0.16.0 to 0.17.0 in /sources/launcher #95's path filters). -
Flakiness policy (Bump unsafe-libyaml from 0.2.8 to 0.2.10 in /sources/pavex/examples/realworld #99): a real-software failure opens an issue automatically and does not block. A test that is flaky twice is quarantined with an issue, not retried silently.
Open questions
- Package
server-everythinginpkgs/(it is an npm package, so it needs an npmDeps hash), or run it vianpxat a pinned version? The first is hermetic; the second needs the network. - Is there an official MCP conformance suite worth running against
mcpx serve//mcpas a server? mcpx is also an MCP server, and that direction has no real-client test today. Real clients to consider are opencode itself and the MCP Inspector CLI. - Which protocol revisions must the subset cover: only the client's
2025-11-25, or also old-revision servers (2025-03-26,2025-06-18), since mcpx negotiates down? A pinned old version of a reference server would cover the latter. - Should the pinned server versions be printed by
mcpx doctoror/v1/protocolin the test image, so a bug report says which ones were tested? - A real-software test that randomises (for example, concurrency order) takes the run seed (Bump golang.org/x/crypto from 0.10.0 to 0.17.0 in /sources/graph #93).
- Does a feature tested only against real software count as covered in Update dependency @chenglou/pretext to ^0.0.9 [SECURITY] #132's ledger? Phase 5: alias display rules #164 argued it must be excluded from the denominator: "only tested against real software" is a finding, not coverage, and counting it hides gaps behind a nightly job.
RealServercan still record into the ledger as a label (C above) without counting as coverage. - A pinned server withdrawn upstream: fail, or report unavailable and continue? Probably fail on a pre-release run and report on a nightly (from Phase 5: alias display rules #164).
Supportedincludes2026-07-28: does any real server speak it yet, and can the subset prove mcpx's newest-first negotiation downgrades correctly against servers that do not (from Phase 5: alias display rules #164)?- An HTTP/SSE-transport server belongs in the set, since
docs/story.md:790promises "Streamable HTTP and SSE are spoken directly; no mcp-remote" (from Phase 5: alias display rules #164;mcp-proxyin the table above is one way). - Run the subset in the flake image (Update Rust crate rand to v0.9.3 [SECURITY] #134) with a warmed npm/uv cache? It is the difference between a short nightly and a long one if any server is still fetched (from Phase 5: alias display rules #164).
Ease · Impact · Related
Ease M (tag, helper, reference servers: M; server-everything packaging: S-M; stress.sh fold: S) · Impact med-high (the only proof mcpx works with anything but its own fakes)
Depends on / related: #132 (ledger, inventory), #134 (flake image, MCPX_TEST_REQUIRE), #138 (isolation), #136 (tiers), #65 (no internet in normal tests; registry isolated), #95 (nightly/release workflow, flake.lock trigger), #99 (flake quarantine policy), #93 (seed), #49 (building mcpx-real-servers as a flake package).
Security/auth deferred; see tracker.
Acceptance criteria
- A labelled real-software subset, pinned by the flake, runs nightly in CI and its results are visible/recorded as the body specifies.
- The work described in the Summary/Scope above is delivered via a branch + PR (no direct push to main); verification runs in GitHub Actions CI, not on the sandbox.
- Evidence (CI links, timings, or probe output as applicable) is recorded on this issue before it is closed.
Activity
developing-today commentedon Oct 28, 2026
CODE-915
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#137 (verbatim, oldest first):
@dezren39 · 2026-09-30T06:51:56Z
Overlaps #164, written concurrently by a second worker on the same slice. Both are kept until reconciled; see the tracker (#97) for the differences and which one to keep.
@dezren39 · 2026-09-30T07:21:11Z
Integration pass. Placed in the DAG rooted at #181.
@dezren39 · 2026-09-30T12:03:29Z
Issues pass (2026-09-30, at 05c78b2).
Supportedlist (internal/mcpserver/server.go:778), the two-revision mock fleet (keep it), the/v1/protocolgolden file plus a build-time-generated packaging label as the real meaning of "the supported mcp version" (the image label is input for Update Rust crate rand to v0.9.3 [SECURITY] #134; the Containerfile is the GIT architect's), the-shortrejection, the pinned-server manifest variant, and five open questions (ledger denominator, withdrawn servers, 2026-07-28, HTTP/SSE server, warmed caches).dezren39 commentedon Oct 28, 2026
Intake review
🤖 Agent note · state: filed
34 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · subset landed — PR #3704 merged (8553720)
realsoftwarebuild tag +real_*_test.gosubset (time / memory / filesystem + stress.sh latency leg folded in as recorded-not-gated);testsupport.RealServerresolving fromMCPX_REAL_SERVERS_DIR(fails instead of skipping whenMCPX_TEST_REQUIREnames the server or the subset — mcpx testing: environments -- nix flake, container, local, remote -- as CI matrix entries #1983's variable, same spelling, so the flip is automatic when PR mcpx: MCPX_TEST_REQUIRE — required runtimes fail instead of skipping (#1983 step 1) #3696 lands);mcpx-real-serversflake package (time, memory, filesystem, git, fetch, sequential-thinking from the pinned nixpkgs;pathsToLink=["/bin"]— filesystem+memory ship an identical monorepo lib tree that collides in a full buildEnv merge); guard test in the default build; nightly workflowmcpx-realsoftware-nightly.yml(first dispatch: run 41313241355).ls190 ms), guard;go vetclean both tag modes. PR CI red only on the pre-existing mcpx: internal/conformance test backends do not compile against current mcpserver.Backend Complete signature #3519 conformance compile break, proven identical on clean main and dispositioned on the PR.destructiveHint=true, so unconfirmed calls are cancelled by mcpx's gate — the tests confirm through the elicitation flow; (2)mcp-server-memory's default persistence path is its install dir = the read-only Nix store under the pin (EROFS) — config sets itsMEMORY_FILE_PATHknob to scratch.server-everythingpackaging underpkgs/(not in nixpkgs — prompts/resources/sampling/elicitation feature legs), playwright-mcp isolation + leak legs from stress.sh, fetch/mcp-proxy HTTP-transport legs, the real-registry job (only internet leg), and the protocol golden/passthrustamping from the issue's pinning section.dezren39 commentedon Nov 1, 2026
🤖 Agent note · reopened — closure was a merge-bot side effect, not a merits ruling
Merge pull request #3716 from developing-today/fix/realsoftware-night…
dezren39 commentedon Nov 1, 2026
🤖 Agent note · nightly leg GREEN (queue task 5587, final evidence)
accept-flake-config = truepulled in the flake nixConfig, whose cgroups requirement the stock CI installer cannot satisfy. Fixed in PR mcpx realsoftware nightly: drop accept-flake-config (cgroups) (#1990) #3716 (merged c193d62) by matching mcpx-nixbuild's plain-installer pattern.ls17.3 ms, guard pass. (Verified twice: run list + raw step logs.)