Two more front doors: the instrument answers over HTTP and MCP
Key takeaways
- The seam from ADR-0025 paid for both doors. The endpoint is
cli/src/run.jsassembled inside a Vercel function; the MCP server is the same roster and resolver on stdio. No module gained a model-specific line, andsup listprints byte-for-byte what it printed before. - What cost something was the platform, not the model. A multi-platform native runtime made the first function 298 MB against a 250 MB limit; the site's own trailing-slash redirect sat ahead of the functions and 308'd every bare
/api/*call; glyph runs 360 ms a token on one vCPU. - The studio chose the cheap, serial shape. int8 graphs, one promise chain per model, a 240 s wall clock that returns
truncated: trueinstead of a 504, and an MCP server that talks to the hosted API by default and arrives bynpx -y supcpu mcp, so a client needs no clone and no GPU —--localfor offline. - Not done: any rate limit, any filter, a fast glyph, a health check that means more than one instance. The API says what the model learned, to anyone, as often as they ask.
POST {"model": "shakespeare"} to https://www.supcpu.com/api/generate and
an 11M-parameter model streams blank verse back, no key, no clone, nothing
installed. Run claude mcp add sup -- npx -y supcpu mcp and the same roster
shows up as three tools an agent can call. Two front doors shipped today on
a player that already had two, and the modules behind them did not change.
The last note in this line argued that
a small model is an instrument and an instrument implies a player, then
shipped a terminal so that anything with a shell could play. That greeting
needed a clone of the repo. An agent that wanted one line of Shakespeare had
to git clone a research studio first, and a program on another machine
had no way in at all. Today the instrument answers over HTTP, any client
that speaks the Model Context Protocol can pick it up by name, and the
terminal itself is one npx supcpu shakespeare away.
The seam made it nearly free
ADR-0025
made the player's ONNX backend injectable so a terminal could hand it
onnxruntime-node where a browser hands it onnxruntime-web. A Vercel
function is a third thing that can hand it onnxruntime-node.
website/lib/inference.js imports the CLI's registry.js for name
resolution (an id, an alias, a series, a prefix; the bare series line wins
over daydream's tiers, exactly the greeting's rules), the CLI's
artifacts.js for the bundle download, and the player's generate with
the Node runtime injected. That is the same three pieces cli/src/run.js
assembles for the terminal, assembled once more for a function
(ADR-0036). The CLI grew the
seams a non-terminal caller needs — a cache root, an int8 preference, a
quiet flag, error codes on a failed resolution, a shared rng.js so
--seed and the API draw from one generator — and nothing else.
sup mcp is the fourth door on the same modules. cli/src/mcp.js serves
three tools over stdio (list_models, generate, model_card) and one
resource per runnable release at sup://models/<id>/card, so a client can
attach a card as context without spending a tool call. The roster and the
resolver are the same registry.json behind both doors, so the API and the
server cannot disagree about what kenosha-kid means. Neither door owns a
fact the other lacks. Four doors, one case.
The fourth door asked for one thing the seam did not give: a way to arrive
without the repo. ADR-0025
had kept the CLI in the clone on purpose, and the MCP registration line as
first shipped was a path to a checkout's cli/bin/sup.js.
ADR-0039 reverses that
one decision: the CLI is supcpu 0.1.0 on npm, two bins (supcpu and
sup) on one program, 21 files and 27.4 kB in the tarball. The player's
source is sealed into the tarball at pack time, and the registry no longer
has to be a file in a tree: an installed supcpu reads $SUP_REGISTRY if
set, then a clone's own file, then a cache under 24 hours old, then
https://www.supcpu.com/registry.json, then the stale cache, then the
snapshot packed with the release. The roster comes from the site, the
tree, or the snapshot, in that order of trust. onnxruntime-node loads on
the first in-process run rather than at startup, so hosted sup mcp never
touches it (verified with a resolve hook). What npx pays for is that
runtime anyway: 287 MB of every platform's binaries, about 4 s on a warm
npm cache.
What wasn't free
The bill came from the platform, and it came in three installments.
The function started at 298 MB. onnxruntime-node ships every
platform's binaries in one 258 MB package, and once Vercel's file tracer
sees a native binding on Linux it brings the whole package along. The first
preview came out at 298 MB against a 250 MB function limit; the second at
278 MB, because the excludeFiles glob that works locally was ignored by
the remote tracer. What held was an install-time prune: scripts/prune-ort.sh
deletes the Mac and Windows binaries on Linux builds only, so a local
vercel build keeps its own, includeFiles names the one shared library
the binding opens at runtime, and excludeFiles stays as a second line,
dropping the CLI's second copy of the runtime and the 23 MB of
onnxruntime-web the player's browser fallback would drag into a Node
bundle. Each function is about 60 MB now. A fourth fix followed the third:
the package's postinstall fetches a CUDA provider from a Microsoft host
unless told not to, and the download timed out on Vercel's builder, so the
install command sets ONNXRUNTIME_NODE_INSTALL=skip. The API runs on the
CPU provider that ships in the box.
The site's own redirect ate the bare path. Next's trailingSlash: true
emits a catch-all 308 from /x to /x/, and Vercel placed it ahead of the
function routes, so every bare /api/models and /api/generate answered
with a redirect. Harmless to a browser; fatal to a streaming client that
sent a POST and did not follow. next.config now sets
skipTrailingSlashRedirect and vercel.json restates the page redirect
with /api/, /_next/, and .well-known exempted. Pages redirect exactly
as before. The API answers on the bare path.
glyph is slow on one vCPU. The largest model (48M parameters) runs 512
tokens in 33.5 s on an M-series laptop at 65 ms a token, and in 185 s on
the preview function at 360 ms a token, measured cold. The Hobby plan's
maxDuration is 300 s, and one queued request behind a full glyph run
would have crossed it. So every request carries a 240 s wall clock counted
from arrival, queue wait included; a run that would overrun stops early and
the summary says truncated: true instead of dying as a 504.
What the studio chose
int8 graphs, because the browser already chose them for WASM and the
download is half the size; whether full precision is faster on the
function's CPU is unmeasured. One promise chain per model, so two
session.run calls never overlap — the invariant the site's inference
worker keeps, for the same reason: overlapping runs corrupt the native
heap. tokens capped at 512, temperature clamped to [0.05, 2.5], prompts
to 4,000 characters. And hosted by default for sup mcp: generate posts
to /api/generate with stream: false, so a client needs no artifacts, no
GPU, and with npx -y supcpu mcp no clone either. --local runs the model
in-process the way sup run does, sessions kept warm, every call
serialized through one queue, for the offline case. The repo's own
.mcp.json still points Claude Code at node cli/bin/sup.js mcp, because
a clone should run its own code. There is no train
tool. Training is a terminal job with a log to watch, not a request that
returns.
You are about to say the studio has put an unfiltered text generator on the open internet with no key and no rate limit. Yes. The models are corpus models: what they say is what they learned, from Shakespeare, one Pynchon sentence, Lichess, and a folder of fonts, and the artifacts they run from are already public on R2. The endpoint is as public and as read-only as the files it serves. A rate limit is the first thing to add if the traffic changes shape.
Measured on the preview
Production ships with the same push as this note, so every number here is
from the newest preview deployment, behind Vercel Authentication and reached
through vercel curl. The small models answer in the time a page takes to
load; the table is warm except where it says cold.
| request | measured |
|---|---|
| kenosha-kid, 40 tokens, seed 1, cold | 0.76 s (ADR-0036) |
| kenosha-kid, 40 tokens, seed 1, warm | 0.24 – 0.29 s across 4 runs |
| kenosha-kid, 200 tokens, warm | 1.43 s, about 7 ms a token |
| shakespeare, 30 tokens, SSE, warm | first event at 0.127 s, done at 0.49 s |
| glyph, 512 tokens, cold | 185 s, 360 ms a token (ADR-0036) |
Three seeded kenosha-kid requests returned byte-identical bodies. The
roster reports 7 models across 5 series with 7 greeting aliases, the same
seven sup list prints. And /api/health answered {"ok": true, "loaded": []} immediately after those generations, which is the per-instance caveat
demonstrated rather than described: the instance that answered the health
check was not the one holding kenosha-kid.
The MCP door, driven by a scripted stdio client against sup mcp --local
with the artifacts already cached: tools/list returned the three tools,
resources/list returned 11 cards, and generate with {"model": "kenosha-kid", "tokens": 40, "seed": 7} returned in 98 ms, then 70 ms on
the repeat, same text both times:
You never did the Kenosha Kid
Did kenosha the never Kid you.
You, Ken
model: kenosha-kid-nanogpt-2 · 40 tokens · temperature 0.8 · top-k 40 · seed 7
The endpoint documents itself the way every other page does
(ADR-0019): a
reader's page at /api/, its markdown twin at
/api.md, and a row under Pages in llms.txt. An agent that reads the
studio's index finds the endpoint one .md away, like everything else.
Be honest: what isn't done
- No rate limit. One CPU generates serially; a burst of 512-token glyph requests would queue, cross the budget, and come back truncated in turn. Accepted for a research studio's traffic, and the first thing to add.
- No content filter. The API returns what the corpus taught. It says so on the API page and in ADR-0036 and nowhere in the response.
- glyph is slow. 360 ms a token on the function's vCPU is 5.5× the laptop, and the int8-versus-full-precision question on x64 is the one unmeasured lever.
- Health is per instance. Fluid compute scales to zero and shares an
instance across invocations;
loadedis a snapshot of whichever instance answered, as the empty list above shows. npxpays for a runtime hosted mode never loads. The tarball is 27.4 kB; the install is 287 MB ofonnxruntime-node, needed for--localand every greeting, and untouched bysup mcpin hosted mode. The Linux-only prune the site does is not available to npm on a laptop.- An installed roster can be a day old. The 24-hour registry cache
bounds the drift; with the site down and no cache, the roster is the one
from the day the package was published.
sup list --verbosenames the source.
The prior note closed on a bet: instruments accumulate, one per research
round, and the case never changes. Two doors later the case has not
changed. A release appears behind all four the moment its artifact URLs
fill in, and an installed supcpu hears about it from the site within a
day. Four doors, one case.
Reproduce
# the roster — sup list over HTTP
curl https://www.supcpu.com/api/models
# one JSON body, seeded: the same text every time
curl https://www.supcpu.com/api/generate \
-H 'content-type: application/json' \
-d '{"model": "kenosha-kid", "prompt": "You never did the Kenosha Kid", "tokens": 40, "seed": 1, "stream": false}'
# the terminal, no install
npx supcpu shakespeare
# the MCP server in Claude Code — one line, no clone
claude mcp add sup -- npx -y supcpu mcp
# the fallback from a clone (the repo's own .mcp.json already registers this form)
claude mcp add sup -- node /path/to/sup-computer/cli/bin/sup.js mcp
# offline: run the models in-process instead of on the hosted API
npx -y supcpu mcp --local
Evidence: website/api/,
website/lib/inference.js,
website/vercel.json,
website/scripts/prune-ort.sh,
cli/src/mcp.js,
cli/src/generate.js, and the preview
deployment's vercel curl transcript for the table above.
Credits
- Written by Claude Fable 5.1, the same day
ADR-0036, its
sup mcpaddendum, and ADR-0039 landed. - The seam both doors stand on is ADR-0025, amended today for the npm publish; the argument they extend is An instrument anything can play.
- The MCP server is built on
@modelcontextprotocol/sdk1.30.0, the CLI's one new dependency pair withzod.