Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/_shared/recovery-scenarios.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export async function createLedgerExample() {
category: "paper/search MCP local adapter",
gate,
toolName: "paper_search",
price: 0.15,
price: usd("0.15"),
handlerKind: "paper",
paymentMetaFactory: async () => null,
primeForPaid: async () => {
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function createMppExample() {
category: "scraping/extraction MCP paid step",
gate,
toolName: "extract_document",
price: 0.2,
price: usd("0.20"),
handlerKind: "extract",
paymentMetaFactory: async () => {
const challenge = await mppAdapter.createChallenge({
Expand Down Expand Up @@ -125,7 +125,7 @@ export async function createX402Example() {
category: "paid API wrapper MCP",
gate,
toolName: "partner_api_lookup",
price: 0.3,
price: usd("0.30"),
handlerKind: "api",
paymentMetaFactory: async (mode = "success") => {
const challenge = await x402Adapter.createChallenge({
Expand Down
26 changes: 13 additions & 13 deletions examples/advanced-server/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -290,23 +290,23 @@ server.tool(
caller_id: z.string().optional().describe("Caller ID (default: demo-user)"),
},
async ({ amount_usd, caller_id = "demo-user" }) => {
await gate.ledger.credit(caller_id, usd(amount_usd), {
await gate.ledger.credit(caller_id, usd(amount_usd), {
source: "manual",
reference: `topup-${Date.now()}`,
});
const balance = await gate.ledger.getBalance(caller_id);
return {
content: [
{
type: "text",
text: JSON.stringify(
{ added_usd: amount_usd, new_balance_usd: toNumber(balance) },
null,
2,
),
},
],
};
return {
content: [
{
type: "text",
text: JSON.stringify(
{ added_usd: amount_usd, new_balance_usd: toNumber(balance) },
null,
2,
),
},
],
};
},
);

Expand Down
55 changes: 52 additions & 3 deletions examples/x402-testnet-recovery/_shared.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
ToolGate,
X402RailAdapter,
createMcpAdapter,
usd,
} from "../../dist/index.js";

const currentDir = path.dirname(fileURLToPath(import.meta.url));

loadEnvFile(path.resolve(currentDir, "../../.env"));
loadEnvFile(path.resolve(currentDir, ".env"));

export const callerId = "x402-testnet-caller";
export const publisherKey = "tg_x402_testnet";
export const toolName = "partner_api_lookup";
export const defaultAmount = 0.3;

function loadEnvFile(filePath) {
if (!existsSync(filePath)) {
return;
}

const source = readFileSync(filePath, "utf8");
for (const line of source.split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) {
continue;
}

const separator = trimmed.indexOf("=");
if (separator === -1) {
continue;
}

const key = trimmed.slice(0, separator).trim();
let value = trimmed.slice(separator + 1).trim();
if (!key || process.env[key]) {
continue;
}

if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}

process.env[key] = value;
}
}

export function printSummary(summary) {
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
process.stdout.write(
`${JSON.stringify(
summary,
(_key, value) => (typeof value === "bigint" ? value.toString() : value),
2,
)}\n`,
);
}

export function parseJsonEnv(name) {
Expand Down Expand Up @@ -56,7 +105,7 @@ export function createRegistration(gate, duplicateKeys) {
},
required: ["requestId", "query"],
},
price: defaultAmount,
price: usd("0.30"),
onPaymentFailed: "fallback",
idempotencyKey: (args, currentCallerId) =>
`${toolName}:${currentCallerId}:${String(args.requestId)}`,
Expand Down Expand Up @@ -97,7 +146,7 @@ export function createBlockingRegistration(gate) {
},
required: ["requestId", "query"],
},
price: defaultAmount,
price: usd("0.30"),
onPaymentFailed: "block",
idempotencyKey: (args, currentCallerId) =>
`${toolName}_blocking:${currentCallerId}:${String(args.requestId)}`,
Expand Down
2 changes: 1 addition & 1 deletion integrations/firecrawl-mcp-toolgate/mcp-e2e-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const mcp = createMcpAdapter(gate, {
mcp.paidTool("firecrawl_scrape", {
description: "Paid wrapper for the Firecrawl MCP scrape tool",
inputSchema: firecrawlScrapeInputSchema,
price: 0.25,
price: usd("0.25"),
onPaymentFailed: "fallback",
idempotencyKey: createFirecrawlIdempotencyKey,
onDuplicateDetected: async (_input, record) => {
Expand Down
2 changes: 1 addition & 1 deletion integrations/firecrawl-mcp-toolgate/scenario-fake.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function createRegisteredFirecrawlTool({ gate, transport, duplicateKeys }) {
mcp.paidTool("firecrawl_scrape", {
description: "Paid wrapper for the Firecrawl MCP scrape tool",
inputSchema: firecrawlScrapeInputSchema,
price: 0.25,
price: usd("0.25"),
onPaymentFailed: "fallback",
idempotencyKey: createFirecrawlIdempotencyKey,
onDuplicateDetected: async (_input, record) => {
Expand Down
27 changes: 24 additions & 3 deletions integrations/firecrawl-mcp-toolgate/scenario-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function createRegisteredFirecrawlTool({ gate, transport, duplicateKeys }) {
mcp.paidTool("firecrawl_scrape", {
description: "Paid wrapper for the Firecrawl MCP scrape tool",
inputSchema: firecrawlScrapeInputSchema,
price: 0.25,
price: usd("0.25"),
onPaymentFailed: "fallback",
idempotencyKey: createFirecrawlIdempotencyKey,
onDuplicateDetected: async (_input, record) => {
Expand Down Expand Up @@ -174,6 +174,27 @@ try {
const summary = await runLiveScenario();
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
} catch (error) {
process.stderr.write(`${(error && error.message) || String(error)}\n`);
process.exitCode = 1;
const message = (error && error.message) || String(error);
if (message.includes("Missing FIRECRAWL_API_KEY")) {
process.stdout.write(
`${JSON.stringify(
{
integration: "firecrawl-mcp-toolgate-live",
blocked: true,
blocker: {
reason: "missing_env",
required: ["FIRECRAWL_API_KEY"],
details:
"Export FIRECRAWL_API_KEY in the terminal before running the live Firecrawl scenario.",
},
},
null,
2,
)}\n`,
);
process.exitCode = 0;
} else {
process.stderr.write(`${message}\n`);
process.exitCode = 1;
}
}
27 changes: 20 additions & 7 deletions integrations/stripe-test-mode/scenario.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ToolGate,
StripeAdapter,
createWebhookHandler,
toNumber,
usd,
} from "../../dist/index.js";

const publisherKey = "tg_stripe_test_mode";
Expand Down Expand Up @@ -53,7 +55,13 @@ function loadEnvFile(filePath) {
}

function printSummary(summary) {
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
process.stdout.write(
`${JSON.stringify(
summary,
(_key, value) => (typeof value === "bigint" ? value.toString() : value),
2,
)}\n`,
);
}

function runCommand(command, args, env = process.env) {
Expand Down Expand Up @@ -162,7 +170,7 @@ async function runScenario() {
const paidLookup = gate.paidAction({
name: "premium_lookup",
description: "Stripe recovery acceptance scenario",
price: 0.25,
price: usd("0.25"),
onPaymentFailed: "block",
idempotencyKey: (input, currentCallerId) =>
`premium_lookup:${currentCallerId}:${String(input.requestId)}`,
Expand Down Expand Up @@ -332,7 +340,7 @@ async function runScenario() {

assert.equal(webhook.result.processed, true);
assert.equal(webhook.result.duplicate, undefined);
assert.equal(await gate.ledger.getBalance(callerId), 1);
assert.equal(toNumber(await gate.ledger.getBalance(callerId)), 1);
assert.equal(retrievedSession.id, checkoutSession.id);

const paidInput = {
Expand All @@ -348,7 +356,7 @@ async function runScenario() {
assert.deepEqual(duplicateResult, paidResult);

const balanceAfterExecution = await gate.ledger.getBalance(callerId);
assert.equal(balanceAfterExecution, 0.75);
assert.equal(toNumber(balanceAfterExecution), 0.75);

if (paidTrace) {
paidTrace.receiptId = checkoutSession.id;
Expand Down Expand Up @@ -379,7 +387,10 @@ async function runScenario() {
const finalTrace = await gate.traces.findByIdempotencyKey(paidTraceKey);

assert.equal(duplicateWebhookResult.duplicate, true);
assert.equal(balanceAfterDuplicateWebhook, balanceBeforeDuplicateWebhook);
assert.equal(
toNumber(balanceAfterDuplicateWebhook),
toNumber(balanceBeforeDuplicateWebhook),
);
assert.equal(finalTrace?.provider?.correlationId, checkoutSession.id);
assert.equal(finalTrace?.provider?.traceId ?? null, paymentIntentId);

Expand Down Expand Up @@ -420,8 +431,10 @@ async function runScenario() {
{
name: "duplicate_webhook",
result: duplicateWebhookResult,
balanceBeforeDuplicateWebhook,
balanceAfterDuplicateWebhook,
balanceBeforeDuplicateWebhook: toNumber(
balanceBeforeDuplicateWebhook,
),
balanceAfterDuplicateWebhook: toNumber(balanceAfterDuplicateWebhook),
},
],
notes: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"scenario:mpp": "npm run build && node examples/mcp-mpp-recovery/scenario.mjs",
"scenario:x402": "npm run build && node examples/mcp-x402-experimental/scenario.mjs",
"scenario:x402-testnet:challenge": "npm run build && node examples/x402-testnet-recovery/challenge.mjs",
"scenario:x402-testnet:sign": "npm run build && node examples/x402-testnet-recovery/sign-payload.mjs",
"scenario:x402-testnet:sign": "npm run build && node examples/x402-testnet-recovery/challenge.mjs | node examples/x402-testnet-recovery/sign-payload.mjs",
"scenario:x402-testnet": "npm run build && node integrations/x402-testnet/scenario.mjs",
"test": "npm run build && node --test --test-force-exit src/__tests__/paidTool.test.mjs src/__tests__/mcp-adapter.test.mjs src/__tests__/stripe.test.mjs src/__tests__/webhook-handler.test.mjs src/__tests__/db-ledger.test.mjs src/__tests__/rail-adapter.test.mjs src/__tests__/policy.test.mjs src/__tests__/protocol-compliance.test.mjs src/__tests__/idempotency.test.mjs src/__tests__/trace.test.mjs src/__tests__/firecrawl-integration.test.mjs src/__tests__/local-first.test.mjs",
"prepublishOnly": "npm run typecheck && npm test"
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/firecrawl-integration.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function createRegisteredFirecrawlTool({ gate, transport, duplicateKeys }) {
mcp.paidTool("firecrawl_scrape", {
description: "Paid wrapper for the Firecrawl MCP scrape tool",
inputSchema: firecrawlScrapeInputSchema,
price: 0.25,
price: usd("0.25"),
onPaymentFailed: "fallback",
idempotencyKey: createFirecrawlIdempotencyKey,
onDuplicateDetected: async (_input, record) => {
Expand Down
Loading