Chio/Docs

TypeScript SDK

The TypeScript SDK is published as @chio-protocol/sdk. Node.js 22 or newer, ESM only, pure TypeScript with no native dependencies.

Installation

bash
$ npm install @chio-protocol/sdk
$ yarn add @chio-protocol/sdk
$ pnpm add @chio-protocol/sdk
$ bun add @chio-protocol/sdk

Node.js 22 is the minimum. The package declares "type": "module" in its package.json; consumers built with CommonJS should use a dynamic import().

Entry Points

The SDK exposes three top-level modules. Import only what you need so tree-shaking drops unused code paths from your bundle.

ModulePurpose
@chio-protocol/sdkTop-level surface: client, session, auth, errors, DPoP, receipt query.
@chio-protocol/sdk/invariantsPure verification helpers: canonical JSON, SHA-256, Ed25519, receipt/capability/manifest parsing and verification.
@chio-protocol/sdk/transportStreamable HTTP transport: RPC message parsing, session lifecycle, header builders.

Invariants Module

The invariants module is dependency-free. Every exported function is synchronous where possible, async only when the underlying Web Crypto primitive is async. These are the building blocks you reach for when you need to verify protocol artifacts without touching the network.

Exported Functions

  • canonicalizeJson: serialize any JSON-compatible value to RFC 8785 canonical bytes.
  • sha256Hex: SHA-256 of a byte array, lowercase hex.
  • verifyReceipt: verify a parsed receipt object (signature plus parameter hash).
  • verifyReceiptJson: parse and verify in one call from a JSON string.
  • verifyCapability: signature check plus time-status classification.
  • verifyManifest: signed tool-manifest verification.
  • verifySignature: raw Ed25519 signature verification for arbitrary bytes.
  • parseReceiptJson: JSON string to strongly typed receipt.
  • parseCapabilityJson: JSON string to strongly typed capability token.
  • signDpopProof: build and sign a DPoP proof for a tool invocation.

Canonical JSON

typescript
import { canonicalizeJson, canonicalizeJsonString } from "@chio-protocol/sdk/invariants";

// Serialize an object to RFC 8785 canonical bytes.
const bytes: Uint8Array = canonicalizeJson({ b: 2, a: 1 });
// bytes decodes to '{"a":1,"b":2}'

// Canonicalize an existing JSON string.
const str: string = canonicalizeJsonString('{"b":2,"a":1}');
// str === '{"a":1,"b":2}'

Hashing

typescript
import { sha256Hex, sha256HexBytes, sha256HexUtf8 } from "@chio-protocol/sdk/invariants";

sha256Hex(new Uint8Array([1, 2, 3]));
sha256HexBytes(new Uint8Array([1, 2, 3]));
sha256HexUtf8("hello world");

Receipts

typescript
import {
  parseReceiptJson,
  verifyReceipt,
  verifyReceiptJson,
  receiptBodyCanonicalJson,
} from "@chio-protocol/sdk/invariants";
import type { ChioReceipt, ReceiptVerification } from "@chio-protocol/sdk/invariants";

const receipt: ChioReceipt = parseReceiptJson(jsonString);
const result: ReceiptVerification = await verifyReceipt(receipt);
// result.signatureValid, result.parameterHashValid

const quick: ReceiptVerification = await verifyReceiptJson(jsonString);
const body: string = receiptBodyCanonicalJson(receipt);

Capabilities

typescript
import {
  parseCapabilityJson,
  verifyCapability,
  verifyCapabilityJson,
} from "@chio-protocol/sdk/invariants";
import type {
  CapabilityToken,
  CapabilityVerification,
  CapabilityTimeStatus,
} from "@chio-protocol/sdk/invariants";

const cap: CapabilityToken = parseCapabilityJson(jsonString);
const status: CapabilityVerification = await verifyCapability(cap);
// status.signatureValid: boolean
// status.timeStatus: "valid" | "not_yet_valid" | "expired"

Manifests

typescript
import {
  parseSignedManifestJson,
  verifySignedManifest,
  verifySignedManifestJson,
} from "@chio-protocol/sdk/invariants";
import type { SignedManifest, ManifestVerification } from "@chio-protocol/sdk/invariants";

const manifest: SignedManifest = parseSignedManifestJson(jsonString);
const result: ManifestVerification = await verifySignedManifest(manifest);

Ed25519

typescript
import {
  signJsonStringEd25519,
  signUtf8MessageEd25519,
  verifyJsonStringSignatureEd25519,
  verifyUtf8MessageEd25519,
  isValidPublicKeyHex,
  isValidSignatureHex,
  publicKeyHexMatches,
} from "@chio-protocol/sdk/invariants";

const sig = await signJsonStringEd25519('{"key":"value"}', privateKeyHex);
const ok = await verifyJsonStringSignatureEd25519(
  '{"key":"value"}',
  sig.signature,
  publicKeyHex,
);

isValidPublicKeyHex(hex);  // 64 hex chars
isValidSignatureHex(hex);  // 128 hex chars

ChioClient and ChioSession

ChioClient is the top-level entry into the SDK. It composes transport, authentication, and session management. Call initialize() to open a remote MCP session against a chio edge.

typescript
import { ChioClient, staticBearerAuth } from "@chio-protocol/sdk";

const client = new ChioClient({
  baseUrl: "https://edge.example.com/mcp",
  auth: staticBearerAuth(process.env.CHIO_TOKEN!),
});

const session = await client.initialize();

const tools = await session.listTools();
const result = await session.callTool("read_file", { path: "./README.md" });

await session.close();

ChioSession exposes the common MCP surface (listTools, callTool, listResources, readResource, listPrompts, getPrompt) along with lower-level hooks like request, notification, and sendEnvelope.

Transport Module

The transport module implements MCP-compatible Streamable HTTP with explicit session lifecycle. Use it directly when you need full control, or let ChioClient drive it for you.

typescript
import {
  initializeSession,
  postRpc,
  postNotification,
  deleteSession,
  buildRpcHeaders,
  parseRpcMessages,
  readRpcMessagesUntilTerminal,
} from "@chio-protocol/sdk/transport";
import type {
  SessionState,
  InitializeSessionResult,
  RpcExchange,
  JsonRpcMessage,
} from "@chio-protocol/sdk/transport";

const { sessionState }: InitializeSessionResult = await initializeSession(
  "https://edge.example.com/mcp",
  { onMessage: (msg) => console.log("recv", msg) },
);

const exchange: RpcExchange = await postRpc(
  "https://edge.example.com/mcp",
  sessionState,
  { method: "tools/list", params: {} },
);

await deleteSession("https://edge.example.com/mcp", sessionState);

DPoP Proofs

signDpopProof constructs a canonical proof body, hashes the tool arguments with SHA-256, and signs the body with the agent Ed25519 seed. The output is accepted by any chio kernel that runs verify_dpop_proof.

typescript
import { signDpopProof, DPOP_SCHEMA } from "@chio-protocol/sdk";
import type { DpopProof, DpopProofBody } from "@chio-protocol/sdk";

const proof: DpopProof = await signDpopProof({
  capabilityId: "cap_7f3a...e91d",
  toolServer: "srv-files",
  toolName: "read_file",
  actionArgs: { path: "./workspace/README.md" },
  agentSeedHex: process.env.CHIO_AGENT_SEED_HEX!,
  nonce: "optional-server-nonce",
  issuedAt: 1744537862, // optional, defaults to now
});

// proof.body fields (sorted keys for canonical JSON):
//   action_hash, agent_key, capability_id, issued_at, nonce,
//   schema (= DPOP_SCHEMA), tool_name, tool_server
// proof.signature is a hex-encoded Ed25519 signature over the canonical body.

ReceiptQueryClient

ReceiptQueryClient wraps GET /v1/receipts/query with TypeScript types and automatic bearer-token injection. Filter fields are camelCase in JavaScript; the client converts them to the wire query string.

Construction

typescript
import { ReceiptQueryClient, staticBearerAuth } from "@chio-protocol/sdk";
import type { ChioReceipt, ReceiptQueryPage } from "@chio-protocol/sdk";

const client = new ReceiptQueryClient({
  baseUrl: "https://receipts.example.com",
  auth: staticBearerAuth(process.env.RECEIPT_TOKEN!),
});

One-Shot Query

typescript
const page: ReceiptQueryPage = await client.query({
  capabilityId: "cap_7f3a...e91d",
  toolServer: "srv-files",
  toolName: "read_file",
  outcome: "deny",          // "allow" | "deny" | "cancelled" | "incomplete"
  since: 1744500000,        // Unix seconds
  until: 1744600000,
  minCost: 1,
  maxCost: 1000,
  limit: 50,
  cursor: undefined,        // opaque cursor from a prior page
});

console.log(page.totalCount, page.nextCursor, page.receipts.length);

Async Iteration

typescript
for await (const receipt of client.iterate({ outcome: "deny" })) {
  const r: ChioReceipt = receipt;
  console.log(r.id, r.tool_name, r.decision.verdict);
}

The iterator drives the cursor field automatically and stops when the server omits nextCursor. Query failures throw QueryError with the HTTP status attached; transport failures throw TransportError.

Framework Adapters

Three framework adapters wrap the SDK for common Node.js servers. Each one exposes a middleware or plugin that consults a sidecar for a policy decision, attaches the resulting receipt IDs to the response, and surfaces any deny reason to the request handler.

FrameworkImport Path
Express@chio-protocol/sdk/express
Fastify@chio-protocol/sdk/fastify
Elysia@chio-protocol/sdk/elysia
server.ts
import express from "express";
import { chioMiddleware } from "@chio-protocol/sdk/express";

const app = express();

app.use(chioMiddleware({
  sidecarUrl: "http://localhost:7391",
  bearerToken: process.env.CHIO_TOKEN!,
}));

app.post("/tools/read_file", (req, res) => {
  // req.chio.receiptId is set by the middleware on allow.
  res.json({ ok: true });
});

app.listen(8080);

Error Hierarchy

All SDK errors extend ChioError. Catch the base class for broad recovery, or the specific subclass when you need to branch on failure mode.

typescript
import {
  ChioError,
  DpopSignError,
  QueryError,
  TransportError,
} from "@chio-protocol/sdk";

try {
  const page = await client.query({ capabilityId: "cap_abc" });
} catch (err) {
  if (err instanceof QueryError) {
    console.error("query failed", err.status, err.message);
  } else if (err instanceof TransportError) {
    console.error("network failed", err.message);
  } else if (err instanceof ChioError) {
    console.error("chio error", err.code, err.message);
  } else {
    throw err;
  }
}

Invariant errors are separate

ChioInvariantError lives under @chio-protocol/sdk/invariants and does not extend ChioError. Catch it on its own if you call the low-level verification helpers directly.

Full Quickstart

The snippet below walks a new agent from zero to one verified tool call: open a session, list tools, sign a DPoP proof, call a tool, then verify the returned receipt offline.

quickstart.ts
import {
  ChioClient,
  ReceiptQueryClient,
  signDpopProof,
  staticBearerAuth,
} from "@chio-protocol/sdk";
import { verifyReceiptJson } from "@chio-protocol/sdk/invariants";

const client = new ChioClient({
  baseUrl: "https://edge.example.com/mcp",
  auth: staticBearerAuth(process.env.CHIO_TOKEN!),
});

const session = await client.initialize();

const tools = await session.listTools();
console.log("available tools:", tools.map((t) => t.name));

const args = { path: "./README.md" };
const proof = await signDpopProof({
  capabilityId: process.env.CHIO_CAPABILITY_ID!,
  toolServer: "srv-files",
  toolName: "read_file",
  actionArgs: args,
  agentSeedHex: process.env.CHIO_AGENT_SEED_HEX!,
});

const { receiptJson, content } = await session.callTool("read_file", args, {
  dpopProof: proof,
});

const verification = await verifyReceiptJson(receiptJson);
if (!verification.signatureValid || !verification.parameterHashValid) {
  throw new Error("receipt failed local verification");
}

console.log("content:", content);
await session.close();

Conformance

The TypeScript SDK passes the cross-language conformance suite through green wave 5. Canonical JSON output, SHA-256 digests, Ed25519 signatures, receipt verification, capability verification, and manifest verification all produce byte-for-byte identical results against the Rust reference.

bash
$ cd packages/sdk/chio-ts && npm test
$ npm run test:conformance