@aifinpay/mcp 2.0.0-rc.5 → 2.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/LICENSE +21 -0
- package/README.md +117 -64
- package/bin/aifinpay-mcp.js +115 -67
- package/dist/api.d.ts +2 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +11 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -11
- package/dist/config.js.map +1 -1
- package/dist/identity.d.ts +9 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +81 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -6
- package/dist/index.js.map +1 -1
- package/dist/safe-fetch.d.ts.map +1 -1
- package/dist/safe-fetch.js +73 -19
- package/dist/safe-fetch.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +108 -22
- package/dist/server.js.map +1 -1
- package/dist/tools/agent-address.d.ts.map +1 -1
- package/dist/tools/agent-address.js +5 -4
- package/dist/tools/agent-address.js.map +1 -1
- package/dist/tools/{payable-fetch.d.ts → agent-history.d.ts} +21 -23
- package/dist/tools/agent-history.d.ts.map +1 -0
- package/dist/tools/agent-history.js +141 -0
- package/dist/tools/agent-history.js.map +1 -0
- package/dist/tools/agent-quota.d.ts.map +1 -1
- package/dist/tools/agent-quota.js +3 -1
- package/dist/tools/agent-quota.js.map +1 -1
- package/dist/tools/{agent-quote.d.ts → dev-payment-quote.d.ts} +10 -12
- package/dist/tools/dev-payment-quote.d.ts.map +1 -0
- package/dist/tools/dev-payment-quote.js +80 -0
- package/dist/tools/dev-payment-quote.js.map +1 -0
- package/dist/tools/production-control.d.ts +104 -0
- package/dist/tools/production-control.d.ts.map +1 -1
- package/dist/tools/production-control.js +177 -14
- package/dist/tools/production-control.js.map +1 -1
- package/dist/tools/supported-chains.d.ts +38 -0
- package/dist/tools/supported-chains.d.ts.map +1 -0
- package/dist/tools/supported-chains.js +132 -0
- package/dist/tools/supported-chains.js.map +1 -0
- package/package.json +28 -6
- package/skills/SKILL.md +271 -0
- package/smithery.yaml +137 -0
- package/dist/directory.d.ts +0 -55
- package/dist/directory.d.ts.map +0 -1
- package/dist/directory.js +0 -56
- package/dist/directory.js.map +0 -1
- package/dist/tools/agent-call.d.ts +0 -68
- package/dist/tools/agent-call.d.ts.map +0 -1
- package/dist/tools/agent-call.js +0 -129
- package/dist/tools/agent-call.js.map +0 -1
- package/dist/tools/agent-claim-self.d.ts +0 -66
- package/dist/tools/agent-claim-self.d.ts.map +0 -1
- package/dist/tools/agent-claim-self.js +0 -299
- package/dist/tools/agent-claim-self.js.map +0 -1
- package/dist/tools/agent-quote.d.ts.map +0 -1
- package/dist/tools/agent-quote.js +0 -81
- package/dist/tools/agent-quote.js.map +0 -1
- package/dist/tools/pay-with-split.d.ts +0 -93
- package/dist/tools/pay-with-split.d.ts.map +0 -1
- package/dist/tools/pay-with-split.js +0 -138
- package/dist/tools/pay-with-split.js.map +0 -1
- package/dist/tools/payable-fetch.d.ts.map +0 -1
- package/dist/tools/payable-fetch.js +0 -135
- package/dist/tools/payable-fetch.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AiFinPay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,105 +1,158 @@
|
|
|
1
1
|
# @aifinpay/mcp
|
|
2
2
|
|
|
3
|
-
MCP server
|
|
4
|
-
|
|
3
|
+
AiFinPay MCP server for persistent agent identity, Agent Passport resolution,
|
|
4
|
+
route discovery and non-signing settlement invoices. Canonical domain:
|
|
5
|
+
**aifinpay.io**.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Stable `2.0.0` release. This release does not expose payment-signing tools. Returning a
|
|
8
|
+
wallet address or preparing an invoice does not authorize or execute a payment.
|
|
9
|
+
Signing remains gated on the verified SDK v2 executor and its release evidence.
|
|
10
|
+
Local MCP tools for wallet discovery, payment history, quotas and non-signing
|
|
11
|
+
payment preparation. Read the bundled [skill](skills/SKILL.md), also exposed
|
|
12
|
+
as the MCP resource `aifinpay://skill`.
|
|
11
13
|
|
|
12
14
|
## Tools
|
|
13
15
|
|
|
14
|
-
| Tool
|
|
15
|
-
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
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. |
|
|
16
|
+
| Tool | Purpose |
|
|
17
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
18
|
+
| `agent_address` | Read the current Solana, EVM and Casper addresses. |
|
|
19
|
+
| `agent_reload` | Reload local wallet files in the current MCP connection. |
|
|
20
|
+
| `agent_quota` | Read the agent's quota. |
|
|
21
|
+
| `agent_passport_resolve` | Resolve the global Agent Passport identity. |
|
|
22
|
+
| `settlement_routes` | Read the available verified settlement routes. |
|
|
23
|
+
| `settlement_invoice` | Prepare a non-signing settlement invoice. |
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
Legacy `payable_fetch`, `agent_call`, `agent_quote`, `pay_with_split`,
|
|
26
|
+
`quote_split` and `agent_claim_self` tools are not registered by this RC.
|
|
27
|
+
|
|
28
|
+
## Persistent wallet selection
|
|
29
|
+
|
|
30
|
+
The CLI (`init` and stdio), programmatic server and `agent_reload` use the same
|
|
31
|
+
priority:
|
|
32
|
+
|
|
33
|
+
1. `SEED_HASH`: an existing 32-byte seed encoded as 64 hex characters, optionally
|
|
34
|
+
prefixed with `0x`. It is passed directly to `AiFinPayAgent.fromSeed`; do not
|
|
35
|
+
hash it again or supply a mnemonic.
|
|
36
|
+
2. `./aifinpay/agents.json`, relative to the MCP process's working directory.
|
|
37
|
+
Set an absolute `AIFINPAY_AGENTS_FILE` when a desktop client's working
|
|
38
|
+
directory differs from the project directory. The file contains an `agents`
|
|
39
|
+
array of records with `id` and private `seed_hash` fields. If there is more
|
|
40
|
+
than one record, select exactly one with `AIFINPAY_AGENT_ID`.
|
|
41
|
+
3. `AIFINPAY_AGENT_SECRET`: a legacy base58 Solana secret.
|
|
42
|
+
4. `~/.aifinpay/agent.json`, or `AIFINPAY_HOME/agent.json`: the legacy CLI
|
|
43
|
+
keystore. Encrypted files require `AIFINPAY_WALLET_PASSPHRASE`.
|
|
44
|
+
|
|
45
|
+
Keep seeds and wallet files private and out of chat, source control and logs.
|
|
46
|
+
Invalid or ambiguous configured inputs fail; they never generate a replacement
|
|
47
|
+
wallet. With no configured wallet at all the server has an ephemeral identity:
|
|
48
|
+
**do not fund it**.
|
|
49
|
+
|
|
50
|
+
## Initialize and connect
|
|
51
|
+
|
|
52
|
+
This release does not register payment-signing tools. Its legacy SDK dependency
|
|
53
|
+
supplies wallet derivation only; installing this MCP does not enable v2
|
|
54
|
+
settlement. An invoice or quote is not a completed payment.
|
|
55
|
+
|
|
56
|
+
## Local configuration
|
|
25
57
|
|
|
26
58
|
```bash
|
|
27
|
-
|
|
28
|
-
# (installs the latest stable — the old @alpha tag is retired, don't use it)
|
|
29
|
-
npm install -g @aifinpay/mcp
|
|
59
|
+
npx @aifinpay/mcp init
|
|
30
60
|
```
|
|
31
61
|
|
|
32
|
-
|
|
62
|
+
Select the wallet in this order: `SEED_HASH` → `./aifinpay/agents.json` →
|
|
63
|
+
legacy `AIFINPAY_AGENT_SECRET` → `~/.aifinpay/agent.json`.
|
|
64
|
+
SEED_HASH means a 32-byte hex seed, passed directly to SDK `fromSeed`.
|
|
65
|
+
See the skill for the exact project-file schema and multi-agent selection.
|
|
66
|
+
`init` prints the selected wallet's public addresses. If a seed or project wallet
|
|
67
|
+
is already configured, it does not create a second legacy wallet. With no
|
|
68
|
+
wallet, it creates the legacy keystore with mode `600`; existing keystores are
|
|
69
|
+
retained. Back up that file privately. Use the published `2.0.0` release or
|
|
70
|
+
build this source checkout.
|
|
33
71
|
|
|
34
|
-
|
|
72
|
+
Use an absolute `AIFINPAY_AGENTS_FILE` path when the host's working directory
|
|
73
|
+
is not your project directory. Do not put the seed itself in a shared config.
|
|
74
|
+
A client configuration can use the keystore without embedding its secret:
|
|
35
75
|
|
|
36
76
|
```json
|
|
37
77
|
{
|
|
38
78
|
"mcpServers": {
|
|
39
79
|
"aifinpay": {
|
|
40
80
|
"command": "npx",
|
|
41
|
-
"args": ["@aifinpay/mcp"],
|
|
81
|
+
"args": ["-y", "@aifinpay/mcp@2.0.0"],
|
|
42
82
|
"env": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
83
|
+
"AIFINPAY_AGENTS_FILE": "/absolute/project/aifinpay/agents.json",
|
|
84
|
+
"AIFINPAY_AGENT_ID": "research-agent"
|
|
45
85
|
}
|
|
46
86
|
}
|
|
47
87
|
}
|
|
48
88
|
}
|
|
49
89
|
```
|
|
50
90
|
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
The release above must be published before this npx command can install it.
|
|
92
|
+
For a source checkout, run `npm ci && npm run build` in mcp/ and configure
|
|
93
|
+
`node /absolute/path/to/mcp/bin/aifinpay-mcp.js` instead.
|
|
53
94
|
|
|
54
|
-
|
|
95
|
+
If using the legacy keystore, run `npx @aifinpay/mcp init` once and omit the
|
|
96
|
+
project-file variables. Optional `AIFINPAY_WALLET_PASSPHRASE` encrypts that
|
|
97
|
+
legacy file at creation. An already connected server can load it with
|
|
98
|
+
`agent_reload`; it does not need a new conversation. A changed package or
|
|
99
|
+
launch environment requires a server reconnect, subject to host support.
|
|
55
100
|
|
|
56
|
-
|
|
57
|
-
keypair and **prints it to stderr** at startup:
|
|
101
|
+
## History
|
|
58
102
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Save the secret to `AIFINPAY_AGENT_SECRET` in your client config so the
|
|
67
|
-
agent identity (and any funded Seat) persists across restarts.
|
|
103
|
+
Connect the MCP server once. After `init` or a wallet file update, call
|
|
104
|
+
`agent_reload` in that same connection; no new conversation is required. A
|
|
105
|
+
failed reload keeps the previous wallet. Changed shell environment variables
|
|
106
|
+
require reconnecting the MCP process, since its environment is a startup
|
|
107
|
+
snapshot. Verify `agent_address` against the wallet you intend to use.
|
|
68
108
|
|
|
69
|
-
##
|
|
109
|
+
## Other environment variables
|
|
70
110
|
|
|
71
|
-
|
|
|
72
|
-
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
111
|
+
| Variable | Default | Purpose |
|
|
112
|
+
| ---------------------------- | --------------------- | ------------------------------------------------------------------------------------ |
|
|
113
|
+
| `AIFINPAY_BASE_URL` | `https://aifinpay.io` | Backend URL. |
|
|
114
|
+
| `AIFINPAY_TIMEOUT_MS` | `30000` | Request timeout. |
|
|
115
|
+
| `AIFINPAY_MAX_USD` | — | Configures the underlying agent's per-call cap; it does not enable signing. |
|
|
116
|
+
| `AIFINPAY_GATEWAY_ORIGINS` | SDK default | Comma-separated exact HTTPS origins trusted for AIFP-1 settlement. |
|
|
117
|
+
| `AIFINPAY_GATEWAY_PATH_MODE` | `gateway` | Use `gateway` for merchant-slug identity or `direct` for full request-path identity. |
|
|
77
118
|
|
|
78
|
-
|
|
119
|
+
`agent_history({address, source:"transactions"})` reads indexed Polygon
|
|
120
|
+
settlements; `source:"receipts"` reads retained batches, including test-mode
|
|
121
|
+
payments. Pass `passport` instead of, or alongside, `address` to resolve a
|
|
122
|
+
public passport first. A backend without the passport resolver cannot satisfy
|
|
123
|
+
passport-only queries. No secret API key is needed for public metadata.
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
Responses do not include bearer receipts. Limits are 1..100; follow
|
|
126
|
+
`next_offset`. History source/coverage is explicit: it is not a full wallet
|
|
127
|
+
explorer. Dev networks currently use receipt history, not the Polygon ledger.
|
|
83
128
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
agentSecretB58: "your-secret-here",
|
|
87
|
-
maxAmountUsd: 0.10,
|
|
88
|
-
});
|
|
129
|
+
```
|
|
130
|
+
const { server } = await createServer(loadConfigFromEnv());
|
|
89
131
|
await server.connect(new StdioServerTransport());
|
|
90
132
|
```
|
|
91
133
|
|
|
92
|
-
|
|
134
|
+
The programmatic equivalents of identity environment options are `seedHash`,
|
|
135
|
+
`agentsFile`, `agentId`, `agentSecretB58`, `walletHome` and `walletPassphrase`.
|
|
136
|
+
|
|
137
|
+
- AIFINPAY_BASE_URL: backend origin; default https://aifinpay.io.
|
|
138
|
+
- AIFINPAY_HOME: directory of the legacy agent.json keystore.
|
|
139
|
+
- AIFINPAY_MODE=dev: expose dev_payment_quote; requires a separate dev base URL.
|
|
140
|
+
|
|
141
|
+
- AIFINPAY_TIMEOUT_MS: SDK request timeout.
|
|
142
|
+
- AIFINPAY_MAX_USD: legacy per-call budget; does not enable signing.
|
|
143
|
+
- AIFINPAY_TRUSTED_HOSTS: exact hosts allowed to bypass the DNS pre-check.
|
|
144
|
+
- AIFINPAY_ALLOW_PRIVATE_FETCH=1: explicit local-development network access.
|
|
145
|
+
Never enable this on the public hosted MCP.
|
|
93
146
|
|
|
94
|
-
1
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
147
|
+
For a self-hosted AIFP-1 gateway, set `AIFINPAY_GATEWAY_ORIGINS` to exact HTTPS
|
|
148
|
+
origins. `AIFINPAY_GATEWAY_PATH_MODE=direct` makes the process use each request's
|
|
149
|
+
full pathname as its AIFP-1 resource identity; `gateway` uses the merchant slug.
|
|
150
|
+
The mode is process-wide, does not broaden the trusted-origin allowlist, and
|
|
151
|
+
does not enable the retired signing tools in the production RC.
|
|
99
152
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
153
|
+
Dev quoting does not bypass wallet signatures, issuer verification or
|
|
154
|
+
receipt metering. See the bundled skill for backend prerequisites and the
|
|
155
|
+
remaining Amoy executor requirement.
|
|
103
156
|
|
|
104
157
|
## License
|
|
105
158
|
|
package/bin/aifinpay-mcp.js
CHANGED
|
@@ -22,12 +22,25 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Configure via env (all optional; the secret is read from the keystore below
|
|
24
24
|
* when the variable is unset):
|
|
25
|
-
*
|
|
25
|
+
* SEED_HASH 32-byte hex seed — highest priority
|
|
26
|
+
* AIFINPAY_AGENTS_FILE project agents file — second priority
|
|
27
|
+
* AIFINPAY_AGENT_SECRET legacy base58 secret — overrides the legacy keystore
|
|
26
28
|
* AIFINPAY_BASE_URL default https://aifinpay.io
|
|
27
29
|
* AIFINPAY_TIMEOUT_MS default 30000
|
|
28
30
|
* AIFINPAY_MAX_USD hard cap per single payment (no default)
|
|
29
31
|
*/
|
|
30
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
readFileSync,
|
|
34
|
+
writeFileSync,
|
|
35
|
+
mkdirSync,
|
|
36
|
+
existsSync,
|
|
37
|
+
openSync,
|
|
38
|
+
closeSync,
|
|
39
|
+
fchmodSync,
|
|
40
|
+
fsyncSync,
|
|
41
|
+
linkSync,
|
|
42
|
+
unlinkSync,
|
|
43
|
+
} from "node:fs";
|
|
31
44
|
import { join } from "node:path";
|
|
32
45
|
import { homedir } from "node:os";
|
|
33
46
|
import { createRequire } from "node:module";
|
|
@@ -62,7 +75,7 @@ Run init once. It writes ${KEYSTORE} with mode 600 and the server picks
|
|
|
62
75
|
it up automatically — you do not have to put the secret in a config file.
|
|
63
76
|
|
|
64
77
|
Env (all optional):
|
|
65
|
-
AIFINPAY_AGENT_SECRET base58 secret;
|
|
78
|
+
SEED_HASH 32-byte hex seed; highest priority\n AIFINPAY_AGENTS_FILE default ./aifinpay/agents.json; second priority\n AIFINPAY_AGENT_ID select one record when the file has multiple agents\n AIFINPAY_AGENT_SECRET legacy base58 secret; after project wallet sources
|
|
66
79
|
AIFINPAY_MAX_USD hard cap per single payment — set this
|
|
67
80
|
AIFINPAY_BASE_URL default https://aifinpay.io
|
|
68
81
|
AIFINPAY_TIMEOUT_MS default 30000
|
|
@@ -88,27 +101,50 @@ const PASSPHRASE = process.env.AIFINPAY_WALLET_PASSPHRASE || null;
|
|
|
88
101
|
// is unaffected: no passphrase => plaintext, exactly as before. scrypt to
|
|
89
102
|
// stretch, AES-256-GCM so tampering is detected rather than decrypting to junk.
|
|
90
103
|
function encryptSecret(secretB58) {
|
|
91
|
-
const salt = randomBytes(16),
|
|
92
|
-
|
|
104
|
+
const salt = randomBytes(16),
|
|
105
|
+
iv = randomBytes(12);
|
|
106
|
+
const key = scryptSync(PASSPHRASE, salt, 32, {
|
|
107
|
+
N: 1 << 15,
|
|
108
|
+
r: 8,
|
|
109
|
+
p: 1,
|
|
110
|
+
maxmem: 64 * 1024 * 1024,
|
|
111
|
+
});
|
|
93
112
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
94
113
|
const ct = Buffer.concat([cipher.update(secretB58, "utf8"), cipher.final()]);
|
|
95
|
-
return {
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
return {
|
|
115
|
+
enc: "scrypt-aes-256-gcm",
|
|
116
|
+
salt: salt.toString("base64"),
|
|
117
|
+
iv: iv.toString("base64"),
|
|
118
|
+
tag: cipher.getAuthTag().toString("base64"),
|
|
119
|
+
ct: ct.toString("base64"),
|
|
120
|
+
created: new Date().toISOString(),
|
|
121
|
+
};
|
|
98
122
|
}
|
|
99
123
|
function decryptSecret(store) {
|
|
100
124
|
if (!PASSPHRASE) throw new Error(`${KEYSTORE} is encrypted but AIFINPAY_WALLET_PASSPHRASE is not set.`);
|
|
101
|
-
const key = scryptSync(PASSPHRASE, Buffer.from(store.salt, "base64"), 32, {
|
|
125
|
+
const key = scryptSync(PASSPHRASE, Buffer.from(store.salt, "base64"), 32, {
|
|
126
|
+
N: 1 << 15,
|
|
127
|
+
r: 8,
|
|
128
|
+
p: 1,
|
|
129
|
+
maxmem: 64 * 1024 * 1024,
|
|
130
|
+
});
|
|
102
131
|
const dc = createDecipheriv("aes-256-gcm", key, Buffer.from(store.iv, "base64"));
|
|
103
132
|
dc.setAuthTag(Buffer.from(store.tag, "base64"));
|
|
104
|
-
try {
|
|
105
|
-
|
|
133
|
+
try {
|
|
134
|
+
return Buffer.concat([dc.update(Buffer.from(store.ct, "base64")), dc.final()]).toString("utf8");
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`could not decrypt ${KEYSTORE}: wrong AIFINPAY_WALLET_PASSPHRASE or the file was modified.`);
|
|
137
|
+
}
|
|
106
138
|
}
|
|
107
139
|
|
|
108
140
|
function readKeystore() {
|
|
109
141
|
if (!existsSync(KEYSTORE)) return null;
|
|
110
142
|
let parsed;
|
|
111
|
-
try {
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(readFileSync(KEYSTORE, "utf8"));
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
112
148
|
// A decrypt failure THROWS rather than returning null — null reads as "no
|
|
113
149
|
// wallet" and mints a new one, which is how a mistyped passphrase loses a key.
|
|
114
150
|
if (parsed?.enc) return { secretB58: decryptSecret(parsed), created: parsed.created, encrypted: true };
|
|
@@ -118,47 +154,82 @@ function readKeystore() {
|
|
|
118
154
|
if (arg === "init") {
|
|
119
155
|
const { Agent, AiFinPayAgent } = await import("@aifinpay/agent");
|
|
120
156
|
|
|
157
|
+
const { loadConfigFromEnv } = await import("../dist/config.js");
|
|
158
|
+
const { loadWalletIdentity } = await import("../dist/identity.js");
|
|
159
|
+
const selected = loadWalletIdentity(loadConfigFromEnv());
|
|
160
|
+
if (selected && selected.source !== "legacy-keystore") {
|
|
161
|
+
const agent = selected.seedHash
|
|
162
|
+
? await AiFinPayAgent.fromSeed(selected.seedHash)
|
|
163
|
+
: await AiFinPayAgent.fromSolanaSecret(selected.secretB58);
|
|
164
|
+
process.stdout.write(
|
|
165
|
+
`Using ${selected.source}; no replacement wallet created.\n` +
|
|
166
|
+
`EVM ${agent.evmAddress}\nSolana ${agent.solanaAddress}\nCasper ${agent.casperAddress}\n` +
|
|
167
|
+
`Keep your configured seed backed up privately. Call agent_reload in an already connected MCP server.\n`
|
|
168
|
+
);
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
|
|
121
172
|
let store = readKeystore();
|
|
122
|
-
|
|
123
|
-
if (store) {
|
|
124
|
-
// Never silently overwrite. The file is the only copy of a key that may
|
|
125
|
-
// already hold funds; a second `init` that regenerated it would destroy a
|
|
126
|
-
// wallet to save one line of output.
|
|
127
|
-
process.stdout.write(`Existing wallet found at ${KEYSTORE} — keeping it.\n\n`);
|
|
128
|
-
} else {
|
|
173
|
+
let freshlyCreated = false;
|
|
174
|
+
if (!store) {
|
|
129
175
|
mkdirSync(HOME, { recursive: true, mode: 0o700 });
|
|
130
176
|
const secretB58 = Agent.new().secretB58;
|
|
131
|
-
|
|
132
|
-
const onDisk = PASSPHRASE ? encryptSecret(secretB58) :
|
|
133
|
-
|
|
134
|
-
|
|
177
|
+
const candidate = { secretB58, created: new Date().toISOString() };
|
|
178
|
+
const onDisk = PASSPHRASE ? encryptSecret(secretB58) : candidate;
|
|
179
|
+
const temporary = join(HOME, `.agent-init-${process.pid}-${randomBytes(12).toString("hex")}.tmp`);
|
|
180
|
+
// Publish a complete file without replacing an existing wallet. Two init
|
|
181
|
+
// processes may both see no keystore; only one exclusive link can win.
|
|
182
|
+
// A direct exclusive write would expose a partially written file to readers.
|
|
183
|
+
let fd;
|
|
184
|
+
let ownsTemporary = false;
|
|
185
|
+
try {
|
|
186
|
+
fd = openSync(temporary, "wx", 0o600);
|
|
187
|
+
ownsTemporary = true;
|
|
188
|
+
fchmodSync(fd, 0o600);
|
|
189
|
+
writeFileSync(fd, JSON.stringify(onDisk, null, 2) + "\n");
|
|
190
|
+
fsyncSync(fd);
|
|
191
|
+
closeSync(fd);
|
|
192
|
+
fd = undefined;
|
|
193
|
+
try {
|
|
194
|
+
linkSync(temporary, KEYSTORE);
|
|
195
|
+
store = candidate;
|
|
196
|
+
freshlyCreated = true;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error.code !== "EEXIST") throw error;
|
|
199
|
+
store = readKeystore();
|
|
200
|
+
if (!store) throw new Error(`Existing ${KEYSTORE} is unreadable; refusing to replace it.`);
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
if (fd !== undefined) closeSync(fd);
|
|
204
|
+
if (ownsTemporary) unlinkSync(temporary);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (freshlyCreated) {
|
|
135
208
|
process.stdout.write(
|
|
136
209
|
PASSPHRASE
|
|
137
210
|
? `Created ${KEYSTORE} (mode 600, ENCRYPTED). Keep AIFINPAY_WALLET_PASSPHRASE — the wallet is unrecoverable without it.\n\n`
|
|
138
|
-
: `Created ${KEYSTORE} (mode 600, plaintext). For at-rest encryption, set AIFINPAY_WALLET_PASSPHRASE before init.\n\n
|
|
211
|
+
: `Created ${KEYSTORE} (mode 600, plaintext). For at-rest encryption, set AIFINPAY_WALLET_PASSPHRASE before init.\n\n`
|
|
139
212
|
);
|
|
213
|
+
} else {
|
|
214
|
+
process.stdout.write(`Existing wallet found at ${KEYSTORE} — keeping it.\n\n`);
|
|
140
215
|
}
|
|
141
216
|
|
|
142
217
|
// The one-time recovery output.
|
|
143
218
|
//
|
|
144
|
-
// Shown
|
|
145
|
-
//
|
|
146
|
-
// a key written to CHAT or LOGS, where an LLM provider or a log shipper keeps
|
|
147
|
-
// it forever. This is the deliberate one-time backup prompt every wallet CLI
|
|
148
|
-
// shows, on a channel the user controls. Opposite things: the owner reading
|
|
149
|
-
// their own recovery phrase once, vs a secret leaking into a transcript.
|
|
219
|
+
// Shown only to the successful creator of a plaintext wallet on a TTY.
|
|
220
|
+
// Piped output (including agent subprocess logs) contains public data only.
|
|
150
221
|
//
|
|
151
222
|
// Encrypted keystores print nothing here: recovery there IS the keystore file
|
|
152
223
|
// plus the passphrase, and re-printing the plaintext secret would defeat the
|
|
153
224
|
// encryption the user just asked for.
|
|
154
|
-
if (freshlyCreated && !PASSPHRASE) {
|
|
225
|
+
if (freshlyCreated && !PASSPHRASE && process.stdout.isTTY) {
|
|
155
226
|
process.stdout.write(
|
|
156
227
|
`\n RECOVERY KEY (shown once, never again):\n\n` +
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
228
|
+
` ${store.secretB58}\n\n` +
|
|
229
|
+
` Save this off this machine NOW. Anyone with it controls the wallet and\n` +
|
|
230
|
+
` its funds. Do NOT paste it into a chat, an issue, or a config file — the\n` +
|
|
231
|
+
` keystore at ${KEYSTORE} already holds it (encrypt it with\n` +
|
|
232
|
+
` AIFINPAY_WALLET_PASSPHRASE). This line is your OFF-machine backup.\n\n`
|
|
162
233
|
);
|
|
163
234
|
}
|
|
164
235
|
|
|
@@ -169,26 +240,26 @@ if (arg === "init") {
|
|
|
169
240
|
` EVM ${agent.evmAddress}\n` +
|
|
170
241
|
` Solana ${agent.solanaAddress}\n` +
|
|
171
242
|
` Casper ${agent.casperAddress}\n\n` +
|
|
172
|
-
`Add this to your MCP client config and
|
|
243
|
+
`Add this to your MCP client config and connect/reconnect this MCP server:\n\n` +
|
|
173
244
|
JSON.stringify(
|
|
174
245
|
{
|
|
175
246
|
mcpServers: {
|
|
176
247
|
aifinpay: {
|
|
177
248
|
command: "npx",
|
|
178
|
-
args: ["-y",
|
|
249
|
+
args: ["-y", `@aifinpay/mcp@${VERSION}`],
|
|
179
250
|
env: { AIFINPAY_MAX_USD: "0.10" },
|
|
180
251
|
},
|
|
181
252
|
},
|
|
182
253
|
},
|
|
183
254
|
null,
|
|
184
|
-
2
|
|
255
|
+
2
|
|
185
256
|
) +
|
|
186
257
|
`\n\nThe secret is NOT in that block on purpose — the server reads the\n` +
|
|
187
|
-
`keystore.
|
|
258
|
+
`keystore. If this server is already connected, call agent_reload after init.\nA new conversation is not required by this server.\n\n` +
|
|
188
259
|
`Back up ${KEYSTORE}. It is the only copy. The derivation is not\n` +
|
|
189
260
|
`BIP-39, so no standard wallet can recover this from a phrase.\n\n` +
|
|
190
|
-
`The
|
|
191
|
-
`
|
|
261
|
+
`The EVM address is used for Polygon payments. Check its balance before\n` +
|
|
262
|
+
`funding it or paying for calls.\n`
|
|
192
263
|
);
|
|
193
264
|
process.exit(0);
|
|
194
265
|
}
|
|
@@ -200,31 +271,8 @@ if (arg && !arg.startsWith("-")) {
|
|
|
200
271
|
|
|
201
272
|
// ── server ────────────────────────────────────────────────────────────────
|
|
202
273
|
|
|
203
|
-
//
|
|
204
|
-
// a
|
|
205
|
-
if (!process.env.AIFINPAY_AGENT_SECRET) {
|
|
206
|
-
const store = readKeystore();
|
|
207
|
-
if (store) {
|
|
208
|
-
try {
|
|
209
|
-
const mode = statSync(KEYSTORE).mode & 0o777;
|
|
210
|
-
if (mode & 0o077) {
|
|
211
|
-
process.stderr.write(
|
|
212
|
-
`[warn] [aifinpay-mcp] ${KEYSTORE} is mode ${mode.toString(8)} — readable beyond your user. ` +
|
|
213
|
-
`Run: chmod 600 ${KEYSTORE}\n`,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
} catch {
|
|
217
|
-
/* stat failure is not a reason to refuse to start */
|
|
218
|
-
}
|
|
219
|
-
process.env.AIFINPAY_AGENT_SECRET = store.secretB58;
|
|
220
|
-
} else {
|
|
221
|
-
process.stderr.write(
|
|
222
|
-
`[info] [aifinpay-mcp] no wallet configured. Run \`npx @aifinpay/mcp init\` once ` +
|
|
223
|
-
`for a persistent one; starting with a throwaway identity for now.\n`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
274
|
+
// Identity selection is shared with the programmatic server and agent_reload.
|
|
275
|
+
// Do not copy a file secret into process.env: it would mask later file updates.
|
|
228
276
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
229
277
|
const { createServer, loadConfigFromEnv } = await import("../dist/index.js");
|
|
230
278
|
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD"}
|
package/dist/api.js
ADDED
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,IAAY;IAC/C,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,iBAAiB,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnG,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC;AAC9C,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/** Runtime configuration loaded from env. */
|
|
2
2
|
export interface McpConfig {
|
|
3
|
-
/**
|
|
4
|
-
* generated AND printed to stderr at startup with a "save this!" warning. */
|
|
3
|
+
/** Legacy base58 secret, after SEED_HASH and the project agents file. */
|
|
5
4
|
agentSecretB58?: string;
|
|
5
|
+
/** 32-byte hex seed, consumed exactly as AiFinPayAgent.fromSeed does. */
|
|
6
|
+
seedHash?: string;
|
|
7
|
+
agentsFile?: string;
|
|
8
|
+
agentId?: string;
|
|
9
|
+
walletHome?: string;
|
|
10
|
+
walletPassphrase?: string;
|
|
11
|
+
/** Enables dev discovery/quoting only; never enables settlement signing. */
|
|
12
|
+
devMode?: boolean;
|
|
6
13
|
/** Custom AiFinPay backend URL. Defaults to production. */
|
|
7
14
|
baseUrl?: string;
|
|
8
15
|
/** Request timeout in ms. */
|
|
@@ -22,6 +29,8 @@ export interface McpConfig {
|
|
|
22
29
|
* unrecognised host means paying whoever answered. Naming a host here is a
|
|
23
30
|
* statement that you know who that is. */
|
|
24
31
|
gatewayOrigins?: string[];
|
|
32
|
+
/** AIFP-1 resource identity: merchant slug or full direct request path. */
|
|
33
|
+
gatewayPathMode?: "gateway" | "direct";
|
|
25
34
|
/** Hosts whose DNS pre-check is skipped, exact names only.
|
|
26
35
|
*
|
|
27
36
|
* safe-fetch resolves a hostname and refuses if any answer is a private
|
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;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,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,2EAA2E;IAC3E,eAAe,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAEvC;;;;;;;;;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,CAmB7C"}
|