Framework recipes
Claude Code, Cursor, LangGraph and the Vercel AI SDK.
Two of these are MCP configuration and two are code — the split is whether the framework speaks MCP natively.
export WARRANT_API_KEY=wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44Every sample below runs against sandbox with a wr_test_ key.
Claude Code
claude mcp add --scope project warrant -- npx -y @gowarrant/mcp@latest--scope project writes .mcp.json in the repo, so the whole team gets the
same tools. Drop it for a user-level install.
The agent then has the five tools from the MCP page. Ask it to
"open a warrant for the invoice API and verify it" and it will call
warrant_open then warrant_verify. It cannot settle — that is not a tool it
has.
Cursor
npx gowarrant init --client cursorWrites .cursor/mcp.json. Restart Cursor and the tools appear.
LangGraph
LangGraph has no MCP client of its own, so wire the SDK as tools.
# The Warrant SDK is TypeScript; call the API directly from Python.
import os, uuid, requests
BASE = "https://api.gowarrant.xyz"
HEADERS = {"Authorization": f"Bearer {os.environ['WARRANT_API_KEY']}"}
def open_warrant(payee: str, amount: str, source_url: str) -> dict:
"""amount is the smallest unit as a string: "8400000" is 8.40 USDC."""
r = requests.post(
f"{BASE}/v1/warrants",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={
"amount": amount,
"payee": payee,
"deadline": "2026-08-16T12:00:00.000Z",
"spec": {"clauses": [{"kind": "http.status", "equals": 200}]},
},
timeout=20,
)
r.raise_for_status()
return r.json()Bind that as a tool on your graph node. The important part is that amount is a
string of the smallest unit — Python would happily send 8.4 as a float, and
the API rejects it.
Keep settlement out of the graph. A node that can release funds is a node that can be prompt-injected into releasing funds.
Vercel AI SDK
import { tool } from "ai";
import { z } from "zod";
import { Warrant, usdc } from "@gowarrant/sdk";
const warrant = new Warrant();
export const openWarrant = tool({
description: "Hold funds against a deliverable that can be checked.",
parameters: z.object({
payee: z.string(),
// A decimal string, never a number: the model will write 8.4 given a chance.
amount: z.string().regex(/^\d+(\.\d+)?$/),
sourceUrl: z.string().url(),
}),
execute: async ({ payee, amount, sourceUrl }) => {
const w = await warrant.open({
amount: usdc(amount),
payee,
spec: { clauses: [{ kind: "http.status", equals: 200 }] },
deadline: new Date(Date.now() + 3_600_000),
});
return { id: w.id, state: w.state, sourceUrl };
},
});
export const verifyWarrant = tool({
description: "Check the deliverable and let the policy decide.",
parameters: z.object({ id: z.string(), sourceUrl: z.string().url() }),
execute: async ({ id, sourceUrl }) => {
const after = await warrant.verify(id, { source_url: sourceUrl });
return { state: after.state };
},
});usdc() takes the decimal string and produces the Amount the SDK requires, so
the conversion happens once, in typed code, rather than in a prompt.
The rule across all four
Give an agent open and verify. Do not give it release, partialRelease or
refund.
Those three endpoints refuse an API key regardless, so exposing them as tools produces a 403 rather than a payment — but the reason to leave them out is simpler than that. The value of this product is that a person looked at the evidence when the check did not cleanly pass. A tool that skips them is a tool that removes the reason to use Warrant at all.
Amounts
Whatever the framework, what reaches the wire is an integer string of the
smallest unit, validated against ^[0-9]+$.
"8400000" 8.40 USDC
8400000 rejected — a JSON number
"8.40" rejected — not the smallest unitThe SDK's usdc() and the MCP tool schema both accept the decimal form and do
the conversion. Calling the API directly, as the LangGraph sample does, means
doing it yourself.