@venlyfinance/settlement-mcp 0.1.1 → 0.3.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/CHANGELOG.md +89 -0
- package/README.md +98 -31
- package/dist/client/sdk-client.d.ts +67 -0
- package/dist/client/sdk-client.js +183 -0
- package/dist/constants.d.ts +8 -4
- package/dist/constants.js +16 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +15 -7
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +43 -0
- package/dist/reconcile.d.ts +7 -0
- package/dist/reconcile.js +26 -2
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +106 -0
- package/dist/results.d.ts +18 -0
- package/dist/results.js +25 -0
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +28 -4
- package/dist/server.js +15 -1
- package/dist/staging-smoke.d.ts +17 -0
- package/dist/staging-smoke.js +199 -0
- package/dist/tools/read-tools.js +91 -14
- package/dist/tools/write-tools.js +246 -22
- package/dist/types.d.ts +69 -127
- package/dist/types.js +5 -13
- package/package.json +18 -10
- package/scripts/staging-smoke.mjs +13 -0
- package/skills/build-international-account.md +26 -0
- package/skills/mock-to-staging.md +11 -0
- package/dist/client/http-client.d.ts +0 -58
- package/dist/client/http-client.js +0 -163
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 – 2026-08-04
|
|
4
|
+
|
|
5
|
+
Wording-is-the-safety-surface release. An outside integrator audit (Report 1,
|
|
6
|
+
2026-08-04) found the server's words disagreeing with its behavior in three
|
|
7
|
+
places; all fixed, plus the SDK under the mock now teaches the documented
|
|
8
|
+
lifecycle (see @venlyfinance/sdk 0.2.0).
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Default environment is `mock`** (was `staging`). A mock-first product must
|
|
13
|
+
not point at real infrastructure when unconfigured. Set `VENLY_ENV=staging`
|
|
14
|
+
or `production` explicitly for real calls; the safety resource and README
|
|
15
|
+
say so.
|
|
16
|
+
- **State-accurate startup banner.** Mock: "writes execute against local
|
|
17
|
+
fixtures - no network, no credentials, nothing real". Staging/production
|
|
18
|
+
disarmed: "mutations return dry-run previews". Armed: "confirmed writes hit
|
|
19
|
+
the live API". No more "DISARMED" next to a write that visibly executes.
|
|
20
|
+
- **Explicit `dryRun: true|false` on every mutation result** - agents no longer
|
|
21
|
+
infer persistence from `mode`.
|
|
22
|
+
- **`reconcile_by_reference_code` reads real remittance text**: matching is
|
|
23
|
+
case- and separator-insensitive, transactions match by containment
|
|
24
|
+
("invoice ref abc 123 ty" finds REF-ABC-123), and codes under 4 alphanumeric
|
|
25
|
+
characters are refused.
|
|
26
|
+
- **Receiver XOR enforced**: `create_fiat_transfer` / `create_crypto_transfer`
|
|
27
|
+
reject a transfer with zero or two receivers (exactly one of
|
|
28
|
+
`receiverAccountId` / `receiverExternalId`), stated in the tool schema.
|
|
29
|
+
- Requires `@venlyfinance/sdk` ^0.2.0: in mock mode, created parties/accounts
|
|
30
|
+
start verification-pending (`mock.advanceVerification`), transfers start
|
|
31
|
+
`PENDING` (`mock.advanceTransfer`), and request bodies are spec-validated.
|
|
32
|
+
|
|
33
|
+
### Deprecated
|
|
34
|
+
|
|
35
|
+
- **`stage_transfer`** is now plainly marked deprecated (legacy alias of
|
|
36
|
+
`create_fiat_transfer`); it will be removed in 0.4.0.
|
|
37
|
+
|
|
38
|
+
## 0.2.0 – 2026-08-03
|
|
39
|
+
|
|
40
|
+
The Settlement MCP becomes the SDK-backed **Venly Finance MCP** builder while retaining
|
|
41
|
+
its package name and compatibility binary throughout the 0.x line.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- 23 atomic read, write, reconciliation, and quote tools covering the first
|
|
46
|
+
international-account builder journey.
|
|
47
|
+
- Explicit `mock`, `staging`, and `production` environments with synthetic mock writes
|
|
48
|
+
and a separate production-write flag.
|
|
49
|
+
- Discoverable capability, safety, international-account, and mock-to-staging
|
|
50
|
+
resources.
|
|
51
|
+
- The `build_international_account` MCP prompt and matching portable workflow skills.
|
|
52
|
+
- Machine-readable structured tool results with credential and token redaction.
|
|
53
|
+
- `createServer` refuses to start when an injected client declares an environment that
|
|
54
|
+
disagrees with `VENLY_ENV`: the mock gate auto-arms writes, so the two must never
|
|
55
|
+
diverge.
|
|
56
|
+
- A zero-network golden journey covering party, account, wallet, EUR receiving account,
|
|
57
|
+
transfer, history, and reconciliation behavior.
|
|
58
|
+
- Compile-time assertions that MCP payload types remain exact aliases of the generated
|
|
59
|
+
Finance and Fundflow SDK contracts.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Finance and Fundflow execution now uses `@venlyfinance/sdk`; the duplicate HTTP,
|
|
64
|
+
OAuth, retry, and hand-written API type implementation was removed.
|
|
65
|
+
- `venly-finance-mcp` is the preferred binary. `venly-settlement-mcp` remains an alias
|
|
66
|
+
to the same server.
|
|
67
|
+
- The minimum Node.js version is 20.
|
|
68
|
+
- The official MCP SDK is upgraded to 1.30.0 and resolves Hono 2.0.12.
|
|
69
|
+
|
|
70
|
+
### Deprecated
|
|
71
|
+
|
|
72
|
+
- `stage_transfer` remains available for 0.x compatibility but normalizes its legacy
|
|
73
|
+
fields into the current Finance transfer contract. Its dry-run now previews the exact
|
|
74
|
+
normalized request a live call sends, a caller-supplied idempotency key survives
|
|
75
|
+
normalization, and the retired `cryptocurrency` field is rejected with guidance
|
|
76
|
+
instead of silently dropped. New integrations should use `create_fiat_transfer`.
|
|
77
|
+
|
|
78
|
+
### Safety and product boundaries
|
|
79
|
+
|
|
80
|
+
- Live writes remain dry-run unless confirmation, credentials, and the environment
|
|
81
|
+
flags are present; production requires an additional explicit flag.
|
|
82
|
+
- Creating a party does not complete KYC/KYB. Live EUR receiving-account provisioning
|
|
83
|
+
requires an eligible verified account.
|
|
84
|
+
- The MCP does not imply universal bank-account coverage, a bank charter, card issuing,
|
|
85
|
+
external-bank payouts, or autonomous production money movement.
|
|
86
|
+
|
|
87
|
+
### Security
|
|
88
|
+
|
|
89
|
+
- Runtime dependency audits report zero findings after the MCP SDK/Hono upgrade.
|
package/README.md
CHANGED
|
@@ -1,39 +1,63 @@
|
|
|
1
|
-
# Venly
|
|
1
|
+
# Venly Finance MCP
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
x402, the machine-to-machine agent-payments rail.
|
|
3
|
+
Build and operate international money-product experiences over the Venly Finance
|
|
4
|
+
and Fundflow APIs. The server exposes atomic financial tools, contract-backed
|
|
5
|
+
product resources and a `build_international_account` prompt to MCP clients and
|
|
6
|
+
coding agents.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
**Tools expose financial capabilities. Prompts and skills assemble products. The
|
|
9
|
+
host coding agent builds the interface.** This is one expanded implementation of
|
|
10
|
+
the original Settlement MCP, not a second server.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
Start in explicit mock mode with no credentials or network. Move the same SDK
|
|
13
|
+
business logic to staging only after reviewing capabilities, compliance state and
|
|
14
|
+
the normalized write requests. Staging and production writes fail closed.
|
|
15
|
+
|
|
16
|
+
The Venly Finance builder surface documented here is the v0.2.0 release line.
|
|
17
17
|
|
|
18
18
|
## What it is
|
|
19
19
|
|
|
20
20
|
- Built on the official MCP TypeScript SDK (`@modelcontextprotocol/sdk`), Node
|
|
21
|
-
>=
|
|
21
|
+
>= 20, stdio transport.
|
|
22
22
|
- A thin layer over the Venly Finance API (`finance.yaml`, servers
|
|
23
23
|
`https://api.venlyfinance.com/v1`) and the Fundflow API (`fundflow.yaml`,
|
|
24
24
|
servers `https://api-fundflow.venly.io`). Endpoint truth is the published API
|
|
25
25
|
reference at [docs.venlyfinance.com](https://docs.venlyfinance.com/api-reference).
|
|
26
26
|
- Transport is injected through the `VenlyClient` interface (`src/types.ts`).
|
|
27
|
-
`
|
|
27
|
+
`SdkVenlyClient` delegates to the published `@venlyfinance/sdk`; tests inject
|
|
28
|
+
a lightweight mock.
|
|
29
|
+
|
|
30
|
+
### Transport
|
|
31
|
+
|
|
32
|
+
The MCP uses [`@venlyfinance/sdk`](../) for OAuth2 client credentials, token
|
|
33
|
+
caching/refresh, retries, idempotency and normalized API errors. It does not
|
|
34
|
+
maintain a second HTTP or authentication implementation.
|
|
35
|
+
|
|
36
|
+
## Try the builder in mock mode
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"mcpServers": {
|
|
41
|
+
"venly-finance": {
|
|
42
|
+
"command": "npx",
|
|
43
|
+
"args": ["-y", "@venlyfinance/settlement-mcp"],
|
|
44
|
+
"env": { "VENLY_ENV": "mock" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
28
49
|
|
|
29
|
-
|
|
50
|
+
Then select the `build_international_account` prompt or ask the agent to read:
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
- `venly://capabilities`
|
|
53
|
+
- `venly://safety`
|
|
54
|
+
- `venly://workflows/international-account`
|
|
55
|
+
- `venly://workflows/mock-to-staging`
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
Mock mutations execute against SDK fixtures, are labelled `mode: "mock"`, use no
|
|
58
|
+
credentials and make no network requests.
|
|
59
|
+
|
|
60
|
+
## Tool tiers
|
|
37
61
|
|
|
38
62
|
### 1. Read tools (always on)
|
|
39
63
|
|
|
@@ -43,11 +67,16 @@ Call GETs only. No mutation, safe by default.
|
|
|
43
67
|
|---|---|
|
|
44
68
|
| `list_ramp_requests` | fundflow `GET /v1/ramp-requests` |
|
|
45
69
|
| `get_ramp_request` | fundflow `GET /v1/ramp-requests/{id}` |
|
|
70
|
+
| `list_accounts` | finance `GET /accounts` |
|
|
46
71
|
| `get_account` | finance `GET /accounts/{accountId}` |
|
|
72
|
+
| `list_wallets` | finance `GET /accounts/{accountId}/wallets` (including token balances) |
|
|
47
73
|
| `list_virtual_bank_accounts` | finance `GET /accounts/{accountId}/virtual-bank-accounts` |
|
|
74
|
+
| `get_virtual_bank_account` | finance `GET /accounts/{accountId}/virtual-bank-accounts/{id}` |
|
|
48
75
|
| `reconcile_by_reference_code` | composite: lists vIBANs, matches supplied transactions by referenceCode |
|
|
76
|
+
| `list_transfers` | finance `GET /accounts/{accountId}/transfers` |
|
|
49
77
|
| `get_transfer` | finance `GET /accounts/{accountId}/transfers/{transferId}` |
|
|
50
78
|
| `list_parties` | finance `GET /parties` |
|
|
79
|
+
| `get_party` | finance `GET /parties/{partyId}` |
|
|
51
80
|
| `get_reference_data` | fundflow chains / fiat-currencies / crypto-currencies / fees |
|
|
52
81
|
|
|
53
82
|
`reconcile_by_reference_code` is the EUR vIBAN reconciliation: a customer sends
|
|
@@ -58,8 +87,13 @@ matched vIBAN, the matched transactions, and the total amount.
|
|
|
58
87
|
|
|
59
88
|
### 2. Write tools (present, DISARMED by default)
|
|
60
89
|
|
|
61
|
-
|
|
62
|
-
`
|
|
90
|
+
Builder writes: `create_party`, `create_account`,
|
|
91
|
+
`create_virtual_bank_account`, `create_fiat_transfer`,
|
|
92
|
+
`create_crypto_transfer`, `create_payment_session`.
|
|
93
|
+
|
|
94
|
+
Operator writes: `approve_ramp_request`, `reject_ramp_request`. The legacy
|
|
95
|
+
`stage_transfer` name remains as a compatibility tool; new builds use
|
|
96
|
+
`create_fiat_transfer` and its current OpenAPI field names.
|
|
63
97
|
|
|
64
98
|
Each is dry-run by default and returns the exact request it would send. See the
|
|
65
99
|
safety model below.
|
|
@@ -74,8 +108,8 @@ facilitator decision and live rails.
|
|
|
74
108
|
|
|
75
109
|
## Safety model (fail closed)
|
|
76
110
|
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
Outside explicit mock mode, read-only/dry-run is the default posture. A staging
|
|
112
|
+
write executes a live call only when all three hold:
|
|
79
113
|
|
|
80
114
|
1. the tool argument `confirm === true`, and
|
|
81
115
|
2. the environment flag `VENLY_MCP_LIVE === "1"`, and
|
|
@@ -86,6 +120,10 @@ it would have sent and never touches the transport. This is proven by
|
|
|
86
120
|
`test/write-tools.test.ts`, including the critical case: `confirm:true` with
|
|
87
121
|
`VENLY_MCP_LIVE` unset still dry-runs and does not call the live client.
|
|
88
122
|
|
|
123
|
+
Production additionally requires `VENLY_ENV=production` and
|
|
124
|
+
`VENLY_MCP_PRODUCTION=1`. There is no implicit fallback from a live environment
|
|
125
|
+
to mock data.
|
|
126
|
+
|
|
89
127
|
Other invariants:
|
|
90
128
|
|
|
91
129
|
- Credentials are read from env, never logged, never returned in tool output.
|
|
@@ -110,7 +148,7 @@ The fastest path once published to npm - one line in any MCP client config:
|
|
|
110
148
|
```json
|
|
111
149
|
{
|
|
112
150
|
"mcpServers": {
|
|
113
|
-
"venly-
|
|
151
|
+
"venly-finance": {
|
|
114
152
|
"command": "npx",
|
|
115
153
|
"args": ["-y", "@venlyfinance/settlement-mcp"]
|
|
116
154
|
}
|
|
@@ -132,10 +170,11 @@ MCP client config example:
|
|
|
132
170
|
```json
|
|
133
171
|
{
|
|
134
172
|
"mcpServers": {
|
|
135
|
-
"venly-
|
|
173
|
+
"venly-finance": {
|
|
136
174
|
"command": "node",
|
|
137
175
|
"args": ["/absolute/path/to/settlement-mcp/dist/index.js"],
|
|
138
176
|
"env": {
|
|
177
|
+
"VENLY_ENV": "staging",
|
|
139
178
|
"VENLY_FINANCE_BASE_URL": "https://api-staging.venlyfinance.com/v1",
|
|
140
179
|
"VENLY_FUNDFLOW_BASE_URL": "https://api-fundflow-staging.venly.io"
|
|
141
180
|
}
|
|
@@ -151,16 +190,34 @@ Override via env:
|
|
|
151
190
|
|
|
152
191
|
| Env var | Default (staging) |
|
|
153
192
|
|---|---|
|
|
193
|
+
| `VENLY_ENV` | Defaults to `mock` (since 0.3.0): an unconfigured server never points at real infrastructure. Set `staging` or `production` explicitly |
|
|
154
194
|
| `VENLY_FINANCE_BASE_URL` | `https://api-staging.venlyfinance.com/v1` |
|
|
155
195
|
| `VENLY_FUNDFLOW_BASE_URL` | `https://api-fundflow-staging.venly.io` |
|
|
156
196
|
| `VENLY_TOKEN_URL` | `https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token` (staging; use `login.venly.io` for production) |
|
|
157
197
|
| `VENLY_CLIENT_ID` | unset (read-only until set) |
|
|
158
198
|
| `VENLY_CLIENT_SECRET` | unset |
|
|
159
199
|
| `VENLY_MCP_LIVE` | unset (writes disarmed) |
|
|
200
|
+
| `VENLY_MCP_PRODUCTION` | unset (production writes disarmed) |
|
|
160
201
|
|
|
161
202
|
Fundflow also exposes a QA sandbox (`https://api-fundflow-qa.venly.io`). If your
|
|
162
203
|
tenant uses different endpoints, override them via the env vars above.
|
|
163
204
|
|
|
205
|
+
## Safe staging smoke
|
|
206
|
+
|
|
207
|
+
Build the server and verify its complete discovery surface, credentialled staging
|
|
208
|
+
reads, and fail-closed write gate without mutating staging:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
VENLY_CLIENT_ID=... VENLY_CLIENT_SECRET=... npm run smoke:staging
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The command starts the MCP with `VENLY_ENV=staging`, lists the expected 23 tools,
|
|
215
|
+
four resources, and builder prompt, then reads parties, accounts, and reference data.
|
|
216
|
+
It deliberately removes `VENLY_MCP_LIVE` and `VENLY_MCP_PRODUCTION` from the child
|
|
217
|
+
process before submitting one confirmed `create_party` request. Passing requires that
|
|
218
|
+
request to return `mode: dry-run`, `environment: staging`, and an unarmed gate. Output
|
|
219
|
+
contains counts and status only – not party/account payloads, credentials, or tokens.
|
|
220
|
+
|
|
164
221
|
## Enabling live writes (a deliberate operator decision)
|
|
165
222
|
|
|
166
223
|
Live writes are OFF. To arm them, the operator must:
|
|
@@ -172,6 +229,10 @@ Live writes are OFF. To arm them, the operator must:
|
|
|
172
229
|
Until all three are set, every write tool dry-runs. Arming the flag and
|
|
173
230
|
provisioning credentials is a deliberate, human decision, not a default.
|
|
174
231
|
|
|
232
|
+
Production also requires `VENLY_MCP_PRODUCTION=1` on the server. Mock mode does
|
|
233
|
+
not use these flags because it cannot reach the network; results remain clearly
|
|
234
|
+
labelled synthetic.
|
|
235
|
+
|
|
175
236
|
## x402 position
|
|
176
237
|
|
|
177
238
|
The machine-to-machine agent-payments rail is consolidating on x402 (Cloudflare
|
|
@@ -190,6 +251,8 @@ MCP-consumable workflow docs under `skills/`:
|
|
|
190
251
|
- `stage-and-confirm-transfer.md`
|
|
191
252
|
- `payment-session-lifecycle.md`
|
|
192
253
|
- `x402-quote-walkthrough.md`
|
|
254
|
+
- `build-international-account.md`
|
|
255
|
+
- `mock-to-staging.md`
|
|
193
256
|
|
|
194
257
|
## Layout
|
|
195
258
|
|
|
@@ -202,11 +265,15 @@ settlement-mcp/
|
|
|
202
265
|
index.ts entry, stdio transport
|
|
203
266
|
server.ts createServer(client, env), registers all tiers
|
|
204
267
|
constants.ts
|
|
205
|
-
types.ts
|
|
268
|
+
types.ts Generated SDK aliases + MCP-owned compatibility types
|
|
206
269
|
safety.ts the write-gate (confirm + env + creds)
|
|
207
270
|
reconcile.ts pure reconciliation logic
|
|
271
|
+
resources.ts capability, safety and workflow resources
|
|
272
|
+
prompts.ts build_international_account prompt
|
|
273
|
+
results.ts text + structured output and error redaction
|
|
274
|
+
staging-smoke.ts safe discovery/read/dry-run staging verification
|
|
208
275
|
client/
|
|
209
|
-
|
|
276
|
+
sdk-client.ts Adapter over @venlyfinance/sdk
|
|
210
277
|
tools/
|
|
211
278
|
read-tools.ts tier 1
|
|
212
279
|
write-tools.ts tier 2 (fail closed)
|
|
@@ -217,8 +284,8 @@ settlement-mcp/
|
|
|
217
284
|
stage-and-confirm-transfer.md
|
|
218
285
|
payment-session-lifecycle.md
|
|
219
286
|
x402-quote-walkthrough.md
|
|
220
|
-
|
|
221
|
-
|
|
287
|
+
build-international-account.md
|
|
288
|
+
mock-to-staging.md
|
|
222
289
|
test/
|
|
223
290
|
helpers.ts in-memory MCP harness + mock client
|
|
224
291
|
read-tools.test.ts
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FundflowClient, VenlyFinanceClient, type MockCall } from "@venlyfinance/sdk";
|
|
2
|
+
import { type VenlyEnvironment } from "../constants.js";
|
|
3
|
+
import type { Account, CryptoCurrency, CreateAccountInput, CreateCryptoTransferInput, CreateFiatTransferInput, CreatePartyInput, CreatePayInSessionRequest, CreateVirtualBankAccountInput, CurrentCreateFiatTransferInput, FiatCurrency, ListRampRequestsParams, OptimisticLockingBody, Party, PaymentSession, RampRequestDto, RampRequestListItem, SupportedChains, Transfer, VenlyClient, VirtualBankAccount, VenlyFee, Wallet } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Normalize the legacy stage_transfer input to the current Finance
|
|
6
|
+
* CreateFiatTransferInput wire shape. Exported so the write tool can show the
|
|
7
|
+
* exact normalized request in its dry-run preview – the preview and the live
|
|
8
|
+
* call must never diverge. The retired `cryptocurrency` field is rejected
|
|
9
|
+
* rather than silently dropped: the current contract resolves the fiat amount
|
|
10
|
+
* to the account's settlement asset itself.
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeLegacyFiatTransfer(body: CreateFiatTransferInput): CurrentCreateFiatTransferInput;
|
|
13
|
+
/**
|
|
14
|
+
* Adapter from the generated Venly Finance SDK surface to the stable MCP
|
|
15
|
+
* client contract. This keeps tools transport-agnostic while ensuring auth,
|
|
16
|
+
* retries, idempotency and endpoint schemas come from the published SDK.
|
|
17
|
+
*/
|
|
18
|
+
export declare class SdkVenlyClient implements VenlyClient {
|
|
19
|
+
private readonly finance;
|
|
20
|
+
private readonly fundflow;
|
|
21
|
+
readonly environment: VenlyEnvironment;
|
|
22
|
+
private readonly hasCredentials;
|
|
23
|
+
constructor(finance: VenlyFinanceClient, fundflow: FundflowClient, environment: VenlyEnvironment, hasCredentials?: boolean);
|
|
24
|
+
static mock(): SdkVenlyClient;
|
|
25
|
+
static fromEnv(env: Record<string, string | undefined>): SdkVenlyClient;
|
|
26
|
+
/** Exposed for deterministic zero-network adapter and journey tests. */
|
|
27
|
+
get financeMockCalls(): readonly MockCall[];
|
|
28
|
+
/** Exposed for deterministic zero-network adapter and journey tests. */
|
|
29
|
+
get fundflowMockCalls(): readonly MockCall[];
|
|
30
|
+
private assertReady;
|
|
31
|
+
listRampRequests(params?: ListRampRequestsParams): Promise<RampRequestListItem[]>;
|
|
32
|
+
getRampRequest(id: string): Promise<RampRequestDto>;
|
|
33
|
+
getAccount(accountId: string): Promise<Account>;
|
|
34
|
+
listAccounts(params?: {
|
|
35
|
+
page?: number;
|
|
36
|
+
size?: number;
|
|
37
|
+
}): Promise<Account[]>;
|
|
38
|
+
listWallets(accountId: string, params?: {
|
|
39
|
+
page?: number;
|
|
40
|
+
size?: number;
|
|
41
|
+
}): Promise<Wallet[]>;
|
|
42
|
+
listVirtualBankAccounts(accountId: string): Promise<VirtualBankAccount[]>;
|
|
43
|
+
getVirtualBankAccount(accountId: string, virtualBankAccountId: string): Promise<VirtualBankAccount>;
|
|
44
|
+
listTransfers(accountId: string, params?: {
|
|
45
|
+
page?: number;
|
|
46
|
+
size?: number;
|
|
47
|
+
}): Promise<Transfer[]>;
|
|
48
|
+
getTransfer(accountId: string, transferId: string): Promise<Transfer>;
|
|
49
|
+
listParties(params?: {
|
|
50
|
+
page?: number;
|
|
51
|
+
size?: number;
|
|
52
|
+
}): Promise<Party[]>;
|
|
53
|
+
getParty(partyId: string): Promise<Party>;
|
|
54
|
+
getSupportedChains(): Promise<SupportedChains[]>;
|
|
55
|
+
getFiatCurrencies(): Promise<FiatCurrency[]>;
|
|
56
|
+
getCryptocurrencies(): Promise<CryptoCurrency[]>;
|
|
57
|
+
getCompanyFees(): Promise<VenlyFee[]>;
|
|
58
|
+
createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
|
|
59
|
+
createParty(body: CreatePartyInput): Promise<Party>;
|
|
60
|
+
createAccount(body: CreateAccountInput): Promise<Account>;
|
|
61
|
+
createVirtualBankAccount(accountId: string, body: CreateVirtualBankAccountInput): Promise<VirtualBankAccount>;
|
|
62
|
+
createCurrentFiatTransfer(senderAccountId: string, body: CurrentCreateFiatTransferInput): Promise<Transfer>;
|
|
63
|
+
createCryptoTransfer(senderAccountId: string, body: CreateCryptoTransferInput): Promise<Transfer>;
|
|
64
|
+
approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
|
|
65
|
+
rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
|
|
66
|
+
createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { FundflowClient, VenlyFinanceClient, } from "@venlyfinance/sdk";
|
|
2
|
+
import { resolveVenlyEnvironment, } from "../constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Normalize the legacy stage_transfer input to the current Finance
|
|
5
|
+
* CreateFiatTransferInput wire shape. Exported so the write tool can show the
|
|
6
|
+
* exact normalized request in its dry-run preview – the preview and the live
|
|
7
|
+
* call must never diverge. The retired `cryptocurrency` field is rejected
|
|
8
|
+
* rather than silently dropped: the current contract resolves the fiat amount
|
|
9
|
+
* to the account's settlement asset itself.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeLegacyFiatTransfer(body) {
|
|
12
|
+
if (body.cryptocurrency !== undefined) {
|
|
13
|
+
throw new Error("cryptocurrency is not part of the current fiat-transfer contract and would be ignored; " +
|
|
14
|
+
"omit it (the fiat amount is resolved to the account's settlement asset) or use " +
|
|
15
|
+
"create_crypto_transfer for an asset-denominated transfer.");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
receiverAccountId: body.receiverAccountId,
|
|
19
|
+
...(body.receiverExternalId === undefined
|
|
20
|
+
? {}
|
|
21
|
+
: { receiverExternalId: body.receiverExternalId }),
|
|
22
|
+
currency: body.fiatCurrency,
|
|
23
|
+
amount: Number(body.fiatAmount),
|
|
24
|
+
description: body.description,
|
|
25
|
+
merchantReference: body.merchantReference,
|
|
26
|
+
idempotencyKey: body.idempotencyKey ?? crypto.randomUUID(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Adapter from the generated Venly Finance SDK surface to the stable MCP
|
|
31
|
+
* client contract. This keeps tools transport-agnostic while ensuring auth,
|
|
32
|
+
* retries, idempotency and endpoint schemas come from the published SDK.
|
|
33
|
+
*/
|
|
34
|
+
export class SdkVenlyClient {
|
|
35
|
+
finance;
|
|
36
|
+
fundflow;
|
|
37
|
+
environment;
|
|
38
|
+
hasCredentials;
|
|
39
|
+
constructor(finance, fundflow, environment, hasCredentials = true) {
|
|
40
|
+
this.finance = finance;
|
|
41
|
+
this.fundflow = fundflow;
|
|
42
|
+
this.environment = environment;
|
|
43
|
+
this.hasCredentials = hasCredentials;
|
|
44
|
+
}
|
|
45
|
+
static mock() {
|
|
46
|
+
return new SdkVenlyClient(new VenlyFinanceClient({ environment: "mock" }), new FundflowClient({ environment: "mock" }), "mock", true);
|
|
47
|
+
}
|
|
48
|
+
static fromEnv(env) {
|
|
49
|
+
const environment = resolveVenlyEnvironment(env);
|
|
50
|
+
if (environment === "mock")
|
|
51
|
+
return SdkVenlyClient.mock();
|
|
52
|
+
const clientId = env.VENLY_CLIENT_ID ?? "";
|
|
53
|
+
const clientSecret = env.VENLY_CLIENT_SECRET ?? "";
|
|
54
|
+
return new SdkVenlyClient(new VenlyFinanceClient({
|
|
55
|
+
clientId,
|
|
56
|
+
clientSecret,
|
|
57
|
+
environment,
|
|
58
|
+
baseUrl: env.VENLY_FINANCE_BASE_URL,
|
|
59
|
+
tokenUrl: env.VENLY_TOKEN_URL,
|
|
60
|
+
}), new FundflowClient({
|
|
61
|
+
clientId,
|
|
62
|
+
clientSecret,
|
|
63
|
+
environment,
|
|
64
|
+
baseUrl: env.VENLY_FUNDFLOW_BASE_URL,
|
|
65
|
+
tokenUrl: env.VENLY_TOKEN_URL,
|
|
66
|
+
}), environment, Boolean(env.VENLY_CLIENT_ID && env.VENLY_CLIENT_SECRET));
|
|
67
|
+
}
|
|
68
|
+
/** Exposed for deterministic zero-network adapter and journey tests. */
|
|
69
|
+
get financeMockCalls() {
|
|
70
|
+
return this.finance.mock?.calls ?? [];
|
|
71
|
+
}
|
|
72
|
+
/** Exposed for deterministic zero-network adapter and journey tests. */
|
|
73
|
+
get fundflowMockCalls() {
|
|
74
|
+
return this.fundflow.mock?.calls ?? [];
|
|
75
|
+
}
|
|
76
|
+
assertReady() {
|
|
77
|
+
if (this.environment !== "mock" && !this.hasCredentials) {
|
|
78
|
+
throw new Error("Missing Venly credentials. Set VENLY_CLIENT_ID and VENLY_CLIENT_SECRET.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async listRampRequests(params) {
|
|
82
|
+
this.assertReady();
|
|
83
|
+
const page = await this.fundflow.rampRequests.list(params);
|
|
84
|
+
return page.items;
|
|
85
|
+
}
|
|
86
|
+
async getRampRequest(id) {
|
|
87
|
+
this.assertReady();
|
|
88
|
+
return this.fundflow.rampRequests.get(id);
|
|
89
|
+
}
|
|
90
|
+
async getAccount(accountId) {
|
|
91
|
+
this.assertReady();
|
|
92
|
+
return this.finance.accounts.get(accountId);
|
|
93
|
+
}
|
|
94
|
+
async listAccounts(params) {
|
|
95
|
+
this.assertReady();
|
|
96
|
+
const page = await this.finance.accounts.list(params);
|
|
97
|
+
return page.items;
|
|
98
|
+
}
|
|
99
|
+
async listWallets(accountId, params) {
|
|
100
|
+
this.assertReady();
|
|
101
|
+
const page = await this.finance.wallets.list(accountId, params);
|
|
102
|
+
return page.items;
|
|
103
|
+
}
|
|
104
|
+
async listVirtualBankAccounts(accountId) {
|
|
105
|
+
this.assertReady();
|
|
106
|
+
const page = await this.finance.virtualBankAccounts.list(accountId);
|
|
107
|
+
return page.items;
|
|
108
|
+
}
|
|
109
|
+
async getVirtualBankAccount(accountId, virtualBankAccountId) {
|
|
110
|
+
this.assertReady();
|
|
111
|
+
return this.finance.virtualBankAccounts.get(accountId, virtualBankAccountId);
|
|
112
|
+
}
|
|
113
|
+
async listTransfers(accountId, params) {
|
|
114
|
+
this.assertReady();
|
|
115
|
+
const page = await this.finance.transfers.list(accountId, params);
|
|
116
|
+
return page.items;
|
|
117
|
+
}
|
|
118
|
+
async getTransfer(accountId, transferId) {
|
|
119
|
+
this.assertReady();
|
|
120
|
+
return this.finance.transfers.get(accountId, transferId);
|
|
121
|
+
}
|
|
122
|
+
async listParties(params) {
|
|
123
|
+
this.assertReady();
|
|
124
|
+
const page = await this.finance.parties.list(params);
|
|
125
|
+
return page.items;
|
|
126
|
+
}
|
|
127
|
+
async getParty(partyId) {
|
|
128
|
+
this.assertReady();
|
|
129
|
+
return this.finance.parties.get(partyId);
|
|
130
|
+
}
|
|
131
|
+
async getSupportedChains() {
|
|
132
|
+
this.assertReady();
|
|
133
|
+
return this.fundflow.referenceData.chains();
|
|
134
|
+
}
|
|
135
|
+
async getFiatCurrencies() {
|
|
136
|
+
this.assertReady();
|
|
137
|
+
return this.fundflow.referenceData.fiatCurrencies();
|
|
138
|
+
}
|
|
139
|
+
async getCryptocurrencies() {
|
|
140
|
+
this.assertReady();
|
|
141
|
+
return this.fundflow.referenceData.cryptoCurrencies();
|
|
142
|
+
}
|
|
143
|
+
async getCompanyFees() {
|
|
144
|
+
this.assertReady();
|
|
145
|
+
return this.fundflow.fees.listCompanyFees();
|
|
146
|
+
}
|
|
147
|
+
async createFiatTransfer(senderAccountId, body) {
|
|
148
|
+
this.assertReady();
|
|
149
|
+
return this.finance.transfers.createFiat(senderAccountId, normalizeLegacyFiatTransfer(body));
|
|
150
|
+
}
|
|
151
|
+
async createParty(body) {
|
|
152
|
+
this.assertReady();
|
|
153
|
+
return this.finance.parties.create(body);
|
|
154
|
+
}
|
|
155
|
+
async createAccount(body) {
|
|
156
|
+
this.assertReady();
|
|
157
|
+
return this.finance.accounts.create(body);
|
|
158
|
+
}
|
|
159
|
+
async createVirtualBankAccount(accountId, body) {
|
|
160
|
+
this.assertReady();
|
|
161
|
+
return this.finance.virtualBankAccounts.create(accountId, body);
|
|
162
|
+
}
|
|
163
|
+
async createCurrentFiatTransfer(senderAccountId, body) {
|
|
164
|
+
this.assertReady();
|
|
165
|
+
return this.finance.transfers.createFiat(senderAccountId, body);
|
|
166
|
+
}
|
|
167
|
+
async createCryptoTransfer(senderAccountId, body) {
|
|
168
|
+
this.assertReady();
|
|
169
|
+
return this.finance.transfers.createCrypto(senderAccountId, body);
|
|
170
|
+
}
|
|
171
|
+
async approveRampRequest(id, body) {
|
|
172
|
+
this.assertReady();
|
|
173
|
+
return this.fundflow.rampRequests.approve(id, body);
|
|
174
|
+
}
|
|
175
|
+
async rejectRampRequest(id, body) {
|
|
176
|
+
this.assertReady();
|
|
177
|
+
return this.fundflow.rampRequests.reject(id, body);
|
|
178
|
+
}
|
|
179
|
+
async createPayInSession(accountId, body) {
|
|
180
|
+
this.assertReady();
|
|
181
|
+
return this.finance.paymentSessions.create(accountId, body);
|
|
182
|
+
}
|
|
183
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
/** Shared constants.
|
|
2
|
-
* touches
|
|
3
|
-
export declare const SERVER_NAME = "venly-
|
|
4
|
-
export declare const SERVER_VERSION = "0.
|
|
1
|
+
/** Shared constants. The default environment is MOCK so an unconfigured run
|
|
2
|
+
* never touches real infrastructure; staging/production are explicit. */
|
|
3
|
+
export declare const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
+
export declare const SERVER_VERSION = "0.3.0";
|
|
5
|
+
export declare const ENVIRONMENT_FLAG = "VENLY_ENV";
|
|
6
|
+
export type VenlyEnvironment = "mock" | "staging" | "production";
|
|
7
|
+
export declare function resolveVenlyEnvironment(env: Record<string, string | undefined>): VenlyEnvironment;
|
|
5
8
|
/** The env flag that must equal "1" for any write tool to execute live. */
|
|
6
9
|
export declare const LIVE_FLAG = "VENLY_MCP_LIVE";
|
|
10
|
+
export declare const PRODUCTION_FLAG = "VENLY_MCP_PRODUCTION";
|
|
7
11
|
/** Default base URLs (STAGING). Production values live in the vendored specs:
|
|
8
12
|
* finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
|
|
9
13
|
export declare const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
|
package/dist/constants.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
/** Shared constants.
|
|
2
|
-
* touches
|
|
3
|
-
export const SERVER_NAME = "venly-
|
|
4
|
-
export const SERVER_VERSION = "0.
|
|
1
|
+
/** Shared constants. The default environment is MOCK so an unconfigured run
|
|
2
|
+
* never touches real infrastructure; staging/production are explicit. */
|
|
3
|
+
export const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
+
export const SERVER_VERSION = "0.3.0";
|
|
5
|
+
export const ENVIRONMENT_FLAG = "VENLY_ENV";
|
|
6
|
+
export function resolveVenlyEnvironment(env) {
|
|
7
|
+
// Default is MOCK (since 0.3.0): the mock-first product must not point at
|
|
8
|
+
// real infrastructure when unconfigured. Set VENLY_ENV explicitly for
|
|
9
|
+
// staging or production.
|
|
10
|
+
const value = env[ENVIRONMENT_FLAG] ?? "mock";
|
|
11
|
+
if (value === "mock" || value === "staging" || value === "production") {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`${ENVIRONMENT_FLAG} must be one of mock, staging, production; received ${JSON.stringify(value)}`);
|
|
15
|
+
}
|
|
5
16
|
/** The env flag that must equal "1" for any write tool to execute live. */
|
|
6
17
|
export const LIVE_FLAG = "VENLY_MCP_LIVE";
|
|
18
|
+
export const PRODUCTION_FLAG = "VENLY_MCP_PRODUCTION";
|
|
7
19
|
/** Default base URLs (STAGING). Production values live in the vendored specs:
|
|
8
20
|
* finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
|
|
9
21
|
export const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Entry point. Builds the server over
|
|
4
|
-
* connects it over stdio. Read-only by default
|
|
5
|
-
* unless VENLY_MCP_LIVE=1 and
|
|
3
|
+
* Entry point. Builds the server over the official Venly Finance SDK and
|
|
4
|
+
* connects it over stdio. Read-only by default outside explicit mock mode;
|
|
5
|
+
* staging/production write tools stay disarmed unless VENLY_MCP_LIVE=1 and
|
|
6
|
+
* credentials are set.
|
|
6
7
|
*
|
|
7
|
-
* Credentials are read from env inside
|
|
8
|
+
* Credentials are read from env inside SdkVenlyClient and never logged.
|
|
8
9
|
*/
|
|
9
10
|
export {};
|