@aifinpay/mcp 1.5.1 → 2.0.0-rc.3
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 +11 -15
- package/bin/aifinpay-mcp.js +161 -4
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -0
- package/dist/config.js.map +1 -1
- package/dist/safe-fetch.d.ts +2 -0
- package/dist/safe-fetch.d.ts.map +1 -1
- package/dist/safe-fetch.js +21 -1
- package/dist/safe-fetch.js.map +1 -1
- package/dist/server.d.ts +10 -7
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +43 -81
- package/dist/server.js.map +1 -1
- package/dist/tools/agent-address.d.ts.map +1 -1
- package/dist/tools/agent-address.js +6 -27
- package/dist/tools/agent-address.js.map +1 -1
- package/dist/tools/agent-quota.d.ts +40 -0
- package/dist/tools/agent-quota.d.ts.map +1 -0
- package/dist/tools/agent-quota.js +95 -0
- package/dist/tools/agent-quota.js.map +1 -0
- package/dist/tools/payable-fetch.d.ts.map +1 -1
- package/dist/tools/payable-fetch.js +44 -16
- package/dist/tools/payable-fetch.js.map +1 -1
- package/dist/tools/production-control.d.ts +108 -0
- package/dist/tools/production-control.d.ts.map +1 -0
- package/dist/tools/production-control.js +131 -0
- package/dist/tools/production-control.js.map +1 -0
- package/package.json +19 -9
package/README.md
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
# @aifinpay/mcp
|
|
2
2
|
|
|
3
|
-
MCP server exposing AiFinPay
|
|
4
|
-
agent
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
and XRPL EVM (native-token path), while the MCP `pay_with_split` /
|
|
12
|
-
`quote_split` tools stay Polygon + Solana (backend invoice flow). The
|
|
13
|
-
protocol itself is live across 13 networks — see
|
|
14
|
-
[aifinpay.io/llms.txt](https://aifinpay.io/llms.txt).
|
|
3
|
+
MCP server exposing AiFinPay payment and quote primitives to MCP-aware
|
|
4
|
+
agent runtimes. Canonical domain: **aifinpay.io**.
|
|
5
|
+
|
|
6
|
+
AIFP-1 is gross-inclusive: payer total equals the quote, merchant receives 99%,
|
|
7
|
+
AiFinPay receives 1%, creator/referral receives 0%. AIFP-2/x402 currently
|
|
8
|
+
charges 0% at the protocol layer. Fund-moving paths fail closed until the exact
|
|
9
|
+
deployment, runtime hash, governance profile, merchant target, asset and paid
|
|
10
|
+
E2E evidence are verified.
|
|
15
11
|
|
|
16
12
|
## Tools
|
|
17
13
|
|
|
@@ -21,8 +17,8 @@ protocol itself is live across 13 networks — see
|
|
|
21
17
|
| `agent_address()` | Return the agent's funding addresses on **both** chains — Polygon `0x…` (default settlement: io.net, Exa, Venice bridges) and Solana base58 (Seat PDA / leaderboard). One seed, two chains — fund either. |
|
|
22
18
|
| `agent_quote(url)` | Inspect a 402 challenge without paying. Shows the merchant's quoted amount + facilitator flavor. |
|
|
23
19
|
| `agent_call(provider, …)` | Call a live provider from the AiFinPay directory (io.net, Exa, Venice, …) with automatic payment. |
|
|
24
|
-
| `pay_with_split(
|
|
25
|
-
| `quote_split(
|
|
20
|
+
| `pay_with_split(…)` | Retired compatibility tool. Returns `legacy_split_route_retired`; never creates an invoice or moves funds. |
|
|
21
|
+
| `quote_split(…)` | Retired compatibility tool. Use a canonical AIFP-1 quote. |
|
|
26
22
|
| `agent_claim_self(magic_link_url)` | Link this agent to your dashboard account via a one-shot magic link from `aifinpay.io/login` — spend history shows up in the dashboard. |
|
|
27
23
|
|
|
28
24
|
## Install
|
|
@@ -77,7 +73,7 @@ agent identity (and any funded Seat) persists across restarts.
|
|
|
77
73
|
| `AIFINPAY_AGENT_SECRET` | — | Base58 secret. If absent → ephemeral agent printed to stderr. |
|
|
78
74
|
| `AIFINPAY_BASE_URL` | `https://aifinpay.io` | Backend URL for nonce + funding probes. |
|
|
79
75
|
| `AIFINPAY_TIMEOUT_MS` | `30000` | Request timeout. |
|
|
80
|
-
| `AIFINPAY_MAX_USD` | — |
|
|
76
|
+
| `AIFINPAY_MAX_USD` | — | Mandatory positive finite operator ceiling for any fund-moving tool. |
|
|
81
77
|
|
|
82
78
|
## Programmatic use
|
|
83
79
|
|
package/bin/aifinpay-mcp.js
CHANGED
|
@@ -4,14 +4,171 @@
|
|
|
4
4
|
* in stdio mode. Compatible with Claude Desktop, MCP Inspector, and any
|
|
5
5
|
* MCP-aware agent runtime.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Two commands, and the first one only has to be run once:
|
|
8
|
+
*
|
|
9
|
+
* npx @aifinpay/mcp init create a persistent wallet, print the config
|
|
10
|
+
* npx @aifinpay/mcp start the server
|
|
11
|
+
*
|
|
12
|
+
* WHY `init` EXISTS
|
|
13
|
+
*
|
|
14
|
+
* Running the server with nothing configured produced an EPHEMERAL wallet and
|
|
15
|
+
* a "DO NOT FUND" warning — correct, and a dead end. The warning pointed at
|
|
16
|
+
* `aifinpay init`, which is not published to npm: `@aifinpay/cli` and
|
|
17
|
+
* `aifinpay` both 404. So the documented way out of the dead end did not exist,
|
|
18
|
+
* and the only real path was to write a Node script against the SDK, which is
|
|
19
|
+
* not something you ask of someone whose goal is "give my agent a wallet".
|
|
20
|
+
*
|
|
21
|
+
* The gap was exactly one subcommand. This is it.
|
|
22
|
+
*
|
|
23
|
+
* Configure via env (all optional; the secret is read from the keystore below
|
|
24
|
+
* when the variable is unset):
|
|
25
|
+
* AIFINPAY_AGENT_SECRET base58 secret — overrides the keystore
|
|
9
26
|
* AIFINPAY_BASE_URL default https://aifinpay.io
|
|
10
27
|
* AIFINPAY_TIMEOUT_MS default 30000
|
|
11
28
|
* AIFINPAY_MAX_USD hard cap per single payment (no default)
|
|
12
29
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
30
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, chmodSync } from "node:fs";
|
|
31
|
+
import { join } from "node:path";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
import { createRequire } from "node:module";
|
|
34
|
+
|
|
35
|
+
const HOME = process.env.AIFINPAY_HOME || join(homedir(), ".aifinpay");
|
|
36
|
+
const KEYSTORE = join(HOME, "agent.json");
|
|
37
|
+
|
|
38
|
+
const require_ = createRequire(import.meta.url);
|
|
39
|
+
const VERSION = (() => {
|
|
40
|
+
try {
|
|
41
|
+
return require_("../package.json").version;
|
|
42
|
+
} catch {
|
|
43
|
+
return "unknown";
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
|
|
47
|
+
const arg = (process.argv[2] || "").toLowerCase();
|
|
48
|
+
|
|
49
|
+
// ── help / version ────────────────────────────────────────────────────────
|
|
50
|
+
// These used to start the server. `--help` launching a stdio server is not a
|
|
51
|
+
// harmless quirk: the process appears to hang, because it is waiting for MCP
|
|
52
|
+
// framing on stdin that a human is never going to type.
|
|
53
|
+
|
|
54
|
+
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
55
|
+
process.stdout.write(`aifinpay-mcp ${VERSION}
|
|
56
|
+
|
|
57
|
+
npx @aifinpay/mcp init create a persistent wallet and print the config
|
|
58
|
+
npx @aifinpay/mcp start the MCP server (stdio)
|
|
59
|
+
|
|
60
|
+
Run init once. It writes ${KEYSTORE} with mode 600 and the server picks
|
|
61
|
+
it up automatically — you do not have to put the secret in a config file.
|
|
62
|
+
|
|
63
|
+
Env (all optional):
|
|
64
|
+
AIFINPAY_AGENT_SECRET base58 secret; overrides the keystore
|
|
65
|
+
AIFINPAY_MAX_USD hard cap per single payment — set this
|
|
66
|
+
AIFINPAY_BASE_URL default https://aifinpay.io
|
|
67
|
+
AIFINPAY_TIMEOUT_MS default 30000
|
|
68
|
+
AIFINPAY_HOME default ~/.aifinpay
|
|
69
|
+
`);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (arg === "--version" || arg === "-v" || arg === "version") {
|
|
74
|
+
process.stdout.write(`${VERSION}\n`);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── init ──────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function readKeystore() {
|
|
81
|
+
if (!existsSync(KEYSTORE)) return null;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(readFileSync(KEYSTORE, "utf8"));
|
|
84
|
+
return typeof parsed?.secretB58 === "string" ? parsed : null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (arg === "init") {
|
|
91
|
+
const { Agent, AiFinPayAgent } = await import("@aifinpay/agent");
|
|
92
|
+
|
|
93
|
+
let store = readKeystore();
|
|
94
|
+
if (store) {
|
|
95
|
+
// Never silently overwrite. The file is the only copy of a key that may
|
|
96
|
+
// already hold funds; a second `init` that regenerated it would destroy a
|
|
97
|
+
// wallet to save one line of output.
|
|
98
|
+
process.stdout.write(`Existing wallet found at ${KEYSTORE} — keeping it.\n\n`);
|
|
99
|
+
} else {
|
|
100
|
+
mkdirSync(HOME, { recursive: true, mode: 0o700 });
|
|
101
|
+
store = { secretB58: Agent.new().secretB58, created: new Date().toISOString() };
|
|
102
|
+
writeFileSync(KEYSTORE, JSON.stringify(store, null, 2) + "\n", { mode: 0o600 });
|
|
103
|
+
chmodSync(KEYSTORE, 0o600); // writeFileSync honours umask; this does not
|
|
104
|
+
process.stdout.write(`Created ${KEYSTORE} (mode 600).\n\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const agent = await AiFinPayAgent.fromSolanaSecret(store.secretB58);
|
|
108
|
+
|
|
109
|
+
process.stdout.write(
|
|
110
|
+
`Your agent's addresses — the EVM one is the same on every EVM chain:\n\n` +
|
|
111
|
+
` EVM ${agent.evmAddress}\n` +
|
|
112
|
+
` Solana ${agent.solanaAddress}\n` +
|
|
113
|
+
` Casper ${agent.casperAddress}\n\n` +
|
|
114
|
+
`Add this to your MCP client config and restart it:\n\n` +
|
|
115
|
+
JSON.stringify(
|
|
116
|
+
{
|
|
117
|
+
mcpServers: {
|
|
118
|
+
aifinpay: {
|
|
119
|
+
command: "npx",
|
|
120
|
+
args: ["-y", "@aifinpay/mcp"],
|
|
121
|
+
env: { AIFINPAY_MAX_USD: "0.10" },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
null,
|
|
126
|
+
2,
|
|
127
|
+
) +
|
|
128
|
+
`\n\nThe secret is NOT in that block on purpose — the server reads the\n` +
|
|
129
|
+
`keystore. Config files get pasted into chats and committed to git.\n\n` +
|
|
130
|
+
`Back up ${KEYSTORE}. It is the only copy. The derivation is not\n` +
|
|
131
|
+
`BIP-39, so no standard wallet can recover this from a phrase.\n\n` +
|
|
132
|
+
`The addresses hold nothing yet. Send POL to the EVM address to let the\n` +
|
|
133
|
+
`agent pay for calls.\n`,
|
|
134
|
+
);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (arg && !arg.startsWith("-")) {
|
|
139
|
+
process.stderr.write(`aifinpay-mcp: unknown command "${arg}". Try --help.\n`);
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── server ────────────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
// Fall back to the keystore so `init` once is genuinely enough. Env still wins:
|
|
146
|
+
// a caller that sets AIFINPAY_AGENT_SECRET explicitly means it.
|
|
147
|
+
if (!process.env.AIFINPAY_AGENT_SECRET) {
|
|
148
|
+
const store = readKeystore();
|
|
149
|
+
if (store) {
|
|
150
|
+
try {
|
|
151
|
+
const mode = statSync(KEYSTORE).mode & 0o777;
|
|
152
|
+
if (mode & 0o077) {
|
|
153
|
+
process.stderr.write(
|
|
154
|
+
`[warn] [aifinpay-mcp] ${KEYSTORE} is mode ${mode.toString(8)} — readable beyond your user. ` +
|
|
155
|
+
`Run: chmod 600 ${KEYSTORE}\n`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
/* stat failure is not a reason to refuse to start */
|
|
160
|
+
}
|
|
161
|
+
process.env.AIFINPAY_AGENT_SECRET = store.secretB58;
|
|
162
|
+
} else {
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
`[info] [aifinpay-mcp] no wallet configured. Run \`npx @aifinpay/mcp init\` once ` +
|
|
165
|
+
`for a persistent one; starting with a throwaway identity for now.\n`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
171
|
+
const { createServer, loadConfigFromEnv } = await import("../dist/index.js");
|
|
15
172
|
|
|
16
173
|
const { server } = await createServer(loadConfigFromEnv());
|
|
17
174
|
const transport = new StdioServerTransport();
|
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,30 @@ export interface McpConfig {
|
|
|
9
9
|
timeoutMs?: number;
|
|
10
10
|
/** Hard cap on a single payment to prevent runaway agents. */
|
|
11
11
|
maxAmountUsd?: number;
|
|
12
|
+
/** Gateway origins this agent may settle against.
|
|
13
|
+
*
|
|
14
|
+
* The SDK has always supported this (parseGatewayUrl in @aifinpay/agent) and
|
|
15
|
+
* this wrapper never exposed it, so a self-hosted merchant was unreachable:
|
|
16
|
+
* payable_fetch reached the 402 and then refused with "dev.ratersapp.com is
|
|
17
|
+
* not a known AiFinPay gateway; allowed: https://gateway.aifinpay.io".
|
|
18
|
+
* Observed in an external E2E run on 2026-08-27.
|
|
19
|
+
*
|
|
20
|
+
* Set by the OPERATOR, exact origins only, never a wildcard. The refusal it
|
|
21
|
+
* relaxes is not paranoia: a 402 is unauthenticated, so paying an
|
|
22
|
+
* unrecognised host means paying whoever answered. Naming a host here is a
|
|
23
|
+
* statement that you know who that is. */
|
|
24
|
+
gatewayOrigins?: string[];
|
|
25
|
+
/** Hosts whose DNS pre-check is skipped, exact names only.
|
|
26
|
+
*
|
|
27
|
+
* safe-fetch resolves a hostname and refuses if any answer is a private
|
|
28
|
+
* address — an SSRF guard. Behind an HTTP proxy the client cannot resolve at
|
|
29
|
+
* all (getaddrinfo EAI_AGAIN), so every host is refused for the wrong reason.
|
|
30
|
+
*
|
|
31
|
+
* This is deliberately NOT a proxy-detection switch that disables the check
|
|
32
|
+
* globally: that would turn one environment quirk into a blanket SSRF
|
|
33
|
+
* bypass. An operator names the hosts they vouch for, one at a time, and the
|
|
34
|
+
* guard stays on for everything else. */
|
|
35
|
+
trustedHosts?: string[];
|
|
12
36
|
/** Optional log destination (defaults to stderr). */
|
|
13
37
|
logFn?: (level: "info" | "warn" | "error", msg: string) => void;
|
|
14
38
|
}
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB;kFAC8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB;kFAC8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;+CAW2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;;;;;;;8CAS0C;IAC1C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB,qDAAqD;IACrD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,CAa7C"}
|
package/dist/config.js
CHANGED
|
@@ -8,6 +8,52 @@ export function loadConfigFromEnv() {
|
|
|
8
8
|
maxAmountUsd: process.env.AIFINPAY_MAX_USD
|
|
9
9
|
? Number(process.env.AIFINPAY_MAX_USD)
|
|
10
10
|
: undefined,
|
|
11
|
+
gatewayOrigins: splitOrigins(process.env.AIFINPAY_GATEWAY_ORIGINS),
|
|
12
|
+
trustedHosts: splitList(process.env.AIFINPAY_TRUSTED_HOSTS),
|
|
11
13
|
};
|
|
12
14
|
}
|
|
15
|
+
/** Comma-separated list → trimmed entries, or undefined when unset/empty.
|
|
16
|
+
* Undefined and [] mean different things downstream: undefined keeps the SDK
|
|
17
|
+
* default, [] would mean "no origin is payable". An operator who sets the
|
|
18
|
+
* variable to an empty string means the former. */
|
|
19
|
+
function splitList(raw) {
|
|
20
|
+
if (!raw)
|
|
21
|
+
return undefined;
|
|
22
|
+
const out = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
23
|
+
return out.length ? out : undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Same, but each entry must be a bare https origin.
|
|
26
|
+
* A path or a wildcard here would read as allowed and match nothing, which is
|
|
27
|
+
* the failure that looks like the feature is broken rather than misconfigured. */
|
|
28
|
+
function splitOrigins(raw) {
|
|
29
|
+
const list = splitList(raw);
|
|
30
|
+
if (!list)
|
|
31
|
+
return undefined;
|
|
32
|
+
for (const entry of list) {
|
|
33
|
+
let u;
|
|
34
|
+
try {
|
|
35
|
+
u = new URL(entry);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error(`AIFINPAY_GATEWAY_ORIGINS: "${entry}" is not a URL`);
|
|
39
|
+
}
|
|
40
|
+
if (u.origin !== entry.replace(/\/+$/, "")) {
|
|
41
|
+
throw new Error(`AIFINPAY_GATEWAY_ORIGINS: "${entry}" must be a bare origin like https://dev.example.com `
|
|
42
|
+
+ `(got path/query "${u.pathname}${u.search}")`);
|
|
43
|
+
}
|
|
44
|
+
// WHATWG URL accepts "*" in a hostname, so new URL("https://*.example.com")
|
|
45
|
+
// parses and its origin round-trips — the entry would be stored, matched
|
|
46
|
+
// against nothing, and read as "I allowed this host". Rejecting it here is
|
|
47
|
+
// the difference between a misconfiguration that says so and one that looks
|
|
48
|
+
// like the feature is broken.
|
|
49
|
+
if (!/^[a-z0-9.-]+$/i.test(u.hostname) || u.hostname.startsWith("-") || u.hostname.includes("..")) {
|
|
50
|
+
throw new Error(`AIFINPAY_GATEWAY_ORIGINS: "${entry}" is not a plain hostname. `
|
|
51
|
+
+ `Wildcards are not supported — name each origin you settle against.`);
|
|
52
|
+
}
|
|
53
|
+
if (u.protocol !== "https:" && u.hostname !== "localhost" && u.hostname !== "127.0.0.1") {
|
|
54
|
+
throw new Error(`AIFINPAY_GATEWAY_ORIGINS: "${entry}" is not https — refusing to settle over plaintext`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return list;
|
|
58
|
+
}
|
|
13
59
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA6CA,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,SAAS;QAC9D,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,SAAS;QACnD,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,mBAAmB;YACxC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;YACzC,CAAC,CAAC,SAAS;QACb,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB;YACxC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACtC,CAAC,CAAC,SAAS;QACb,cAAc,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;QAClE,YAAY,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED;;;oDAGoD;AACpD,SAAS,SAAS,CAAC,GAAuB;IACxC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC;AAED;;mFAEmF;AACnF,SAAS,YAAY,CAAC,GAAuB;IAC3C,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,CAAM,CAAC;QACX,IAAI,CAAC;YAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;QAC3B,MAAM,CAAC;YAAC,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,gBAAgB,CAAC,CAAC;QAAC,CAAC;QAC/E,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,8BAA8B,KAAK,uDAAuD;kBACxF,oBAAoB,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,MAAM,IAAI,CAChD,CAAC;QACJ,CAAC;QACD,4EAA4E;QAC5E,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,8BAA8B;QAC9B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAClG,MAAM,IAAI,KAAK,CACb,8BAA8B,KAAK,6BAA6B;kBAC9D,oEAAoE,CACvE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;YACxF,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,oDAAoD,CAAC,CAAC;QAC3G,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/safe-fetch.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class BlockedRequestError extends Error {
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function assertRequestAllowed(rawUrl: string, opts?: {
|
|
14
14
|
allowPrivate?: boolean;
|
|
15
|
+
trustedHosts?: string[];
|
|
15
16
|
}): Promise<URL>;
|
|
16
17
|
/**
|
|
17
18
|
* A `fetch` that validates the target, and every redirect target, before going.
|
|
@@ -21,5 +22,6 @@ export declare function assertRequestAllowed(rawUrl: string, opts?: {
|
|
|
21
22
|
*/
|
|
22
23
|
export declare function makeSafeFetch(opts?: {
|
|
23
24
|
allowPrivate?: boolean;
|
|
25
|
+
trustedHosts?: string[];
|
|
24
26
|
}): typeof fetch;
|
|
25
27
|
//# sourceMappingURL=safe-fetch.d.ts.map
|
package/dist/safe-fetch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-fetch.d.ts","sourceRoot":"","sources":["../src/safe-fetch.ts"],"names":[],"mappings":"AA4DA,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmBtD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,
|
|
1
|
+
{"version":3,"file":"safe-fetch.d.ts","sourceRoot":"","sources":["../src/safe-fetch.ts"],"names":[],"mappings":"AA4DA,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmBtD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAC7D,OAAO,CAAC,GAAG,CAAC,CAyEd;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,OAAO,KAAK,CA4B1G"}
|
package/dist/safe-fetch.js
CHANGED
|
@@ -116,12 +116,32 @@ export async function assertRequestAllowed(rawUrl, opts = {}) {
|
|
|
116
116
|
}
|
|
117
117
|
return url;
|
|
118
118
|
}
|
|
119
|
+
// An operator naming this exact host has vouched for it, so the resolution
|
|
120
|
+
// step is skipped for it and only for it.
|
|
121
|
+
//
|
|
122
|
+
// Why this exists: behind an HTTP proxy the client does not resolve at all —
|
|
123
|
+
// the proxy does — so lookup() fails with getaddrinfo EAI_AGAIN and every
|
|
124
|
+
// host is refused as "cannot resolve". That is the guard misfiring on the
|
|
125
|
+
// environment rather than on a threat, and it made the MCP server unusable in
|
|
126
|
+
// proxied setups (external E2E, 2026-08-27).
|
|
127
|
+
//
|
|
128
|
+
// Deliberately per-host and not a proxy-detection switch. "We seem to be
|
|
129
|
+
// behind a proxy, disable the SSRF check" turns one environment quirk into a
|
|
130
|
+
// blanket bypass, which is the shape of the vulnerability this file exists to
|
|
131
|
+
// prevent. Exact hostnames only — no suffix matching, because "example.com"
|
|
132
|
+
// trusting "evil-example.com" is the classic way an allowlist stops meaning
|
|
133
|
+
// anything.
|
|
134
|
+
if (opts.trustedHosts?.some((h) => h.toLowerCase() === host.toLowerCase())) {
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
119
137
|
let addresses;
|
|
120
138
|
try {
|
|
121
139
|
addresses = await lookup(host, { all: true });
|
|
122
140
|
}
|
|
123
141
|
catch (e) {
|
|
124
|
-
throw new BlockedRequestError(`cannot resolve ${host}: ${e.message}`
|
|
142
|
+
throw new BlockedRequestError(`cannot resolve ${host}: ${e.message}. `
|
|
143
|
+
+ `If this host is reachable only through a proxy, name it in `
|
|
144
|
+
+ `AIFINPAY_TRUSTED_HOSTS to skip the DNS pre-check for it alone.`);
|
|
125
145
|
}
|
|
126
146
|
if (!addresses.length) {
|
|
127
147
|
throw new BlockedRequestError(`${host} resolved to nothing`);
|
package/dist/safe-fetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../src/safe-fetch.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../src/safe-fetch.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,wEAAwE;AACxE,8EAA8E;AAC9E,+EAA+E;AAC/E,uEAAuE;AACvE,EAAE;AACF,gFAAgF;AAChF,mEAAmE;AACnE,sEAAsE;AACtE,6EAA6E;AAC7E,4EAA4E;AAC5E,EAAE;AACF,6EAA6E;AAC7E,gFAAgF;AAChF,uDAAuD;AAEvD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhC,qDAAqD;AACrD,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAuB;IACrC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,YAAY;IACtC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,aAAa;IACvC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,4BAA4B;IACtD,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,uBAAuB;IACjD,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,uCAAuC;IACjE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,gBAAgB;IAC1C,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,eAAe;IACzC,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,iBAAiB;IAC3C,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,6BAA6B;IACvD,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,wBAAwB;IAClD,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,mCAAmC;CAC9D,CAAC;AAEF,SAAS,OAAO,CAAC,EAAU;IACzB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG;YAAE,OAAO,IAAI,CAAC;QAC1D,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,CAAC,4CAA4C;QACzE,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACzD,yEAAyE;QACzE,8DAA8D;QAC9D,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC5D,IAAI,MAAM;YAAE,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACnD,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;QAC3E,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,uBAAuB;QAC1E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,wBAAwB;AACvC,CAAC;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAc,EACd,OAA4D,EAAE;IAE9D,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAAC,oBAAoB,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,IAAI,CAAC,YAAY;QAAE,OAAO,GAAG,CAAC,CAAC,wCAAwC;IAE3E,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,mBAAmB,CAC3B,YAAY,GAAG,CAAC,QAAQ,qDAAqD,CAC9E,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,6CAA6C;QAC7C,MAAM,IAAI,mBAAmB,CAC3B,mBAAmB,GAAG,CAAC,IAAI,mCAAmC,CAC/D,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACf,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,mBAAmB,CAAC,YAAY,IAAI,wBAAwB,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,2EAA2E;IAC3E,0CAA0C;IAC1C,EAAE;IACF,6EAA6E;IAC7E,0EAA0E;IAC1E,0EAA0E;IAC1E,8EAA8E;IAC9E,6CAA6C;IAC7C,EAAE;IACF,yEAAyE;IACzE,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,YAAY;IACZ,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC3E,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,SAAgC,CAAC;IACrC,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,mBAAmB,CAC3B,kBAAkB,IAAI,KAAM,CAAW,CAAC,OAAO,IAAI;cACjD,6DAA6D;cAC7D,gEAAgE,CACnE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QACtB,MAAM,IAAI,mBAAmB,CAAC,GAAG,IAAI,sBAAsB,CAAC,CAAC;IAC/D,CAAC;IACD,2EAA2E;IAC3E,mDAAmD;IACnD,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,SAAS,EAAE,CAAC;QACpC,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,mBAAmB,CAC3B,YAAY,IAAI,oBAAoB,OAAO,iCAAiC,CAC7E,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAA4D,EAAE;IAC1F,MAAM,SAAS,GAAG,KAAK,EACrB,KAAkC,EAClC,IAAkC,EACf,EAAE;QACrB,IAAI,MAAM,GACR,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK;YACjC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE;gBACzC,CAAC,CAAE,KAAiB,CAAC,GAAG,CAAC;QAC3B,IAAI,OAAO,GAAG,IAAI,CAAC;QAEnB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC;YAC9C,MAAM,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACzC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAE/E,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5F,IAAI,CAAC,QAAQ;gBAAE,OAAO,GAAG,CAAC;YAE1B,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC9C,sEAAsE;YACtE,mEAAmE;YACnE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACnE,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YAC3D,CAAC;QACH,CAAC;QACD,MAAM,IAAI,mBAAmB,CAAC,iCAAiC,aAAa,GAAG,CAAC,CAAC;IACnF,CAAC,CAAC;IACF,OAAO,SAAyB,CAAC;AACnC,CAAC"}
|
package/dist/server.d.ts
CHANGED
|
@@ -2,15 +2,18 @@ import { AiFinPayAgent } from "@aifinpay/agent";
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import type { McpConfig } from "./config.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Production-RC MCP surface.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* IMPORTANT: the old agent_call, payable_fetch, pay_with_split, quote_split,
|
|
8
|
+
* agent_claim_self and agent_quote tools are deliberately NOT registered.
|
|
9
|
+
* They depend on @aifinpay/agent 1.x legacy splitter/x402 semantics and must not
|
|
10
|
+
* be available to a model after the 0% AIFP-2 / gross-inclusive AIFP-1 change.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* This server remains useful before the SDK 2.0 package is published: it can
|
|
13
|
+
* expose the wallet address, resolve Agent Passport identity, read verified
|
|
14
|
+
* v1.3 routes, and construct a non-signing settlement invoice. Signing/moving
|
|
15
|
+
* value returns only after MCP depends on the published v2 executor and its E2E
|
|
16
|
+
* release gate has passed.
|
|
14
17
|
*/
|
|
15
18
|
export declare function createServer(config?: McpConfig): Promise<{
|
|
16
19
|
server: Server<{
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAKnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAwB7C;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAAC,MAAM,GAAE,SAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFxD;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D"}
|
package/dist/server.js
CHANGED
|
@@ -1,41 +1,36 @@
|
|
|
1
1
|
import { AiFinPayAgent } from "@aifinpay/agent";
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
import { payableFetchTool, runPayableFetch } from "./tools/payable-fetch.js";
|
|
5
4
|
import { agentAddressTool, runAgentAddress } from "./tools/agent-address.js";
|
|
6
|
-
import {
|
|
7
|
-
import { agentCallTool, runAgentCall } from "./tools/agent-call.js";
|
|
8
|
-
import { agentClaimSelfTool, runAgentClaimSelf } from "./tools/agent-claim-self.js";
|
|
5
|
+
import { agentQuotaTool, runAgentQuota } from "./tools/agent-quota.js";
|
|
9
6
|
import { makeSafeFetch } from "./safe-fetch.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// caller — or a prompt injection reaching the tool — could name
|
|
16
|
-
// http://127.0.0.1:4001 or http://169.254.169.254/latest/meta-data/ and read
|
|
17
|
-
// the answer. It is the tool exposed over HTTP today.
|
|
18
|
-
//
|
|
19
|
-
// Set AIFINPAY_ALLOW_PRIVATE_FETCH=1 to lift it, which is what a local bridge
|
|
20
|
-
// on localhost needs and what a public deployment must never have.
|
|
7
|
+
import { loadConfigFromEnv } from "./config.js";
|
|
8
|
+
import { agentPassportResolveTool, runAgentPassportResolve, settlementRoutesTool, runSettlementRoutes, settlementInvoiceTool, runSettlementInvoice, } from "./tools/production-control.js";
|
|
9
|
+
// Every backend/public request made by this server goes through safeFetch.
|
|
10
|
+
// Public deployments never lift private-network protection; local development
|
|
11
|
+
// may explicitly use AIFINPAY_ALLOW_PRIVATE_FETCH=1.
|
|
21
12
|
const safeFetch = makeSafeFetch({
|
|
22
13
|
allowPrivate: process.env.AIFINPAY_ALLOW_PRIVATE_FETCH === "1",
|
|
14
|
+
// Exact hosts an operator vouches for. Skips only the DNS pre-check, and only
|
|
15
|
+
// for those names — see safe-fetch.ts for why this is not a proxy switch.
|
|
16
|
+
trustedHosts: loadConfigFromEnv().trustedHosts,
|
|
23
17
|
});
|
|
24
18
|
/**
|
|
25
|
-
*
|
|
19
|
+
* Production-RC MCP surface.
|
|
26
20
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
21
|
+
* IMPORTANT: the old agent_call, payable_fetch, pay_with_split, quote_split,
|
|
22
|
+
* agent_claim_self and agent_quote tools are deliberately NOT registered.
|
|
23
|
+
* They depend on @aifinpay/agent 1.x legacy splitter/x402 semantics and must not
|
|
24
|
+
* be available to a model after the 0% AIFP-2 / gross-inclusive AIFP-1 change.
|
|
29
25
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* This server remains useful before the SDK 2.0 package is published: it can
|
|
27
|
+
* expose the wallet address, resolve Agent Passport identity, read verified
|
|
28
|
+
* v1.3 routes, and construct a non-signing settlement invoice. Signing/moving
|
|
29
|
+
* value returns only after MCP depends on the published v2 executor and its E2E
|
|
30
|
+
* release gate has passed.
|
|
34
31
|
*/
|
|
35
32
|
export async function createServer(config = {}) {
|
|
36
33
|
const log = config.logFn ?? defaultLog;
|
|
37
|
-
// Agent identity: load from env secret if provided, else generate one
|
|
38
|
-
// and print to stderr so the human knows what to fund.
|
|
39
34
|
const agent = config.agentSecretB58
|
|
40
35
|
? await AiFinPayAgent.fromSolanaSecret(config.agentSecretB58, {
|
|
41
36
|
fetchImpl: safeFetch,
|
|
@@ -48,19 +43,6 @@ export async function createServer(config = {}) {
|
|
|
48
43
|
baseUrl: config.baseUrl,
|
|
49
44
|
timeoutMs: config.timeoutMs,
|
|
50
45
|
});
|
|
51
|
-
// NEVER log private key material. stderr is captured by Docker,
|
|
52
|
-
// journald, CI logs, support bundles and monitoring, so printing the
|
|
53
|
-
// secret here leaked it into all of them.
|
|
54
|
-
//
|
|
55
|
-
// The warning below stays, but not for the reason it once gave. That
|
|
56
|
-
// reason — that new() produced an EVM key independent of the Solana
|
|
57
|
-
// one, so no backup could restore both — was fixed: everything now
|
|
58
|
-
// derives from a single random seed. What makes this identity
|
|
59
|
-
// ephemeral is narrower and still true: the seed is never printed and
|
|
60
|
-
// never written anywhere, deliberately, because stderr ends up in
|
|
61
|
-
// Docker, journald, CI logs and support bundles. Recoverable in
|
|
62
|
-
// principle, unrecoverable in practice, so still not an address to
|
|
63
|
-
// fund.
|
|
64
46
|
log("warn", `[aifinpay-mcp] no AIFINPAY_AGENT_SECRET set — generated an EPHEMERAL, NON-RECOVERABLE agent.\n` +
|
|
65
47
|
` solana_address: ${a.solanaAddress}\n` +
|
|
66
48
|
` evm_address: ${a.evmAddress}\n` +
|
|
@@ -69,71 +51,51 @@ export async function createServer(config = {}) {
|
|
|
69
51
|
` (AiFinPayAgent.fromSeed / \`aifinpay init\`) and set AIFINPAY_AGENT_SECRET.`);
|
|
70
52
|
return a;
|
|
71
53
|
})();
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// the cap there too, otherwise the primary tool has NO runaway protection.
|
|
54
|
+
// Keep the legacy agent budget configured even though this RC exposes no
|
|
55
|
+
// signing tool. It remains an additional defence for downstream/private code
|
|
56
|
+
// and for the subsequent v2 MCP executor integration.
|
|
76
57
|
if (config.maxAmountUsd !== undefined && Number.isFinite(config.maxAmountUsd)) {
|
|
77
58
|
agent.setBudget({ per_call_usd: config.maxAmountUsd });
|
|
78
59
|
log("info", `[aifinpay-mcp] per-call cap: $${config.maxAmountUsd} (AIFINPAY_MAX_USD)`);
|
|
79
60
|
}
|
|
80
|
-
|
|
81
|
-
log("warn", "[aifinpay-mcp] AIFINPAY_MAX_USD not set — agent_call/payable_fetch have NO per-payment cap. Strongly recommended.");
|
|
82
|
-
}
|
|
83
|
-
log("info", `[aifinpay-mcp] solana: ${agent.solanaAddress} · evm: ${agent.evmAddress}`);
|
|
61
|
+
log("info", `[aifinpay-mcp] production RC safe surface · solana: ${agent.solanaAddress} · evm: ${agent.evmAddress}`);
|
|
84
62
|
const server = new Server({
|
|
85
63
|
name: "@aifinpay/mcp",
|
|
86
|
-
version: "
|
|
87
|
-
}, {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
agentClaimSelfTool(),
|
|
98
|
-
payableFetchTool(),
|
|
99
|
-
agentQuoteTool(),
|
|
100
|
-
payWithSplitTool(),
|
|
101
|
-
quoteSplitTool(),
|
|
102
|
-
],
|
|
103
|
-
};
|
|
104
|
-
});
|
|
64
|
+
version: "2.0.0-rc.1",
|
|
65
|
+
}, { capabilities: { tools: {} } });
|
|
66
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
67
|
+
tools: [
|
|
68
|
+
agentAddressTool(),
|
|
69
|
+
agentQuotaTool(),
|
|
70
|
+
agentPassportResolveTool(),
|
|
71
|
+
settlementRoutesTool(),
|
|
72
|
+
settlementInvoiceTool(),
|
|
73
|
+
],
|
|
74
|
+
}));
|
|
105
75
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
106
76
|
const { name, arguments: args } = request.params;
|
|
107
77
|
const ctx = { agent, config, log };
|
|
108
78
|
switch (name) {
|
|
109
79
|
case "agent_address":
|
|
110
80
|
return runAgentAddress(ctx, args ?? {});
|
|
111
|
-
case "
|
|
112
|
-
return
|
|
113
|
-
case "
|
|
114
|
-
return
|
|
115
|
-
case "
|
|
116
|
-
return
|
|
117
|
-
case "
|
|
118
|
-
return
|
|
119
|
-
case "pay_with_split":
|
|
120
|
-
return runPayWithSplit(ctx, args ?? {});
|
|
121
|
-
case "quote_split":
|
|
122
|
-
return runQuoteSplit(ctx, args ?? {});
|
|
81
|
+
case "agent_quota":
|
|
82
|
+
return runAgentQuota(ctx, args ?? {});
|
|
83
|
+
case "agent_passport_resolve":
|
|
84
|
+
return runAgentPassportResolve(ctx, args ?? {});
|
|
85
|
+
case "settlement_routes":
|
|
86
|
+
return runSettlementRoutes(ctx, args ?? {});
|
|
87
|
+
case "settlement_invoice":
|
|
88
|
+
return runSettlementInvoice(ctx, args ?? {});
|
|
123
89
|
default:
|
|
124
90
|
return {
|
|
125
91
|
isError: true,
|
|
126
|
-
content: [
|
|
127
|
-
{ type: "text", text: `unknown tool: ${name}` },
|
|
128
|
-
],
|
|
92
|
+
content: [{ type: "text", text: `unknown or retired tool: ${name}` }],
|
|
129
93
|
};
|
|
130
94
|
}
|
|
131
95
|
});
|
|
132
96
|
return { server, agent };
|
|
133
97
|
}
|
|
134
98
|
function defaultLog(level, msg) {
|
|
135
|
-
// MCP stdio servers MUST NOT write to stdout — the transport owns it.
|
|
136
|
-
// stderr is safe.
|
|
137
99
|
process.stderr.write(`[${level}] ${msg}\n`);
|
|
138
100
|
}
|
|
139
101
|
//# sourceMappingURL=server.js.map
|