run402-mcp
Version:
MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.
567 lines • 22.6 kB
JavaScript
import { Run402Error } from "../errors.js";
export const DEFAULT_PAYMENT_MAX_USD_MICROS = 100_000;
export const X402_COMMERCE_RESULT_SCHEMA_VERSION = "x402-commerce-result.v1";
export const X402_PAYMENT_POLICY_ERROR_CODES = [
"MERCHANT_RECEIPT_REQUIRED",
"MERCHANT_RECEIPT_UNAVAILABLE",
];
export const X402_GATEWAY_AVAILABILITY_ERROR_CODE = "MERCHANT_EVIDENCE_UNAVAILABLE";
export const X402_MUTATION_STATES = [
"not_started",
"committed",
"unknown",
];
export const X402_RECOVERY_ACTIONS = [
"retry",
"reconcile_payment",
];
export const X402_EVIDENCE_STATUSES = [
"verified",
"absent",
"invalid",
"untrusted",
"unavailable",
];
export function payFetchResultToJson(result, body) {
const payment = result.payment;
return {
schema_version: X402_COMMERCE_RESULT_SCHEMA_VERSION,
http_status: result.response.status,
body,
payment: payment
? {
payment_id: payment.paymentId,
amount_usd_micros: payment.amountUsdMicros,
asset: payment.asset,
network: payment.network,
payer: payment.payer,
pay_to: payment.payTo,
transaction: payment.transaction,
resource_url: payment.resourceUrl,
settlement: payment.settlement,
funds_moved: payment.fundsMoved,
deduplicated: payment.deduplicated,
delivery: payment.delivery,
offer: {
status: payment.offer.status,
resource_url: payment.offer.resourceUrl,
valid_until: payment.offer.validUntil,
},
merchant_receipt: {
status: payment.merchantReceipt.status,
claim: payment.merchantReceipt.claim,
issued_at: payment.merchantReceipt.issuedAt,
},
signer_relationship: {
kind: payment.signerRelationship.kind,
merchant_root: payment.signerRelationship.merchantRoot,
signer: payment.signerRelationship.signer,
authorization_expires_at: payment.signerRelationship.authorizationExpiresAt,
},
policy: {
require_receipt: payment.policy.requireReceipt,
status: payment.policy.status,
},
evidence: {
offer: payment.evidence.offer,
merchant_receipt: payment.evidence.merchantReceipt,
signer_authorization: payment.evidence.signerAuthorization,
},
}
: null,
outcome: payment ? "paid" : "not_paid",
replay: result.replay,
next_actions: (result.nextActions ?? []).map((action) => ({
type: action.type,
why: action.why,
...(action.request ? { request: action.request } : {}),
...(action.reusePayer ? { reuse_payer: true } : {}),
...(action.reuseIdempotencyKey
? { reuse_idempotency_key: true }
: {}),
})),
};
}
/** Structured local failure from {@link Pay.fetch}. */
export class PaymentBuyerError extends Run402Error {
kind = "payment_buyer_error";
code;
fundsMoved;
paymentId;
intentState;
delivery;
deduplicated;
settledAt;
cause;
constructor(init) {
const mutationState = init.fundsMoved === false
? "not_started"
: init.fundsMoved === true
? "completed"
: "ambiguous";
const body = init.body ?? {
error: init.code,
code: init.code,
message: init.message,
category: "payment",
source: "sdk",
retryable: init.retryable ?? false,
safe_to_retry: init.safeToRetry ?? init.fundsMoved === false,
mutation_state: mutationState,
details: {
funds_moved: init.fundsMoved,
...(init.details ?? {}),
},
next_actions: init.nextActions,
};
super(init.message, init.status ?? null, body, "paying an x402-priced URL");
this.code = init.code;
this.fundsMoved = init.fundsMoved;
this.paymentId = stringOrNull(body.payment_id);
this.intentState = stringOrNull(body.intent_state);
this.delivery = paymentDelivery(body.delivery);
this.deduplicated = booleanOrNull(body.deduplicated);
this.settledAt = stringOrNull(body.settled_at);
if (init.cause !== undefined)
this.cause = init.cause;
}
toJSON() {
return {
...super.toJSON(),
fundsMoved: this.fundsMoved,
paymentId: this.paymentId,
intentState: this.intentState,
delivery: this.delivery,
deduplicated: this.deduplicated,
settledAt: this.settledAt,
};
}
}
/**
* A receipt policy failed after a paid response was received. The original
* response and commerce result remain available for reconciliation.
*/
export class PaymentPolicyError extends PaymentBuyerError {
response;
result;
mutationState;
safeToRetry;
nextActions;
constructor(init) {
super({
code: "MERCHANT_RECEIPT_UNAVAILABLE",
message: init.message,
fundsMoved: init.fundsMoved,
safeToRetry: init.safeToRetry,
details: {
merchant_receipt_status: init.result.payment?.merchantReceipt.status ?? "unavailable",
},
nextActions: init.nextActions.map((action) => ({
type: action.type,
why: action.why,
...(action.request ? { request: action.request } : {}),
...(action.reusePayer ? { reuse_payer: action.reusePayer } : {}),
...(action.reuseIdempotencyKey
? { reuse_idempotency_key: action.reuseIdempotencyKey }
: {}),
})),
body: {
error: "MERCHANT_RECEIPT_UNAVAILABLE",
code: "MERCHANT_RECEIPT_UNAVAILABLE",
message: init.message,
category: "payment_policy",
source: "sdk",
retryable: init.safeToRetry,
safe_to_retry: init.safeToRetry,
mutation_state: init.mutationState,
funds_moved: init.fundsMoved,
details: {
merchant_receipt_status: init.result.payment?.merchantReceipt.status ?? "unavailable",
},
next_actions: init.nextActions,
},
});
this.response = init.response;
this.result = init.result;
this.mutationState = init.mutationState;
this.safeToRetry = init.safeToRetry;
this.nextActions = init.nextActions;
}
}
export function isPaymentBuyerError(error) {
return Boolean(error &&
typeof error === "object" &&
error.isRun402Error === true &&
error.kind === "payment_buyer_error");
}
/**
* Duck-typed Response check: the buyer's transport may hand back npm
* undici's Response (vault-owned-dispatcher), which is not an instance
* of the GLOBAL Response class even though it satisfies the same contract.
*/
function isResponseLike(value) {
return (typeof value === "object" &&
value !== null &&
typeof value.status === "number" &&
typeof value.headers === "object");
}
export function isPaymentPolicyError(error) {
return Boolean(isPaymentBuyerError(error) &&
error.code ===
"MERCHANT_RECEIPT_UNAVAILABLE" &&
isResponseLike(error.response) &&
typeof error.result === "object");
}
/** Arbitrary-URL buyer namespace. Node supplies the x402 executor; isomorphic callers may inject one. */
export class Pay {
client;
executor;
constructor(client, executor) {
this.client = client;
this.executor = executor;
}
async fetch(url, init, options = {}) {
const normalizedUrl = normalizePayUrl(url);
const maxUsdMicros = normalizeMax(options.maxUsdMicros);
const nextInit = withIdempotencyKey(init, options.idempotencyKey);
if (this.executor) {
return this.executor(normalizedUrl, nextInit, {
maxUsdMicros,
requireReceipt: options.requireReceipt === true,
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
});
}
const response = await this.client.fetch(normalizedUrl, nextInit);
if (response.status === 402) {
if (options.requireReceipt) {
throw merchantReceiptRequiredError();
}
throw walletUnavailableError();
}
return {
response,
payment: null,
outcome: "not_required",
replay: await responseSignalsReplay(response, options.idempotencyKey !== undefined),
...payResponseMetadata(response),
};
}
}
export function merchantReceiptRequiredError(details = {}) {
return new PaymentBuyerError({
code: "MERCHANT_RECEIPT_REQUIRED",
message: "No eligible payment requirement carried a valid wallet-rooted merchant offer.",
fundsMoved: false,
safeToRetry: true,
details,
nextActions: [],
body: {
error: "MERCHANT_RECEIPT_REQUIRED",
code: "MERCHANT_RECEIPT_REQUIRED",
message: "No eligible payment requirement carried a valid wallet-rooted merchant offer.",
category: "payment_policy",
source: "sdk",
retryable: false,
safe_to_retry: true,
mutation_state: "not_started",
funds_moved: false,
details,
next_actions: [],
},
});
}
export function paymentExceedsMaxError(challengedAmountUsdMicros, maxUsdMicros) {
return new PaymentBuyerError({
code: "PAYMENT_EXCEEDS_MAX",
message: `The endpoint requested ${challengedAmountUsdMicros} usd_micros, above this call's ` +
`${maxUsdMicros} ceiling. Raise it explicitly with { maxUsdMicros: ${challengedAmountUsdMicros} } ` +
`or CLI --max-usd ${(challengedAmountUsdMicros / 1_000_000).toFixed(6)}.`,
fundsMoved: false,
details: {
challenged_amount_usd_micros: challengedAmountUsdMicros,
max_usd_micros: maxUsdMicros,
},
nextActions: [
{
type: "edit_request",
field: "maxUsdMicros",
value: challengedAmountUsdMicros,
why: "Raise the ceiling only after confirming this exact price is intended.",
},
],
});
}
export function walletUnavailableError(details = {}) {
return new PaymentBuyerError({
code: "PAYMENT_WALLET_UNFUNDED",
message: "No funded x402 wallet is available for this payment.",
fundsMoved: false,
details,
nextActions: [
{
type: "fund_wallet",
why: "Fund the configured Run402 wallet with USDC on a challenge network, then retry.",
},
{
type: "run_command",
command: "run402 wallets fund",
why: "Open the canonical wallet funding flow.",
},
{
type: "run_command",
command: "run402 init",
why: "Create a wallet and use the testnet faucet when no wallet is configured.",
},
],
});
}
/**
* The paid stack is an OPTIONAL peer set, so a consumer can legitimately be
* unable to pay while holding a perfectly funded wallet. Reporting that as
* PAYMENT_WALLET_UNFUNDED sends the caller to fund an already-funded wallet —
* an unattended agent then loops between `init` (reports funded) and the
* payment (reports unfunded). The remedy is an install, so say so.
*/
export function paidStackUnavailableError(missingPackages, details = {}) {
const packages = [...missingPackages].sort();
return new PaymentBuyerError({
code: "PAYMENT_DEPENDENCY_MISSING",
message: `This payment needs optional peer dependencies that are not installed: ${packages.join(", ")}. ` +
`The wallet is not the problem — install them and retry.`,
fundsMoved: false,
details: { missing_packages: packages, ...details },
nextActions: [
{
type: "run_command",
command: `npm install ${packages.join(" ")}`,
why: "Install the paid-request dependencies this environment is missing, then retry.",
},
],
});
}
export async function responseSignalsReplay(response, inspect) {
const declared = booleanHeader(response.headers.get("x-run402-payment-deduplicated"));
if (declared !== null)
return declared;
if (!inspect || !response.headers.get("content-type")?.includes("application/json"))
return false;
const declaredLength = Number(response.headers.get("content-length") ?? 0);
if (Number.isFinite(declaredLength) && declaredLength > 1_000_000)
return false;
try {
const body = await response.clone().json();
return body?.deduplicated === true;
}
catch {
return false;
}
}
/** Parse platform-owned payment response headers without consuming the response body. */
export function payResponseMetadata(response) {
const funds = response.headers.get("x-run402-payment-funds-moved");
const delivery = response.headers.get("x-run402-payment-delivery");
return {
paymentId: nonEmpty(response.headers.get("x-run402-payment-id")),
deduplicated: booleanHeader(response.headers.get("x-run402-payment-deduplicated")),
fundsMoved: funds === "true" ? true : funds === "false" ? false : funds === "unknown" ? "unknown" : null,
delivery: delivery === "first" || delivery === "replay" || delivery === "none" ? delivery : null,
settledAt: nonEmpty(response.headers.get("x-run402-payment-settled-at")),
intentState: nonEmpty(response.headers.get("x-run402-payment-intent-state")),
};
}
export const RUN402_PENDING_CLASSIFIER_VERSION = 1;
const RESERVED_RUN402_LABELS = new Set([
"api", "admin", "assets", "cdn", "dashboard", "docs", "help", "mail",
"sites", "static", "status", "support", "www",
]);
/** Exact DNS-label classifier for Run402-owned tenant and deployment hosts. */
export function isTrustedRun402PaymentUrl(input, options = {}) {
let url;
try {
url = input instanceof URL ? input : new URL(input);
}
catch {
return false;
}
if (options.allowTestLocalhost && url.protocol === "http:" &&
(url.hostname === "localhost" || url.hostname === "127.0.0.1"))
return true;
if (url.protocol !== "https:" || url.username || url.password || url.port)
return false;
const labels = url.hostname.toLowerCase().split(".");
if (labels.length === 3 && labels[1] === "run402" &&
(labels[2] === "com" || labels[2] === "app")) {
return labels[0].length > 0 && !RESERVED_RUN402_LABELS.has(labels[0]);
}
return labels.length === 4 && labels[1] === "sites" && labels[2] === "run402" &&
(labels[3] === "com" || labels[3] === "app") && labels[0].length > 0;
}
/**
* Trust a pending result only at the complete signed-response boundary. The
* caller supplies `paymentBearing` and `redirectsDisabled` from its executor;
* shape alone is intentionally insufficient for arbitrary/custom domains.
*/
export function isTrustedRun402PendingResponse(input) {
if (!input.paymentBearing || !input.redirectsDisabled || input.response.redirected)
return false;
if (input.response.status !== 409 || input.envelope?.code !== "PAYMENT_INTENT_PENDING")
return false;
if (input.response.headers.get("x-run402-payment-intent-state") !== "pending")
return false;
if (!input.response.url)
return false;
try {
const requestUrl = new URL(input.requestUrl);
const responseUrl = new URL(input.response.url);
return requestUrl.origin === responseUrl.origin &&
isTrustedRun402PaymentUrl(requestUrl, { allowTestLocalhost: input.allowTestLocalhost });
}
catch {
return false;
}
}
export async function readPaymentErrorEnvelope(response) {
if (!response.headers.get("content-type")?.includes("application/json"))
return null;
const length = Number(response.headers.get("content-length") ?? 0);
if (Number.isFinite(length) && length > 1_000_000)
return null;
try {
const value = await response.clone().json();
return value && typeof value === "object" && !Array.isArray(value)
? value
: null;
}
catch {
return null;
}
}
export function gatewayPaymentBuyerError(response, envelope) {
const code = envelope.code;
if (!isPaymentBuyerErrorCode(code))
return null;
const metadata = payResponseMetadata(response);
const declaredFunds = envelope.funds_moved;
const fundsMoved = declaredFunds === true || declaredFunds === false || declaredFunds === "unknown"
? declaredFunds
: metadata.fundsMoved ?? "unknown";
const body = {
...envelope,
...(envelope.payment_id === undefined && metadata.paymentId !== null
? { payment_id: metadata.paymentId }
: {}),
...(envelope.deduplicated === undefined && metadata.deduplicated !== null
? { deduplicated: metadata.deduplicated }
: {}),
...(envelope.delivery === undefined && metadata.delivery !== null
? { delivery: metadata.delivery }
: {}),
...(envelope.settled_at === undefined && metadata.settledAt !== null
? { settled_at: metadata.settledAt }
: {}),
...(envelope.intent_state === undefined && metadata.intentState !== null
? { intent_state: metadata.intentState }
: {}),
...(envelope.funds_moved === undefined && metadata.fundsMoved !== null
? { funds_moved: metadata.fundsMoved }
: {}),
};
return new PaymentBuyerError({
code,
message: typeof envelope.message === "string" ? envelope.message : code,
fundsMoved,
details: isRecord(envelope.details) ? envelope.details : {},
nextActions: Array.isArray(envelope.next_actions) ? envelope.next_actions : [],
retryable: typeof envelope.retryable === "boolean" ? envelope.retryable : false,
safeToRetry: typeof envelope.safe_to_retry === "boolean" ? envelope.safe_to_retry : false,
status: response.status,
body,
});
}
function isPaymentBuyerErrorCode(value) {
return typeof value === "string" && new Set([
"PAYMENT_EXCEEDS_MAX", "PAYMENT_WALLET_UNFUNDED", "PAYMENT_NETWORK_UNSUPPORTED",
"PAYMENT_INTENT_PENDING", "PAYMENT_DESTINATION_DRAINING",
"PAYMENT_INTENT_DESTINATION_CHANGED", "PAYMENT_INTENT_FENCE_EXPIRED",
"PAYMENT_AUTHORIZATION_LIFETIME_EXCEEDED", "PAYMENT_CALLER_IDENTITY_NOT_ACTIVE",
"IDEMPOTENCY_KEY_REUSED",
"INVALID_IDEMPOTENCY_KEY", "IDEMPOTENCY_KEY_PAYER_REQUIRED",
"PAYMENT_SETTLEMENT_FAILED",
"MERCHANT_RECEIPT_REQUIRED", "MERCHANT_RECEIPT_UNAVAILABLE",
]).has(value);
}
function nonEmpty(value) {
const trimmed = value?.trim();
return trimmed ? trimmed : null;
}
function booleanHeader(value) {
return value === "true" ? true : value === "false" ? false : null;
}
function stringOrNull(value) {
return typeof value === "string" && value.length > 0 ? value : null;
}
function booleanOrNull(value) {
return typeof value === "boolean" ? value : null;
}
function paymentDelivery(value) {
return value === "first" || value === "replay" || value === "none" ? value : null;
}
function isRecord(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function normalizePayUrl(input) {
let url;
try {
url = input instanceof URL ? input : new URL(input);
}
catch (cause) {
throw new PaymentBuyerError({
code: "PAYMENT_SETTLEMENT_FAILED",
message: "pay.fetch requires an absolute HTTP(S) URL.",
fundsMoved: false,
details: { field: "url" },
nextActions: [{ type: "edit_request", field: "url", why: "Pass the complete priced endpoint URL." }],
cause,
});
}
if (url.protocol !== "https:" && url.protocol !== "http:") {
throw new PaymentBuyerError({
code: "PAYMENT_SETTLEMENT_FAILED",
message: "pay.fetch supports only HTTP(S) URLs.",
fundsMoved: false,
details: { field: "url", protocol: url.protocol },
nextActions: [{ type: "edit_request", field: "url", why: "Use an https:// or http:// endpoint." }],
});
}
return url.toString();
}
function normalizeMax(value) {
const normalized = value ?? DEFAULT_PAYMENT_MAX_USD_MICROS;
if (!Number.isSafeInteger(normalized) || normalized < 0) {
throw new PaymentBuyerError({
code: "PAYMENT_SETTLEMENT_FAILED",
message: "maxUsdMicros must be a non-negative safe integer.",
fundsMoved: false,
details: { field: "maxUsdMicros", value: normalized },
nextActions: [{ type: "edit_request", field: "maxUsdMicros", why: "Pass an integer number of usd_micros." }],
});
}
return normalized;
}
function withIdempotencyKey(init, key) {
if (key === undefined)
return init;
if (key.trim() === "") {
throw new PaymentBuyerError({
code: "PAYMENT_SETTLEMENT_FAILED",
message: "idempotencyKey must not be empty.",
fundsMoved: false,
details: { field: "idempotencyKey" },
nextActions: [{ type: "edit_request", field: "idempotencyKey", why: "Use a stable non-empty key for this paid intent." }],
});
}
const headers = new Headers(init?.headers);
headers.set("Idempotency-Key", key);
return { ...init, headers };
}
//# sourceMappingURL=pay.js.map