@aifinpay/mcp 2.0.0-rc.5 → 2.2.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 +161 -69
- 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 +17 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +66 -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/payment-state.d.ts +35 -0
- package/dist/payment-state.d.ts.map +1 -0
- package/dist/payment-state.js +127 -0
- package/dist/payment-state.js.map +1 -0
- 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 +5 -15
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +127 -38
- 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/agent-history.d.ts +58 -0
- 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/payable-fetch.d.ts +3 -29
- package/dist/tools/payable-fetch.d.ts.map +1 -1
- package/dist/tools/payable-fetch.js +231 -109
- package/dist/tools/payable-fetch.js.map +1 -1
- 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 +30 -7
- package/skills/SKILL.md +332 -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/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,197 @@
|
|
|
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
|
+
Source candidate **2.2.0**, paired with Node SDK **2.1.0**. The published
|
|
8
|
+
MCP 2.1.0 remains read-only; publish the coordinated dependencies before using
|
|
9
|
+
this candidate through npm. Source builds support owner-enabled native Polygon
|
|
10
|
+
v1.4 purchases with `payable_fetch`. Without payment configuration, the server
|
|
11
|
+
keeps its inspection-only tool inventory.
|
|
12
|
+
|
|
13
|
+
## Enable native paid GET requests
|
|
14
|
+
|
|
15
|
+
Use a persistent wallet created with public `npx @aifinpay/mcp init` or an
|
|
16
|
+
existing configured identity. Keep its passphrase private. The owner must set:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"AIFINPAY_PAYMENTS_ENABLED": "1",
|
|
21
|
+
"AIFINPAY_GATEWAY_ORIGINS": "https://merchant.example",
|
|
22
|
+
"AIFINPAY_GATEWAY_PATH_MODE": "direct",
|
|
23
|
+
"AIFINPAY_MAX_USD": "0.12",
|
|
24
|
+
"AIFINPAY_DAILY_USD": "1.00",
|
|
25
|
+
"AIFINPAY_MAX_GAS_POL": "0.05"
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Limits are examples, not authorization. USD limits cover purchases; gas has its
|
|
30
|
+
own per-transaction POL cap. Origins are exact HTTPS origins authorized by the
|
|
31
|
+
owner. `direct` uses the full path for self-hosted sites; `gateway` uses the
|
|
32
|
+
merchant slug on the hosted gateway. Restart/reconnect the MCP process after
|
|
33
|
+
changing its environment.
|
|
34
|
+
|
|
35
|
+
Call `payable_fetch({"url":"https://merchant.example/api/data"})`. The tool
|
|
36
|
+
uses the original signed v1.4 quote, independent fresh POL/USD pricing, SDK
|
|
37
|
+
runtime/signer verification and owner limits. It persists the prepared transaction
|
|
38
|
+
before sending, verifies the receipt, and reuses the purchased batch. No special
|
|
39
|
+
merchant script, alternate contract or facilitator fallback is used. GET, native
|
|
40
|
+
POL, Polygon live mode only; Amoy payment receipts and stable-token execution are
|
|
41
|
+
not part of this candidate.
|
|
42
|
+
|
|
43
|
+
Private recovery state lives at `AIFINPAY_HOME/payments/<evm-address>/state.json`
|
|
44
|
+
(default home `~/.aifinpay`). State is mode600 in mode700 directories, atomically
|
|
45
|
+
written and fsynced. One operation lock serializes the wallet across processes.
|
|
46
|
+
A retry recovers a pending receipt before allowing another purchase. A process
|
|
47
|
+
crash leaves the lock for owner reconciliation; it is never stolen automatically.
|
|
48
|
+
If a prepared transaction was never broadcast or reverted, reconcile its exact
|
|
49
|
+
hash before clearing anything. Never put raw transactions, receipt JWTs or the
|
|
50
|
+
state file in chat. Unconfirmed payments consume the budget conservatively.
|
|
11
51
|
|
|
12
52
|
## Tools
|
|
13
53
|
|
|
14
|
-
| Tool
|
|
15
|
-
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
|
|
54
|
+
| Tool | Purpose |
|
|
55
|
+
| ------------------------ | -------------------------------------------------------- |
|
|
56
|
+
| `agent_address` | Read the current Solana, EVM and Casper addresses. |
|
|
57
|
+
| `agent_reload` | Reload local wallet files in the current MCP connection. |
|
|
58
|
+
| `agent_quota` | Read the agent's quota. |
|
|
59
|
+
| `agent_passport_resolve` | Resolve the global Agent Passport identity. |
|
|
60
|
+
| `settlement_routes` | Read the available verified settlement routes. |
|
|
61
|
+
| `settlement_invoice` | Prepare a non-signing settlement invoice. |
|
|
62
|
+
|
|
63
|
+
`payable_fetch` appears only with valid owner payment configuration and a persistent wallet.
|
|
64
|
+
Legacy `agent_call`, `agent_quote`, `pay_with_split`, `quote_split` and
|
|
65
|
+
`agent_claim_self` remain unregistered.
|
|
66
|
+
|
|
67
|
+
## Persistent wallet selection
|
|
68
|
+
|
|
69
|
+
The CLI (`init` and stdio), programmatic server and `agent_reload` use the same
|
|
70
|
+
priority:
|
|
71
|
+
|
|
72
|
+
1. `SEED_HASH`: an existing 32-byte seed encoded as 64 hex characters, optionally
|
|
73
|
+
prefixed with `0x`. It is passed directly to `AiFinPayAgent.fromSeed`; do not
|
|
74
|
+
hash it again or supply a mnemonic.
|
|
75
|
+
2. `./aifinpay/agents.json`, relative to the MCP process's working directory.
|
|
76
|
+
Set an absolute `AIFINPAY_AGENTS_FILE` when a desktop client's working
|
|
77
|
+
directory differs from the project directory. The file contains an `agents`
|
|
78
|
+
array of records with `id` and private `seed_hash` fields. If there is more
|
|
79
|
+
than one record, select exactly one with `AIFINPAY_AGENT_ID`.
|
|
80
|
+
3. `AIFINPAY_AGENT_SECRET`: a legacy base58 Solana secret.
|
|
81
|
+
4. `~/.aifinpay/agent.json`, or `AIFINPAY_HOME/agent.json`: the legacy CLI
|
|
82
|
+
keystore. Encrypted files require `AIFINPAY_WALLET_PASSPHRASE`.
|
|
23
83
|
|
|
24
|
-
|
|
84
|
+
Keep seeds and wallet files private and out of chat, source control and logs.
|
|
85
|
+
Invalid or ambiguous configured inputs fail; they never generate a replacement
|
|
86
|
+
wallet. With no configured wallet at all the server has an ephemeral identity:
|
|
87
|
+
**do not fund it**.
|
|
88
|
+
|
|
89
|
+
## Initialize and connect
|
|
90
|
+
|
|
91
|
+
A quote or invoice is not a completed payment. Enable the owner-configured
|
|
92
|
+
`payable_fetch` tool only after installing the coordinated candidate dependencies.
|
|
93
|
+
|
|
94
|
+
## Local configuration
|
|
25
95
|
|
|
26
96
|
```bash
|
|
27
|
-
|
|
28
|
-
# (installs the latest stable — the old @alpha tag is retired, don't use it)
|
|
29
|
-
npm install -g @aifinpay/mcp
|
|
97
|
+
npx @aifinpay/mcp init
|
|
30
98
|
```
|
|
31
99
|
|
|
32
|
-
|
|
100
|
+
Select the wallet in this order: `SEED_HASH` → `./aifinpay/agents.json` →
|
|
101
|
+
legacy `AIFINPAY_AGENT_SECRET` → `~/.aifinpay/agent.json`.
|
|
102
|
+
SEED_HASH means a 32-byte hex seed, passed directly to SDK `fromSeed`.
|
|
103
|
+
See the skill for the exact project-file schema and multi-agent selection.
|
|
104
|
+
`init` prints the selected wallet's public addresses. If a seed or project wallet
|
|
105
|
+
is already configured, it does not create a second legacy wallet. With no
|
|
106
|
+
wallet, it creates the legacy keystore with mode `600`; existing keystores are
|
|
107
|
+
retained. Back up that file privately. Use the published release or
|
|
108
|
+
build this source checkout.
|
|
33
109
|
|
|
34
|
-
|
|
110
|
+
Use an absolute `AIFINPAY_AGENTS_FILE` path when the host's working directory
|
|
111
|
+
is not your project directory. Do not put the seed itself in a shared config.
|
|
112
|
+
A client configuration can use the keystore without embedding its secret:
|
|
35
113
|
|
|
36
114
|
```json
|
|
37
115
|
{
|
|
38
116
|
"mcpServers": {
|
|
39
117
|
"aifinpay": {
|
|
40
118
|
"command": "npx",
|
|
41
|
-
"args": ["@aifinpay/mcp"],
|
|
119
|
+
"args": ["-y", "@aifinpay/mcp@2.2.0"],
|
|
42
120
|
"env": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
121
|
+
"AIFINPAY_AGENTS_FILE": "/absolute/project/aifinpay/agents.json",
|
|
122
|
+
"AIFINPAY_AGENT_ID": "research-agent"
|
|
45
123
|
}
|
|
46
124
|
}
|
|
47
125
|
}
|
|
48
126
|
}
|
|
49
127
|
```
|
|
50
128
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
If
|
|
57
|
-
|
|
129
|
+
The release above must be published before this npx command can install it.
|
|
130
|
+
After the pinned dependencies are published, source checkouts can run
|
|
131
|
+
`npm ci && npm run build` in mcp/ and configure
|
|
132
|
+
`node /absolute/path/to/mcp/bin/aifinpay-mcp.js` instead.
|
|
133
|
+
|
|
134
|
+
If using the legacy keystore, run `npx @aifinpay/mcp init` once and omit the
|
|
135
|
+
project-file variables. Optional `AIFINPAY_WALLET_PASSPHRASE` encrypts that
|
|
136
|
+
legacy file at creation. An already connected server can load it with
|
|
137
|
+
`agent_reload`; it does not need a new conversation. A changed package or
|
|
138
|
+
launch environment requires a server reconnect, subject to host support.
|
|
139
|
+
|
|
140
|
+
## History
|
|
141
|
+
|
|
142
|
+
Connect the MCP server once. After `init` or a wallet file update, call
|
|
143
|
+
`agent_reload` in that same connection; no new conversation is required. A
|
|
144
|
+
failed reload keeps the previous wallet. Changed shell environment variables
|
|
145
|
+
require reconnecting the MCP process, since its environment is a startup
|
|
146
|
+
snapshot. Verify `agent_address` against the wallet you intend to use.
|
|
147
|
+
|
|
148
|
+
## Other environment variables
|
|
149
|
+
|
|
150
|
+
| Variable | Default | Purpose |
|
|
151
|
+
| ---------------------------- | --------------------- | ------------------------------------------------------------------------------------ |
|
|
152
|
+
| `AIFINPAY_BASE_URL` | `https://aifinpay.io` | Backend URL. |
|
|
153
|
+
| `AIFINPAY_TIMEOUT_MS` | `30000` | Request timeout. |
|
|
154
|
+
| `AIFINPAY_MAX_USD` | — | Configures the underlying agent's per-call cap; it does not enable signing. |
|
|
155
|
+
| `AIFINPAY_GATEWAY_ORIGINS` | SDK default | Comma-separated exact HTTPS origins trusted for AIFP-1 settlement. |
|
|
156
|
+
| `AIFINPAY_GATEWAY_PATH_MODE` | `gateway` | Use `gateway` for merchant-slug identity or `direct` for full request-path identity. |
|
|
157
|
+
|
|
158
|
+
`agent_history({address, source:"transactions"})` reads indexed Polygon
|
|
159
|
+
settlements; `source:"receipts"` reads retained batches, including test-mode
|
|
160
|
+
payments. Pass `passport` instead of, or alongside, `address` to resolve a
|
|
161
|
+
public passport first. A backend without the passport resolver cannot satisfy
|
|
162
|
+
passport-only queries. No secret API key is needed for public metadata.
|
|
163
|
+
|
|
164
|
+
Responses do not include bearer receipts. Limits are 1..100; follow
|
|
165
|
+
`next_offset`. History source/coverage is explicit: it is not a full wallet
|
|
166
|
+
explorer. Dev networks currently use receipt history, not the Polygon ledger.
|
|
58
167
|
|
|
59
168
|
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
secret: 4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
63
|
-
>> Save this secret to AIFINPAY_AGENT_SECRET to keep the agent across restarts.
|
|
169
|
+
const { server } = await createServer(loadConfigFromEnv());
|
|
170
|
+
await server.connect(new StdioServerTransport());
|
|
64
171
|
```
|
|
65
172
|
|
|
66
|
-
|
|
67
|
-
|
|
173
|
+
The programmatic equivalents of identity environment options are `seedHash`,
|
|
174
|
+
`agentsFile`, `agentId`, `agentSecretB58`, `walletHome` and `walletPassphrase`.
|
|
68
175
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|---|---|---|
|
|
73
|
-
| `AIFINPAY_AGENT_SECRET` | — | Base58 secret. If absent → ephemeral agent printed to stderr. |
|
|
74
|
-
| `AIFINPAY_BASE_URL` | `https://aifinpay.io` | Backend URL for nonce + funding probes. |
|
|
75
|
-
| `AIFINPAY_TIMEOUT_MS` | `30000` | Request timeout. |
|
|
76
|
-
| `AIFINPAY_MAX_USD` | — | Mandatory positive finite operator ceiling for any fund-moving tool. |
|
|
77
|
-
|
|
78
|
-
## Programmatic use
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
import { createServer, loadConfigFromEnv } from "@aifinpay/mcp";
|
|
82
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
83
|
-
|
|
84
|
-
const { server } = await createServer({
|
|
85
|
-
...loadConfigFromEnv(),
|
|
86
|
-
agentSecretB58: "your-secret-here",
|
|
87
|
-
maxAmountUsd: 0.10,
|
|
88
|
-
});
|
|
89
|
-
await server.connect(new StdioServerTransport());
|
|
90
|
-
```
|
|
176
|
+
- AIFINPAY_BASE_URL: backend origin; default https://aifinpay.io.
|
|
177
|
+
- AIFINPAY_HOME: directory of the legacy agent.json keystore.
|
|
178
|
+
- AIFINPAY_MODE=dev: expose dev_payment_quote; requires a separate dev base URL.
|
|
91
179
|
|
|
92
|
-
|
|
180
|
+
- AIFINPAY_TIMEOUT_MS: SDK request timeout.
|
|
181
|
+
- AIFINPAY_MAX_USD: legacy per-call budget; does not enable signing.
|
|
182
|
+
- AIFINPAY_TRUSTED_HOSTS: exact hosts allowed to bypass the DNS pre-check.
|
|
183
|
+
- AIFINPAY_ALLOW_PRIVATE_FETCH=1: explicit local-development network access.
|
|
184
|
+
Never enable this on the public hosted MCP.
|
|
93
185
|
|
|
94
|
-
1
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
186
|
+
For a self-hosted AIFP-1 gateway, set `AIFINPAY_GATEWAY_ORIGINS` to exact HTTPS
|
|
187
|
+
origins. `AIFINPAY_GATEWAY_PATH_MODE=direct` makes the process use each request's
|
|
188
|
+
full pathname as its AIFP-1 resource identity; `gateway` uses the merchant slug.
|
|
189
|
+
The mode is process-wide, does not broaden the trusted-origin allowlist, and
|
|
190
|
+
does not enable the retired signing tools in the production RC.
|
|
99
191
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
192
|
+
Dev quoting does not bypass wallet signatures, issuer verification or
|
|
193
|
+
receipt metering. See the bundled skill for backend prerequisites. The low-level SDK has an Amoy
|
|
194
|
+
executor; this MCP candidate does not expose Amoy paid receipt purchases.
|
|
103
195
|
|
|
104
196
|
## License
|
|
105
197
|
|
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,12 +1,23 @@
|
|
|
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. */
|
|
9
16
|
timeoutMs?: number;
|
|
17
|
+
/** Owner opt-in; never accepted from tool arguments. */
|
|
18
|
+
paymentsEnabled?: boolean;
|
|
19
|
+
dailyAmountUsd?: number;
|
|
20
|
+
maxGasPol?: string;
|
|
10
21
|
/** Hard cap on a single payment to prevent runaway agents. */
|
|
11
22
|
maxAmountUsd?: number;
|
|
12
23
|
/** Gateway origins this agent may settle against.
|
|
@@ -22,6 +33,8 @@ export interface McpConfig {
|
|
|
22
33
|
* unrecognised host means paying whoever answered. Naming a host here is a
|
|
23
34
|
* statement that you know who that is. */
|
|
24
35
|
gatewayOrigins?: string[];
|
|
36
|
+
/** AIFP-1 resource identity: merchant slug or full direct request path. */
|
|
37
|
+
gatewayPathMode?: "gateway" | "direct";
|
|
25
38
|
/** Hosts whose DNS pre-check is skipped, exact names only.
|
|
26
39
|
*
|
|
27
40
|
* safe-fetch resolves a hostname and refuses if any answer is a private
|
|
@@ -37,4 +50,6 @@ export interface McpConfig {
|
|
|
37
50
|
logFn?: (level: "info" | "warn" | "error", msg: string) => void;
|
|
38
51
|
}
|
|
39
52
|
export declare function loadConfigFromEnv(): McpConfig;
|
|
53
|
+
/** Signing is available only after the owner configures all spending boundaries. */
|
|
54
|
+
export declare function validatePaymentConfig(config: McpConfig): bigint;
|
|
40
55
|
//# sourceMappingURL=config.d.ts.map
|
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,wDAAwD;IACxD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,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,CAyB7C;AA0DD,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CA+B/D"}
|