@justhandledlabs/agent-client 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/client.js +20 -4
- package/dist/config.d.ts +6 -0
- package/dist/config.js +9 -0
- package/dist/mcp.js +3 -4
- package/examples/agent-run-evidence-pack.sample.json +24 -0
- package/examples/api-response-contract-drift.json +18 -0
- package/examples/filename-portability.json +6 -0
- package/examples/policy-change-evidence.json +15 -0
- package/examples/webhook-contract-replay.json +20 -0
- package/package.json +2 -1
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# JustHandled Agent Client
|
|
2
2
|
|
|
3
|
-
A guarded CLI, JavaScript client, and local MCP server for the [JustHandled Agent Utility Gateway](https://justhandled-agent-gateway.netlify.app). The package exposes
|
|
3
|
+
A guarded CLI, JavaScript client, and local MCP server for the [JustHandled Agent Utility Gateway](https://justhandled-agent-gateway.netlify.app). The package exposes twenty deterministic preflights and evidence products. Nineteen individual checks cost $0.05 USDC; the consolidated six-check Agent Run Evidence Pack costs $0.25. Every paid call returns a versioned evidence receipt.
|
|
4
4
|
|
|
5
|
-
The client fails closed before signing. It accepts only the pinned JustHandled gateway, Base mainnet,
|
|
5
|
+
The client fails closed before signing. It accepts only the pinned JustHandled gateway, Base mainnet, canonical Base USDC, the exact per-product price, and the published merchant receiver. Customer inputs are not persisted by the gateway.
|
|
6
6
|
|
|
7
7
|
## Inspect without a wallet
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx --package @justhandledlabs/agent-client justhandled-agent catalog
|
|
11
11
|
npx --package @justhandledlabs/agent-client justhandled-agent preview filename-portability-preflight --json '{"paths":["CON.txt"]}'
|
|
12
|
+
npx --package @justhandledlabs/agent-client justhandled-agent preview agent-run-evidence-pack --file agent-run-evidence-pack.sample.json
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
Preview performs an unpaid request and validates the returned x402 terms. It does not sign or spend.
|
|
15
16
|
|
|
17
|
+
The catalog includes API-response contract drift, webhook contract and replay, and policy-change evidence products. Packaged example inputs are available in `examples/`.
|
|
18
|
+
|
|
16
19
|
Machine-readable products, exact payment terms, and free deterministic fixtures are available from the gateway's [product catalog](https://justhandled-agent-gateway.netlify.app/api/product-catalog), [OpenAPI document](https://justhandled-agent-gateway.netlify.app/openapi.json), and [sandbox index](https://justhandled-agent-gateway.netlify.app/sandbox/index.json).
|
|
17
20
|
|
|
18
21
|
## Execute one paid utility
|
|
@@ -52,6 +55,7 @@ The MCP server exposes a free catalog tool plus one paid tool per gateway utilit
|
|
|
52
55
|
## Security model
|
|
53
56
|
|
|
54
57
|
- Price, chain, asset, receiver, and gateway origin are pinned in code.
|
|
58
|
+
- The Agent Run Evidence Pack's 250,000-base-unit price is pinned separately from 50,000-base-unit individual checks.
|
|
55
59
|
- Every execution starts with an unpaid 402 preview.
|
|
56
60
|
- A mismatched term aborts before signing.
|
|
57
61
|
- The package never prints the private key.
|
package/dist/client.js
CHANGED
|
@@ -2,13 +2,23 @@ import { decodePaymentRequiredHeader, decodePaymentResponseHeader } from "@x402/
|
|
|
2
2
|
import { ExactEvmScheme } from "@x402/evm";
|
|
3
3
|
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
|
|
4
4
|
import { privateKeyToAccount } from "viem/accounts";
|
|
5
|
-
import { BASE_MAINNET, BASE_USDC, GATEWAY_ORIGIN, JUSTHANDLED_RECEIVER,
|
|
5
|
+
import { BASE_MAINNET, BASE_USDC, GATEWAY_ORIGIN, JUSTHANDLED_RECEIVER, expectedBaseUnitsForUtility, expectedPriceForUtility, } from "./config.js";
|
|
6
6
|
const UTILITY_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
7
7
|
function endpointFor(utility) {
|
|
8
8
|
if (!UTILITY_ID.test(utility))
|
|
9
9
|
throw new Error("utility id must be lowercase kebab-case");
|
|
10
10
|
return `${GATEWAY_ORIGIN}/api/run/${utility}`;
|
|
11
11
|
}
|
|
12
|
+
function utilityFromEndpoint(resourceUrl) {
|
|
13
|
+
const url = new URL(resourceUrl);
|
|
14
|
+
if (url.origin !== GATEWAY_ORIGIN || url.search || url.hash || url.username || url.password) {
|
|
15
|
+
throw new Error("refusing payment: payment resource is not a canonical JustHandled endpoint");
|
|
16
|
+
}
|
|
17
|
+
const match = /^\/api\/run\/([a-z0-9]+(?:-[a-z0-9]+)*)$/.exec(url.pathname);
|
|
18
|
+
if (!match)
|
|
19
|
+
throw new Error("refusing payment: payment resource is not a canonical JustHandled endpoint");
|
|
20
|
+
return match[1];
|
|
21
|
+
}
|
|
12
22
|
function requestFor(input, source) {
|
|
13
23
|
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(source))
|
|
14
24
|
throw new Error("source must be a bounded lowercase attribution slug");
|
|
@@ -27,7 +37,7 @@ export async function getCatalog(fetchImpl = fetch) {
|
|
|
27
37
|
throw new Error("gateway returned a malformed catalog");
|
|
28
38
|
for (const utility of catalog.utilities) {
|
|
29
39
|
const expectedEndpoint = `${GATEWAY_ORIGIN}/api/run/${utility.id}`;
|
|
30
|
-
if (!UTILITY_ID.test(utility.id) || utility.endpoint !== expectedEndpoint || utility.price !==
|
|
40
|
+
if (!UTILITY_ID.test(utility.id) || utility.endpoint !== expectedEndpoint || utility.price !== expectedPriceForUtility(utility.id)) {
|
|
31
41
|
throw new Error("gateway catalog contains an untrusted utility endpoint or price");
|
|
32
42
|
}
|
|
33
43
|
}
|
|
@@ -37,13 +47,19 @@ export function selectGuardedRequirement(required, expectedResourceUrl) {
|
|
|
37
47
|
if (expectedResourceUrl && required.resource?.url !== expectedResourceUrl) {
|
|
38
48
|
throw new Error("refusing payment: payment resource does not match the requested JustHandled endpoint");
|
|
39
49
|
}
|
|
50
|
+
const resourceUrl = expectedResourceUrl ?? required.resource?.url;
|
|
51
|
+
if (!resourceUrl)
|
|
52
|
+
throw new Error("refusing payment: payment resource is missing");
|
|
53
|
+
const utility = utilityFromEndpoint(resourceUrl);
|
|
54
|
+
const expectedAmount = expectedBaseUnitsForUtility(utility);
|
|
55
|
+
const expectedDisplay = expectedPriceForUtility(utility);
|
|
40
56
|
const requirement = required.accepts.find((candidate) => candidate.scheme === "exact" &&
|
|
41
57
|
candidate.network === BASE_MAINNET &&
|
|
42
58
|
candidate.asset.toLowerCase() === BASE_USDC.toLowerCase() &&
|
|
43
|
-
candidate.amount ===
|
|
59
|
+
candidate.amount === expectedAmount &&
|
|
44
60
|
candidate.payTo.toLowerCase() === JUSTHANDLED_RECEIVER.toLowerCase());
|
|
45
61
|
if (!requirement) {
|
|
46
|
-
throw new Error(
|
|
62
|
+
throw new Error(`refusing payment: gateway requirements do not match the pinned Base network, USDC asset, ${expectedDisplay} amount, and JustHandled receiver`);
|
|
47
63
|
}
|
|
48
64
|
return requirement;
|
|
49
65
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export declare const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
|
4
4
|
export declare const JUSTHANDLED_RECEIVER = "0xd7edc83c55c994850a14604a628639c3599665bc";
|
|
5
5
|
export declare const PRICE_BASE_UNITS = "50000";
|
|
6
6
|
export declare const PRICE_USDC = "$0.05";
|
|
7
|
+
export declare const AGENT_RUN_EVIDENCE_PACK = "agent-run-evidence-pack";
|
|
8
|
+
export declare const AGENT_RUN_EVIDENCE_PACK_BASE_UNITS = "250000";
|
|
9
|
+
export declare const AGENT_RUN_EVIDENCE_PACK_PRICE_USDC = "$0.25";
|
|
10
|
+
export declare function expectedPriceForUtility(utility: string): string;
|
|
11
|
+
export declare function expectedBaseUnitsForUtility(utility: string): string;
|
|
7
12
|
export interface UtilityCatalogItem {
|
|
8
13
|
id: string;
|
|
9
14
|
title: string;
|
|
@@ -16,6 +21,7 @@ export interface UtilityCatalogItem {
|
|
|
16
21
|
inputSchema: Record<string, unknown>;
|
|
17
22
|
sampleInput: Record<string, unknown>;
|
|
18
23
|
endpoint: string;
|
|
24
|
+
priceBaseUnits?: string;
|
|
19
25
|
}
|
|
20
26
|
export interface UtilityCatalog {
|
|
21
27
|
schemaVersion: string;
|
package/dist/config.js
CHANGED
|
@@ -4,3 +4,12 @@ export const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
|
4
4
|
export const JUSTHANDLED_RECEIVER = "0xd7edc83c55c994850a14604a628639c3599665bc";
|
|
5
5
|
export const PRICE_BASE_UNITS = "50000";
|
|
6
6
|
export const PRICE_USDC = "$0.05";
|
|
7
|
+
export const AGENT_RUN_EVIDENCE_PACK = "agent-run-evidence-pack";
|
|
8
|
+
export const AGENT_RUN_EVIDENCE_PACK_BASE_UNITS = "250000";
|
|
9
|
+
export const AGENT_RUN_EVIDENCE_PACK_PRICE_USDC = "$0.25";
|
|
10
|
+
export function expectedPriceForUtility(utility) {
|
|
11
|
+
return utility === AGENT_RUN_EVIDENCE_PACK ? AGENT_RUN_EVIDENCE_PACK_PRICE_USDC : PRICE_USDC;
|
|
12
|
+
}
|
|
13
|
+
export function expectedBaseUnitsForUtility(utility) {
|
|
14
|
+
return utility === AGENT_RUN_EVIDENCE_PACK ? AGENT_RUN_EVIDENCE_PACK_BASE_UNITS : PRICE_BASE_UNITS;
|
|
15
|
+
}
|
package/dist/mcp.js
CHANGED
|
@@ -3,8 +3,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { getCatalog, runPaidUtility } from "./client.js";
|
|
6
|
-
|
|
7
|
-
const server = new Server({ name: "justhandled-agent-gateway", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
6
|
+
const server = new Server({ name: "justhandled-agent-gateway", version: "0.2.1" }, { capabilities: { tools: {} } });
|
|
8
7
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
9
8
|
const catalog = await getCatalog();
|
|
10
9
|
return {
|
|
@@ -16,7 +15,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
16
15
|
},
|
|
17
16
|
...catalog.utilities.map((utility) => ({
|
|
18
17
|
name: `justhandled_${utility.id.replace(/-/g, "_")}`,
|
|
19
|
-
description: `${utility.summary} Costs ${
|
|
18
|
+
description: `${utility.summary} Costs ${utility.price} USDC on Base mainnet.`,
|
|
20
19
|
inputSchema: utility.inputSchema,
|
|
21
20
|
})),
|
|
22
21
|
],
|
|
@@ -33,7 +32,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
33
32
|
const key = process.env.JH_EVM_PRIVATE_KEY;
|
|
34
33
|
if (!key) {
|
|
35
34
|
return {
|
|
36
|
-
content: [{ type: "text", text: `Paid execution requires JH_EVM_PRIVATE_KEY. This tool will spend exactly ${
|
|
35
|
+
content: [{ type: "text", text: `Paid execution requires JH_EVM_PRIVATE_KEY. This tool will spend exactly ${utility.price} USDC only after validating the pinned Base network, USDC contract, price, and receiver.` }],
|
|
37
36
|
isError: true,
|
|
38
37
|
};
|
|
39
38
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"promise_action": {
|
|
3
|
+
"promises": [{ "id": "p1", "description": "Publish package" }],
|
|
4
|
+
"actions": [{ "id": "a1", "status": "completed", "promise_ids": ["p1"], "evidence_ids": ["e1"] }],
|
|
5
|
+
"evidence": [{ "id": "e1", "type": "registry", "uri": "https://registry.example/package" }]
|
|
6
|
+
},
|
|
7
|
+
"tool_calls": {
|
|
8
|
+
"contracts": [{ "tool": "publish", "allowed": true, "max_calls": 1, "required_arguments": ["artifact"], "forbidden_arguments": ["private_key"] }],
|
|
9
|
+
"calls": [{ "id": "c1", "tool": "publish", "arguments": { "artifact": "package.tgz" }, "outcome": "published" }]
|
|
10
|
+
},
|
|
11
|
+
"completion": {
|
|
12
|
+
"outcomes": [{ "id": "publish", "status": "completed", "evidence": [{ "type": "registry", "uri": "https://registry.example/package" }] }]
|
|
13
|
+
},
|
|
14
|
+
"tool_descriptions": {
|
|
15
|
+
"tools": [{ "name": "publish", "description": "Publish one validated package artifact to the configured registry.", "inputs": ["artifact"], "outputs": ["registry receipt"], "side_effects": ["creates a public package version"], "failure_modes": ["registry rejection"], "cost": { "amount": "0", "currency": "USD" }, "idempotency": "fails if the version already exists" }]
|
|
16
|
+
},
|
|
17
|
+
"skill_activation": {
|
|
18
|
+
"cases": [{ "id": "publish-package", "expected_skills": ["publish"], "observed_skills": ["publish"] }]
|
|
19
|
+
},
|
|
20
|
+
"costs": {
|
|
21
|
+
"budget_usd": 0.1,
|
|
22
|
+
"calls": [{ "id": "turn-1", "model": "example-model", "input_tokens": 1000, "output_tokens": 250, "input_usd_per_million": 1, "output_usd_per_million": 4, "tool_cost_usd": 0.01 }]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contracts": [{
|
|
3
|
+
"id": "get-account",
|
|
4
|
+
"expected_status": 200,
|
|
5
|
+
"expected_content_type": "application/json",
|
|
6
|
+
"required_fields": ["data.id", "data.status"],
|
|
7
|
+
"forbidden_fields": ["data.secret"],
|
|
8
|
+
"field_types": { "data.id": "string", "data.status": "string" },
|
|
9
|
+
"stable_values": { "data.status": "active" }
|
|
10
|
+
}],
|
|
11
|
+
"responses": [{
|
|
12
|
+
"id": "run-1",
|
|
13
|
+
"contract_id": "get-account",
|
|
14
|
+
"status": 200,
|
|
15
|
+
"content_type": "application/json",
|
|
16
|
+
"body": { "data": { "id": "acct-1", "status": "active" } }
|
|
17
|
+
}]
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"policies": [{
|
|
3
|
+
"id": "refund-policy",
|
|
4
|
+
"baseline": {
|
|
5
|
+
"version": "1.0",
|
|
6
|
+
"effective_date": "2026-01-01",
|
|
7
|
+
"clauses": [{ "id": "window", "text": "Refunds are available within 30 days.", "applies_to": ["all customers"], "requirements": ["receipt"] }]
|
|
8
|
+
},
|
|
9
|
+
"current": {
|
|
10
|
+
"version": "1.0",
|
|
11
|
+
"effective_date": "2026-01-01",
|
|
12
|
+
"clauses": [{ "id": "window", "text": "Refunds are available within 30 days.", "applies_to": ["all customers"], "requirements": ["receipt"] }]
|
|
13
|
+
}
|
|
14
|
+
}]
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"observed_at": "2026-08-01T15:00:00.000Z",
|
|
3
|
+
"contracts": [{
|
|
4
|
+
"event": "invoice.paid",
|
|
5
|
+
"allowed_versions": ["2026-01"],
|
|
6
|
+
"required_fields": ["invoice.id", "invoice.amount"],
|
|
7
|
+
"signature_required": true,
|
|
8
|
+
"idempotency_required": true,
|
|
9
|
+
"max_age_seconds": 300
|
|
10
|
+
}],
|
|
11
|
+
"deliveries": [{
|
|
12
|
+
"id": "delivery-1",
|
|
13
|
+
"event": "invoice.paid",
|
|
14
|
+
"version": "2026-01",
|
|
15
|
+
"payload": { "invoice": { "id": "inv-1", "amount": 2500 } },
|
|
16
|
+
"signature_present": true,
|
|
17
|
+
"idempotency_key": "evt-1",
|
|
18
|
+
"event_created_at": "2026-08-01T14:58:00.000Z"
|
|
19
|
+
}]
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justhandledlabs/agent-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Guarded CLI and MCP client for JustHandled's x402 utility gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"files": [
|
|
25
25
|
"dist",
|
|
26
|
+
"examples",
|
|
26
27
|
"README.md",
|
|
27
28
|
"LICENSE",
|
|
28
29
|
"server.json"
|
package/server.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "com.justhandledlabs/agent-gateway",
|
|
4
4
|
"title": "JustHandled Agent Utility Gateway",
|
|
5
|
-
"description": "
|
|
6
|
-
"version": "0.
|
|
5
|
+
"description": "Twenty deterministic preflights and evidence products with guarded per-product USDC x402 execution.",
|
|
6
|
+
"version": "0.2.1",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/justhandledlabs/justhandled-agent-client",
|
|
9
9
|
"source": "github"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@justhandledlabs/agent-client",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.2.1",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|