Skip to main content
Glama

@ainize/mcp — Ainize as an MCP server, and as an MCP client

ainize-mcp puts the Ainize knowledge marketplace behind the Model Context Protocol, so Claude Code, Cursor and any other MCP client can do the five things the product exists for:

  • find knowledge — trained memory-table patches a node applies into a running LLM;

  • prove it — the same question answered twice, by the bare model and by the model with the knowledge loaded, together with who verified it and with what score. This is the signature capability and the hardest thing to fake;

  • price it honestly — a quote that includes the base stack a delta child needs, and a session budget the model cannot raise;

  • buy it over x402 — quote → explicit confirm → settle, journalled so a retry can never pay twice;

  • teach it something permanently — questions and answers become a knowledge, optionally on top of an existing one, and the result says what the model learned and what it did not.

It is also an MCP client: McpDataSource connects to somebody else's MCP server, runs a query you specify, maps the answer into the same {prompt, answer} rows the teach door eats, and records where every row came from. The worked example does that against The Graph's hosted Subgraph MCP — live, or not at all.

The design this implements is docs/mcp-integration-design.md. The Graph-specific pipelines and the four-arm benchmark live in graph/, per graph/README.md; this package owns the seam between the two directions (src/rows.ts, src/datasource.ts).


What this server will never do without you

This is the part worth reading before anything else.

  1. It will never spend money without a quote you saw. buy has no id parameter. What gets bought is whatever a quote named, the quoted total has to be restated character for character, and confirm: true is a separate required field so a schema-filling model cannot approve by copying one number.

  2. It cannot spend more than the cap in its own environment. AINIZE_MCP_SESSION_BUDGET is server configuration. A tool argument (max_price) may only lower it for one call. Going over is budget_exceeded with the four numbers — never a silent clamp, never a partial purchase. With the default budget of 0, the buy tool is not registered at all.

  3. It will never pay twice. An intent is journalled before the node is called, a repeat with the same key never reaches the node, a knowledge this node already bought is refused with its purchase date and tx hash, and nothing in the money tier is ever retried automatically. After any failure, reconcile_purchase says which of four things happened — complete, settled_no_body, recovered, never_paid — without paying anything.

  4. It will never hand the model a secret. No tool takes or returns a password, a session token, a private key or a signature: the input schemas have no field to put one in, and an outbound scrubber redacts anything key-shaped from every result and every error, including a purchase manifest's download token and an upstream 401 body.

  5. It will never block your agent. Everything that touches the shared model is a job: call, get a job_id, poll job_status. The model lock is cross-process and the node waits up to 20 minutes on it, so a blocking tool would time out in the client, which would retry, which would deepen the very queue it is waiting on.

  6. It will never change a shared model behind your back. apply_knowledge / remove_knowledge are not registered unless AINIZE_MCP_ALLOW_APPLY=1, remove needs confirm: true, and both say plainly that the change is visible to every node on the machine and survives a restart.

  7. It will never publish to a permanent ledger by accident. publish_knowledge is not registered unless AINIZE_MCP_ALLOW_PUBLISH=1; on a node whose ledger is the shared AIN chain it is refused outright with permanent_ledger_refused; both consents are required inputs with no default; and the confirmation phrase has to contain the lesson id, so a model cannot approve a publish by pattern-matching "yes".

  8. It will never spend a daily lesson on nothing. A lesson is charged by the node the moment it is submitted and is never refunded, so teach asks the model what it already knows first and refuses with nothing_to_train when every probed question is already answered — and it never retries a failed lesson by itself.

  9. It will never turn somebody else's data into training by itself. The MCP client half stops at a training set. Reviewing the rows and spending the GPU are separate, confirmed calls.

이 서버가 당신 없이 하지 않는 일 (한국어)

  1. 견적 없이는 돈을 쓰지 않습니다. buy에는 id 인자가 없습니다. 무엇을 사는지는 quote가 정하고, 견적 총액을 글자 그대로 다시 적어야 하며, confirm: true는 별도의 필수 항목입니다.

  2. 환경변수의 한도를 넘길 수 없습니다. AINIZE_MCP_SESSION_BUDGET은 서버 설정이고, 도구 인자로는 낮출 수만 있습니다. 기본값 0이면 buy 도구 자체가 등록되지 않습니다.

  3. 두 번 결제하지 않습니다. 결제 전에 의도를 기록하고, 같은 키로 다시 부르면 노드까지 가지 않으며, 이미 산 지식은 구매일과 tx 해시와 함께 거절합니다. 실패했을 때는 재시도가 아니라 reconcile_purchase입니다.

  4. 비밀은 모델에게 넘어가지 않습니다. 비밀번호·세션 토큰·개인키·서명을 받는 인자도, 돌려주는 필드도 없습니다.

  5. 에이전트를 붙잡아 두지 않습니다. 모델을 쓰는 모든 작업은 job이고 job_status로 확인합니다. 모델이 잠겨 있으면 누가, 얼마나 오래 잡고 있는지 문장으로 알려줍니다.

  6. 공유 모델을 몰래 바꾸지 않습니다. apply_knowledge/remove_knowledge는 명시적으로 켜야 하고, 이 기계의 모든 노드에 영향을 준다는 경고를 함께 돌려줍니다.

  7. 오늘의 수업을 헛되이 쓰지 않습니다. 노드는 제출 순간에 하루 수업 한 번을 차감하고 환불하지 않습니다. 그래서 teach는 먼저 모델이 이미 아는지 물어보고, 전부 알고 있으면 nothing_to_train으로 거절합니다. 실패한 수업을 스스로 다시 돌리지 않습니다.

  8. 발행은 되돌릴 수 없습니다. publish_knowledge는 기본으로 꺼져 있고, 공용 AIN 체인 노드에서는 아예 거절하며, 두 개의 동의와 수업 id가 들어간 확인 문구를 요구합니다. 수익 배분은 발행 화면의 70 %가 아니라 실제 계산값 (부모가 있으면 49 %)을 보여줍니다.

  9. 남의 데이터를 혼자 학습으로 바꾸지 않습니다. MCP 클라이언트 쪽은 학습 세트까지만 만들고 멈춥니다.


Related MCP server: Cache Overflow

Install and build

export PATH="$HOME/.local/node/bin:$PATH"      # Node 24
npm install                                     # the workspace picks packages/mcp up automatically
npm run build -w packages/core -w packages/mcp
node packages/mcp/dist/bin.js --help

Configure a client

Secrets belong in your shell profile, never in a checked-in file. claude mcp add -e … writes what you pass into ~/.claude.json, so pass the reference, not the value.

Claude Code (stdio, recommended for a local node):

claude mcp add ainize \
  -e AINIZE_NODE_URL=http://localhost:3422 \
  -e AINIZE_TEACH_KEY="$AINIZE_TEACH_KEY" \
  -e AINIZE_MCP_SESSION_BUDGET=0 \
  -- node /abs/path/knowledge-marketplace/packages/mcp/dist/bin.js

Streamable HTTP (one process, several clients):

node packages/mcp/dist/bin.js --http 3499
claude mcp add --transport http ainize http://127.0.0.1:3499/mcp

--http refuses to start with a non-zero budget or with ALLOW_APPLY / ALLOW_PUBLISH set, unless you also pass --i-am-the-only-user: a port is reachable by people who are not the operator.

Cursor / Claude Desktop / Windsurf (~/.cursor/mcp.json or the app's config) and a project .mcp.json use the same object shape — a commented, ready-to-paste version of both blocks is in client-config.example.json:

{
  "mcpServers": {
    "ainize": {
      "command": "node",
      "args": ["/abs/path/knowledge-marketplace/packages/mcp/dist/bin.js"],
      "env": { "AINIZE_NODE_URL": "http://localhost:3422", "AINIZE_MCP_SESSION_BUDGET": "0" }
    }
  }
}

Configuration

Env

Meaning

Default

Ever returned?

AINIZE_NODE_URL

the node this server speaks for — configuration, not a tool argument

http://localhost:3422

yes (it is public)

AINIZE_OPERATOR_PASSWORD

exchanged once at startup for an in-memory bearer

never

AINIZE_TOKEN

an existing operator session token instead of the password

never

AINIZE_TEACH_KEY

64-hex teaching key, or a path to the key-backup JSON

never (its address is public)

AINIZE_MCP_SESSION_BUDGET

total spend allowed for the life of this process

0buy is not registered

yes

AINIZE_MCP_MAX_PER_PURCHASE

ceiling for one purchase

the session budget

yes

AINIZE_MCP_MAX_TEACH_JOBS

daily lessons this server may spend

1

yes

AINIZE_MCP_ALLOW_APPLY

register apply_knowledge / remove_knowledge

off

yes

AINIZE_MCP_ALLOW_PUBLISH

register publish_knowledge (irreversible)

off

yes

AINIZE_MCP_ALLOW_AIN_PUBLISH

also allow it on a node whose ledger is the shared AIN chain

off

yes

AINIZE_MCP_STATE_DIR

where the purchase journal and the provenance records are persisted (mode 0600)

memory only

no

AINIZE_MCP_DOWNLOAD_DIR

where download_lesson writes files

<state dir>/lessons, else the temp dir

yes (the path)

AINIZE_MCP_MAX_DOWNLOAD_MB

ceiling for one downloaded artefact

512

yes

AINIZE_MCP_POLL_MS

how often a running lesson is re-read from the node

3000

no

GRAPH_API_KEY

The Graph gateway key — used by the MCP client example only, never by a tool

never

AINIZE_MCP_CONFIG

a JSON file with the same keys in snake_case; env wins over it

no

Which node to point at

Node

URL

Ledger

Safe for

node-u

http://localhost:3422

local

everything — the default in every example

node-a / b / c

:3402 :3403 :3404

ain (shared chain)

reading, quoting and live tests

a private cluster

AINIZE_CLUSTER_HOME=<tmpdir> AINIZE_PORT_BASE=3512 AINIZE_LEDGER=local AINIZE_SEED=0 scripts/cluster-restart.sh

local

end-to-end money tests

All of them share one model server (http://localhost:8002). A live test run from an MCP client is therefore visible to every other node on the machine, and anything applied changes the "before" column of everyone else's live test until it is removed.


The skill — for an agent that has to do this well

SKILL.md is the agent-facing half of this package, written in the shape The Graph uses for its subgraph and Substreams skills: ~100 tokens of frontmatter, a body under 5,000 tokens, and everything deep in references/. It states the two safety tiers, the five workflows (find · prove · buy within a budget · teach · teach from a subgraph), and the two hard rules — never print a credential, never background-poll a human decision.

File

Contents

SKILL.md

the hub: when to use it, the tiers, the workflows, worked examples, troubleshooting

references/money.md

x402, quote → confirm → settle, caps, idempotency, reconcile, the 49 % split

references/live-test.md

modes, the shared-model caveat, quota arithmetic, reading a verdict

references/teach-and-lineage.md

both doors, base vs compare_with, the 13 states, publish consent

references/verification.md

quorum, attestations, challenges, what "verified" is not

references/errors.md

every code, retryable, and the recovery

references/subgraph-to-dataset.md

direction B end to end, provenance, the volatility rule

references/cli.md

the equivalent ainize CLI commands

EVAL.md

eight plain-English prompts and their mechanical pass conditions — re-runnable by a judge

scripts/validate-skill.mjs checks the frontmatter, the token budget, that every references/*.md is linked and that every tool the body names is actually registered; npm test -w packages/mcp runs it, so the skill cannot drift away from the server. Packaging metadata for claude plugins add is in .claude-plugin/; publishing it as a marketplace additionally needs .claude-plugin/marketplace.json copied to the repository root of a public repo, which is why the file here declares its plugin source as ./packages/mcp.

A skill is guidance, not enforcement — StreamingFast's own EVAL says it plainly: skill text alone does not override model posture. That is why "quote before you buy" is a required quote_id in the buy schema and not a sentence in a document.


The tools

Tool

Tier

Costs

Blocks?

Registered

search_knowledge

READ

free

< 100 ms

always

get_knowledge

READ

free

< 100 ms per include

always

family_tree

READ

free

< 1 s

always

get_training_set

READ

free

< 100 ms

always

node_status

READ

free

< 100 ms (refresh adds a probe)

always

my_library

READ

free

< 200 ms

always (sections omitted with a reason)

knowledge_signals

READ

free

< 200 ms

always

teacher_profile

READ

free

< 100 ms

always

live_test

MODEL

free, 1 of 20/hour shared

no — job handle

when the node serves a model

job_status / job_list

READ

free

wait_ms, locally

always

job_cancel

MODEL

free

< 200 ms

always

create_training_set

MODEL

free (row/byte quota)

< 1 s

teaching key + teach enabled

teach_preflight

MODEL

free live-test units ×2 buckets

no — job handle

teaching key + teach enabled

teach

MODEL

one daily lesson, non-refundable

no — job handle

teaching key + teach enabled

download_lesson

MODEL

free

seconds

teaching key + teach enabled

apply_knowledge / remove_knowledge

MODEL

free

no — job handle

AINIZE_MCP_ALLOW_APPLY=1

publish_knowledge

PERMANENT

free of money, irreversible

seconds

AINIZE_MCP_ALLOW_PUBLISH=1

quote

MONEY (read)

free

< 1 s

always

buy

MONEY

real money

no — job handle

operator + non-zero budget

reconcile_purchase

MONEY (read)

free

seconds

operator configured

Deliberately absent: verify, challenge, announce, forget, runtime_complete, peers, chain setup, policy, bans. They are minutes-long, network-visible or destructive, and an agent has no business driving them.

The workflow the server asks for

Served as ainize://instructions and as the server's instructions on connect:

  1. Before you claim a knowledge helps, prove it — ONE live_test naming the candidate in knowledge; it answers both columns under one hold of the shared model lock. Two separate calls are not equivalent: a knowledge: [] call unloads nothing, so a knowledge another process left on the model answers with the base.

  2. Before you spend, quote — show the total, the base stack and the remaining budget, then stop.

  3. Before you teach, preflight — a daily lesson is scarce and is not refunded. teach does this for you.

  4. Everything that touches the model is a job — poll job_status; if the model is held, report who and how long instead of retrying.

  5. Never print a token, key, password or signature.

  6. Never background-poll a human decision. The turn that asks for approval ends.

  7. When something is not implemented, say sofamily_tree edge kinds, per-knowledge signals and bundle buys return null with a note. Report the note; do not invent the number.

Resources

ainize://instructions · ainize://node/info · ainize://budget · ainize://openapi (with a header saying the node's OpenAPI document is hand-written and incomplete — never generate tools from it).

Errors

Every failure an agent can act on comes back as a normal tool result with isError: true and { code, message, retryable, retry_after_ms?, details }. The node's own codes are passed through unrewritten (quota_chat, base_retired, dataset_private, …). The codes this layer adds:

Code

Meaning

Retryable

quote_required

buy was called without a quote_id

no

quote_expired

the quote is older than 10 minutes, or unknown

no

quote_mismatch

the restated total, or the live price, does not match the quote

no

confirmation_required

confirm: true (or remove_knowledge's confirmation) is missing

no

budget_exceeded

over the session cap; details carries cap/spent/remaining/needed

no

per_purchase_cap_exceeded

over AINIZE_MCP_MAX_PER_PURCHASE or the call's max_price

no

already_purchased

this node already bought it; nothing was charged

no

idempotency_replay

the same key was already used — replays the result, or sends you to reconcile

no

nothing_to_train

every probed question is already answered correctly, so no lesson was submitted

no

teach_quota_consumed

the lesson failed (or the session lesson cap is spent); the day's lesson is gone either way

no

base_rejected / base_retired

the knowledge you asked to build on cannot be a base (rejected, challenged, superseded)

no

permanent_ledger_refused

publishing was aimed at the shared AIN chain, where nothing can be recalled

no

model_busy

the shared runtime lock is held; the message names the holder

yes (30 s)

job_not_found

jobs are session-scoped and evicted 30 minutes after they finish

no

node_unreachable

the node did not answer at all

yes

capability_disabled

reserved: a capability this server was not configured for (today such tools are simply not registered)

no

Two node answers are outcomes, not errors: HTTP 499 {cancelled, charged:false} (a live test given up while queued) and a quote of a knowledge that is already owned.


The async model — start, poll, cancel

Nothing that touches the model is a blocking call. The shared runtime lock is cross-process and the node waits up to 20 minutes on it, so a blocking tool would hit the client's timeout, the client would retry, and the retry would open another queue ticket behind the one it was waiting on.

live_test / teach / teach_preflight / apply_knowledge / remove_knowledge / buy
    → { job_id, state: "queued", poll_after_ms, model_lock: { sentence }, … }        in ~100 ms
job_status { job_id, wait_ms: 25000 }                                                 → the next state change
job_cancel { job_id }                                                                 → charged: true | false
job_list {}                                                                           → what this session started
  • wait_ms long-polls inside this server. It never holds a request open on the node; it waits for a state change locally and answers as soon as one happens. A queued job usually needs two calls (queued → running → done), which is what you want: the intermediate answer carries progress.

  • Jobs are session-scoped and evicted 30 minutes after they finish (job_not_found after that). A lesson can also be polled by its node lesson id, so a conversation that lost its job_id is not stuck.

  • Every job answer names the holder of the model, computed against the node's own clock: "the model is held by pid:2658057 (a live test of krx-all-2761) for 8 s", plus queue.waiting.

  • Cancelling a queued live test is genuinely free (charged: false); cancelling a running one is not, and the answer says so. Cancelling a lesson never gives the daily lesson back.

  • buy is a job too, because a blob download can take minutes. Its failure path is reconcile_purchase, never a retry.

Every tool, with an example call and answer

Answers are abridged with ; every field shown is real. The examples marked (node-a) / (node-u) were captured from the running cluster on 2026-09-04; the rest come from the request-recording fake node the test suite uses (test/fake-node.ts), which answers with the node's own shapes.

search_knowledge — browse and search (node-a)

search_knowledge { "query": "ticker", "limit": 3 }
// also: model, schema, status, author, origin ("operator" | "teach"), sort ("latest"|"popular"|"price"|"rows"), offset
{ "total": 4, "shown": 3, "offset": 0,
  "items": [{
    "id": "krx-all-2761", "name": "KRX ticker codes for 2,761 listed companies (final)",
    "description": "All 2,761 ticker codes of companies listed on the Korea Exchange…",
    "price": "25", "currency": "AIN", "rows": 270053, "size_mb": 331.7,
    "status": "LISTED", "downloads": 93, "quorum": "2/2", "quorum_ok": true, "sellable": true,
    "author_name": "node-a", "taught_by": null, "model": "Qwen3.8-Flash-Next", "schema": "krx-ticker-codes",
    "origin": "operator", "is_addon": false, "requires_count": 0, "node_url": "http://localhost:3402"
  }, … ],
  "facets": { "models": ["Qwen3.8-Flash-Next"], "schemas": ["krx-ticker-codes"] } }

get_knowledge — detail, verifiers, lineage, base stack (node-a)

get_knowledge { "id": "krx-all-2761", "include": ["records", "events", "benchmark_siblings"] }   // include is optional
{ "knowledge": { "id": "krx-all-2761", "price": "25", "status": "LISTED", "topic_path": "finance/krx",
                 "benchmark_queries": 2761, "supersedes": ["krx-all-2761-ep12", "krx-all-2761-ep6", "pixelplus-087600"],
                 "superseded_by": [], … },
  "verification": { "quorum": "2/2", "quorum_ok": true, "sellable": true, "open_challenge": null,
    "attestations": [ { "verifier_name": "node-b", "passed": true,
                        "score": { "free_generation": "26/26", "pre_apply": "1/8" },
                        "verified_on": "vllm:Qwen3.8-Flash-Next" }, … ] },
  "lineage": { "parents": [{ "id": "krx-all-2761-ep12", "status": "SUPERSEDED" }], "children": [] },
  "requires": [], "requires_note": null,
  "availability": { "has_body": true, "purchased": false, "owned": true, "applied": false,
                    "gateway_url": "http://localhost:3402/x402/patch/krx-all-2761" },
  "training_set": null }

family_tree — the version and derivation graph (node-a)

family_tree { "id": "krx-all-2761", "depth": 2 }
{ "root": "krx-all-2761",
  "nodes": [{ "id": "krx-all-2761", "status": "LISTED", "added": null, "signals": null }, … ],
  "edges": [{ "from": "krx-all-2761", "to": "krx-all-2761-ep12", "kind": "extends" },
            { "from": "krx-all-2761", "to": "pixelplus-087600", "kind": "supersedes" }, … ],
  "truncated": false,
  "note": "Edge kinds beyond extends/supersedes, per-node `added` counts and usage signals are not recorded by the node yet (lineage design §12.5, PR L6). `added` and `signals` are null, not 0 — do not report a number here." }

get_training_set — what a knowledge was built from

get_training_set { "id": "k1", "rows": false, "limit": 20 }    // rows: true streams the real rows (public access only)
{ "id": "k1", "sha256": "dddd…", "rows_total": 2761, "access": "public", "license": "CC-BY-4.0",
  "parents": [], "held": true, "include_notes": false, "merkle_root": null,
  "preview": [{ "prompt": "Q", "answer": "A" }, …], "rows": null }

Access levels: public (open) · derivative (a teaching key only — this server signs with its own) · private (refused, with the metadata in the error body).

node_status — the node, the model, the lock, the caps (node-a)

node_status { "refresh": false }     // refresh: true forces a runtime probe (~3 s)
{ "node": { "name": "node-a", "ledger": "ain", "roles": ["seller","verifier","serving"], "quorum": 2,
            "currency": "AIN", "model": "Qwen3.8-Flash-Next", "balance": "3374.29…",
            "royalty_share": 0.3, "contributor_share": 0.7, "peers": 2,
            "counts": { "patches": 4, "listed": 1, "superseded": 3 }, "applied": [] },
  "runtime": { "available": true, "api": "http://localhost:8002", "hook": true, "applied": [] },
  "model_lock": { "holder": { "owner": "pid:2658057", "label": "chat:krx-all-2761", "held_s": 1, "mine": true },
                  "queue": { "running": 1, "waiting": 0 },
                  "sentence": "the model is held by pid:2658057 (a live test of krx-all-2761) for 1 s" },
  "quota": { "live_tests_remaining": null, "limit": 20,
             "note": "the node has no quota endpoint — the remaining count is only known after a live test answers…" },
  "teach_policy": { "enabled": true, "publish": "auto", "trainer": "ready", "backend": "stub", "limits": { … } },
  "capabilities": { "can_read": true, "can_live_test": true, "can_teach": false, "can_buy": false,
                    "can_apply": false, "can_publish": false },
  "capability_reasons": { "can_buy": "no operator credential is configured on this MCP server…", … },
  "budget": { "cap": "0", "spent": "0", "remaining": "0", "currency": "AIN" },
  "warnings": ["this node is on the shared AIN chain: publishing and announcing are refused by this server unless explicitly allowed"] }

my_library — what this node owns, bought, taught and earned

my_library { "include": ["purchases", "published", "applied", "lessons", "datasets"] }   // all five by default
{ "purchases": [{ "id": "k1", "amount": "5", "currency": "CREDIT", "tx_hash": "0x…", "bought_at": 1788…, "body_present": true }],
  "published": [ /* flat knowledge rows */ ], "applied": [], "lessons": [], "datasets": [],
  "omitted": [{ "section": "purchases", "reason": "no operator credential is configured on this MCP server" }],
  "teaching_key_address": "0x2999…" }

The stored manifest is never returned — it carries a download token. The tx hash is.

knowledge_signals — usage, honestly

knowledge_signals { "id": "k1", "limit": 20 }
{ "id": "k1", "downloads": 93, "revenue": "2325",
  "verification": { "quorum": "2/2", "attestations": [ … ] },
  "events": [{ "ts": 1, "level": "info", "kind": "usage", "message": "live test k1" }],
  "signals": null,
  "note": "GET /api/patches/:id/signals and /issues are PR L6 and do not exist yet: `signals` is null, not zero. Events are redacted for non-operators, and everything here is what this one node recorded — not network truth." }

teacher_profile — a data provider's public page

teacher_profile { "address": "0x2222…" }
{ "address": "0x2222…", "name": "a teacher", "lessons": [ … ],
  "earnings": { "total": "0", "currency": "CREDIT" } }

live_testjob_status — the before/after (node-a, real run)

live_test { "question": "픽셀플러스 종목코드 알려줘. 숫자만.", "knowledge": ["krx-all-2761"], "mode": "compare" }
// 91 ms
{ "job_id": "lt_3e19184626c3", "kind": "live_test", "state": "queued", "poll_after_ms": 1500,
  "model_lock": { "sentence": "the model is held by pid:2658069 (a live test of krx-all-2761) for 7 s" },
  "applied_on_this_model": [],
  "quota": { "metered": true, "live_tests_remaining": null, "limit": 20, "note": "…20 per rolling hour per visitor IP, shared by everyone using this MCP server." },
  "next": "call job_status with this job_id (wait_ms lets one call cover the whole wait)" }
job_status { "job_id": "lt_3e19184626c3", "wait_ms": 90000 }
// 18.9 s later
{ "state": "done", "elapsed_ms": 18828,
  "result": {
    "before": { "answer": "058420", "latency_ms": 406, "truncated": false },
    "after":  { "answer": "087600", "latency_ms": 378, "truncated": false },
    "changed": true, "verdict": null,
    "knowledge": [{ "id": "krx-all-2761", "applied_ms": 3789, "was_already_applied": true,
                    "verification": { "quorum": "2/2", "attestations": [ /* node-b 26/26, node-c 26/26 */ ] } }],
    "apply_ms_total": 3789,
    "quota": { "metered": true, "remaining": 17, "limit": 20, "note": "free live tests are metered per visitor IP — this bucket is shared by everyone using this MCP server" },
    // with AINIZE_OPERATOR_PASSWORD set, this reads instead:
    // "quota": { "metered": false, "remaining": null, "limit": null, "note": "not metered: this server signs its live tests in as the node's operator…" },
    "caveats": ["this question is not in the knowledge's own benchmark, so the comparison is unscored — report it as a comparison, not as a verified result"] } }

job_cancel · job_list

job_cancel { "job_id": "lt_…", "reason": "the user changed the question" }
→ { "job_id": "lt_…", "kind": "live_test", "cancelled": true, "reason": "queued", "charged": false,
    "note": "nothing had reached the model, so nothing was charged" }
// once it is running:  "reason": "already_running", "charged": true,
//   "note": "the node had already started this on the model: the work and the metered try stand…"
// a lesson:            "charged": true,
//   "note": "the lesson is cancelled, but the daily lesson it consumed is NOT returned — the node charges one at submit time…"

job_list { "kind": "teach", "state": "running", "limit": 20 }
→ { "jobs": [{ "job_id": "th_…", "kind": "teach", "state": "running", "native_state": "TRAINING",
               "summary": "MCP docs demo", "started_at": 1788…, "finished_at": null }, …] }

create_training_set (node-u, real run)

create_training_set {
  "rows": [{ "prompt": "What is the internal code name of the Ainize MCP bridge?", "answer": "aincp-3" },
           { "prompt": "Which port does the Ainize teach demo node listen on?", "answer": "3422" }],
  "name": "MCP docs demo", "retention": "keep",
  "provenance": { /* from McpDataSource, optional */ } }
{ "dataset_id": "43647481-78ed-46b5-8b40-dc92272442ff", "existing": false, "rows_accepted": 2, "rows_rejected": [],
  "sha256": "67c1b685259a898c9f92ff6a8deedbfa9a2900e8bc3ca016d66840433a143890",
  "predicted_sha256": "67c1b685…", "sha256_matches_prediction": true, "revision": 1, "size_bytes": 172,
  "summary": { "source_rows": 2, "accepted": 2, "duplicates": 0, "conflicts": 0, "too_long": 0, "langs": { "latin": 2, … } },
  "note": "a new training set was created on the node" }

teach_preflightjob_status (node-u, real run)

teach_preflight { "dataset_id": "43647481-…" }        // or { rows: [...] }, plus base / compare_with
→ { "job_id": "tp_28da33a8a678", "state": "queued",
    "cost": "free of money; it spends at least one of the 20 free live-test units per hour, charged to this server's IP and to the teaching key" }
job_status { "job_id": "tp_28da33a8a678", "wait_ms": 120000 }
→ { "state": "done", "result": {
      "trainable": 2, "checked": 2,
      "sampled": { "checked": 2, "of": 2, "note": "a sample of the training set, not the whole of it — the node probes at most 8 questions per call" },
      "items": [{ "question": "What is the internal code name of the Ainize MCP bridge?", "expected": "aincp-3",
                  "status": "will_train", "model_said": "(stub model) I do not know: …",
                  "meaning": "the model gets this wrong today — teaching it is worth a lesson" }, … ],
      "lessons_left_today": { "key": 20, "address": 0 },
      "cost_note": "this preflight spent free live-test units (one per three model calls, at least one)…" } }

(node-u runs the stub trainer, so model_said is stubbed. The shape is identical on a GPU node.)

teachjob_status

teach { "rows": [{ "prompt": "…", "answer": "…" }], "base": ["krx-all-2761"], "mode": "extend",
        "export": "delta", "effort": "balanced", "dry_run": false, "confirm": false }
{ "job_id": "th_5c018e8ae834", "state": "queued", "poll_after_ms": 3000,
  "built_on": [{ "id": "base1", "status": "LISTED", "price": "5", "body_held": false, "training_set": "public", "problem": null }],
  "mode": "extend", "export": "delta",
  "lessons": { "limit": 3, "used_today": 0, "remaining": 3, "session_cap": 3, "session_spent": 1 },
  "what_happens_next": "the training set is uploaded, the model is asked what it already knows (that is the preflight), and the lesson is submitted only if something is left to teach",
  "eta_note": "no measured estimate yet — job_status carries the node's own ETA once the lesson is queued" }
job_status { "job_id": "th_5c018e8ae834", "wait_ms": 25000 }
→ { "state": "done", "node_job_id": "lesson-1", "result": {
      "native_state": "READY", "what_is_happening": "ready: the lesson stuck and passed its checks",
      "eta_s": null, "eta_note": "no measured estimate yet", "progress": null,
      "questions": { "in_the_lesson": 1, "measured": 1, "learned": 1, "not_learned": 0,
                     "still_wrong": [], "taught": [{ "question": "Q1?", "answer": "A1", "alt_phrasing_ok": true }] },
      "checks": { "taught": { "hits": 1, "of": 1, "percent": 100 },
                  "other_phrasing": { "hits": 1, "of": 1, "percent": 100 },
                  "did_not_break_the_base": { "ok": true, "hits": 10, "of": 10 },
                  "did_not_change_unrelated_answers": { "ok": true, "same": 20, "of": 20 },
                  "reversible": null, "publish_gate": "open",
                  "note": "this node trains with the STUB backend: these numbers were simulated, nothing was measured in a live model" },
      "training_set": { "id": "ds_1", "sha256": "…", "rows": 1, "trained_rows": 1 },
      "knowledge_file": { "sha256": "…", "rows": 1, "size_bytes": 1024 },
      "draft_id": "taught-draft-1", "publish_status": "none",
      "next_steps": ["live_test with knowledge: [\"taught-draft-1\"] — prove the new answer against the bare model…",
                     "download_lesson — …the draft stays private until you publish it",
                     "publish_knowledge — irreversible: it writes a record on the ledger and offers the knowledge for sale"],
      "quota": { "key_remaining": 2, "ip_remaining": 5, "rows_remaining": 300 } } }

The refusals are the interesting part:

teach { "rows": [ /* things the model already answers */ ] }
→ isError: true
  { "code": "nothing_to_train",
    "message": "the model already answers all 3 probed questions correctly — no lesson was submitted and none was spent.",
    "retryable": false, "details": { "items": [ /* per-question verdicts */ ] } }

teach { … }            // when the key has one lesson left today
→ { "code": "confirmation_required",
    "message": "this is the last lesson this teaching key has on http://localhost:3422 today (2 of 3 used), and a lesson that fails is not refunded…" }

teach { "base": ["a", "b"] }
→ { "code": "merge_not_available", "message": "combining two knowledges is a merge, and no node supports it yet — build on one of them." }

download_lesson — keep it private

download_lesson { "lesson_id": "lesson-1", "include": ["knowledge_file", "recipe", "notes"] }   // all three by default
→ { "lesson_id": "lesson-1", "directory": "/…/lessons/lesson-1",
    "files": [{ "what": "knowledge_file", "path": "/…/lesson-x.npz", "bytes": 1024 },
              { "what": "recipe", "path": "/…/recipe.json", "bytes": 312 },
              { "what": "notes", "path": "/…/RUN-LOCALLY.md", "bytes": 2048 }],
    "knowledge": { "sha256": "eeee…", "rows": 3, "size_bytes": 1024, "filename": "lesson-x.npz", "model": "Qwen3.8-Flash-Next" },
    "privacy": "this lesson is still a private draft on the node: nothing was published, nothing was announced, and nobody else can see it.",
    "note": "the node's download links carry a short-lived token, which is a credential — this server used them and did not return them." }

publish_knowledge — irreversible, opt-in

publish_knowledge { "lesson_id": "lesson-1", "name": "X", "description": "…", "price": "5",
                    "license": "CC-BY-4.0", "training_set": { "access": "derivative", "source": "own", "no_pii": true },
                    "dry_run": true }
{ "dry_run": true,
  "lesson": { "id": "lesson-1", "status": "READY", "built_on": [] },
  "would_publish": { "name": "X", "price": "0", "currency": "CREDIT", "license": null, "training_set": null },
  "split_preview": { "to_the_people_it_was_built_on": { "amount": "0", "applies": false, "note": "no parent, so no lineage pool" },
                     "to_you_the_teacher": { "of_what_is_left": "70%" },
                     "explanation": "70% of the price, because this knowledge has no parent to pay." },
  "ledger": "local",
  "confirm_phrase_required": "publish lesson-1 permanently",
  "note": "nothing was written. Show the human the split and the fact that this cannot be undone, wait for them, then call again with both consents and the confirmation phrase." }

With a parent, explanation reads "49% of the price: a lineage pool of 30% is paid to what it was built on first" — the real number, never the publish sheet's flat 70 %. The live call additionally needs consent_permanent: true, consent_rights: true and the exact confirm_phrase, and is refused with permanent_ledger_refused on a node whose ledger is ain.

quote — the honest total, free (node-b, a real 25 AIN knowledge against a 10 AIN cap)

quote { "id": "krx-all-2761", "dry_run": true }     // dry_run prices from the catalogue and reserves no nonce
{ "quote_id": "q_16b6e28da2a3", "expires_at": 1788516643556, "binding": false, "dry_run": true,
  "items": [{ "id": "krx-all-2761", "role": "requested", "price": "25", "currency": "AIN", "status": "LISTED",
              "superseded_by": null, "license": null, "seller": "0xF7A9…", "quorum": "2/2", "sellable": true,
              "already_purchased": false, "owned": false, "body_held": true,
              "gateway_url": "http://localhost:3402/x402/patch/krx-all-2761" }],
  "total_requested": "25", "total_with_bases": "25",
  "budget": { "cap": "10", "spent": "0", "reserved": "0", "remaining": "10", "per_purchase_cap": "10" },
  "affordable": { "requested": false, "with_bases": false, "shortfall": "15",
    "explanation": "krx-all-2761 alone costs 25 AIN and does not fit. With the bases it needs the honest total is 25 — 15 over your remaining 10. Buy the add-on now and it sits unusable until the base is bought, raise AINIZE_MCP_SESSION_BUDGET, or look for a stand-alone knowledge that covers the same questions." },
  "confirm_with": { "tool": "buy", "quote_id": "q_16b6e28da2a3", "confirm_total": "25", "confirm": true },
  "next": "show the human the total, the bases and the remaining budget, then STOP. Never call buy in the same turn you first learned the price." }

Without dry_run the answer is binding: true and each item also carries scheme, pay_to and nonce from the seller's own 402.

buy — settles a quote, and only a quote

buy { "quote_id": "q_876cc6d685ec", "confirm_total": "5", "confirm": true, "dry_run": true }
→ { "dry_run": true, "would_buy": "k1", "amount": "5", "currency": "CREDIT",
    "gates_passed": ["quote present and unexpired", "total restated exactly", "confirm: true",
                     "not already purchased", "quorum met", "not challenged", "price unchanged",
                     "within the session cap"],
    "note": "nothing was called on the gateway and no nonce was reserved. Re-run without dry_run to settle." }
buy { "quote_id": "q_876cc6d685ec", "confirm_total": "5", "confirm": true }   // optional: apply, max_price, idempotency_key
→ { "job_id": "by_e8dd1778e6b4", "state": "queued", "patch_id": "k1", "amount": "5",
    "idempotency_key": "q:896b97cf…",
    "budget": { "cap": "10", "spent": "0", "reserved": "5", "remaining": "5" },
    "next": "poll job_status. If it fails or times out, do NOT buy again — call reconcile_purchase with this idempotency_key." }

job_status { "job_id": "by_e8dd1778e6b4", "wait_ms": 60000 }
→ { "state": "done", "result": {
      "patch_id": "k1", "amount": "5", "scheme": "local-credit", "tx_hash": "0xbbbb…",
      "body_present": true, "applied": false,
      "steps": [{ "step": "quorum", "detail": "2 attestation(s) ≥ quorum 2" }, { "step": "402", "detail": "payment required" },
                { "step": "settled", "detail": "paid" }, { "step": "download", "detail": "body fetched" }],
      "budget": { "cap": "10", "spent": "5", "remaining": "5" } } }

reconcile_purchase — after any failure, instead of paying again

reconcile_purchase { "id": "k1" }        // or { idempotency_key }
→ { "state": "complete",
    "purchase": { "amount": "5", "scheme": "local-credit", "tx_hash": "0x…", "bought_at": 1788…, "body_present": true },
    "explanation": "k1 is paid for and recorded on this node (tx …). Buying it again would pay a second time for nothing." }

The four states are complete · settled_no_body (the money moved, the body did not — carries the tx hash) · never_paid (a fresh quote → buy is safe) · recovered.

apply_knowledge / remove_knowledge — off by default

apply_knowledge { "id": "krx-all-2761" }
→ { "job_id": "ap_…", "state": "queued", "patch_id": "krx-all-2761",
    "model_lock": { "sentence": "the model is free" },
    "warning": "this changes the model server every node on this machine shares; the change persists across restarts and is visible to every other user of that model" }

remove_knowledge { "id": "krx-all-2761" }            // without confirm
→ { "code": "confirmation_required",
    "message": "removing krx-all-2761 writes the base model back over every memory row it owns, including rows another loaded knowledge shares — pass confirm: true once the human has agreed." }

Teaching, through MCP

The teach door is the half of Ainize that makes new knowledge rather than reselling it, and it is the part an agent can drive end to end. Four tools, in the order they are meant to be called:

create_training_set   rows in → a training set on the node (free, sub-second, de-duped by content)
teach_preflight       ask the model each question FIRST: will_train / already_known / overlaps_listing / invalid
teach                 spend one daily lesson and train it — optionally ON TOP OF an existing knowledge
download_lesson       take the knowledge file, the recipe and the run-it-yourself notes; the draft stays private
publish_knowledge     irreversible, opt-in: announce it on the ledger and offer it for sale

A lesson is scarce like money. The node charges one of jobs_per_key_per_day the moment a lesson is submitted — before it trains, before it checks — and never refunds it. So:

  • teach runs the preflight itself and refuses with nothing_to_train when every probed question is already answered correctly. The per-question verdicts come back in the refusal, and no lesson is spent.

  • When the key has one lesson left today, teach requires confirm: true and says so.

  • AINIZE_MCP_MAX_TEACH_JOBS caps what one MCP session may spend. It is server configuration; no argument raises it.

  • A failed lesson comes back as teach_quota_consumed, never as an automatic retry. Spending another one is a decision a person makes.

  • dry_run: true resolves the base, the quota and the rows and reports what would happen without uploading, probing or training anything.

base is not compare_with. base is what the lesson is trained on top of: recorded as a parent for good, paid a share of every sale, and required by anyone who buys the child. compare_with is loaded during the lesson for comparison only and is recorded nowhere. They map to the node's base_ids and context_ids; the deprecated builds_on_context is never sent.

teach {
  "rows": [{ "prompt": "픽셀플러스의 종목코드는?", "answer": "087600" }],
  "base": ["krx-all-2761"],        // trained on top of it, and its parent for good
  "mode": "extend",                 // "extend" needs a base; "merge" is not available on any node yet
  "export": "delta",                // "delta" needs the base loaded; "squash" is stand-alone
  "effort": "balanced"
}

Reading the result. job_status on a lesson returns the node's own 13-state machine (native_state), one sentence for what is happening, and — when it lands — what it learned and what it did not: every question that still fails with what the model said instead, the taught/held-out/locality checks, whether the publish gate is open, and the draft_id you can immediately live_test against the bare model. A lesson trained on a stub node says simulated: true and the note says nothing was measured in a live model.

Publishing shows the real revenue split before it writes anything. With a lineage pool of 0.3 and a contributor share of 0.7, a knowledge with a parent pays its teacher 0.7 × 0.7 = 49 % of the price, not the 70 % the web publish sheet prints (docs/ux-critique-3.json, item 186). publish_knowledge computes it from the node's own royalty_share / contributor_share, per royaltySplit in packages/core/src/catalog.ts, and shows it in dry_run before either consent is asked for.


Ainize as an MCP client — subgraph → training set

McpDataSource (src/datasource.ts) is the other direction: it connects to somebody else's MCP server over SSE, Streamable HTTP or stdio, calls a tool you name, and hands back both the answer and a provenance record. It maps the answer into {prompt, answer} rows with a declarative mapping, so the mapping is JSON a human can read and re-run — not a closure buried in a script.

const source = new McpDataSource({
  name: 'subgraph-mcp',
  transport: { kind: 'sse', url: 'https://subgraphs.mcp.thegraph.com/sse', headers: { Authorization: `Bearer ${key}` } },
});
await source.connect();
const { rows, provenance, rejected } = await source.fetchRows({
  tool: 'execute_query_by_subgraph_id',
  arguments: { subgraph_id: '5zvR82…', query: '{ _meta { block { number } } tokens(first: 20) { id symbol name } }' },
  mapping: {
    path: 'data.tokens',
    prompt: 'What is the {chain} contract address of the {name} ({symbol}) token?',
    answer: '{id}',
    require: ['id', 'symbol', 'name'],
    constants: { chain: 'Ethereum mainnet' },
  },
  upstream: { subgraph_id: '5zvR82…', block: 25903086 },
  note_fields: ['subgraph_id', 'block'],
});
// → create_training_set { rows, provenance }   … and STOP.

Provenance is the point. RowProvenance records the server, the negotiated protocol version, whether a credential was presented (never the credential), the tool, the exact arguments and their sha256, the block the answer was pinned to, a hash per row and the sha256 of the canonical JSONL. That last one is the node's own hash (test/rows.test.ts holds this implementation to sha256Rows in packages/node/src/teach-dataset.ts), so a caller knows the training-set id before uploading and identical rows land on the same training set instead of a second copy. Because the node's dataset API has no provenance field yet, the compact line goes into each row's own note — which is what a buyer sees when the training set is published with notes — and the full record is written beside the journal when a state directory is configured.

The hard stop. The pipeline ends at create_training_set. It never chains into teach: one agent turn must not be able to spend a day's lessons on on-chain data nobody has read.

The worked example

export GRAPH_API_KEY=…            # https://thegraph.com/studio/apikeys/
node packages/mcp/dist/examples/subgraph-to-training-set.js \
  --keyword uniswap --subgraph 5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV --first 20 \
  --out /tmp/uniswap.jsonl --upload --name "Uniswap v3 token addresses"

It follows the Subgraph MCP's own mandated workflow, which that server states in its graphql://subgraph resource: search → always check the 30-day query volume → read the schema → run a bounded, block-pinned query. Measured on this machine on 2026-09-04 and reported honestly by the example rather than papered over:

  • the hosted server speaks the legacy HTTP+SSE transport (POST /mcp is 404 there);

  • get_deployment_30day_query_counts currently answers 0 for every deployment, so the ranking signal the workflow depends on is unavailable. The example still calls it, still prints what it said, and then asks for --subgraph <id> instead of guessing — which is what the Subgraph MCP's instructions say to do when volumes cannot decide;

  • the schema is fetched and checked against the mapping before any field name is used;

  • rows are deliberately immutable facts only (an address, a symbol, a name). A price or a TVL changes every block: that is retrieval, not memory, and training it produces a knowledge that is wrong tomorrow.

No key, no run. graph/README.md requires live data, so a missing GRAPH_API_KEY is a clear failure with an instruction, never a silent fall back to fixtures. (--anonymous is offered because the hosted server does answer unauthenticated — the run is then attributable to nobody, which is not what a real integration ships.)


Tests

npm test -w packages/mcp                      # unit + tool handlers against a fake node (fast, no cluster needed)
AINIZE_SMOKE_NODE_URL=http://localhost:3422 npm test -w packages/mcp    # + a real-node smoke test
AINIZE_MCP_SMOKE_LIVE=1 npm test -w packages/mcp                        # + one real before/after on the shared GPU
GRAPH_API_KEY=… npm test -w packages/mcp                                # + the live Subgraph MCP smoke test
npx tsc -p packages/mcp/tsconfig.json --noEmit

test/fake-node.ts answers with the real node's shapes and records every request, so the tests can assert what was not called: a dry-run quote must never touch /x402/… (a 402 reserves a nonce), a refused gate must never reach /api/patches/:id/buy, and a replayed idempotency key must not produce a second purchase.

Driving it from a real MCP client

The unit tests call the handlers. scripts/drive.mjs does not: it spawns dist/bin.js as a subprocess and speaks the protocol to it with the official SDK client — initialize, tools/list, resources/list, tools/call, notifications/tools/list_changed — exactly as Claude Code or Cursor does, and writes every request and every answer to a JSONL transcript under packages/e2e/results/mcp/. Every defect listed in the git log under "a real MCP client found" was found this way and could not have been found by the fake.

node packages/mcp/scripts/drive.mjs packages/mcp/scripts/scenarios/<scenario>.mjs

Scenario

What it drives

Needs

job1-prove.mjs

find a Korean-ticker knowledge and prove it: search → detail → one live_test → before/after with the verifiers' scores

a node with a serving model; AINIZE_OPERATOR_PASSWORD to skip the trial quota

job2-teach.mjs

teach five facts on top of an existing knowledge and keep it private: training set → preflight → lesson → what it learned and what it did not → download

a teach-enabled node (teach.lineage: true), TEACH_KEY_FILE

job3-money.mjs

quote → refuse over the cap → buy with an explicit confirm → receipt, plus every money attack

a PRIVATE local-ledger cluster with something sellable, MONEY_NODE_PASSWORD

job3b-stale-quote.mjs

holds one session open past the quote's 10-minute life and then tries to settle it

the same private cluster (takes 11 minutes)

job4-subgraph.mjs

both directions: Ainize as a client of The Graph's Subgraph MCP, then those rows landing as a training set with provenance

GRAPH_API_KEY, TEACH_KEY_FILE

job5-attacks.mjs

bad ids, credentials in the wrong place, arguments that try to raise a cap, a node that is down, money on a port

any node

job6-http.mjs

the Streamable HTTP transport: handshake, session id, forged session, forged Host, no CORS grant

any node

job7-lock.mjs

two sessions fighting over the shared model lock: who holds it, free give-up, session-scoped jobs

a node with a serving model

demo-cluster-untouched.mjs

the before/after snapshot that says a session left the shared demo cluster alone

the demo cluster

Each run writes <scenario>.jsonl (the transcript) and <scenario>.checks.json (the pass/fail table) and exits non-zero if anything failed. job3 and job3b settle real money and must only ever be pointed at a private local-ledger cluster, never at the demo cluster or the shared AIN chain.

Troubleshooting

Symptom

Cause

What to do

The client shows 0 tools

the server started but the node did not answer

check AINIZE_NODE_URL; curl $AINIZE_NODE_URL/api/info. Read tools register even when the node is down, so 0 tools means the process itself failed — run node packages/mcp/dist/bin.js --help by hand and read stderr

The stdio handshake hangs or garbles

something printed to stdout before the transport connected

this server guards stdout before importing anything (@ainize/core prints secp256k1 unavailable on import). If you add an import that prints, that guard is why the handshake still works

teach is missing

no teaching key, or the node runs no teach worker

set AINIZE_TEACH_KEY; node_statuscapability_reasons.can_teach says which

buy is missing

the session budget is 0 (the default), or no operator credential

set AINIZE_MCP_SESSION_BUDGET and AINIZE_OPERATOR_PASSWORD / AINIZE_TOKEN. Buying is operator-gated on the node itself

publish_knowledge is missing

it is opt-in

AINIZE_MCP_ALLOW_PUBLISH=1, and on an AIN-chain node also AINIZE_MCP_ALLOW_AIN_PUBLISH=1 (think first: nothing on that chain can be recalled)

--http refuses to start

a spending budget or apply/publish is enabled on a listening port

that is the guard. Add --i-am-the-only-user if the port really is private, or drop the budget

Everything is model_busy

another node holds the shared model

the message names the holder and how long. node_status shows the queue. Wait; do not loop

quota_chat after a few tests

20 free live tests per hour per visitor IP, and one MCP server is one IP

retry_after_ms says when they come back. An operator credential removes the cap and the metering with it

quota_ip / quota_key on a lesson

the node's daily lesson limit for this IP or key

measured on node-u while writing these docs: {"code":"quota_ip","message":"daily lesson limit reached for this address"}. Nothing to retry today — use a private cluster for experiments

invalid_signature with a key you know is right

a teaching-key signature is request-bound and single use; something replayed the request

do not cache or reuse a header; do not follow redirects. This server signs per attempt

A live test's "before" already knows the answer

the knowledge is pinned on the shared model server by somebody else

result.knowledge[].was_already_applied and applied_on_this_model say so. Report it; the comparison is not against a bare model

verdict is null

the question is not in the knowledge's own benchmark

say "unscored comparison". It is not a failure

eta_s is null

the node has fewer than three measured samples on a real backend

render "no measured estimate yet". Never 0

A lesson says 100 % and simulated: true

the node trains with the stub backend

nothing was measured in a live model. Say so before anyone believes the number

job_not_found

jobs are session-scoped, evicted 30 min after finishing

job_list; for a lesson, poll by its node lesson id

A buy timed out

the blob download can take minutes

never buy again. reconcile_purchase tells you whether the money moved

The node answers 409 not sold here; gateway is <url>

you are quoting a knowledge another node sells

point the server at that node, or buy from it. The url in the error is the answer

Not in this version

  • merge. Two bases is a merge, which no node supports yet: the schema reserves the value and the refusal quotes the node's own merge_not_available.

  • A provenance field on the dataset manifest. Until the lineage work adds one, provenance rides in each row's note and in a JSON record beside the journal (§ Ainize as an MCP client).

  • reconcile_purchase cannot re-fetch a body itself. It reports settled_no_body with the tx hash and names the recovery, because the blob fetch needs a signature from the node identity key, which this server deliberately does not hold.

  • Prompts (prove_it, shop_for_knowledge, teach_on_top, subgraph_to_knowledge) and the ainize://knowledge/{id} resource template — the tools and ainize://instructions carry the same guidance today.

  • A bundle buy. quote states the whole stack and its honest total; buy purchases the named child only and says so before the money moves, because ?bundle=1 does not exist on the node.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables AI agents to participate in a marketplace for buying, selling, and trading services with atomic escrow and cryptographic verification. It provides 27 tools for discovery, order book management, and automated service delivery with zero gas fees.
    32
    25 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Persistent memory graph, knowledge marketplace, and MCP tool gateway for autonomous AI agents. Agents store experiences, trade knowledge via micropayments, and discover capabilities across the Hive network.
    5 npm
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides a zero-knowledge credential vault for AI agents, allowing secure storage, retrieval, and management of secrets with cross-agent delegation and tamper-evident audit.
    MIT