create_job
Escrow USDC payment by creating a Job that releases funds only after a Provider delivers and an Evaluator approves. Designed for agent-to-agent service delivery.
Instructions
Create a Job that escrows USDC payment until a Provider delivers and an Evaluator approves. Use this for A2A service delivery (vs send_payment for direct transfers). Confirm budget + provider with user first. Provider must have a CardZero wallet (Sprint 9 MVP requirement).
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| providerAddress | Yes | Provider's wallet address (0x-prefixed, must be a CardZero wallet) | |
| budgetUsdc | Yes | Budget in microUSDC (6 decimals), e.g. "10000000" for $10 USDC | |
| expiredAt | Yes | Unix timestamp (seconds). Must be at least 86400 (1 day) from now. | |
| title | Yes | Short title describing the work | |
| description | Yes | Detailed description: what Provider must deliver to be paid | |
| evaluatorRule | Yes | Evaluator rule that decides complete/reject when Provider submits | |
| idempotencyKey | No | Optional: prevent duplicate Jobs on retry |
Implementation Reference
- src/index.ts:231-262 (registration)Registration of the 'create_job' tool on the MCP server. Uses server.tool() with Zod schema for inputs and an async handler.
server.tool( "create_job", "Create a Job that escrows USDC payment until a Provider delivers and an Evaluator approves. Use this for A2A service delivery (vs send_payment for direct transfers). Confirm budget + provider with user first. Provider must have a CardZero wallet (Sprint 9 MVP requirement).", { providerAddress: z.string().describe("Provider's wallet address (0x-prefixed, must be a CardZero wallet)"), budgetUsdc: z.string().describe('Budget in microUSDC (6 decimals), e.g. "10000000" for $10 USDC'), expiredAt: z.number().describe("Unix timestamp (seconds). Must be at least 86400 (1 day) from now."), title: z.string().describe("Short title describing the work"), description: z.string().describe("Detailed description: what Provider must deliver to be paid"), evaluatorRule: z.object({ type: z.enum(["manual", "json_schema", "http_check"]).describe("'manual' = needs human, 'json_schema' = MVP auto-approve on submit, 'http_check' = fetch URL and check status"), url: z.string().optional().describe("For http_check: URL to fetch"), expectedStatus: z.number().optional().describe("For http_check: expected HTTP status (default 200)"), timeoutMs: z.number().optional().describe("For http_check: fetch timeout in ms (default 5000)"), schema: z.object({}).passthrough().optional().describe("For json_schema (reserved for full validation)"), }).describe("Evaluator rule that decides complete/reject when Provider submits"), idempotencyKey: z.string().optional().describe("Optional: prevent duplicate Jobs on retry"), }, async ({ providerAddress, budgetUsdc, expiredAt, title, description, evaluatorRule, idempotencyKey }) => { try { const body: Record<string, unknown> = { providerAddress, budgetUsdc, expiredAt, title, description, evaluatorRule, }; if (idempotencyKey) body.idempotencyKey = idempotencyKey; const res = await callApi("POST", "/jobs", body); if (!res.ok) return errorResponse("Create job failed", res); return successResponse(res.json); } catch (e) { return { content: [{ type: "text" as const, text: `Create job error: ${e}` }], isError: true }; } }, ); - src/index.ts:249-261 (handler)The async handler for create_job. Constructs the request body from inputs, POSTs to '/jobs' via callApi, returns success or error response.
async ({ providerAddress, budgetUsdc, expiredAt, title, description, evaluatorRule, idempotencyKey }) => { try { const body: Record<string, unknown> = { providerAddress, budgetUsdc, expiredAt, title, description, evaluatorRule, }; if (idempotencyKey) body.idempotencyKey = idempotencyKey; const res = await callApi("POST", "/jobs", body); if (!res.ok) return errorResponse("Create job failed", res); return successResponse(res.json); } catch (e) { return { content: [{ type: "text" as const, text: `Create job error: ${e}` }], isError: true }; } }, - src/index.ts:234-248 (schema)Zod schema defining input parameters: providerAddress, budgetUsdc, expiredAt, title, description, evaluatorRule (with type/url/expectedStatus/timeoutMs/schema), and optional idempotencyKey.
{ providerAddress: z.string().describe("Provider's wallet address (0x-prefixed, must be a CardZero wallet)"), budgetUsdc: z.string().describe('Budget in microUSDC (6 decimals), e.g. "10000000" for $10 USDC'), expiredAt: z.number().describe("Unix timestamp (seconds). Must be at least 86400 (1 day) from now."), title: z.string().describe("Short title describing the work"), description: z.string().describe("Detailed description: what Provider must deliver to be paid"), evaluatorRule: z.object({ type: z.enum(["manual", "json_schema", "http_check"]).describe("'manual' = needs human, 'json_schema' = MVP auto-approve on submit, 'http_check' = fetch URL and check status"), url: z.string().optional().describe("For http_check: URL to fetch"), expectedStatus: z.number().optional().describe("For http_check: expected HTTP status (default 200)"), timeoutMs: z.number().optional().describe("For http_check: fetch timeout in ms (default 5000)"), schema: z.object({}).passthrough().optional().describe("For json_schema (reserved for full validation)"), }).describe("Evaluator rule that decides complete/reject when Provider submits"), idempotencyKey: z.string().optional().describe("Optional: prevent duplicate Jobs on retry"), }, - src/index.ts:33-62 (helper)callApi helper function used by the create_job handler to make authenticated HTTP requests to the CardZero API.
async function callApi( method: "GET" | "POST", path: string, body?: Record<string, unknown>, auth = true, ): Promise<ApiResult> { if (auth && !API_KEY) { return { ok: false, status: 401, json: { error: "config_missing", message: "CARDZERO_API_KEY is not set. Get one at https://cardzero.ai", }, }; } const headers: Record<string, string> = {}; if (auth) headers["Authorization"] = `Bearer ${API_KEY}`; if (body) headers["Content-Type"] = "application/json"; const res = await fetch(`${API_URL}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }); const json = await res.json() as Record<string, unknown>; return { ok: res.ok, status: res.status, json }; }