verify402-mcp 1.0.6 → 1.0.8

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 CHANGED
@@ -10,13 +10,13 @@ dedicated hot wallet funded with **Base Sepolia testnet USDC** (`eip155:84532`).
10
10
  The analysis reads mainnet chain data, but the demo never settles mainnet
11
11
  payments. The bridge never signs or sends a trade.
12
12
 
13
- > **Published package note:** `verify402-mcp@1.0.6` is published to npm and
13
+ > **Published package note:** `verify402-mcp@1.0.8` is published to npm and
14
14
  > defaults paid calls to Base Sepolia testnet (`eip155:84532`) for the public
15
15
  > demo. Base mainnet (`eip155:8453`) requires an explicit `VERIFY402_NETWORKS`
16
16
  > opt-in in a separate deployment and is not enabled by this release. The
17
- > previously published `1.0.5` package is immutable; `1.0.4` is also immutable
18
- > and used Base mainnet by default. Do not republish or overwrite either
19
- > version.
17
+ > previously published `1.0.6` package is immutable; `1.0.5` and `1.0.4` are
18
+ > also immutable, and `1.0.4` used Base mainnet by default. Do not republish or
19
+ > overwrite any of them.
20
20
 
21
21
  ## Tools
22
22
 
@@ -41,6 +41,19 @@ resource is CAUTION `resource_identity_unverified` and `resource identity` is
41
41
  listed in `notChecked`. The bridge forwards this policy to the server; the
42
42
  server enforces it.
43
43
 
44
+ Two optional caller-supplied checks are forwarded as-is:
45
+
46
+ - `payment.idempotencyKey` + `policy.obligation.settledKeys` rejects a key the
47
+ caller already settled; `policy.obligation.spentUsd` + `grantUsd` rejects
48
+ cumulative spend over the grant. The bridge does **not** persist this state.
49
+ - `policy.sellerPayoutWallets` is a caller-supplied payout allowlist. A
50
+ mismatch is BLOCK, but a match is not provenance and never adds
51
+ `payee_payout_binding`.
52
+ - `policy.sellerPayoutManifest` fetches a seller-signed manifest from a
53
+ caller-pinned HTTPS origin outside the 402. Only a verified manifest whose
54
+ `payTo` list matches adds `payee_payout_binding`. The response includes
55
+ `payeeBinding` with the source, origin, signer, expiry, and manifest hash.
56
+
44
57
  The gate is advisory at this layer: the bridge does not hold the agent's
45
58
  transaction key and cannot stop an agent that ignores the answer. Enforcement
46
59
  belongs in the executor that signs — verify the receipt, then sign. See
@@ -199,11 +212,16 @@ Once configured, your agent automatically sees the tools in its context. You can
199
212
  - `MAX_PAYMENT_USD` is enforced inside the x402 client, before a signature
200
213
  exists; removing it is a security regression. The caps limit what the bridge
201
214
  can authorize; they do **not** protect a compromised private key.
215
+ - The bridge sends `X-Verify402-Client: verify402-mcp/<version>` and the same
216
+ version in its User-Agent. This is an anonymous label for `/usage`, not an
217
+ account: it contains no wallet, key, or user id, and the server treats it as
218
+ self-declared and spoofable.
202
219
  - `preflight_gate` is a check, not a guarantee. Read `notChecked`: it does not
203
220
  screen recipient reputation or sanctions, audit contracts, simulate full
204
- balance deltas, audit the counterparty, scan tool descriptions or responses
205
- for prompt injection, or protect against MEV. It catches the measurable
206
- signing mistakes, not every scam.
221
+ balance deltas, audit the counterparty, independently prove payee ownership,
222
+ persist an obligation ledger across processes, scan tool descriptions or
223
+ responses for prompt injection, or protect against MEV. It catches the
224
+ measurable signing mistakes, not every scam.
207
225
  - Use the [reference executor](https://github.com/a32116150-ctrl/verify402/blob/master/examples/preflight-guard.mjs)
208
226
  when you want enforcement rather than advice.
209
227
 
package/client-id.mjs ADDED
@@ -0,0 +1,17 @@
1
+ // One shared, anonymous client label for every request the bridge makes.
2
+ //
3
+ // This is not an identity: it contains no wallet, key, account, or user id, and
4
+ // the server treats it as a self-declared hint. Its only job is to let /usage
5
+ // separate bridge traffic from generic HTTP probes. The version comes from the
6
+ // package manifest so a release bump cannot leave the label stale.
7
+
8
+ import { readFileSync } from "node:fs";
9
+
10
+ const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
11
+
12
+ export const CLIENT_ID = "verify402-mcp";
13
+ export const VERSION = pkg.version;
14
+ export const CLIENT_HEADERS = Object.freeze({
15
+ "User-Agent": `${CLIENT_ID}/${VERSION}`,
16
+ "X-Verify402-Client": `${CLIENT_ID}/${VERSION}`,
17
+ });
package/index.mjs CHANGED
@@ -31,6 +31,7 @@ import { config } from "dotenv";
31
31
  import { z } from "zod";
32
32
  import { installConsoleRedaction, logInfo, logError } from "./log.mjs";
33
33
  import { paidPost } from "./paid-call.mjs";
34
+ import { CLIENT_HEADERS, VERSION } from "./client-id.mjs";
34
35
  import { fileURLToPath } from "node:url";
35
36
  import { realpathSync } from "node:fs";
36
37
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -42,20 +43,14 @@ installConsoleRedaction();
42
43
  const IS_MAIN = isMainModule();
43
44
  if (IS_MAIN) config();
44
45
 
45
- const VERSION = "1.0.6";
46
46
  const EVM_PRIVATE_KEY = process.env.EVM_PRIVATE_KEY;
47
47
  // Normalise once so the axios base URL, the payment policy prefix, and the
48
48
  // per-tool expected resource are all the same string (no trailing slash).
49
49
  const SERVER_URL = (process.env.VERIFY402_URL || "https://verify402.onrender.com")
50
50
  .trim()
51
51
  .replace(/\/+$/, "");
52
- // Tell the server which client made the call. This is a label, not an identity:
53
- // it helps separate MCP use from generic HTTP probes without collecting keys or
54
- // wallet addresses.
55
- const CLIENT_HEADERS = {
56
- "User-Agent": `verify402-mcp/${VERSION}`,
57
- "X-Verify402-Client": `mcp/${VERSION}`,
58
- };
52
+ // CLIENT_HEADERS is a label, not an identity: it helps /usage separate MCP
53
+ // calls from generic HTTP probes without collecting keys or wallet addresses.
59
54
  // Preflight is free, so it does not go through the paying client. Keeping the
60
55
  // two clients separate means a misconfigured 402 on a free route can never
61
56
  // turn into a signature.
@@ -222,7 +217,10 @@ const DESCRIPTIONS = {
222
217
  "operator before signing. The receipt expires (default 90s): call again rather than reuse it. " +
223
218
  "Free. Pass `from` (the wallet that will sign) to enable simulation and balance checks. For " +
224
219
  "kind=transaction, chainId is REQUIRED (400 missing_chain without it). For payment intents " +
225
- "with no `from` or no proof, the response is PASS with structural_only:true and names what was not checked.",
220
+ "with no `from` or no proof, the response is PASS with structural_only:true and names what was not checked. " +
221
+ "Optional caller-supplied checks: policy.sellerPayoutWallets is a payout allowlist, not provenance; " +
222
+ "policy.sellerPayoutManifest fetches a seller-signed manifest from a caller-pinned HTTPS origin and is the only source of payee_payout_binding; " +
223
+ "policy.obligation checks a local settled-key ledger and cumulative spend against a grant. The gate does not persist either state.",
226
224
  verify_arbitrage:
227
225
  "Ask verify402 whether a cross-venue arbitrage opportunity is REAL, right now. " +
228
226
  "Quotes come from Uniswap's canonical QuoterV2 and each DEX router at one pinned block. " +
@@ -268,6 +266,7 @@ async function main() {
268
266
  amount: z.string().describe("RAW amount in the asset's smallest unit, as a string"),
269
267
  payTo: z.string().describe("Recipient address from the 402 challenge"),
270
268
  resource: z.string().optional().describe("The resource URL the payment is for"),
269
+ idempotencyKey: z.string().max(256).optional().describe("Caller-local payment idempotency key (not the paid-route x-idempotency-key header); enables the policy.obligation replay check"),
271
270
  maxTimeoutSeconds: z.number().optional().describe("maxTimeoutSeconds from the 402 challenge"),
272
271
  extra: z.object({
273
272
  validAfter: z.number().optional(),
@@ -289,6 +288,17 @@ async function main() {
289
288
  allowedAssets: z.array(z.string()).optional().describe("Allowlist of payment assets"),
290
289
  allowedFacilitators: z.array(z.string()).optional().describe("Allowlist of x402 facilitators"),
291
290
  expectedResource: z.string().optional().describe("The exact resource URL the agent intends to pay for"),
291
+ allowedResourceHosts: z.array(z.string()).optional().describe("Bare hostnames allowed in payment.resource; a foreign host is BLOCK"),
292
+ sellerPayoutWallets: z.array(z.string()).optional().describe("Caller-supplied seller payout allowlist; a payTo mismatch is BLOCK. This is not provenance and never adds payee_payout_binding"),
293
+ sellerPayoutManifest: z.object({
294
+ url: z.string().url().describe("HTTPS URL of a seller-signed manifest outside the 402"),
295
+ expectedSigner: z.string().optional().describe("Optional EVM address pin for the manifest signer"),
296
+ }).optional().describe("Seller-signed payout manifest; the only source of payee_payout_binding"),
297
+ obligation: z.object({
298
+ settledKeys: z.array(z.string()).optional().describe("Payment idempotency keys already settled"),
299
+ spentUsd: z.number().nonnegative().optional().describe("Already spent under the grant, in USD"),
300
+ grantUsd: z.number().nonnegative().optional().describe("Total grant in USD; spentUsd and grantUsd must be supplied together"),
301
+ }).optional().describe("Caller-supplied local obligation state. The public gate does not persist it"),
292
302
  forbidUnlimitedApproval: z.boolean().optional().describe("Turn an unlimited approve into a BLOCK"),
293
303
  maxTimeoutSeconds: z.number().optional().describe("Reject challenges with a longer timeout"),
294
304
  }).optional().describe("Operator policy. Omit for structural checks only."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verify402-mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "mcpName": "io.github.a32116150-ctrl/verify402-mcp",
5
5
  "description": "MCP server for verify402: a free, fail-closed preflight gate before signing, plus five paid on-chain checks paid per call via x402.",
6
6
  "type": "module",
@@ -14,6 +14,7 @@
14
14
  "payment-policy.mjs",
15
15
  "paid-call.mjs",
16
16
  "log.mjs",
17
+ "client-id.mjs",
17
18
  "README.md",
18
19
  ".env.example",
19
20
  "LICENSE",