Skip to content

mcpx: Roc as a script language, and whether mcpx should be a Roc platform #1964

Open

Description

@dezren39

Relocated from dezren39/darwin-nix#296 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: #175

Summary

Roc is worth an issue for a reason that has nothing to do with "another
interpreter". Roc's defining idea is the platform/application split: an
application is pure, and a platform supplies every effect it is allowed to
have. That is the exact shape of an mcpx script — pure logic whose only effects
are tool calls — and no other language in the runtime list models it natively.

So this issue has two halves, and they should not be confused. The cheap half is
Roc as a script language alongside TypeScript and Python (#105): a codegen
backend and a runner. The interesting half is mcpx as a Roc platform, where
call, readResource, log and emit are not a generated client but the
platform's effect API, and a script is incapable by construction of doing
anything mcpx did not hand it.

The second half is a research bet on a pre-1.0 language. This issue recommends
shipping the first and scoping the second honestly rather than pretending they
are one piece of work.

Original notes

add another for roc support

you can imagine what i might want build out your dreams

Current state

Three JavaScript runtimes, nothing else. runner.Detect picks from
candidates := []string{"deno", "bun", "node"} (internal/runner/runner.go:73)
and runner.Run spawns one. There is no interpreter selection, no shebang
handling and no second codegen backend. #106 is the issue for shebangs and a
--shell family; #105 is Python 1:1 with TypeScript; #175 is the tracker over
both.

The generated client is the whole ergonomic story. internal/codegen emits
TypeScript from the catalog, and that typed surface — tools.<namespace>.<fn>()
— is what makes a script worth writing instead of a pile of mcpx call
invocations. Any new language is a codegen backend first and a runner second,
which is the framing #105 already uses ("a second codegen backend and a second
runner, not a second product").

Nothing in the codebase knows about Roc, and the issue tracker has no Roc
mention at all. This is a greenfield proposal, not a gap.

What Roc actually is, for the record. A pure functional, ML-family,
statically typed language compiling through LLVM. No runtime exceptions, no
null, tags and records with structural typing and full inference. Its
distinguishing feature is that an application declares a platform —
app [main] { pf: platform "..." } — and the platform defines both the type of
main and every effect available. The host is ordinarily written in Rust, Zig
or C and is linked with the compiled application.

The honest status caveat: Roc is pre-1.0, has no stable release channel, and
makes no compatibility promise between versions. Anything built here is
signing up for breakage on a cadence the project does not control. That is a
real cost and it should be stated in the issue rather than discovered in six
months.

Approaches

Approach A — least recommended: let #106 cover it and call it done

Once mcpx exec runs any shebang, #!/usr/bin/env roc works, and Roc is
"supported" in the sense that the process starts.

This is genuinely zero work and genuinely worthless for Roc specifically. The
script gets no typed client, so every tool call is hand-rolled JSON over the
daemon API — in a language whose entire selling point is that the compiler
proves your data handling correct. Using Roc without generated types is strictly
worse than using TypeScript with them. If this is all that ever happens, do not
open the issue; #106 already covers it and no Roc-shaped work is justified.

Approach B — middle, and what to actually ship: a Roc codegen backend and runner, on an existing platform

Treat Roc exactly as #105 treats Python. A roc runtime in the resolver, and an
internal/codegen backend emitting Roc type definitions and call functions from
the same catalog that drives TypeScript. Scripts run with roc run against a
community platform (basic-cli), and tool calls go over the daemon's HTTP API
like any other non-native client.

Roc is an unusually good target for this, which is the argument for doing it
at all:

  • Tool input schemas map onto Roc records with inferred structural types, and
    enum/oneOf schemas map onto tag unions, which is a closer fit than
    TypeScript's structural-but-unsound interfaces and much closer than Python's
    type hints.
  • Roc has no exceptions, so a tool error has to be a value — Result — and the
    compiler will not let a script ignore a failed call. Today a TypeScript script
    can drop a rejected promise and mcpx finds out from the exit code.
  • Schema-to-type generation is a solved shape in this codebase already; the
    backend is mostly a new emitter, not new design.

B stands alone, ships against today's Roc, and does not depend on resolving any
of the hard questions in C.

Approach C — the destination, staged behind research: mcpx as a Roc platform

Write a platform whose effects are mcpx. A script becomes:

app [main] { pf: platform "https://.../mcpx-platform/main.roc" }

main =
    issues = Mcpx.call! "github.listIssues" { repo: "dezren39/nix", state: "open" }
    Mcpx.emit! { count = List.len issues }

What that buys over B is not syntax, it is a property: the capability boundary
becomes the type system
. A Roc application cannot open a socket, read a file or
spawn a process unless the platform exposes it. Today mcpx's sandboxing story is
Deno's permission flags (internal/runner/runner.go:43 notes only Deno has a
permission model worth the name, and bun and node run unrestricted). With a Roc
platform the guarantee is static and total, and it is the same guarantee for
every runtime Roc targets.

It also makes the generated client disappear. There is no emitted tools.ts to
drift from the catalog — the platform is the surface, and a catalog change is a
platform version bump. That retires a whole class of the drift problems #159 and
#145 exist to police.

The blockers, stated plainly:

So: do B. Open C's research as a tracked question — "can a useful mcpx platform
be hosted without owning a Rust toolchain in this repo?" — and revisit when Roc
cuts a stable release. Do not block B on it.

Open questions

  • Is anyone actually going to write Roc here, or is the platform model the only
    real draw? If the latter, the honest framing is a capability-sandbox
    research issue where Roc is one candidate and WASM is another, and this issue
    is a sub-case of that.
  • Does the Roc backend share the schema→type layer with Python (Bump h2 from 0.3.18 to 0.3.24 in /sources/pavex/examples/skeleton #105) and
    TypeScript, or is a third hand-rolled emitter acceptable? Bump checkmarx/ast-github-action from 2.0.19 to 2.0.23 #111 (shared schema
    validation) is the adjacent consolidation.
  • roc run compiles before running. What is the cold-start cost against the
    script cache (Phase 4: joiner with missing pre-join control messages stalls silently #187), and does a compiled Roc script become a cacheable
    artefact keyed by source hash + catalog hash?
  • Roc's effects are explicit (!). Does the generated client expose every tool
    as an effect, or are read-only tools modelled differently? There is a real
    design here about annotating tools as pure/impure that would also benefit the
    TypeScript side.
  • Version pinning: which Roc does a declaration target, and does mcpx doctor
    check it? Same drift problem as a generated adapter declaration's binary
    version.

Ease · Impact · Related

Ease: M for the codegen backend and runner (Approach B), mostly mirroring
whatever #105 establishes for Python; XL and speculative for the platform
(Approach C), with a hard dependency on a non-Go toolchain. Impact: low in
users reached, high in what it would prove — C is the only proposal on the
tracker that makes the sandbox a type-system property rather than a flag set.

Depends on / related: #105 (Python 1:1 — establishes the second-backend
pattern this follows; do that first), #106 (shebangs — Approach A, and the floor
below this issue), #175 (parent tracker: runtimes, languages, dependencies),
#100 and #108 (runtime selection and its sharp edges), #103/#104/#187
(dependencies, import graphs and the cache a compiled platform would use),
#111 (shared schema validation), #158 (release artefacts, if a platform host
ever ships), #84 (embedding — a C ABI would make a Roc host dramatically more
tractable and is the single highest-leverage dependency for Approach C).

Acceptance criteria

  • A written decision splits the two halves: Roc as a script language (codegen backend + runner) vs mcpx as a Roc platform, with a recommendation for each.
  • If the script-language half proceeds: a Roc script can call at least one catalog tool through a generated client surface comparable to the TypeScript one, selected via the existing runner detection.
  • The platform half is scoped honestly (pre-1.0 risk, maintenance cost) with a named trigger for revisiting, rather than silently dropped.
  • Existing TypeScript/Python script behaviour is unchanged.

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#296 (verbatim, oldest first):


    @dezren39 · 2026-10-27T14:29:57Z

    Intake review

    🤖 Agent note · filed

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: repo-fleet-monitor run 2026-10-27T14:14Z
    • Found via: monitor reconciliation (intake enrichment, protocol §5)
    • Task: intake pass — liveness check, labels normalized, acceptance criteria appended to the body
    • Expectation: see the effort label on this issue
    • Links: n/a

    Related: Related: parent #175; #105 (Python parity) and #106 (shebangs) are the sibling script-language work. No merged PR resolves it.

    Risks / notes: Pre-1.0 language risk is real and named in the body. The failure mode to avoid is letting the platform bet swallow the cheap script-language half.

    Suggested approach: Ship/scope the halves separately: evaluate the codegen-backend half against the existing runner, and record explicit revisit triggers for the platform half.

    Liveness: searched merged PRs and recent fleet records this run — no merged PR explicitly resolves this issue, so it stays open. Marked meta/enriched.


    @dezren39 · 2026-10-28T19:25:30Z

    Triage verdict (task 2322): DEFERRED BY ITS OWN DESIGN — not actionable yet; stays open as the tracked research question. No fix packet enqueued, deliberately.

    Verified at developing-today/code main 35726e8e / dezren39/nix main efd3161:

    • Nothing has changed since filing: no Roc anywhere in mcpx (no codegen backend, no runner candidate, no docs mention beyond this proposal's own framing), and scripts remain the TypeScript/JavaScript runtimes.
    • The issue's own sequencing gates the work: Approach B (Roc as a script language) 'mirrors whatever Bump h2 from 0.3.18 to 0.3.24 in /sources/pavex/examples/skeleton #105 establishes for Python; do that first' — and Bump h2 from 0.3.18 to 0.3.24 in /sources/pavex/examples/skeleton #105 (Python 1:1) is still OPEN, as is Bump h2 from 0.3.19 to 0.3.24 in /sources/pavex/examples/realworld #106 (shebangs, Approach A, 'the floor below this issue'). The second-backend pattern B would follow does not exist yet.
    • Approach C (mcpx as a Roc platform) carries the issue's own named revisit trigger — 'revisit when Roc cuts a stable release' — plus a hard dependency on a non-Go (Rust/Zig/C) host toolchain this repo does not own. There is no in-repo evidence that trigger has fired; Roc remains pre-1.0 with no compatibility promise, exactly the caveat this issue states.
    • The issue's first acceptance item asks for a written decision splitting the halves with a recommendation for each: the issue body already contains it (do B after Bump h2 from 0.3.18 to 0.3.24 in /sources/pavex/examples/skeleton #105; scope C as a tracked research question — 'can a useful mcpx platform be hosted without owning a Rust toolchain in this repo?'). This comment confirms that split stands at current main.

    Route: this issue remains the tracker for both halves. Unblock order: #105 (and #106) first → then a B packet can be scoped against the established second-backend pattern; C revisits on Roc stable or on #84 (C ABI embedding) landing. Labelling status/blocked with those blockers named here, per fleet convention.

  3. added
    source/humanFiled directly by a human
    priority/lowNice to have; do when convenient
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    queue/queuedHas a queued task in the Supabase agent queue
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  4. 26 remaining items

  5. added and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 4, 2026
  6. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor (repo-fleet-monitor)
    • Session / ID: task 7817 · repo-fleet-monitor run 2026-11-03 18:12 CST
    • Found via: monitor reconciliation
    • Task: Claim evaporated — task 7817 returned to queued and unclaimed in repo-health-code-pilot after the holding run ended without a terminal verb (checkpoint stage: handed_off). Labels reset to the truth: queue/queued + status/needs-triage. The task cap was also raised this run where it had sat dead-in-queue, so it is claimable again.
    • Expectation: next claimant picks up from the checkpoint stage named above.
  7. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 4, 2026
  8. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (triage; issue stays open)

    Fresh verification

    At developing-today/code main d0a5b03 (2026-11-03), #1964 is OPEN with no open PR addressing it. PR #2591 landed the codegen Language dimension and typed Python client; PR #4991 merged 2026-11-03 and added Python script execution/client installation plus named .py discovery. #2167 is CLOSED via merged PR #2611. Thus the prior missing-Python-runner blocker is stale.

    Issue #1917 remains OPEN. Its latest completion note says Python preflight typechecking (pyright/mypy), script.language / script.pythonRuntime settings, Python diagnostic scanning, and docs parity remain. Current main confirms there is no script.language setting yet (internal/runner/language.go says dispatch uses shebang/extension), no Python implementation in internal/preflight or internal/diagnose, and docs/runtimes.md still marks the Python row pending. These remaining parity phases are the current Approach B gate.

    Routing

    Expectation: keep #1964 OPEN with status/blocked; re-evaluate Approach B when #1917 closes. No follow-up task was enqueued deliberately.

    Links: #1917, #2591, #4991, #2167, #2611, #1919, #158; https://github.com/roc-lang/roc/releases

  10. added
    status/blockedQueue or user escalation state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/lLarge: multi-day or open-ended; expect staged check-inskind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/lowNice to have; do when convenientsource/humanFiled directly by a humanstatus/blockedQueue or user escalation statetask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions