Skip to content

mcpx: anything as an MCP server -- scripts, bare binaries, recipes, plugins, as first-class namespaces #1875

Open

Description

@dezren39

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 command is a script file works today if the file is executable. What is missing is a runtime field, so { "command": "./tools/report.py", "runtime": "python" } resolves uv run / python3 / bun / deno / bash without the author hard-coding an interpreter path. This is a small, self-contained addition and the natural home for the notes' bash/js/ts/py list. It must be its own resolver, not script.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.apis path); a program with a --help that 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 jq got 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 → an Extra or 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 a servers entry. Declarative, reviewable, in git.
  • dynamic-add — the existing POST /v1/servers generalised: a body that may carry a kind (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 in internal/api/ops*.go means 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 /v1 op 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 named git and an upstream server named git. Today adapters flatten to name_tool and 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 from d956689 to 2901d28 #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/list in a repo with fifty scripts. Probably a declaration marker, defaulting to on, with a budget in mcpx catalog.
  • For bare binaries: is --help parsing 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 check generalises to mcpx source check — is that worth the rename, and what does it do for a plugin (start it and tools/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

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    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.


    @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:

    #181 row Collision Owner What the owner decides
    4 namespacing and collision rules designed twice #83 #83's tool-source interface defines namespaces and collision rules; #114's views, cross-server namespaces and script-backed tools are built on those rules and do not define their own.

    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).


    @dezren39 · 2026-10-28T21:05:50Z

    Triage verdict — confirmed with partial progress; mis-homed in this repo.

    Verified against current main (2026-10-28):

    Route: no small_fix packet 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 in developing-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

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 29, 2026
  4. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    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:

    • Adapters are first-class now. 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-1634 now 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; the WithExtras path remains only for /v1 op tools (serve.go:543-546).
    • Still owed, verified absent at 42796be: (slice 1) recipes as individual MCP tools — no recipe presence in internal/mcpserver/; recipes remain reachable only via /v1/recipes and intent matching. (slice 3) runtime on a declaration — no Runtime field in internal/adapter/adapter.go, so bash/js/ts/py scripts still cannot be declared directly. (slice 4) generalised live add — /v1 has 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):

    • A — scripts as first-class tools: recipes as individual MCP tools (opt-in marker + catalog budget per the issue's open question) + runtime on adapter declarations for bash/js/ts/py scripts.
    • B — live add + bare binaries: generalised live add for adapters/API specs/recipes alongside /v1/servers; generated declarations for bare binaries (one-shot generation into an editable file, recording the binary's version); verify OpenAPI namespaces ride the same tool-source interface as adapters, or bring them onto it.
  5. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    Agent note — triage closed (queue task 2769)

  6. 24 remaining items

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    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.

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    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.

  9. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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

  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    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:

    Outcome: triaged, parent stays open while #2118/#4637 proceeds. No Drew decision needed — user/review here 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 triage task 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)

  12. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestsource/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this itemuser/reviewNeeds a user-look: Muse reviews first and handles what it can

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions