Repository navigation
Description
Relocated from dezren39/darwin-nix#295 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: #83
Summary
Writing an adapter declaration by hand is the tax on every binary somebody wants
to reach. #83 puts "generated declarations for bare binaries" last in its ship
order (step 5) and #114 sketches the deterministic parse inside a much larger
view/generator design. Both are right and both are large. This issue is the
narrow, shippable front half that neither owns: mcpx adapter import, a
command that writes a declaration file the user keeps, from sources that are
already machine-regular, with no model anywhere in the loop.
It also settles the thing people actually ask for first — "can I just declare
git and pass subcommands through?" — as --bare: supported, opt-in, and loud
about what it costs.
Original notes
is there an issue for automating binary to mcpx (outside the ai version,
using idk, --help or something) can i just saygitis an mcp with a command
that accepts the subcommand and flags and args? not ideal but fast? ideally
with some easy commandadapter import --bareor something. go can exec a
program so that it cant shell escape right?
Current state
Declarations are hand-written, and that is the whole input path.
loadAdapters (internal/cli/adapter.go:22) reads paths.adapters
(internal/settings/registry.go:298), unmarshals AdapterFile or a bare array,
validates and de-duplicates by name. There is no writer — nothing in the binary
ever produces a Spec.
mcpx adapter has four subcommands and none of them generate. CmdAdapter
(internal/cli/adapter.go:66) dispatches list, check, call and tools.
The doc comment explains the subcommand-over-flag choice: "the three things
somebody wants -- what is declared, does it work, run one -- are different
questions". Generation is a fourth different question.
The execution model is already argv-only. Spec.Call
(internal/adapter/adapter.go:206) builds an argv and runs
exec.CommandContext(cctx, s.Command, full...) at
internal/adapter/adapter.go:237. No shell is interposed at any point, so there
is no word-splitting, no globbing, no ;, no $(), no |. This matters for
--bare below and is the reason it is defensible at all.
Both parent designs already want this and both defer it. #114 contains the
most concrete plan on record: run --help, help, -h, --help=full, parse
usage lines, indented flag blocks and subcommand tables, noting that
"Cobra/clap/argparse produce highly regular help text" and that a model's job
reduces to descriptions and ambiguous types. #83 asks the sceptical question
back: "is --help parsing worth it at all, given how irregular help output
is?", and separately asks whether a generated declaration should record the
binary's version. This issue exists to answer both with a shipped command.
The degenerate case is on record as acceptable-but-unloved. #83 Approach A
rejects a generic shell escape, and its Approach B calls the argv passthrough
"Approach A wearing a hat", to be "opt-in and loud". That is a specification,
not a rejection, and nothing implements it.
Approaches
Approach A — least recommended: infer the declaration at runtime
Parse --help on daemon start and synthesise tools on the fly, no file.
Zero friction and badly wrong, for the reason #83 already recorded: a tool
surface that changes when somebody upgrades a CLI. An agent's context silently
shifting because Homebrew bumped jq is a bug that costs a day to find and
cannot be reproduced from the repository. It also makes mcpx catalog budgets
unpredictable and makes every generated client non-reproducible. Rejected.
Approach B — middle: --help parsing only
Implement exactly #114's plan and stop. Useful, and it is most of the value, but
it leads with the least regular source available and leaves the "I just want
git now" request unanswered, which is the request that actually recurs.
Approach C — recommended: a source ladder, best-evidence-first, emitting a file with provenance
mcpx adapter import <binary> [--out FILE] [--source auto|completion|man|help|bare]
mcpx adapter import --completion <file> [--shell bash|zsh|fish] [--name NAME]
Sources are tried in descending order of regularity and the winner is recorded.
-
Shell completion scripts —
--source completion. Cobra, clap and
argparse all emit bash/zsh/fish completions, and these are generated data
structures, not prose: fish'scomplete -c tool -l flag -d 'desc'lines are
almost a declaration already, and zsh_argumentsspecs carry flag, argument
type and description in a fixed grammar. This is the highest-fidelity source
that exists for the widest range of programs and Update checkmarx/ast-github-action digest to 6c56658 - autoclosed #114 does not mention it.
Lead with it.Two ways in, and both are required.
- Extract from the binary.
import <binary>probes for the program's own
completion output —tool completion {bash,zsh,fish}, Cobra's hidden
__complete, clap's--generate-completion. Convenient, and it only works
when the program ships a generator and names it conventionally. - Accept one as input.
import --completion <file>takes a completion
script directly. This matters more than it looks:- Distributions already ship them, for programs that have no generator
at all —/usr/share/bash-completion/completions/*,
/usr/share/zsh/site-functions/_*,$(brew --prefix)/share/..., and
Nix's$out/share/bash-completion. These are often hand-written by
packagers for old tools whose--helpis worst, which is exactly the
case extraction cannot reach. - It makes the parser testable without the binary present. A corpus of
completion files is a fixture; probing a binary is an integration test
that needs the program installed. This alone justifies the input path. - It lets a user fix the source rather than the output — hand-edit a
completion script and re-import, instead of hand-editing generated JSON
and losing it on the next import. - It covers the program mcpx cannot run: a different architecture, a
container image, a binary you have the completion for but not the tool.
- Distributions already ship them, for programs that have no generator
So the extraction path is sugar over the input path — probe the binary, then
feed whatever came back to the same parser. Build the file parser first; it
is the part with the logic, and the testable one. - Extract from the binary.
-
A published machine interface —
--source spec. If the program ships an
OpenAPI or JSON Schema document, this is not an adapter problem at all: hand
it topaths.apis, which already exists.importshould detect this and
say so rather than producing a worse declaration. -
man pages —
--source man.SYNOPSISandOPTIONSare a fixed troff
structure, andmandoc -T markdowngives a stable intermediate. Less
complete than completions, more regular than help text. -
--helptext —--source help. Update checkmarx/ast-github-action digest to 6c56658 - autoclosed #114's plan, as the fallback it should
be rather than the lead. -
--bare— the escape hatch, below.
Emit a file, never a live inference, which is #83's non-negotiable. The
emitted file carries provenance as a comment header and machine fields: the
source used, the binary's resolved path, its --version output, and the date.
That answers #83's open question directly — on a version mismatch mcpx adapter check says "this declaration was written against jq 1.7, found 1.8" rather than
drifting silently. Making check do that comparison is part of this issue.
--bare, specifically. One tool per binary taking args: string[], plus
optional stdin:
{ "name": "git", "command": "git", "bare": true,
"description": "git, unrestricted — argv passed through" }To answer the question in the notes directly: yes, Go's exec makes this
meaningfully safer than a shell escape. exec.CommandContext takes an argv
slice straight to execve; there is no /bin/sh -c anywhere in
internal/adapter, so a model cannot inject ;, $(), backticks, globs or
redirections. It is an argv hole, not a shell hole, and that is a genuinely
smaller one.
It is still a real hole, and the declaration should say so:
- It gives the model
gitwith every subcommand, includingpush --force,
configand anything with--exec/-cthat reaches a shell itself. The
sandbox is only as good as the binary's own argv surface, andgit -c core.pager='sh -c ...'is a shell escape that mcpx cannot see.--bareon
a program with an argv-level exec escape is equivalent to Approach A, and
the docs must say which common binaries those are. - No schema beyond "array of strings", so there is nothing for an agent to
reason about and nothing formcpx doctorto check past existence.
So: opt-in per declaration, never a default, never what bare import produces
without the flag; rendered in mcpx adapter list with a visible bare marker
next to ok/missing; and description required when bare is set, so
the author has to state the intent. A settings gate (adapters.allowBare,
default off) is worth considering so an organisation can forbid it centrally.
Ship order, each step useful alone: (1) --bare plus the list marker and the
required description — this is a day and answers the recurring request; (2)
--source help, since #114 already designed it; (3) provenance fields and the
check version comparison; (4) completions, parser first and extraction after;
(5) man.
Open questions
- Does
importwrite topaths.adapters' first entry, or to stdout by default?
Writing a file by default is friendly and surprising; stdout with--outis
unixy and makes the common case two commands. Leaning stdout-by-default with a
printed hint. - Extraction needs the program's completion output, which means guessing a
subcommand (completion,__complete,--generate-completion). Acceptable
to probe, or does it need a declared hint? - Should
importalso search the system completion directories for a matching
file before probing the binary —/usr/share/bash-completion/completions/<n>,
zshsite-functions, the Homebrew and Nix prefixes? That turns the common
case intomcpx adapter import jqfinding a packager-written completion with
no probe at all, and it is the strongest version of this feature. - Which shell's completion is the best parse target when several are available?
fish is the most declarative and the easiest; zsh carries the most type
information; bash is the most widely shipped and the least structured.
Probably parse fish or zsh where present, bash as a last resort. - Should
importrun the binary at all? It must, for--help/--version. On an
untrusted binary that is already an execution. Probably fine — you are about
to expose it as a tool — but it deserves a sentence in the docs. - Re-import of an edited declaration: merge, or refuse and emit beside it? Merge
needs a notion of which fields are hand-edited. Refusing is honest and
annoying. - Does
--barebelong inSpecas a field, or is it a distinctkindonce chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to36464cf#83's
tool-source interface exists? Field now, revisit under chore(deps): bump build/buck/preludes/buck2-prelude fromd956689to36464cf#83. - Does a bare tool get a
cwdparameter?gitwithout a working directory is
nearly useless, andSpec.Diris fixed at declaration time.
Ease · Impact · Related
Ease: S for --bare and the list marker; M for --source help and
provenance; M for the completion-file parser and S for extraction on top of it;
M–L for man. Impact: high — this is the
difference between "mcpx can wrap any CLI" as a property of the design and as a
property somebody actually gets without an afternoon of JSON.
Depends on / related: #83 (owns the tool-source design; this is its step 5
pulled forward, and it answers two of its open questions), #114 (owns generators
that emit declarations; its --help plan is this issue's step 2), #102 (an
adapted binary is not script-callable — an imported one inherits that defect
until #102 lands), #106 and #105 (a declaration runtime for scripts is the
adjacent resolution problem), #107 (a flake ref as a program — another source a
declaration could point at), #113 (mcpx doctor/operation surface, where the
version-drift check surfaces).
Acceptance criteria
mcpx adapter import <binary>writes a declaration file the user keeps, generated deterministically from completions, man pages, and/or--helpoutput — no model in the loop.mcpx adapter import --bare <binary>produces a pass-through declaration and prints a loud warning about what pass-through costs (no per-subcommand validation).- Generated declarations load through the existing
loadAdaptersvalidation/dedup path unchanged, and a generated declaration for a well-known binary (e.g.git) passesmcpx adapter check. - Sources that are not machine-regular fail with a clear error naming what could not be parsed, rather than emitting a partial declaration silently.
Activity
developing-today commentedon Oct 28, 2026
CODE-887
dezren39 commentedon Oct 28, 2026
History carried over from dezren39/darwin-nix#295 (verbatim, oldest first):
@dezren39 · 2026-10-27T14:30:18Z
Intake review
🤖 Agent note · filed
Related: Related: parent #83 (generated declarations are step 5 of its ship order) and #114 (the larger view/generator design) — this issue is deliberately the narrow front half neither owns. No merged PR resolves it.
Risks / notes: Deterministic parsing of
--help/man/completions varies wildly across binaries; the--bareescape hatch must stay loud so a pass-through declaration is never mistaken for a validated one.Suggested approach: Build the import pipeline for one regular source first (completions), prove it on a well-known binary through
adapter check, then add man/--helpsources and--bare.Liveness: searched merged PRs and recent fleet records this run — no merged PR explicitly resolves this issue, so it stays open. Marked
meta/enriched.45 remaining items
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 40 (priority/normal). 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 · triaged (task 8553, lane code-k, direct)
Triage verdict — still live, partially shipped, remainder gated on issue #2118.
Verified at current main
32d47f73b691(2026-11-03T20:10Z),pkgs/mcpx:--bare) is DONE.CmdAdapterdispatchesimport(internal/cli/adapter.go:82);adapterImport(internal/cli/adapter.go:233) implements--barewith a required--description, stdout-by-default with--out FILE, atomic file write, and the loud stderr warning (bareImportWarning) naming the argv-escape cost.Spec.Bareexists (internal/adapter/adapter.go:57),adapter listrenders thebaremarker, and tests exist (internal/adapter/bare_test.go,internal/cli/adapter_import_test.go). Acceptance criterion 2 is met; criterion 3 is met for bare declarations (they build throughSpec.Validateand load via the unchangedloadAdapterspath).--bareimport is refused loudly — no silent partial declaration (criterion 4's spirit) — with the reason recorded in code: "declaration generation from --help, completions and man pages is mcpx:mcpx adapter import— a declaration generated from completions, man and --help, and a loud --bare for the rest #1962 steps 2-5, pending reconciliation with mcpx: generalised live add + generated declarations for bare binaries (split of #1875) #2118". No provenance fields exist onSpecyet, so step 3'scheckversion comparison is also unimplemented.--source help), step 3 (provenance +checkversion comparison), step 4 (completion-file parser, then extraction), step 5 (man). No duplicate fix packet is enqueued from this triage — the remainder is already owned by mcpx: generalised live add + generated declarations for bare binaries (split of #1875) #2118's task and the in-code gate.d956689to36464cf#83 / Update checkmarx/ast-github-action digest to 6c56658 - autoclosed #114 in this issue's body are historical dezren39/nix numbers from before the 2026-10-28 relocation; in this repo those numbers are unrelated closed issues. Follow issue mcpx: generalised live add + generated declarations for bare binaries (split of #1875) #2118 and this issue, not those numbers.Issue stays OPEN. Status: triaged.