Nukez

Docs · MCP

MCP

Live · Cloud Run

Overview

Nukez MCP Server

The Nukez Model Context Protocol Server.

The canonical MCP flow uses the hosted streamable HTTP JSON-RPC endpoint, raw client-side tool calls, local Ed25519 envelope signing, and externally executed payment transactions. The MCP server coordinates Nukez gateway operations without custodying a wallet or client signing key.

EndpointStreamable HTTP

JSON-RPC MCP endpoint hosted on Cloud Run.

PaymentExternal transfer

Client signs and submits the selected chain payment locally.

AuthClient envelopes

Protected locker operations pass signed Ed25519 envelopes.

Model

Hosted MCP, local payment, local envelopes

The server wraps the gateway; clients keep custody of payment keys and protected signing.

text
MCP endpoint: https://mcp.nukez.xyz/mcpGateway:      https://api.nukez.xyzNetwork:      solana-mainnetTool surface: 15 thick MCP tools Payment flow:nukez_quote -> external chain transfer -> nukez_pay -> nukez_provision Post-provisioning auth:client-signed Ed25519 envelopes passed as envelope

Code notes

The production MCP server is a streamable HTTP JSON-RPC server. It wraps the Nukez gateway, but it does not hold a server wallet or signing key. Clients execute payment externally, sign operation envelopes locally, and pass those envelopes through MCP tool arguments.

PyNukez is not required for the canonical MCP client flow. The guide uses raw JSON-RPC, local Ed25519 envelope signing, and an explicit Solana transfer helper built with solders.

Setup

Quick path: the nukez-mcp package

pip install nukez-mcp replaces the hand-rolled JSON-RPC transport; signing and payment stay in client code.

python
pip install nukez-mcp from nukez_mcp import NukezMCP with NukezMCP() as mcp:    info = mcp.connect()    print(info["serverInfo"])          # {'name': 'nukez', 'version': '1.3.0'}     tools = mcp.list_tools()           # 15 tools    status = mcp.call_tool("nukez_status")    print(status["capabilities"]["payment_rails"])

Code notes

The official Python client wraps the JSON-RPC transport: stateless per-call POSTs to https://mcp.nukez.xyz/mcp that transparently parse both plain-JSON and text/event-stream bodies, with the MCP protocol version pinned to 2025-03-26. Use it instead of hand-rolling rpc and call; signing and payment stay in client code.

The package does no envelope signing, key handling, or payment execution. Envelope dicts built with build_envelope pass through call_tool arguments verbatim. The constructor accepts an endpoint= override plus timeout and client metadata; rpc(method, params) is the raw JSON-RPC escape hatch.

Setup

Install and configure the direct client runtime

The zero-dependency alternative: raw JSON-RPC plus local signing — the transport nukez-mcp implements under the hood.

python
pip install httpx pynacl base58 solders import json, hashlib, os, time, base64, pathlibimport httpxfrom nacl.signing import SigningKeyimport base58 BASE = "https://mcp.nukez.xyz/mcp"HEADERS = {    "Content-Type": "application/json",    "Accept": "application/json, text/event-stream",} KEYPAIR_PATH = pathlib.Path("/path/to/svm_key.json")_kp_bytes = json.loads(KEYPAIR_PATH.read_text())_sk = SigningKey(bytes(_kp_bytes[:32]))PUBKEY = base58.b58encode(_sk.verify_key.encode()).decode() RPC_URL = "https://mainnet.helius-rpc.com/?api-key=<API_KEY>"

Code notes

Run the shared setup once before the examples. Replace the keypair path and RPC URL with the client environment's values. The client needs httpx for transport, PyNaCl/base58 for envelope signing, and solders only when the example executes a Solana transfer.

Never send keypair bytes to the gateway or MCP server. The public key and transaction signatures are the only payment-side values the hosted tools need.

Transport

Call tools and build signed envelopes

Use tools/call for MCP operations and canonical signed envelopes for protected locker actions.

python
_req_id = 0 def rpc(method: str, params: dict = None):    global _req_id    _req_id += 1    payload = {"jsonrpc": "2.0", "id": _req_id, "method": method}    if params:        payload["params"] = params    resp = httpx.post(BASE, json=payload, headers=HEADERS, timeout=120)    ct = resp.headers.get("content-type", "")    if "text/event-stream" in ct:        for line in resp.text.splitlines():            if line.startswith("data: "):                data = json.loads(line[6:])                return data.get("result") or data.get("error")    body = resp.json()    return body.get("result") or body.get("error") def call(tool: str, args: dict = None):    params = {"name": tool}    if args:        params["arguments"] = args    result = rpc("tools/call", params)    if result and "content" in result:        texts = [c["text"] for c in result["content"] if c.get("type") == "text"]        if texts:            try:                return json.loads(texts[0])            except json.JSONDecodeError:                return texts[0]    return result def build_envelope(receipt_id, method, path, ops=None, body=None, ttl=300, query=None):    canonical_body = json.dumps(body, separators=(",", ":"), sort_keys=True) if body is not None else None    body_hash = hashlib.sha256((canonical_body or "").encode()).hexdigest()    envelope = {        "v": 1, "locker_id": compute_locker_id(receipt_id), "receipt_id": receipt_id,        "nonce": os.urandom(16).hex(), "iat": int(time.time()), "exp": int(time.time()) + ttl,        "ops": ops or [], "method": method.upper(), "path": path,        "body_sha256": body_hash, "sig_alg": "ed25519",    }    if query is not None:        envelope["query"] = query  # e.g. locker:attest binds to the request query    envelope_json = json.dumps(envelope, separators=(",", ":"), sort_keys=True)    sig = base58.b58encode(_sk.sign(envelope_json.encode()).signature).decode()    env_b64 = base64.urlsafe_b64encode(envelope_json.encode()).decode().rstrip("=")    result = {"headers": {"X-Nukez-Envelope": env_b64, "X-Nukez-Signature": sig}}    if canonical_body is not None:        result["body"] = canonical_body    return result

Code notes

The canonical client uses a small rpc helper, a tools/call wrapper, and a canonical JSON envelope builder. Authenticated locker operations bind method, path, receipt id, locker id, nonce, expiry, operation scope, and body hash into the signed payload.

Use locker:provision for provisioning, locker:write for create/delete/write flows, locker:list for file listings, locker:read for per-file reads, and locker:attest (with the query field) for attestation.

Lifecycle

Initialize, quote, pay, confirm, and provision

Follow the canonical two-step provisioning sequence from the executed notebook.

python
init = rpc("initialize", {    "protocolVersion": "2025-03-26",    "capabilities": {},    "clientInfo": {"name": "mcp-client", "version": "1.0"},})tools = rpc("tools/list") status = call("nukez_status")quote = call("nukez_quote", {"units": 1, "provider": "gcs"})sol_opt = next(o for o in quote["payment_options"] if o["pay_asset"] == "SOL")pay_req_id = quote["pay_req_id"] tx_sig = solana_transfer(sol_opt["pay_to_address"], int(sol_opt["amount"]))pay = call("nukez_pay", {"pay_req_id": pay_req_id, "tx_sig": tx_sig, "chain": "solana-mainnet"}) # SPL payments (BETA/USDC/USDT/WETH): send transferChecked to the option's# token account directly and ALWAYS include "pay_asset" in nukez_pay AND# nukez_provision — otherwise the gateway verifies the tx as native SOL. confirm = call("nukez_provision", {"pay_req_id": pay_req_id, "tx_sig": tx_sig})receipt_id = confirm["receipt_id"]locker_id = confirm["locker_id"] provision_env = build_envelope(    receipt_id, "POST", "/v1/storage/signed_provision",    ops=["locker:provision"], body={"receipt_id": receipt_id},)provision = call("nukez_provision", {"receipt_id": receipt_id, "envelope": provision_env})

Code notes

Provisioning is intentionally two-step. First, confirm the externally executed payment with nukez_provision and receive action_required: sign_provision_envelope. Then sign the provided provision operation and call nukez_provision again with receipt_id and envelope.

Save receipt_id permanently after payment confirmation. It is the durable handle for future sessions.

Storage

Store files through the right byte path

Inline data_b64 for small content; nukez_create_file with a direct PUT and confirm for large files.

python
body = {"filename": "smoke.txt", "content_type": "text/plain", "ttl_min": 30}env = build_envelope(    receipt_id, "POST", f"/v1/lockers/{locker_id}/files",    ops=["locker:write"], body=body,)store = call("nukez_store", {    "receipt_id": receipt_id,    "envelope": env,    "files": [{"name": "smoke.txt", "data_b64": base64.b64encode(b"smoke\n").decode()}],}) env = build_envelope(    receipt_id, "POST", f"/v1/lockers/{locker_id}/files",    ops=["locker:write", "file:create", "locker:create_file",         "locker:store", "locker:confirm", "file:confirm"],    body={"filename": "report.pdf"},)created = call("nukez_create_file", {    "filename": "report.pdf",    "content_type": "application/pdf",    "receipt_id": receipt_id,    "envelope": env,}) with httpx.Client(timeout=300, follow_redirects=True) as c:    up = c.put(created["upload_url"], content=big_bytes,               headers={"Content-Type": "application/pdf"}) confirmed = call("nukez_confirm", {"filename": "report.pdf", "receipt_id": receipt_id})

Code notes

Post-provisioning writes require a signed locker:write envelope for POST /v1/lockers/{locker_id}/files. Use inline data_b64 for small or moderate controlled clients. For large local files, use nukez_create_file to get a signed upload_url, PUT the raw bytes directly (they never transit the MCP server), then finalize with nukez_confirm.

Bytes never transit the MCP server on this path. Reserve nukez_upload_chunk for runtimes where a direct PUT is impossible.

Proof

Bootstrap, verify, attest, and audit

Use signed status for session bootstrap, two-phase attestation to anchor, and recompute-verify for byte-level audits.

python
env_list = build_envelope(    receipt_id, "GET", f"/v1/lockers/{locker_id}/files",    ops=["locker:list"],)files = call("nukez_retrieve", {"receipt_id": receipt_id, "envelope": env_list})status = call("nukez_status", {"receipt_id": receipt_id, "envelope": env_list}) verify_pre = call("nukez_verify", {"receipt_id": receipt_id, "push": False}) v1 = call("nukez_verify", {"receipt_id": receipt_id, "push": True})spec = v1["envelopes_needed"][0]  # includes a query field the signature must coverattest_env = build_envelope(receipt_id, spec["method"], spec["path"],                            ops=spec["ops"], body=spec["body"],                            query=spec["query"])v2 = call("nukez_verify", {"receipt_id": receipt_id, "push": True,                           "envelope": attest_env})assert v2["attestation"]["push_ok"] proof = call("nukez_verify", {"receipt_id": receipt_id, "filename": "report.pdf", "push": False})recheck = call("nukez_recompute_verify", {"receipt_id": receipt_id})

Code notes

For returning sessions, pass a signed locker:list envelope to nukez_status for a one-call bootstrap. Use verify with push=false for a structural check, the two-phase push=true flow to anchor on-chain, filename for a Merkle inclusion proof, and nukez_recompute_verify for a byte-level audit.

Re-run the two-phase attest after storing or deleting files to refresh the on-chain attestation.

Memory

Remember and recall

Two-phase plan/execute memory writes with keyless recall for agent session continuity.

python
mem = {    "receipt_id": receipt_id,    "key": "mainnet_test_note_1",    "content": json.dumps({"test": "mcp mainnet log"}),    "summary": "MCP mainnet test note",    "namespace": "test",    "tags": "mcp,mainnet,test",}plan = call("nukez_remember", {**mem, "envelope": []})assert plan["action_required"] == "upload_memory_files" for fkey in ("record_file", "index_file"):    f = plan[fkey]    body = {"filename": f["filename"], "content_type": f["content_type"], "ttl_min": 30}    env = build_envelope(receipt_id, "POST", f"/v1/lockers/{locker_id}/files",                         ops=["locker:write"], body=body)    call("nukez_store", {"receipt_id": receipt_id, "envelope": env,                         "files": [{"name": f["filename"],                                    "content_type": f["content_type"],                                    "data_b64": f["data_b64"]}]}) recall = call("nukez_recall", {"receipt_id": receipt_id, "key": "mainnet_test_note_1"})

Code notes

Memory writes are two-phase: the plan call (empty envelope) returns action_required upload_memory_files with two server-prepared files — the record and the updated index. Store each with a signed locker:write envelope like any other file. Recall reads through the public receipt proxy and needs no envelope.

nukez_remember accepts supersedes=<older-key> to chain session checkpoints; recall hides superseded records unless include_archived=True.

§ next