Repository navigation
Description
Relocated from dezren39/darwin-nix#83 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: #173
Summary
"Turn anything into an MCP server" is already most of what mcpx does — it is the premise of paths.adapters and paths.apis — but the coverage is uneven and the results are second-class. A command-line program can become tools; a shell script, a Python script or a bare binary cannot, except by writing an adapter declaration by hand. And what adapters and OpenAPI produce are loose MCP tools, not namespaces: they are absent from mcpx ls, from the generated TypeScript, and from scripts.
This issue asks for two things. One: extend "anything becomes a server" to scripts of any language, to bare binaries with no declaration, and to plugins. Two: make everything produced that way a first-class namespace, registerable from configuration and live, so a declaration, a plugin and an upstream MCP server are indistinguishable once loaded.
Original notes
- support adding bash/js/ts/py scripts as mcp
- support embedding/config/dynamic-add "mcp" for any plugin above
- support embedding/config/dynamic-add any binary as mcp
- binary mcp
Current state (what mcpx does today, with file refs)
Command-line programs → tools: shipped. internal/adapter/adapter.go takes a declaration — name, command, args, env, dir, timeout, and a list of tools, each a fixed-arg subcommand with typed params (string/number/integer/boolean/array, flag or positional, repeat, default, enum, plus stdin for a parameter written to stdin and json to parse the output). Declarations are loaded from paths.adapters (internal/settings/registry.go:292, internal/cli/adapter.go:22). mcpx adapter check reports whether the binaries exist, and mcpx doctor includes the same check (internal/cli/doctor.go:230-244). Two deliberate details are recorded in docs/story.md:1626-1630: a boolean parameter becomes the flag's presence, and a non-zero exit is a result, not an error.
OpenAPI → tools: shipped. paths.apis and mcpx api --spec <path-or-url> (internal/cli/openapi_tools.go).
Saved scripts already carry a tool schema. A recipe is a script on the scripts search path that declares its holes in a header comment: // @param repo:string which repository, with types string/number/boolean/json, defaults, and optional markers (internal/recipes/recipes.go). That is a JSON Schema in all but spelling. Recipes are reachable as GET /v1/recipes, GET /v1/recipes/{name}, POST /v1/recipes/{name} and POST /v1/recipes/{name}/run (internal/api/ops_consumer.go:42-95), and through mcpx prompt / POST /v1/intent matching. They are not individual MCP tools. An agent cannot see close_stale_issues(repo, days) in tools/list; it can only see the recipe machinery.
Live add exists, for servers only. POST /v1/servers writes the entry to a config file and reloads, so the server is callable immediately and still there after a restart; DELETE /v1/servers/{name} removes it (internal/api/ops_settings.go:82,111). There is no equivalent for an adapter, an API spec or a recipe-as-tool.
The second-class problem, precisely. Adapter tools and OpenAPI operations are attached as mcpserver.Extra when the MCP server is constructed (internal/cli/serve.go:328-346 at 05c78b2; WithExtras at :346). That gets them into tools/list over /mcp and mcpx serve --transport stdio, and nowhere else: not into the pool, not into mcpx ls or mcpx catalog, not into the codegen, and therefore not callable as gitx.log() from a script. docs/story.md:1619-1620 says an adapted program "is callable from a script" — and so does the adapter package's own doc comment ("appearing in mcpx ls, callable from a script", internal/adapter/adapter.go:8-10); against the code as it stands neither is true, and this issue should fix the code rather than the sentence. The narrower defect (fix or retract the claim, and guard that tools/list and the generated client agree) is #102, in flight; this issue is the design it belongs to. Extras are also computed once at MCP-server construction, so there is no live add/remove path for them at all.
No Python or shell runtime anywhere. internal/runner/runner.go:73 resolves deno, bun, node only. Python 3.14.7 and uv 0.9.28 are installed on this machine but mcpx has no notion of them.
Approaches
Approach A — least recommended: a generic "run this command as a tool" escape hatch
{ "name": "sh", "command": "bash", "tools": [{ "name": "run", "params": [{"name": "script"}] }] } — one tool that takes a string and runs it. Or its more honest spelling, a shell server type.
Two lines of config and it covers every bullet in the notes, which is why it is tempting. It is wrong for a reason the adapter package already wrote down (internal/adapter/adapter.go:12-14): "This is deliberately not a general shell escape. A tool here is a named subcommand with declared parameters, so a model cannot invent a command line, and what is reachable is exactly what somebody wrote down." A generic runner discards every property that makes the adapter useful: no schema, so no typed client and nothing for an agent to reason about; no mcpx doctor check, because there is nothing to check; no namespace, no descriptions, no mcpx catalog entry with any information in it. The agent ends up writing shell, badly, with mcpx as a very slow bash -c.
Note also that mcpx already has a better general escape hatch: mcpx exec runs TypeScript with typed clients for everything, preflight diagnostics, artifacts and content-addressed workdirs. A shell escape is a worse version of a feature that shipped.
Approach B — middle: extend the adapter declaration to cover scripts and bare binaries
Keep the declaration model and widen what it can point at:
-
Scripts. An adapter whose
commandis a script file works today if the file is executable. What is missing is aruntimefield, so{ "command": "./tools/report.py", "runtime": "python" }resolvesuv run/python3/bun/deno/bashwithout the author hard-coding an interpreter path. This is a small, self-contained addition and the natural home for the notes'bash/js/ts/pylist. It must be its own resolver, notscript.runtime(internal/settings/registry.go:176), which is about the JS runtime that executes mcpx-generated scripts and means something different. -
Bare binaries with no declaration. For a program that already emits a machine-readable interface there is no reason to write one by hand. Three real sources, in descending order of how often they exist: a program that ships an OpenAPI or JSON Schema document (feed it to the existing
paths.apispath); a program with a--helpthat a generation step — run once, by a human, possibly with a model — turns into a declaration file the user then edits and commits; and the degenerate case, one tool per binary taking an argv array, which is Approach A wearing a hat and should be opt-in and loud.The important design point: generation produces a file the user keeps, not a runtime inference. A declaration inferred fresh on every daemon start makes a tool surface that changes when someone upgrades a CLI, and an agent's context changing because
jqgot a new flag is a bug that will take a day to find. -
Scripts-as-tools from recipes. A recipe already declares typed parameters; publishing each one as an MCP tool is mostly plumbing (
internal/recipes/recipes.go→ a JSON Schema → anExtraor a namespace). This is the highest value-per-line item in the whole issue: the schemas exist, the runner exists, the search path exists.
All correct and all incremental. B's limit is that it leaves everything in the Extra path, so all of it stays absent from mcpx ls, the codegen and scripts, and none of it can be added live.
Approach C — recommended: one internal notion of a tool source, with declaration, plugin and upstream server as three implementations
Adopt the interface docs/proposals.md:427-429 already proposes — "something that can list tools with JSON Schemas and call one" — and make every producer implement it: an MCP server (today's default), a CLI adapter declaration, an OpenAPI document, a recipe, a plugin (sibling issue), and a script or binary through B's widened declaration. Then do the part that makes it matter: register them through the same path as servers, not as Extras.
What that buys, in one list:
| property | today, for an adapter | with a tool source |
|---|---|---|
in tools/list over /mcp |
yes | yes |
in mcpx ls / mcpx catalog / mcpx search |
no | yes |
| in the generated TypeScript, callable from a script | no | yes |
namespaced, with mcpx.namespace/description |
partly | yes |
| live add/remove | no | yes, one path |
pooled, idle-reaped, sharing/scope |
n/a | where it applies |
preflight diagnostics find calls to it (internal/diagnose) |
no | yes |
And the three ways to add one collapse into one mechanism seen from three angles, which is what the notes' "embedding/config/dynamic-add" triplet is asking for:
- config — a declaration on
paths.adapters/paths.apis/paths.plugins/ the scripts search path, or aserversentry. Declarative, reviewable, in git. - dynamic-add — the existing
POST /v1/serversgeneralised: a body that may carry akind(server|adapter|api|script|plugin) and the corresponding fields, writing to the right file and reloading, exactly as it does now for servers. Prefer extending that operation over minting/v1/adapters,/v1/scripts,/v1/plugins: it already does the hard parts — choose a scope, write the file, reload, keep other instances alive — and one table entry ininternal/api/ops*.gomeans one CLI command, one MCP tool and one OpenAPI operation rather than four of each. A new endpoint is justified only if the bodies turn out to have nothing in common, which they mostly do not. - embedding — the daemon holding its own in-process tool source rather than spawning anything: recipes and
/v1op tools are already this. Covered further in the embedding sibling issue.
Recommended because it is the smallest set of concepts that covers every bullet, it retires the "adapter tools are invisible to scripts" gap rather than widening it, and it is the interface the project already wrote down as the right shape and deferred. Do it now precisely because plugins are about to be the fourth producer: adding a fourth Extra list is how this ends up with four half-integrated surfaces.
Ship order that keeps every step useful on its own: (1) recipes as tools, since the schemas already exist; (2) the tool-source interface with adapters and OpenAPI moved onto it, which is where the mcpx ls/codegen win lands; (3) runtime on a declaration, for bash/js/ts/py; (4) generalised live add; (5) generated declarations for bare binaries.
Open questions
- Namespacing collisions: an adapter named
git, a plugin namedgitand an upstream server namedgit. Today adapters flatten toname_tooland servers namespace properly. One rule, and a loud error on collision.
Owns Phase 3: sync, blob gate, and relays #181 collision 4: the tool-source interface defines namespaces and collision rules for every producer. Update checkmarx/ast-github-action digest to 6c56658 - autoclosed #114's tool views, cross-server namespaces and script-backed tools are built on these rules and do not define their own; chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to2901d28#80's plugins are a namespace under them. - Does a CLI adapter belong in the pool? It has no long-lived process — every call is a fresh
exec. It needs to be a namespace without being a pooled server, which is the main piece of real design work in C. - Is a recipe-as-tool automatic for every recipe, or opt-in per recipe? Automatic is friendlier and can flood
tools/listin a repo with fifty scripts. Probably a declaration marker, defaulting to on, with a budget inmcpx catalog. - For bare binaries: is
--helpparsing worth it at all, given how irregular help output is? A one-shot, model-assisted generation into a file the user edits is defensible; a runtime parser is not. - Should a generated declaration record the binary's version, so an upgrade produces a visible "this declaration was written against jq 1.7" warning rather than silent drift?
mcpx adapter checkgeneralises tomcpx source check— is that worth the rename, and what does it do for a plugin (start it andtools/list?) versus a binary (does it exist?)?stdin,json, boolean-as-presence and non-zero-exit-as-result are adapter-specific semantics. Which of them survive into the general interface, and which stay adapter-local?
Ease · Impact · Related
Ease: M for recipes-as-tools and a declaration runtime; L for the tool-source interface and generalised live add; XL for all of it including generated declarations. Impact: high — this is the feature mcpx is for, and the Extra gap currently caps it.
Depends on / related: #80 (a plugin is the fourth tool source, and the reason to unify now), #84 (mcpx as its own upstream, in-process sources), #102 (in flight: the documented-but-false "callable from a script" claim; slice 2 here delivers the capability), #114 (builds on this issue's namespace and collision rules, collision 4), #7 (placeholders/@param spelling is shared with recipes), #6, #76 (/v1 operations without CLI commands — the same parity discipline applies to any new registration verb), #79 (codegen and catalog cost when the tool count grows), #105/#106 (a declaration runtime for Python and shebangs is the same resolution problem), #5.
Security/auth deferred; see tracker.
Activity
developing-today commentedon Oct 28, 2026
CODE-802
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#83 (verbatim, oldest first):
@dezren39 · 2026-09-30T07:21:28Z
Integration pass. Placed in the DAG rooted at #181.
d956689to2901d28#80 chore(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 #81 chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to5f79ad1#82 chore(deps): bump prettier-plugin-toml from 1.0.0 to 2.0.1 #84@dezren39 · 2026-09-30T07:27:56Z
Integration pass -- cross-theme collision. Surfaced only when the themes were put side by side; the authors of these issues could not see each other's work. Recorded at the root in #181.
Namespacing and collision rules are being designed twice
#83's tool-source interface and #114's cross-server namespaces, script-backed tools and generated declarations are substantially the same problem, in two different themes (#173 and #176). Two namespace schemes that must later be merged.
Also on: #114. Nobody currently owns this.
@dezren39 · 2026-09-30T11:05:49Z
Collision ownership (issues pass, 2026-09-30, at 05c78b2). #181 listed these cross-theme collisions with no owner. Each now has one:
Where this issue is the owner, the decision is made here; where it is not, it adopts the owner's decision rather than making its own.
@dezren39 · 2026-09-30T12:12:11Z
Issues pass (2026-09-30, at 05c78b2).
Parent: #173and the Owns Phase 3: sync, blob gate, and relays #181 collision 4 line in the namespacing open question. The tool-source interface defines namespaces and collision rules. Update checkmarx/ast-github-action digest to 6c56658 - autoclosed #114 builds on them and chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to2901d28#80's plugins are namespaces under them.docs/story.md(internal/adapter/adapter.go:8-10). Linked Bump build/buck/preludes/buck2-prelude fromd956689tof12c8d1#102 (in flight) as the narrower defect this issue's slice 2 delivers.serve.go:328-346,story.md:1619-1620and:1626-1630,registry.go:292,doctor.go:230-244,adapter.go:12-14,proposals.md:427-429.d956689tof12c8d1#102 and Bump h2 from 0.3.18 to 0.3.24 in /sources/pavex/examples/skeleton #105/Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106. Added thedesignlabel.@dezren39 · 2026-10-28T21:05:50Z
Triage verdict — confirmed with partial progress; mis-homed in this repo.
Verified against current main (2026-10-28):
dezren39/nix@8c42c2c, origin/main) has no mcpx Go source —pkgs/mcpx/deleted 2026-10-19 (lab-ops-hub container crawler accepts inet_aton IPv4 spellings (127.1, 2130706433, 0x7f.1) — loopback SSRF bypass in validate_crawl_url #468,planning/plans/mcpx-extraction.mdsteps 4–5); mcpx lives solely indeveloping-today/codeatpkgs/mcpx/, consumed here as a pinned source-only flake input.developing-today/code@528886e, origin/main): adapters still do not go through the pool as first-class namespaces.adapterToolsinpkgs/mcpx/internal/cli/serve.go:611-627still converts adapter declarations into[]mcpserver.Extra— the exact mechanism this issue (and chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to2901d28#80) identifies as inheriting the not-in-pool / not-in-mcpx ls/ not-in-generated-TypeScript limitation.mcpx adapter servemode where one adapter is served as its own MCP server ("one adapter is the whole server and the namespace already says which"), and the related script-callability defect Bump build/buck/preludes/buck2-prelude fromd956689tof12c8d1#102 is CLOSED. So one-at-a-time serving exists; the general claim of this issue — scripts, bare binaries, recipes and plugins as pooled, first-class namespaces alongsideserversentries — does not.d956689to2901d28#80/chore(deps): bump stefanzweifel/git-auto-commit-action from 4 to 5 #81/chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to5f79ad1#82 under tracker Phase 3: blob gate is not wired into serve, so blobs are still served to any peer #173 and shares their route.Route: no
small_fixpacket in the nix queue — implementing namespace routing here would recreate the vendored mcpx copy, and a duplicate code filing now would violate the one-issue-per-change rule while code#309 owns the transfer. Keep open here until code#309 re-files it indeveloping-today/code, then close here with that link.agent-note:v1 · agent: repo-queue-worker-shift-nix (lane A) · task 2070 · kind: triage · outcome: confirmed, adapters still Extra-routed at code HEAD (adapter serve is partial progress only); routed to #309 re-home; no nix follow-up packet
dezren39 commentedon Oct 29, 2026
Agent note — triage verdict (queue task 2769): NOT a duplicate; scope PARTIALLY LANDED (adapters became first-class); remainder split into two follow-ups
Dedupe (checked 2026-10-29, code issues, all states): no existing issue covers this subject. Neighbours, none a duplicate: tracker #1885 (extensibility) is the parent; #1933 (tool views, the #114 successor) builds on this issue's namespace/collision rules and does not define them; #1870 defines plugins as one tool source; #1873 is the privileged surface; #1893 (declared-surface guards tracker) is where the "callable from a script" claim class is policed generally.
Scope vs current main (origin/main 42796be) — the issue's slice 2 has substantially landed for adapters:
mcpx adapter serve <name>exists (internal/cli/adapter.go:176): the daemon spawns each adapter as a stdio MCP server — "an upstream server like any other" — so adapter tools reach the pool,mcpx ls, codegen and scripts.docs/story.md:1629-1634now states the script call form (await gitx.log(...)) and a collision rule ("an adapter cannot share a name with a configured server"). The adapter package's doc-comment claim that the issue flagged as false is true for adapters today; theWithExtraspath remains only for /v1 op tools (serve.go:543-546).internal/mcpserver/; recipes remain reachable only via /v1/recipes and intent matching. (slice 3)runtimeon a declaration — no Runtime field ininternal/adapter/adapter.go, so bash/js/ts/py scripts still cannot be declared directly. (slice 4) generalised live add —/v1has servers_add/servers_remove only; no live add for adapters, API specs, or recipes. (slice 5) generated declarations for bare binaries — no evidence in tree. OpenAPI's position on the tool-source interface (slice 2's other half) was not verified either way and is carried into follow-up B as a verify-first item.Follow-ups (split, both enqueued on this queue):
runtimeon adapter declarations for bash/js/ts/py scripts.dezren39 commentedon Oct 29, 2026
Agent note — triage closed (queue task 2769)
runtimeon declarations) = task 3103; mcpx: generalised live add + generated declarations for bare binaries (split of #1875) #2118 (generalised live add + generated declarations for bare binaries + OpenAPI verification) = task 3104.24 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1
event: picked-up
task: 5161 (repo-health-code-pilot, OpenChamber scheduled lane)
state: claimed
verified: #1875 is OPEN. Fresh review confirms its remaining scope is represented by #2118, which is OPEN and carries queue/queued; the latest parent triage comment routes that remainder as queue task 5574. No open PR directly matches #1875. I reviewed user/review: this is already-approved follow-up work and needs no Drew decision.
next: Record the existing route as the outcome; do not enqueue a duplicate follow-up.
dezren39 commentedon Nov 2, 2026
agent-note:v1
event: done
task: 5161 (repo-health-code-pilot, OpenChamber scheduled lane)
state: triaged
verified: #1875 remains OPEN as the parent. #2118 is OPEN with queue/queued, and the existing route is task 5574; no direct open PR matches #1875. user/review was reviewed; no Drew decision is needed.
outcome: No duplicate follow-up was created. Queue task 5161 completed as triaged; #1875 stays open while #2118 work proceeds.
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass)
Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 30 (priority/none). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
Found via: coverage pass 2026-11-03
dezren39 commentedon Nov 3, 2026
agent-note:v1
state: triaged (resume — route unchanged, no new work)
Third triage of this parent; nothing has changed and nothing should be created. Task 5161 (2026-11-02) already established the route and this coverage pass re-queued #1875 anyway.
Fresh evidence at main @
4c75cc808, 2026-11-03:queue/queued+queue/parked.queue/6260-2118-apis-live-add, OPEN, non-draft, authored by dezren39), held by parked task 6260 with a saved ownership gate. Last note 2026-11-03 09:11 UTC.Outcome: triaged, parent stays open while #2118/#4637 proceeds. No Drew decision needed —
user/reviewhere is Muse's lane and the work is already approved.Note for the coverage join: an open parent whose split child is live (open issue + live task/PR) keeps getting a fresh
triagetask on every pass — this is at least the third for #1875. Each costs a full claim/session without changing anything. Treating "parent with a live child" as in-flight would stop the churn. Queue task 7748.Found via: triage packet for #1875 (coverage pass 2026-11-03)