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
$ npm install @chio-protocol/sdk
$ yarn add @chio-protocol/sdk
$ pnpm add @chio-protocol/sdk
$ bun add @chio-protocol/sdkNode.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.
| Module | Purpose |
|---|---|
@chio-protocol/sdk | Top-level surface: client, session, auth, errors, DPoP, receipt query. |
@chio-protocol/sdk/invariants | Pure verification helpers: canonical JSON, SHA-256, Ed25519, receipt/capability/manifest parsing and verification. |
@chio-protocol/sdk/transport | Streamable 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
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
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
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
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
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
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 charsChioClient 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.
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.
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.
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
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
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
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.
| Framework | Import Path |
|---|---|
| Express | @chio-protocol/sdk/express |
| Fastify | @chio-protocol/sdk/fastify |
| Elysia | @chio-protocol/sdk/elysia |
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.
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.
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.
$ cd packages/sdk/chio-ts && npm test
$ npm run test:conformance