Back to Browse

Nanoodle MCP Server

Developer ToolsLow Risk10.0MCP RegistryLocalRemote
Free

Server data from the Official MCP Registry

Your saved nanoodle workflow graphs as MCP tools — BYOK or pay per call in Nano (x402)

About

Your saved nanoodle workflow graphs as MCP tools — BYOK or pay per call in Nano (x402)

Remote endpoints: streamable-http: https://mcp.nanoodle.com/mcp

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (1 strong, 1 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry. 1 finding(s) downgraded by scanner intelligence.

10 tools verified · Open access · 2 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

file_system

Check that this permission is expected for this type of plugin.

HTTP Network Access

Connects to external APIs or services over the internet.

env_vars

Check that this permission is expected for this type of plugin.

Shell Command Execution

Runs commands on your machine. Be cautious — only use if you trust this plugin.

What You'll Need

Set these up before or after installing:

Your nano-gpt.com API key — every tool call spends from this key's balanceRequired

Environment variable: NANOGPT_API_KEY

How to Install & Connect

Available as Local & Remote

This plugin can run on your machine or connect to a hosted endpoint. during install.

Documentation

View on GitHub

From the project's GitHub README.

nanoodle-mcp

Build a multi-model media pipeline visually at nanoodle.com — then hand the whole pipeline to your agent as ONE typed tool.

Skill or MCP? Running (or designing) a single workflow? The nanoodle skill is one command and no setup. This server is for when you've built up a folder of saved graphs and want each one exposed to your agent as its own tool.

Point this MCP server at a folder of noodle-graph.json saves from the nanoodle editor and every graph becomes a callable tool with a derived input schema — in Claude Code, Grok, Claude Desktop, Cursor, VS Code, Windsurf, or anything else that speaks the Model Context Protocol. It speaks stdio to your own agent by default, or HTTP to everyone with serve mode — including charging per call in Nano, so strangers' agents can pay to run your noodles with no account anywhere — or selling runs for native NEAR, custom workflows included.

No middleman server, no telemetry — and with wallet mode, no account either. The MCP implementation here is hand-rolled (stdio + streamable HTTP, JSON-RPC 2.0 — small enough to read). Two runtime dependencies: nanoodle (MIT), the zero-dep workflow executor that does all the heavy lifting, and nanocurrency (GPL-3.0) for signing Nano blocks — loaded only on the x402 paths, see Licensing. Your NanoGPT API key goes straight from your machine to nano-gpt.com; it is never logged and never appears on stdout.

NEAR HACKATHON entry

Built at NEAR HACKATHON (2026-11-01 to 2026-11-14), track agents (Agent Economy). Only the NEAR integration described here is the entry; the rest of this README documents the product it was added to.

What it is. nanoodle-mcp is an MCP server that runs nanoodle workflow graphs (multi-step pipelines of NanoGPT text, image, video, audio and 3D models) as tools. In the hackathon it gained a NEAR payment rail: an AI agent composes its own workflow, gets an itemized price for free, pays with one native NEAR transfer, receives the result, and gets the unused part of its deposit back on chain. A second piece, a worker for NEAR Agent Market, sells the same workflow runs as market jobs.

Problem. An agent that wants to run a paid multi-model pipeline needs an account and an API key at the provider, and cannot tell what a multi-step run will cost before it starts. A fixed price per call means the buyer overpays or the seller loses money on expensive runs.

How NEAR fits. NEAR carries the payment and the refund. The quote comes back inside the MCP tool answer: a payment id, the receiving account, an exact yoctoNEAR amount and a 30-minute expiry. The agent sends one native Transfer. The server reads that transaction from NEAR RPC at FINAL finality, checks the receiver, the exact amount, the single action, the timing and that the hash was not used before, runs the workflow once, and signs a refund transfer of the deposit minus the actual cost. Finality in 2–3 seconds and gas of about 0.000045 NEAR per transfer make a deposit and refund per call practical. This is not x402: there is no HTTP 402 and no facilitator, only an in-band MCP quote paid by a plain transfer. Design and threat model: docs/near-payments.md.

Status.

  • Offline: the whole payment flow passes against a local NEAR chain stand-in and a local NanoGPT stand-in (35 checks), and the market worker passes against a local market stand-in that checks every request and answer against the market's OpenAPI file (30 checks).
  • Real testnet, read-only: the transaction checker and the server's own payment verifier read a real testnet transfer from public RPC and pass (step 4 below).
  • Not done yet: this code has not moved real NEAR. The first real testnet payment and refund is the maintainer-run step 5 below. The market worker has not been run against the live market. There is no hosted NEAR-mode endpoint; the hosted server at mcp.nanoodle.com is the Nano-paid mode.
  • NEAR mode first ships in npm version 0.7.0. Until npm view nanoodle-mcp version shows 0.7.0 or later, run it from a clone as below.

Try it: do X, see Y. Node 22.23.4+, 24.22.1+ or 26.12.1+. Steps 1–3 need no network after npm ci, and no keys or funds.

git clone -b near-hackathon https://github.com/nanoodlecom/nanoodle-mcp && cd nanoodle-mcp && npm ci
DoSee
1. npm run simulate:nearThe real server binary starts in NEAR mode against local stand-ins. An agent prices a custom text → llm → image graph, gets a quote, pays one transfer, receives the image and a receipt, and the rest of the deposit is refunded. Then replay, a reused transaction, an unsafe node, an unpriceable model, underpayment, a late payment, a failed run and a server killed mid-run and mid-refund. Ends PASS: all 35 checks passed.
2. npm run simulate:marketThe Agent Market worker runs as a child process against a local market stand-in: a normal job, an unsafe graph, an over-budget graph, a malformed brief, a kill and restart mid-run, and a lost submit answer. Ends 30/30 checks passed.
3. npm testThe full offline test suite. Ends with ℹ fail 0 (705 tests when this was written).
4. node scripts/near-check-tx.mjs --network testnet --rpc https://archival-rpc.testnet.fastnear.com --merchant tx-bench-buddy.testnet --tx 7wg9AhBTJvKoLCTvdwACzxjLdoPACFXV9hKD12aAxYZp --sender tx-bench.testnet --amount-yocto 1Read-only, no keys. The payment rules the server applies (final, succeeded, receiver, one Transfer, exact amount) and the server's own verifier, run on a real public testnet transfer of 1 yoctoNEAR between two accounts unrelated to this project. Ends PASS — 7/7 rules passed. The archival RPC is used because public RPC nodes drop transactions after a few days.
5. Real testnet payment and refund (maintainer-run; moves testnet NEAR, needs two funded testnet accounts and, for the last variant, a NanoGPT key)Prove it on testnet: two scripts create the accounts, pay a quote, check the refund on chain and print explorer links. --dry-run stops before the first transfer.

Built in the hackathon window, and before. The diff of the entry is compare/pre-hackathon...hackathon-submission (branch near-hackathon). The pre-hackathon tag is the last commit on main before the NEAR work (2026-11-06). Commits on main between the hackathon announcement (2026-10-19) and that tag are Nano-payment and maintenance work; they are counted as baseline and not claimed.

  • Before: the nanoodle workflow engine (nanoodle) and this server: saved graphs as typed MCP tools, BYOK, pay per call in Nano (x402), serve mode, the hosted server, and run_noodle for share links.
  • In the window:
    • src/near/: NEAR RPC client, payment verification, refund signing (ed25519 with node:crypto, transactions serialized by hand), exact yoctoNEAR math, NEAR/USD rate, durable payment ledger.
    • src/custom-workflow.mjs: the safety allowlist and itemized pricing for workflows an agent composes.
    • src/near-gate.mjs: the NEAR payment gate, run_noodle with an inline graph, the free price_noodle and noodle_guide tools.
    • src/market/ and scripts/market-*: the NEAR Agent Market worker, its listing catalog and price check (docs/near-agent-market.md).
    • scripts/near-simulate.mjs, scripts/near-check-tx.mjs, scripts/near-pay.mjs, scripts/near-testnet-*.mjs, the tests for all of the above, and the docs.
    • src/near/rates.mjs: NEAR/USD, XNO/USD and the NEAR→XNO swap rate derived from them, an optional swap margin, background and forced refresh, GET /rates; receipts with three money legs (caller payment, model payment, refund) and per-call operator accounting in the ledger.
    • src/rail-router.mjs and scripts/dual-simulate.mjs: one server that accepts Nano or NEAR per call (--charge-usd together with --near-merchant).

Declarations.

  • Baseline (own, pre-hackathon, MIT): the nanoodle engine and the nanoodle-mcp server.
  • Libraries: no new dependencies. The two runtime dependencies predate the hackathon: nanoodle (MIT, own) and nanocurrency (GPL-3.0, Nano signing; see Licensing). The NEAR code uses only Node built-ins. No NEAR SDK, no templates, no boilerplate.
  • AI-generated code: most of the code, tests and documentation in the hackathon diff were written with AI coding assistants, directed by the maintainer.
  • Other chains: the pre-existing product accepts payment in Nano (XNO) through x402. That is not part of the entry.
  • External services the NEAR code calls: public NEAR RPC (fastnear by default), CoinGecko for the NEAR/USD rate (can be pinned with --near-usd), NanoGPT for the models, and, in the testnet scripts only, the NEAR testnet account helper.

Example: a description becomes a playable fighter

Play Iron Verdict: a furnace knight with heavy jumps, punches, ground slams and punishing arena combat. A coding agent built the game using the character-sprites skill.

The character-sprites graph turns a humanoid description into a character reference and a matching parts sheet. The skill's local rig script turns those parts into transparent idle, walk, punch and jump frames with an atlas and timing metadata. The coding agent then implements physics, combat and game rules. The finished game demonstrates that combination; one MCP call returns the reference and parts images, not the complete game.

Mount the character-sprites graph in your server's graph folder to expose it as a tool. Use tools/list to check whether a given server has it mounted and read its input schema before calling. For the published graph, the generation step is:

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"character-sprites","arguments":{"Character":"A compact furnace knight with an ivory helmet, amber visor, navy armor and brass gauntlets. Outlined 2D platformer art. No weapon or wings."}}}

Install the skill for local extraction and animation (Node 22.23.4+, 24.22.1+, or 26.12.1+ and ffmpeg/ffprobe required). A useful agent request is: “Use character-sprites to make an animated fighter from my description, inspect the joints and alpha edges, then build a small playable arena with heavy gravity and impactful combat.”

Install

You need: Node 22.23.4+, 24.22.1+, or 26.12.1+, a folder of saved graphs (say ~/noodles — see Making graphs), and a nano-gpt.com API key in NANOGPT_API_KEY (or passed via --key / --env-file) — or no key at all with a Nano wallet, see wallet mode.

Optional: ffmpeg on PATH. Tools whose graphs include a local-media node (resize, video frames, combine, audio trim/extract) use a pure-JS path for the common case but fall back to ffmpeg/ffprobe for formats it can't handle — JPEG/WebP resize, reading video frames, compressed audio. Without ffmpeg those runs fail (and auto-refund in charge mode); the server prints a warning at startup naming the affected tools. Pure image/LLM/vision tools don't need it. Install with apt install ffmpeg (Debian/Ubuntu) / brew install ffmpeg.

Claude Code

claude mcp add nanoodle --env NANOGPT_API_KEY=your-key-here -- npx -y nanoodle-mcp --graphs ~/noodles

Or install it as a plugin — Claude Code prompts for your noodles folder and API key, and also learns what a noodle is (this repo doubles as a plugin marketplace):

/plugin marketplace add nanoodlecom/nanoodle-mcp
/plugin install nanoodle@nanoodle

Grok

grok mcp add nanoodle -e NANOGPT_API_KEY=your-key-here -- npx -y nanoodle-mcp --graphs ~/noodles

Or, for a remote/serve endpoint (no local process, no API key on your machine):

grok mcp add --transport http noodles https://mcp.nanoodle.com/mcp

That writes [mcp_servers.noodles] into ~/.grok/config.toml (or .grok/config.toml with --scope project). Same shape by hand:

[mcp_servers.noodles]
url = "https://mcp.nanoodle.com/mcp"
enabled = true

Cursor

Install in Cursor (then edit the graphs path and key), or add to .cursor/mcp.json yourself:

{
  "mcpServers": {
    "nanoodle": {
      "command": "npx",
      "args": ["-y", "nanoodle-mcp", "--graphs", "/absolute/path/to/noodles"],
      "env": { "NANOGPT_API_KEY": "your-key-here" }
    }
  }
}

VS Code

.vscode/mcp.json — note VS Code's root key is servers, not mcpServers:

{
  "servers": {
    "nanoodle": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "nanoodle-mcp", "--graphs", "/absolute/path/to/noodles"],
      "env": { "NANOGPT_API_KEY": "your-key-here" }
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "nanoodle": {
      "command": "npx",
      "args": ["-y", "nanoodle-mcp", "--graphs", "/absolute/path/to/noodles"],
      "env": { "NANOGPT_API_KEY": "your-key-here" }
    }
  }
}

Claude Desktop

Same shape as Cursor/Windsurf, in claude_desktop_config.json under mcpServers.

ChatGPT

ChatGPT only connects to remote HTTPS MCP servers. The hosted endpoint is already live — add https://mcp.nanoodle.com/mcp (paid per call in Nano, no API key). Local stdio (npx nanoodle-mcp --graphs …) stays BYOK and is not reachable from ChatGPT.

⚠️ This spends real money

The server runs on your money — either a nano-gpt.com API key (BYOK) or, keyless, your own Nano wallet via x402. Every tools/call executes a workflow against the NanoGPT API and spends from that balance — and the caller is usually an AI agent deciding on its own when to call. Point it only at graphs you're happy to have run, and keep an eye on your balance. Each result ends with a cost: $X.XXXX line so the agent (and you) can see what a call cost.

Wallet mode — no account, no API key (x402)

NanoGPT supports x402 accountless payments: a keyless API call answers HTTP 402 with a Nano invoice, you pay it, the call completes. Give the server a wallet and it does this automatically — no NanoGPT account, no API key, no signup anywhere:

export NANO_SEED=<64-hex seed>        # account 0 pays; or NANO_PRIVATE_KEY=<64-hex key>
nanoodle-mcp --graphs ~/noodles --max-usd 0.50

Per call, the server: sees the 402 invoice → signs a Nano send block locally → broadcasts it through a Nano RPC node (--nano-rpc / NANO_RPC_URL, default rpc.nano.to) → NanoGPT detects the deposit and returns the result. The seed/private key never leaves the process: only the signed block goes to the RPC node, and neither secret is ever logged. An API key, if present, always wins — the wallet is only used keyless.

Proof-of-work reliability. Every send block needs Nano proof-of-work. The server asks the RPC node's work_generate first, but public nodes routinely refuse or throttle it (no GPU, key required); the fallback is local single-threaded CPU work, which can take a minute. Two fast options, both via --work-rpc / NANO_WORK_URL:

  • Hosted GPU work API — sub-second work, no hardware. E.g. nano.to's PoW API (--work-rpc https://rpc.nano.to) or Nanswap Nodes (--work-rpc https://nodes.nanswap.com/XNO — the fast work server is on their paid plan; the free one queues for minutes). Put the API key in NANO_WORK_KEY (env or --env-file, never a flag) — it's sent as both a key body field (nano.to style) and a nodes-api-key header (Nanswap style).
  • Your own nano-work-server on the same machine (nano-work-server --cpu-threads 8 -l 127.0.0.1:7076 → work in a few seconds, free forever).

Order: --work-rpc (2-minute timeout, so a hung server can't stall a payment) → --nano-rpc node → local CPU. The wallet also precomputes work for the next block the moment one publishes, and serve mode prewarms at boot — with any healthy work source, callers effectively never wait on proof-of-work.

The local-CPU last resort runs on the Node main thread: in --serve mode a send-difficulty grind (minutes) freezes the whole HTTP server while it runs. If your remote work sources are dependable, pass --no-local-work — the send then fails cleanly instead (an unpayable invoice is refused up front, and a failed refund/payout send leaves the balance in the wallet for the next settle to move).

This is a hot wallet. Use a dedicated wallet holding pocket money, not your savings: its balance is a natural spend ceiling, and --max-usd adds a per-call one on top. NanoGPT auto-refunds overpayments and failed generations on its side.

Prefer prepay? Wallet mode settles on-chain per call. If you'd rather pay once and draw down a balance, that's exactly what a NanoGPT account is: deposit crypto (Nano included) into an account, take its API key, and run the server in normal BYOK mode — same tools, one payment instead of many.

Serve mode — host your noodles as a service (--serve)

Everything above speaks MCP over stdio to your own agent. --serve speaks MCP over streamable HTTP instead, turning a directory of noodles into a service anyone's agent can connect to:

nanoodle-mcp --graphs ~/noodles --serve 8402

Callers connect with one command — no key, no signup:

claude mcp add --transport http noodles https://your-host/mcp
# or
grok mcp add --transport http noodles https://your-host/mcp

GET / serves a landing page with the tool list and that exact command (Claude and Grok behind a one-click toggle — same endpoint, same flags), so sharing your server's bare URL is the onboarding. Every workflow on it links to its source: an open in editor link (a share link minted from the exact graph file being served — it loads the workflow in the nanoodle editor to inspect, remix, or run on your own key) and its raw graph JSON at /graph/<tool>.json. The page also spells out the economics (deposits settle at metered cost + 20%; the markup is the author's cut on graphs that name a payout address, and the operator's on the rest, and the page says which of yours do) and how to self-host — this server, the editor, and the executor are MIT (licensing). Generated media is served back under unguessable /out/… URLs (small images also ride inline in the tool result). Runs themselves are not logged: free serve mode keeps no record of who called what, and charge mode keeps only a payments ledger (money events, never run telemetry — see below).

Generated media is deleted after --out-ttl hours — a privacy backstop so a hosted server doesn't hoard every caller's generations on disk forever. In --serve mode it defaults to 24h; pass --out-ttl <hours> to change it (fractions like 0.5 are fine) or --out-ttl 0 to keep everything. Stdio mode defaults to off — files you generated locally are yours to keep — but honors the flag if you pass it. The sweep only ever removes media artifacts (images, video, audio); the operator's own costs.json, gate-state.json, and usage.jsonl are never touched. Note the 24h default matches the charge gate's replay window: a paid result stays replayable for 24h and references its /out/ URL, so lowering --out-ttl below 24h in charge mode means a replayed result can point at a file that's already been swept.

Privacy — what your server keeps

Run with the defaults and the same promise the hosted server makes holds on your box, every line checkable in this repo:

  • No accounts, no API keys, no sign-ins. In charge mode, paying is the only thing that authorizes a call.
  • Prompts and inputs are never written to disk or logs. Input media rides in memory only — it is never stored. In charge mode the gate keeps only a short hash binding a payment to its call (hashArgs), never the arguments.
  • Generated media auto-deletes after --out-ttl hours (default 24h in --serve, off for local stdio), served at unguessable /out/… URLs until then. Set --out-ttl 0 to keep everything.
  • Results are held in memory for delivery, not stored — the restart-state file carries no result text or error detail.
  • No analytics, no trackers, no IP logs — the app keeps no request logs. In charge mode the one thing it writes is usage.jsonl, a payments ledger of money-lifecycle events that mirror the public Nano ledger — no run telemetry, no error text. Free serve mode writes no ledger at all.

gate-state.json itself never holds customer content. It exists to keep in-flight money safe across the restart a deploy causes — quotes and owed sends survive — but a paid tool's text output and any upstream error detail are never written to disk. Media results persist only their /out/ pointer and a receipt; a text result persists as nothing, so a restart between a run and the caller's retry simply re-runs the tool (charged once, delivered once — the operator eats one duplicate model call) rather than replaying content from disk, and a failed run replays a fixed placeholder plus its refund status, never the original error message.

One caveat, stated plainly: generation runs on NanoGPT's API, so prompt content in flight is governed by their privacy policy.

Free serve mode runs on your balance — fine on a trusted network, ruinous on the open internet. For that, charge for calls:

Charging per call (--charge-usd)

nanoodle-mcp --graphs ~/noodles --env-file wallet.env \
  --serve 0.0.0.0:8402 --public-url https://noodles.example.com --charge-usd 0.05

Now every tool call is paid in Nano (XNO) by the caller, with no accounts on either side. The flow their agent walks through (the server's MCP instructions teach it automatically):

On each PAYMENT REQUIRED quote the tools/call hangs up with payUrl, watchUrl, and a next imperative ( blocking: false ):

  1. Pay. Show the user ONLY payUrl (QR for the exact amount); any Nano wallet scans it. Never show them watchUrl or the wallet address.
  2. Watch — immediately, on that same payment link. The agent opens watchUrl (GET /x402/watch/<id>, agent-only SSE) the moment it has the quote. One status event per state change; when payment lands the stream closes with done: true and a next field for phase 3. (The pay page uses the same stream; poll GET /x402/status/<id>?wait=1 as a fallback.) Detection is RPC polling or push via --nano-ws — about a second.
  3. Results. ONLY after the watch closes paid: re-call the tool with the same arguments plus _payment_id. That tools/call is the results stream — it runs the workflow (progress heartbeats on a streaming transport) and returns the result with a receipt. Do not open it while still watching for payment. Re-calls with the same _payment_id replay the cached result free.

What they paid is a deposit, not the price: the call settles at the run's actual metered model cost + 20%, and everything above that is sent back to the paying wallet as change — the same deposit→meter→refund model NanoGPT itself uses, one layer up. Nobody ever pays off an estimate.

Nano has no payment memo, so each quote's amount carries a few raw of random dust — the amount is the memo. Quotes expire after 15 minutes; a payment that arrives late is bounced straight back, and a run that fails after payment is refunded automatically. Arguments are validated before a quote is issued (nobody pays for a typo), and run_noodle is withdrawn in Nano charge mode; NEAR mode prices and sells it, custom graphs included — see Pay with NEAR.

Pricing: --charge-usd sets the ceiling deposit, and the opening quote for a tool that has never run. Once a tool has run, its deposit tracks its real cost automatically: quotes become twice the settle price (worst observed metered cost + 20%, from the costs.json sidecar), ceiled to a whole cent with a 1¢ floor — a tool that meters $0.004 quotes a $0.01 deposit, not the flat ceiling. Size the ceiling generously; over-covering costs callers nothing since the difference comes back as change, while an under-sized deposit means runs can cost more than was collected and you eat the difference (the gate warns at startup when a graph's deposit is below its last observed cost + 20%). The XNO conversion comes from NanoGPT itself: the gate fires a keyless x-x402 probe and keeps the invoice's exact raw-XNO/USD pair — the same rate your downstream payments settle at, so quotes and costs can never drift apart on FX, and no market-data service is involved (probe invoices are never paid; they just expire). Cached 60s, stale cache rides out probe outages, and --xno-usd forces a static rate if you ever need one. Per-graph overrides are a hand-added top-level block in the graph JSON:

"x402": { "usd": 0.10, "author": "nano_1abc…" }

humanize-comment quotes each request on its own. The quote is built from the draft, context, guidance, and maxChars actually sent. Each call's max_tokens is sized from that request: maxChars, or the draft's length when maxChars is omitted, plus a small slack. The decider also reserves tokens for its ranking JSON and for GLM reasoning, which shares that max_tokens budget. Tokens are estimated at about 3.5 bytes each; the draft, context, and guidance the decider reads are priced at 2.5 bytes per token, so its estimate grows with context length. Until the decider starts, a hosted run admits a writer, fallback, or re-ask only if it fits the deposit with the decider's estimated cost still counted. That estimate is admission headroom, not money set aside, so it makes a skipped decider less likely but does not rule it out. The priced worst case is every writer once, one extra retry, the decider, and a second decider pass when a protected span can be repaired or the call can ask the decider to confirm one fitting rewrite (a title, a length cap, or a required ending). When Cydonia fails and the Starry Ocean fallback runs, both calls are charged. On a hosted payment the quote caps what the customer pays. A step whose estimate or invoice would push the spend past the quote is skipped. A skipped writer step ends as a partial result, or with nothing owed when nothing was sent. A skipped or unusable decider is an error, not the original draft returned as a successful comment. The deposit is 12% above that priced charge, at least $0.001, rounded up, and never below it. Settlement of a hosted payment charges the customer the lesser of the metered cost plus 20% and the quoted deposit, and returns the difference. When a provider meters more than the quote, the operator absorbs that overrun. This refund and cap apply to hosted payments. Direct-wallet spending has no cap and is not refunded. A short comment is not asked for the longest comment's hold. The graph's usd is only the ceiling for that longest request, used when a model has no price.

usd overrides the deposit for every other tool. author routes the 20% markup of every successful call to that address — computed on what the run actually cost, never on the deposit, and capped by whatever the deposit has left once the cost is taken out (a deposit too small to cover cost + 20% pays the author only the remainder). Nano has no network fees and this server takes no cut of the markup, so creators keep 100% of what does get routed. With no author field the markup stays with the wallet running the server — it is opt-in per graph, not the default. In exact raw, per settled call:

cost   = metered model cost, converted at the deposit's own oracle pair, rounded up
markup = cost / 5                    (20%, integer floor)
take   = min(markup, deposit − cost) → author (or kept, if no author field)
change = deposit − cost − take       → back to the payer

A run costing more than its deposit keeps the whole deposit and the operator absorbs the excess; a run whose model reports no cost settles at $0 and the whole deposit is returned (the caller is never billed off a number the meter didn't produce). The math is integer-exact end to end: USD decimals parse by string into nano-dollars, the rate is the oracle invoice's literal raw/USD pair, every conversion is BigInt ratio arithmetic with an explicit floor or ceil, and floats never touch an on-chain value.

The wallet (NANO_SEED / NANO_PRIVATE_KEY, via --env-file) receives payments, sends refunds and author payouts, and — if you don't set an API key — also pays NanoGPT per call via x402, making the whole service nano-in/nano-out. With an API key, runs spend from the key and the wallet only handles the customer side.

Payment detection

Polling receivable on your Nano RPC node (1s while quotes are pending) is the always-on default. Add --nano-ws wss://… to subscribe to a Nano node websocket for push detection — settlement is then effectively instant and polling relaxes to a safety net. Public websockets exist (e.g. wss://nanoslo.0x.no/websocket worked at the time of writing); your own node is the dependable option. Either way the poller also checks account_history, so payments pocketed by a concurrently-running wallet are still found.

Payments ledger

In charge mode <out>/usage.jsonl is a payments ledger: one line per money event only — quote, paid, refund, change, author_payout — your server's own record of money moving, nothing client-side. It is deliberately not a usage log: there are no run events, no run timing, no success/failure telemetry, and no upstream error strings (which can quote user content). A money event does carry .tool — the tool name is the subject of the payment, not a record of a run — so you can see what earns, but not who ran what when or whether it failed. Refunds record a fixed category (run_failed, late_payment), never the underlying error text — the full error still reaches the caller and the operator's stderr. Free serve mode writes no ledger at all. Some starters:

jq -r 'select(.event=="paid") | .tool' usage.jsonl | sort | uniq -c | sort -rn  # paid calls per tool
jq 'select(.event=="paid") | .settleMs' usage.jsonl                             # quote→settle latency
jq 'select(.event=="paid") | .usd' usage.jsonl | jq -s add                      # gross deposits taken
jq -r 'select(.event=="refund") | .reason' usage.jsonl | sort | uniq -c         # refunds by category

Hosting checklist

  • Reverse proxy / tunnel: bind localhost, put Caddy/nginx/cloudflared in front for HTTPS, and set --public-url to the outside URL (it's what pay links and media links are built from).

  • systemd (adjust paths):

    [Unit]
    Description=nanoodle-mcp serve
    After=network-online.target
    
    [Service]
    ExecStart=/usr/bin/npx nanoodle-mcp --graphs /srv/noodles --out /srv/noodle-out \
      --env-file /srv/wallet.env --serve 8402 --public-url https://noodles.example.com \
      --charge-usd 0.05 --work-rpc http://127.0.0.1:7076
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    
  • Keep the float small: the wallet is hot. Sweep revenue to cold storage regularly; if you run with an API key, cap that account's balance too.

  • Work server: refunds and payouts are Nano sends and need proof-of-work — use a hosted GPU work API (nano.to, Nanswap — key in NANO_WORK_KEY in the env file), or point NANO_WORK_URL at a nano-work-server --gpu on any box you own over a tailnet (work requests carry only block hashes — nothing secret). Pair either with --no-local-work so a work outage can't freeze the server on CPU work. Wallet-material and key env vars all belong in the --env-file, which also takes NANO_WS_URL for a key-bearing websocket URL (e.g. Nanswap's wss://nodes.nanswap.com/ws/?ticker=XNO&api_key=…).

  • There is intentionally no auth: on a charged server, payment is the authorization. Don't serve graphs you wouldn't want strangers running.

How it works

~/noodles/
  generate-hero-image.json   →  tool "generate-hero-image"
  make-jingle.json           →  tool "make-jingle"

Every readable *.json graph in --graphs becomes one MCP tool:

Tool fieldDerived from the graph
namefilename minus .json, sanitized to [a-z0-9_-] (duplicates get -2, -3, …)
descriptionthe graph's first comment (if any), its node chain in dependency order with node names (e.g. text:Feature -> llm -> image:Mockup), a returns … contract (output kinds with the sink's model/size and the saved-to-disk note), a spend warning, and — once the tool has run — its last observed cost (last run $0.018)
inputSchemaone string property per unwired field, exactly like the nanoodle CLI's inspect; dropdown fields become enums; only inputs without a baked-in default are required
media inputsimage / audio / video inputs take a file path or https URL — local files ride inline as base64
resulttext outputs as text blocks; media outputs saved into --out (default ./nanoodle-out) with the absolute path returned; a final text block reports the run's cost; a leading note: block whenever the run changed something the caller asked for (see below)

When the run does not use your prompt verbatim, the result says so. Many image and video models reject an over-long prompt outright, and in a graph the prompt is usually written by an upstream LLM — nobody typed it, so nobody can shorten it. nanoodle (0.8.0+) trims it to the model's cap at a sentence boundary instead of losing the run to a certain 400. That is only defensible if the caller is told, so the trim leads the tool result:

note: prompt trimmed for "Poster" (qwen-image-3) — the prompt was 1320 characters
and this model accepts at most 800, so nanoodle cut it to 791 characters at a
sentence boundary. The run used the shortened prompt.

It matters most in charge mode, where that run is already paid for. The library also emits a process warning, but stderr belongs to whoever started the server, not to the agent that paid.

A ⚖️ Decide gate that answers no is a result, not an error. nanoodle 0.9.0 runs the Decide node. A yes/no gate that says no settles as status gated and stops its branch. The tool result is normal (not isError), the decision's tiny cost is billed, and nothing behind the gate runs or bills. The result leads with:

gated: "Is it a cat?" answered no (yes 12%), so the run stopped there on purpose — skipped, not billed: "Cat poem". This is a result, not an error.
Cat poem: skipped — gate "Is it a cat?" answered no
cost: $0.000003

Protocol behavior worth knowing: malformed calls (unknown tool, unknown / missing / non-string argument) are rejected as JSON-RPC -32602 before any money is spent; a run that fails (network, model error, missing key) comes back as a normal tool result with isError: true.

usage: nanoodle-mcp --graphs <dir> [--graphs <dir> …] [--out dir] [--key K] [--env-file path] [--nano-rpc url] [--work-rpc url] [--max-usd n]

  --graphs dir   directory of noodle-graph.json saves (required; repeat to serve
                 several dirs — scanned in order, so an earlier dir wins name clashes)
  --out dir      where media outputs are saved (default ./nanoodle-out)
  --key K        NanoGPT API key (defaults to NANOGPT_API_KEY)
  --env-file p   read NANOGPT_API_KEY / NANO_SEED / NANO_PRIVATE_KEY / NANO_WORK_URL /
                 NANO_WORK_KEY / NANO_WS_URL from a .env-style file
  --nano-rpc u   Nano RPC node for wallet mode (default https://rpc.nano.to; NANO_RPC_URL)
  --work-rpc u   dedicated work_generate endpoint — a local nano-work-server or a hosted
                 GPU work API (NANO_WORK_URL; NANO_WORK_KEY carries its API key;
                 falls back to --nano-rpc, then local CPU work)
  --max-usd n    wallet mode: refuse any single x402 invoice above $n

Key precedence matches the nanoodle CLI: --key > --env-file > NANOGPT_API_KEY; wallet secrets come only from the environment or --env-file, never argv. The server refuses to start if no directory holds a runnable graph, and says why per file on stderr; stdout is protocol only.

Per-project graphs + a shared library

--graphs is repeatable, so one server can merge several folders. The pattern: commit a noodles/ dir to a repo for that project's own tools, then list your global library after it as a fallback:

nanoodle-mcp --graphs ./noodles --graphs ~/noodles

Dirs are scanned in the order given, and the earlier dir wins a name clash: if both folders have a changelog.json, the project's becomes changelog and the shared one becomes changelog-2. So a project can override a shared tool just by dropping a same-named graph in ./noodles, while everything else in ~/noodles stays available. An unreadable dir (a typo, a folder that isn't there) is a hard startup error naming the offender — no silent half-load.

Wiring it into your editor

MCP config is per-project in most clients, so the two-dir pattern lives in the repo, right next to the graphs it serves. For Claude Code that's a .mcp.json at the project root — commit it along with noodles/ and everyone who clones gets the same tools:

{
  "mcpServers": {
    "nanoodle": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "nanoodle-mcp", "--graphs", "./noodles", "--graphs", "/home/you/noodles"]
    }
  }
}

Relative paths resolve from the project root. A project-scoped server named nanoodle shadows a user-scoped one of the same name, so repos with a .mcp.json get their own graphs and every other directory falls back to your global install untouched.

Two things to keep out of a committed config: your API key (leave env out and let NANOGPT_API_KEY come from the environment, or point --env-file at a file outside the repo — never commit a key) and, for team repos, your personal library path (the second --graphs above is an absolute path on your machine; drop it from a shared repo's config and keep just ./noodles, since ~ is not expanded in MCP args). Cursor (.cursor/mcp.json) and VS Code (.vscode/mcp.json) take the same args in the config shapes shown under Install.

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!