@curless/agentbank-mcp-pay 0.1.0

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 ADDED
@@ -0,0 +1,52 @@
1
+ # @curless/agentbank-mcp-pay
2
+
3
+ Paywall an MCP tool (or any API) so **AI agents pay per call**. Wrap a tool
4
+ handler with `withPaywall()`: an unpaid call returns a payment **gate**, the
5
+ agent pays via agentbank's MPP HTTP-402 endpoint, and the call runs once the
6
+ payment is verified — single-use. Settlement happens on agentbank's
7
+ double-entry ledger + rails (card / stablecoin / Curless); this package only
8
+ gates the tool on a verified, not-yet-used payment.
9
+
10
+ Because MCP (especially stdio) has no HTTP layer to return a `402` on, the gate
11
+ is a normal tool result carrying the terms in `structuredContent`
12
+ (the SolvaPay / ATXP pattern).
13
+
14
+ ## Seller side
15
+
16
+ ```ts
17
+ import { withPaywall, createHttpBackend } from '@curless/agentbank-mcp-pay';
18
+
19
+ const backend = createHttpBackend({
20
+ baseUrl: 'https://api.agentbank…',
21
+ apiKey: process.env.AGENTBANK_KEY!, // needs agent:execute (to look up the PI)
22
+ });
23
+
24
+ server.tool(
25
+ 'deep_search',
26
+ schema,
27
+ withPaywall(deepSearch, { backend, merchantId: 'curless_mch_…', price: 200, currency: 'USD', sku: 'deep_search' }),
28
+ );
29
+ // unpaid call → { structuredContent: { payment_required: { amount: 200, … } } }
30
+ // paid retry → runs deepSearch, returns its result + a receipt
31
+ ```
32
+
33
+ `price` is in minor units (cents / token base units). The default one-time-use
34
+ store is in-process — back it with Redis/DB in production via the `consumed`
35
+ option.
36
+
37
+ ## Agent side
38
+
39
+ ```ts
40
+ import { payMcpGate } from '@curless/agentbank-mcp-pay/agent';
41
+
42
+ const r1 = await callTool('deep_search', { q: '…' });
43
+ const gate = r1.structuredContent.payment_required;
44
+ const { paymentIntentId } = await payMcpGate(gate, {
45
+ baseUrl: 'https://api.agentbank…',
46
+ token: agentAccessToken, // agent:execute
47
+ source: 'did:agent:buyer-1',
48
+ });
49
+ const r2 = await callTool('deep_search', { q: '…', _agentbankPayment: { paymentIntentId } });
50
+ ```
51
+
52
+ MIT
@@ -0,0 +1,13 @@
1
+ import { type FetchLike } from '@curless/agentbank-core';
2
+ import type { PaywallGate } from './types.js';
3
+ export type PayMcpGateOptions = {
4
+ baseUrl: string;
5
+ token: string;
6
+ source: string;
7
+ payload?: Record<string, unknown>;
8
+ fetch?: FetchLike;
9
+ };
10
+ export declare const payMcpGate: (gate: PaywallGate, opts: PayMcpGateOptions) => Promise<{
11
+ paymentIntentId: string;
12
+ }>;
13
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyC9C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAEhB,KAAK,EAAE,MAAM,CAAC;IAEd,MAAM,EAAE,MAAM,CAAC;IAEf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAKF,eAAO,MAAM,UAAU,GACrB,MAAM,WAAW,EACjB,MAAM,iBAAiB,KACtB,OAAO,CAAC;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,CAoCrC,CAAC"}
package/dist/agent.js ADDED
@@ -0,0 +1,77 @@
1
+ import { buildUrl } from '@curless/agentbank-core';
2
+ // Agent-side helper: pay a paywall gate via agentbank's MPP HTTP-402 endpoint
3
+ // and get back the PaymentIntent id to retry the gated tool with.
4
+ //
5
+ // The MPP wire (challenge / credential) is reimplemented here, byte-compatible
6
+ // with @agentbank/protocol-mpp, so this published package needs no internal
7
+ // dependency.
8
+ // base64url(utf8) — portable across Node and edge runtimes.
9
+ const toB64Url = (s) => {
10
+ if (typeof Buffer !== 'undefined')
11
+ return Buffer.from(s, 'utf8').toString('base64url');
12
+ const bytes = new TextEncoder().encode(s);
13
+ let bin = '';
14
+ for (const b of bytes)
15
+ bin += String.fromCharCode(b);
16
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
17
+ };
18
+ // Parse a `Payment id="..", request="..", opaque=".."` WWW-Authenticate value.
19
+ const parseChallengeHeader = (header) => {
20
+ const m = header.trim().match(/^Payment\s+(.+)$/s);
21
+ if (!m?.[1])
22
+ throw new Error('not an MPP Payment challenge');
23
+ const params = {};
24
+ for (const part of m[1].split(',')) {
25
+ const eq = part.indexOf('=');
26
+ if (eq < 0)
27
+ continue;
28
+ const k = part.slice(0, eq).trim();
29
+ params[k] = part
30
+ .slice(eq + 1)
31
+ .trim()
32
+ .replace(/^"/, '')
33
+ .replace(/"$/, '');
34
+ }
35
+ if (!params.id || !params.request || !params.opaque) {
36
+ throw new Error('challenge missing id/request/opaque');
37
+ }
38
+ return { id: params.id, request: params.request, opaque: params.opaque };
39
+ };
40
+ // Drive the 2-step MPP charge for a gate: POST (no credential) → 402 +
41
+ // challenge → POST (with credential) → settled PaymentIntent. Returns the id
42
+ // to retry the gated tool with.
43
+ export const payMcpGate = async (gate, opts) => {
44
+ const fetchImpl = opts.fetch ?? fetch;
45
+ const url = buildUrl(opts.baseUrl.replace(/\/+$/, ''), gate.payment.payEndpoint);
46
+ const headers = {
47
+ 'content-type': 'application/json',
48
+ authorization: `Bearer ${opts.token}`,
49
+ };
50
+ const body = JSON.stringify({ amount: gate.payment.amount, currency: gate.payment.currency });
51
+ // 1. Unpaid → 402 + WWW-Authenticate: Payment challenge.
52
+ const challengeRes = await fetchImpl(url, { method: 'POST', headers, body });
53
+ if (challengeRes.status !== 402) {
54
+ throw new Error(`expected a 402 payment challenge, got ${challengeRes.status}`);
55
+ }
56
+ const wwwAuth = challengeRes.headers.get('www-authenticate');
57
+ if (!wwwAuth)
58
+ throw new Error('challenge response missing WWW-Authenticate header');
59
+ const challenge = parseChallengeHeader(wwwAuth);
60
+ // 2. Retry with a credential echoing the signed challenge.
61
+ const credential = `Payment ${toB64Url(JSON.stringify({
62
+ challenge: { id: challenge.id, request: challenge.request, opaque: challenge.opaque },
63
+ source: opts.source,
64
+ payload: opts.payload ?? { kind: 'delegated_token', ref: opts.source },
65
+ }))}`;
66
+ const paidRes = await fetchImpl(url, {
67
+ method: 'POST',
68
+ headers: { ...headers, 'x-mpp-credential': credential },
69
+ body,
70
+ });
71
+ const out = (await paidRes.json());
72
+ if (paidRes.status !== 200 || !out.paymentIntentId) {
73
+ throw new Error(`MPP charge failed (${paidRes.status}): ${JSON.stringify(out)}`);
74
+ }
75
+ return { paymentIntentId: out.paymentIntentId };
76
+ };
77
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type FetchLike } from '@curless/agentbank-core';
2
+ import type { PaywallBackend } from './types.js';
3
+ export declare const createHttpBackend: (cfg: {
4
+ baseUrl: string;
5
+ apiKey: string;
6
+ fetch?: FetchLike;
7
+ }) => PaywallBackend;
8
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA0C,MAAM,yBAAyB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjD,eAAO,MAAM,iBAAiB,GAAI,KAAK;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,KAAG,cA+BH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { buildUrl, sendJson } from '@curless/agentbank-core';
2
+ // agentbank-backed paywall verification: looks up the presented PaymentIntent
3
+ // and confirms it actually paid this merchant the required amount. The key
4
+ // must carry `agent:execute` (or `agentbank:admin`) — the scope GET
5
+ // /v1/payment-intents/:id requires — and share the org the PI was created in.
6
+ export const createHttpBackend = (cfg) => {
7
+ const fetchImpl = cfg.fetch ?? fetch;
8
+ const baseUrl = cfg.baseUrl.replace(/\/+$/, '');
9
+ const headers = {
10
+ 'content-type': 'application/json',
11
+ authorization: `Bearer ${cfg.apiKey}`,
12
+ };
13
+ return {
14
+ async verifyPayment({ paymentIntentId, merchantId, minAmount, currency }) {
15
+ let pi;
16
+ try {
17
+ pi = await sendJson(fetchImpl, buildUrl(baseUrl, `/v1/payment-intents/${encodeURIComponent(paymentIntentId)}`), 'GET', headers, undefined);
18
+ }
19
+ catch (err) {
20
+ return { ok: false, reason: `payment_intent lookup failed: ${err.message}` };
21
+ }
22
+ if (pi.curlessMerchantId !== merchantId)
23
+ return { ok: false, reason: 'merchant_mismatch' };
24
+ if (pi.status !== 'captured' && pi.status !== 'settled' && pi.status !== 'reconciled') {
25
+ return { ok: false, reason: `payment not captured (status=${pi.status})` };
26
+ }
27
+ if (pi.currency !== currency)
28
+ return { ok: false, reason: 'currency_mismatch' };
29
+ if (pi.amount < minAmount)
30
+ return { ok: false, reason: 'insufficient_amount' };
31
+ return { ok: true, amount: pi.amount, currency: pi.currency };
32
+ },
33
+ };
34
+ };
35
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,QAAQ,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGjG,8EAA8E;AAC9E,2EAA2E;AAC3E,oEAAoE;AACpE,8EAA8E;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAIjC,EAAkB,EAAE;IACnB,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG;QACd,cAAc,EAAE,kBAAkB;QAClC,aAAa,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE;KACtC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,aAAa,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE;YACtE,IAAI,EAAiB,CAAC;YACtB,IAAI,CAAC;gBACH,EAAE,GAAG,MAAM,QAAQ,CACjB,SAAS,EACT,QAAQ,CAAC,OAAO,EAAE,uBAAuB,kBAAkB,CAAC,eAAe,CAAC,EAAE,CAAC,EAC/E,KAAK,EACL,OAAO,EACP,SAAS,CACV,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAkC,GAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1F,CAAC;YACD,IAAI,EAAE,CAAC,iBAAiB,KAAK,UAAU;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;YAC3F,IAAI,EAAE,CAAC,MAAM,KAAK,UAAU,IAAI,EAAE,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;gBACtF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gCAAgC,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC;YAC7E,CAAC;YACD,IAAI,EAAE,CAAC,QAAQ,KAAK,QAAQ;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;YAChF,IAAI,EAAE,CAAC,MAAM,GAAG,SAAS;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;YAC/E,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,QAAQ,EAAE,CAAC;QAChE,CAAC;KACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { PAYMENT_ARG, type WithPaywallOptions, withPaywall } from './paywall.js';
2
+ export { createHttpBackend } from './backend.js';
3
+ export { type PayMcpGateOptions, payMcpGate } from './agent.js';
4
+ export type { ConsumedStore, PaywallBackend, PaywallGate, PaywallProof, ToolHandler, ToolResult, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EACV,aAAa,EACb,cAAc,EACd,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // @curless/agentbank-mcp-pay — paywall an MCP tool / API so AI agents pay per
2
+ // call. Seller side: wrap a tool with withPaywall(). Agent side: payMcpGate()
3
+ // (also exported from "@curless/agentbank-mcp-pay/agent").
4
+ export { PAYMENT_ARG, withPaywall } from './paywall.js';
5
+ export { createHttpBackend } from './backend.js';
6
+ export { payMcpGate } from './agent.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,2DAA2D;AAC3D,OAAO,EAAE,WAAW,EAA2B,WAAW,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAA0B,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ConsumedStore, PaywallBackend, ToolHandler } from './types.js';
2
+ export declare const PAYMENT_ARG = "_agentbankPayment";
3
+ export type WithPaywallOptions = {
4
+ backend: PaywallBackend;
5
+ merchantId: string;
6
+ price: number;
7
+ currency: string;
8
+ sku?: string;
9
+ consumed?: ConsumedStore;
10
+ };
11
+ export declare const withPaywall: <A extends Record<string, unknown> = Record<string, unknown>>(handler: ToolHandler<A>, opts: WithPaywallOptions) => ToolHandler<A>;
12
+ //# sourceMappingURL=paywall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../src/paywall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAGd,WAAW,EAEZ,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AA6CF,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrF,SAAS,WAAW,CAAC,CAAC,CAAC,EACvB,MAAM,kBAAkB,KACvB,WAAW,CAAC,CAAC,CAiCf,CAAC"}
@@ -0,0 +1,77 @@
1
+ // The arg key the agent uses to present a payment proof on the retry call.
2
+ export const PAYMENT_ARG = '_agentbankPayment';
3
+ const inMemoryConsumed = () => {
4
+ const seen = new Set();
5
+ return {
6
+ has: (id) => seen.has(id),
7
+ add: (id) => {
8
+ seen.add(id);
9
+ },
10
+ };
11
+ };
12
+ const gate = (opts, note) => {
13
+ const g = {
14
+ status: 'payment_required',
15
+ payment: {
16
+ merchantId: opts.merchantId,
17
+ amount: opts.price,
18
+ currency: opts.currency,
19
+ sku: opts.sku,
20
+ protocol: 'mpp',
21
+ payEndpoint: `/mpp/${opts.merchantId}/charge`,
22
+ },
23
+ proofArg: PAYMENT_ARG,
24
+ note,
25
+ };
26
+ const what = opts.sku ? ` for "${opts.sku}"` : '';
27
+ const text = `Payment required: ${opts.price} ${opts.currency}${what}. ` +
28
+ `Pay via agentbank MPP (POST ${g.payment.payEndpoint}), then retry this tool with ` +
29
+ `${PAYMENT_ARG}: { paymentIntentId }.${note ? ` (${note})` : ''}`;
30
+ // A gate is NOT an error — the agent is expected to read structuredContent,
31
+ // pay, and retry. (MCP has no HTTP 402; the terms ride in structuredContent.)
32
+ return {
33
+ content: [{ type: 'text', text }],
34
+ structuredContent: { payment_required: g },
35
+ isError: false,
36
+ };
37
+ };
38
+ // Wrap an MCP tool handler with a per-call paywall. An unpaid call returns a
39
+ // payment gate; once the agent pays (via agentbank MPP) and retries with the
40
+ // PaymentIntent id, the payment is verified + consumed (single-use) and the
41
+ // real handler runs. agentbank's ledger + rail did the actual settlement —
42
+ // this only gates the tool on a verified, not-yet-used payment.
43
+ export const withPaywall = (handler, opts) => {
44
+ const consumed = opts.consumed ?? inMemoryConsumed();
45
+ return async (args) => {
46
+ const proof = args[PAYMENT_ARG];
47
+ if (!proof?.paymentIntentId)
48
+ return gate(opts);
49
+ const pi = proof.paymentIntentId;
50
+ if (await consumed.has(pi))
51
+ return gate(opts, `payment ${pi} was already used — pay again`);
52
+ const verdict = await opts.backend.verifyPayment({
53
+ paymentIntentId: pi,
54
+ merchantId: opts.merchantId,
55
+ minAmount: opts.price,
56
+ currency: opts.currency,
57
+ });
58
+ if (!verdict.ok)
59
+ return gate(opts, verdict.reason ?? 'payment could not be verified');
60
+ // Bind the payment to this single execution before running the handler.
61
+ await consumed.add(pi);
62
+ const result = await handler(args);
63
+ return {
64
+ ...result,
65
+ structuredContent: {
66
+ ...(result.structuredContent ?? {}),
67
+ payment: {
68
+ paymentIntentId: pi,
69
+ amount: verdict.amount,
70
+ currency: verdict.currency,
71
+ status: 'paid',
72
+ },
73
+ },
74
+ };
75
+ };
76
+ };
77
+ //# sourceMappingURL=paywall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paywall.js","sourceRoot":"","sources":["../src/paywall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export type ToolResult = {
2
+ content: Array<{
3
+ type: 'text';
4
+ text: string;
5
+ } | {
6
+ type: string;
7
+ [k: string]: unknown;
8
+ }>;
9
+ structuredContent?: Record<string, unknown>;
10
+ isError?: boolean;
11
+ };
12
+ export type ToolHandler<A = Record<string, unknown>> = (args: A) => Promise<ToolResult> | ToolResult;
13
+ export type PaywallGate = {
14
+ status: 'payment_required';
15
+ payment: {
16
+ merchantId: string;
17
+ amount: number;
18
+ currency: string;
19
+ sku?: string;
20
+ protocol: 'mpp';
21
+ payEndpoint: string;
22
+ };
23
+ proofArg: string;
24
+ note?: string;
25
+ };
26
+ export type PaywallProof = {
27
+ paymentIntentId: string;
28
+ };
29
+ export type PaywallBackend = {
30
+ verifyPayment(input: {
31
+ paymentIntentId: string;
32
+ merchantId: string;
33
+ minAmount: number;
34
+ currency: string;
35
+ }): Promise<{
36
+ ok: boolean;
37
+ amount?: number;
38
+ currency?: string;
39
+ reason?: string;
40
+ }>;
41
+ };
42
+ export type ConsumedStore = {
43
+ has(id: string): boolean | Promise<boolean>;
44
+ add(id: string): void | Promise<void>;
45
+ };
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CACrD,IAAI,EAAE,CAAC,KACJ,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;AAKtC,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,KAAK,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IAEF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAGF,MAAM,MAAM,YAAY,GAAG;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAIvD,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,CAAC,KAAK,EAAE;QACnB,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnF,CAAC;AAIF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@curless/agentbank-mcp-pay",
3
+ "version": "0.1.0",
4
+ "description": "Paywall an MCP tool / API so AI agents pay per call. Wrap a tool handler with withPaywall(): unpaid calls return a payment gate, the agent pays via agentbank (MPP/HTTP-402), and the call runs once payment is verified. Settles down to agentbank's ledger + rails (card / stablecoin / Curless).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./agent": {
15
+ "types": "./dist/agent.d.ts",
16
+ "default": "./dist/agent.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "@curless/agentbank-core": "0.0.1"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.1",
31
+ "typescript": "^5.6.3",
32
+ "vitest": "^2.1.9"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "keywords": [
38
+ "agentbank",
39
+ "curless",
40
+ "agent",
41
+ "mcp",
42
+ "paywall",
43
+ "x402",
44
+ "mpp",
45
+ "monetize"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "typecheck": "tsc --noEmit",
50
+ "test": "vitest run",
51
+ "clean": "rm -rf dist"
52
+ }
53
+ }