Getting StartedLive runtime routeRepo-grounded package map

Start from the surface that owns the request, then push until you have a real decision receipt.

This quickstart is built from the packages that are actually in your monorepo: the core runtime SDK, 16 JavaScript wrappers, Go and Rust clients, the Python package, plus CLI and MCP surfaces for operator and agent lanes.

JavaScript wrappers

16

Framework and server packages that let the runtime layer own the request lifecycle.

Language SDKs

4

Direct clients for Go, Rust, and Python services that do not want a JS wrapper.

Operator surfaces

3

CLI, MCP, and the direct runtime SDK all share the same contract line.

@cosantoir/node@cosantoir/next@cosantoir/express@cosantoir/honogithub.com/cosantoir/cosantoir-gocargo add cosantoirpip install cosantoircosantoir logincosantoir-mcp@cosantoir/node@cosantoir/next@cosantoir/express@cosantoir/honogithub.com/cosantoir/cosantoir-gocargo add cosantoirpip install cosantoircosantoir logincosantoir-mcp

Surface selection

Choose your lane

The setup gets cleaner when the package matches the real boundary: framework lifecycle, direct transport control, operator bootstrap, or agent access.

Runtime contract

Bind the contract once

Everything else stays coherent when the gateway origin, runtime key, and site id are identical across SDK, CLI, and MCP entry points.

Gateway origin

The deployed or local API origin that fronts the `/v1/dev/*` evaluator routes.

COSANTOIR_GATEWAY_URL

Runtime key

The request-scoped credential that authorizes evaluator calls and operator tooling.

COSANTOIR_API_KEY

Site id

The policy partition that keeps runtime decisions and analytics attached to the right surface.

COSANTOIR_SITE_ID

Environment

Shared values

env
01COSANTOIR_GATEWAY_URL=http://localhost:4000
02COSANTOIR_API_KEY=dp_live_example
03COSANTOIR_SITE_ID=site_prod_web
Do not let the CLI, SDK, and MCP server drift onto different origins or site ids.

Constructor map

Same contract, different client surface

multi
01JavaScript createCosantoir({ baseUrl, apiKey, siteId })
02Go cosantoir.NewClient(cosantoir.Options{ BaseURL, APIKey, SiteID })
03Rust Cosantoir::new(CosantoirOptions { base_url, api_key, site_id, timeout_ms })
04Python Cosantoir(CosantoirOptions(base_url=..., api_key=..., site_id=...))

First live route

Wire the first route

Do one protected path end-to-end before you fan out across the rest of the framework matrix. The goal is a clean receipt, not broad but shallow coverage.

Framework wrapper

Next.js middleware

ts
01import { createCosantoir } from "@cosantoir/node";
02import { createMiddleware, readNextRequestIp } from "@cosantoir/next";
03 
04const client = createCosantoir({
05 baseUrl: process.env.COSANTOIR_GATEWAY_URL!,
06 apiKey: process.env.COSANTOIR_API_KEY!,
07 siteId: process.env.COSANTOIR_SITE_ID!,
08});
09 
10export const middleware = createMiddleware({
11 client,
12 failOpen: true,
13 ip: (request) => readNextRequestIp(request) ?? "127.0.0.1",
14});
15 
16export const config = {
17 matcher: ["/api/protected/:path*"],
18};

Framework wrapper

Express middleware

ts
01import express from "express";
02import { createCosantoir } from "@cosantoir/node";
03import { createDeveloperProtectionExpressMiddleware } from "@cosantoir/express";
04 
05const client = createCosantoir({
06 baseUrl: process.env.COSANTOIR_GATEWAY_URL!,
07 apiKey: process.env.COSANTOIR_API_KEY!,
08 siteId: process.env.COSANTOIR_SITE_ID!,
09});
10 
11const app = express();
12app.use(
13 "/protected",
14 createDeveloperProtectionExpressMiddleware({ client, failOpen: true }),
15);

Framework wrapper

Hono middleware

ts
01import { Hono } from "hono";
02import { createCosantoir } from "@cosantoir/node";
03import { createDeveloperProtectionHonoMiddleware } from "@cosantoir/hono";
04 
05const client = createCosantoir({
06 baseUrl: process.env.COSANTOIR_GATEWAY_URL!,
07 apiKey: process.env.COSANTOIR_API_KEY!,
08 siteId: process.env.COSANTOIR_SITE_ID!,
09});
10 
11const app = new Hono();
12app.use("/protected/*", createDeveloperProtectionHonoMiddleware({ client }));

Direct SDK

Node.js evaluator call

ts
01import { createCosantoir } from "@cosantoir/node";
02 
03const client = createCosantoir({
04 baseUrl: process.env.COSANTOIR_GATEWAY_URL!,
05 apiKey: process.env.COSANTOIR_API_KEY!,
06 siteId: process.env.COSANTOIR_SITE_ID!,
07});
08 
09const result = await client.waf.evaluate({
10 ip: "198.51.100.20",
11 method: "POST",
12 path: "/signup",
13});
14 
15if (result.result.action === "deny") {
16 throw new Error("request blocked");
17}

Verification

Verify the receipt

A working quickstart returns more than status code. You should be able to inspect the headers, correlation line, and downstream operator proof.

Probe

Send a protected request

bash
01curl -sS -X POST http://localhost:3000/protected/signup \
02 -H "content-type: application/json" \
03 -H "x-forwarded-for: 198.51.100.20" \
04 -d '{"email":"founder@example.com"}' -i
Start with one route you can fully explain. Once that receipt is stable, widen the coverage.

Allow or deny result

A first request should resolve into an explicit decision, not an integration shrug.

Trace headers

Capture `x-request-id` and `x-correlation-id` so the request is explainable later.

Rate-limit receipts

Budgeted routes should emit `X-RateLimit-*` and `Retry-After` when they close.

Shared operator proof

The same request should be inspectable in dashboard views, CLI probes, and MCP tools.

Repo-wide coverage

Extend beyond JavaScript

Once the first route is real, expand into the other package surfaces that already exist in the repo instead of pretending the system ends at Node.

Go client

Direct evaluator lane

go
01client := cosantoir.NewClient(cosantoir.Options{
02 BaseURL: os.Getenv("COSANTOIR_GATEWAY_URL"),
03 APIKey: os.Getenv("COSANTOIR_API_KEY"),
04 SiteID: os.Getenv("COSANTOIR_SITE_ID"),
05})
06 
07result, err := client.EvaluateWAF(ctx, cosantoir.WafInput{
08 IP: "198.51.100.20",
09 Method: "POST",
10 Path: "/signup",
11})

Rust client

Async evaluator lane

rust
01let client = Cosantoir::new(CosantoirOptions {
02 base_url: env::var("COSANTOIR_GATEWAY_URL")?,
03 api_key: env::var("COSANTOIR_API_KEY")?,
04 site_id: Some(env::var("COSANTOIR_SITE_ID")?),
05 timeout_ms: 5000,
06})?;
07 
08let result = client
09 .evaluate_waf("198.51.100.20", "POST", "/signup")
10 .await?;

Python client

Service-side evaluator lane

py
01client = Cosantoir(
02 CosantoirOptions(
03 base_url=os.environ["COSANTOIR_GATEWAY_URL"],
04 api_key=os.environ["COSANTOIR_API_KEY"],
05 site_id=os.environ["COSANTOIR_SITE_ID"],
06 )
07)
08 
09result = client.waf_evaluate(
10 ip="198.51.100.20",
11 method="POST",
12 path="/signup",
13)

Language SDKs

Use the language clients when your evaluator calls live outside the JavaScript wrapper family.

4

Operator and agent surfaces

CLI and MCP should read the same runtime contract instead of inventing their own setup logic.

2
Last updated Mar 24, 2026