@venlyfinance/settlement-mcp 0.1.0 → 0.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/CHANGELOG.md +54 -0
- package/README.md +104 -35
- package/dist/client/sdk-client.d.ts +67 -0
- package/dist/client/sdk-client.js +183 -0
- package/dist/constants.d.ts +9 -5
- package/dist/constants.js +14 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +8 -7
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +43 -0
- package/dist/reconcile.js +6 -0
- 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 +5 -1
- package/dist/safety.js +27 -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 +83 -12
- package/dist/tools/write-tools.js +245 -29
- package/dist/types.d.ts +70 -125
- 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/skills/{payment-link-lifecycle.md → payment-session-lifecycle.md} +8 -7
- package/dist/client/http-client.d.ts +0 -58
- package/dist/client/http-client.js +0 -163
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 – 2026-08-03
|
|
4
|
+
|
|
5
|
+
The Settlement MCP becomes the SDK-backed **Venly Finance MCP** builder while retaining
|
|
6
|
+
its package name and compatibility binary throughout the 0.x line.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- 23 atomic read, write, reconciliation, and quote tools covering the first
|
|
11
|
+
international-account builder journey.
|
|
12
|
+
- Explicit `mock`, `staging`, and `production` environments with synthetic mock writes
|
|
13
|
+
and a separate production-write flag.
|
|
14
|
+
- Discoverable capability, safety, international-account, and mock-to-staging
|
|
15
|
+
resources.
|
|
16
|
+
- The `build_international_account` MCP prompt and matching portable workflow skills.
|
|
17
|
+
- Machine-readable structured tool results with credential and token redaction.
|
|
18
|
+
- `createServer` refuses to start when an injected client declares an environment that
|
|
19
|
+
disagrees with `VENLY_ENV`: the mock gate auto-arms writes, so the two must never
|
|
20
|
+
diverge.
|
|
21
|
+
- A zero-network golden journey covering party, account, wallet, EUR receiving account,
|
|
22
|
+
transfer, history, and reconciliation behavior.
|
|
23
|
+
- Compile-time assertions that MCP payload types remain exact aliases of the generated
|
|
24
|
+
Finance and Fundflow SDK contracts.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Finance and Fundflow execution now uses `@venlyfinance/sdk`; the duplicate HTTP,
|
|
29
|
+
OAuth, retry, and hand-written API type implementation was removed.
|
|
30
|
+
- `venly-finance-mcp` is the preferred binary. `venly-settlement-mcp` remains an alias
|
|
31
|
+
to the same server.
|
|
32
|
+
- The minimum Node.js version is 20.
|
|
33
|
+
- The official MCP SDK is upgraded to 1.30.0 and resolves Hono 2.0.12.
|
|
34
|
+
|
|
35
|
+
### Deprecated
|
|
36
|
+
|
|
37
|
+
- `stage_transfer` remains available for 0.x compatibility but normalizes its legacy
|
|
38
|
+
fields into the current Finance transfer contract. Its dry-run now previews the exact
|
|
39
|
+
normalized request a live call sends, a caller-supplied idempotency key survives
|
|
40
|
+
normalization, and the retired `cryptocurrency` field is rejected with guidance
|
|
41
|
+
instead of silently dropped. New integrations should use `create_fiat_transfer`.
|
|
42
|
+
|
|
43
|
+
### Safety and product boundaries
|
|
44
|
+
|
|
45
|
+
- Live writes remain dry-run unless confirmation, credentials, and the environment
|
|
46
|
+
flags are present; production requires an additional explicit flag.
|
|
47
|
+
- Creating a party does not complete KYC/KYB. Live EUR receiving-account provisioning
|
|
48
|
+
requires an eligible verified account.
|
|
49
|
+
- The MCP does not imply universal bank-account coverage, a bank charter, card issuing,
|
|
50
|
+
external-bank payouts, or autonomous production money movement.
|
|
51
|
+
|
|
52
|
+
### Security
|
|
53
|
+
|
|
54
|
+
- Runtime dependency audits report zero findings after the MCP SDK/Hono upgrade.
|
package/README.md
CHANGED
|
@@ -1,37 +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
|
-
|
|
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.
|
|
15
17
|
|
|
16
18
|
## What it is
|
|
17
19
|
|
|
18
20
|
- Built on the official MCP TypeScript SDK (`@modelcontextprotocol/sdk`), Node
|
|
19
|
-
>=
|
|
21
|
+
>= 20, stdio transport.
|
|
20
22
|
- A thin layer over the Venly Finance API (`finance.yaml`, servers
|
|
21
|
-
`https://api.venlyfinance.com/
|
|
23
|
+
`https://api.venlyfinance.com/v1`) and the Fundflow API (`fundflow.yaml`,
|
|
22
24
|
servers `https://api-fundflow.venly.io`). Endpoint truth is the published API
|
|
23
25
|
reference at [docs.venlyfinance.com](https://docs.venlyfinance.com/api-reference).
|
|
24
26
|
- Transport is injected through the `VenlyClient` interface (`src/types.ts`).
|
|
25
|
-
`
|
|
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
|
+
```
|
|
26
49
|
|
|
27
|
-
|
|
50
|
+
Then select the `build_international_account` prompt or ask the agent to read:
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
- `venly://capabilities`
|
|
53
|
+
- `venly://safety`
|
|
54
|
+
- `venly://workflows/international-account`
|
|
55
|
+
- `venly://workflows/mock-to-staging`
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
Mock mutations execute against SDK fixtures, are labelled `mode: "mock"`, use no
|
|
58
|
+
credentials and make no network requests.
|
|
59
|
+
|
|
60
|
+
## Tool tiers
|
|
35
61
|
|
|
36
62
|
### 1. Read tools (always on)
|
|
37
63
|
|
|
@@ -41,11 +67,16 @@ Call GETs only. No mutation, safe by default.
|
|
|
41
67
|
|---|---|
|
|
42
68
|
| `list_ramp_requests` | fundflow `GET /v1/ramp-requests` |
|
|
43
69
|
| `get_ramp_request` | fundflow `GET /v1/ramp-requests/{id}` |
|
|
70
|
+
| `list_accounts` | finance `GET /accounts` |
|
|
44
71
|
| `get_account` | finance `GET /accounts/{accountId}` |
|
|
72
|
+
| `list_wallets` | finance `GET /accounts/{accountId}/wallets` (including token balances) |
|
|
45
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}` |
|
|
46
75
|
| `reconcile_by_reference_code` | composite: lists vIBANs, matches supplied transactions by referenceCode |
|
|
76
|
+
| `list_transfers` | finance `GET /accounts/{accountId}/transfers` |
|
|
47
77
|
| `get_transfer` | finance `GET /accounts/{accountId}/transfers/{transferId}` |
|
|
48
78
|
| `list_parties` | finance `GET /parties` |
|
|
79
|
+
| `get_party` | finance `GET /parties/{partyId}` |
|
|
49
80
|
| `get_reference_data` | fundflow chains / fiat-currencies / crypto-currencies / fees |
|
|
50
81
|
|
|
51
82
|
`reconcile_by_reference_code` is the EUR vIBAN reconciliation: a customer sends
|
|
@@ -56,8 +87,13 @@ matched vIBAN, the matched transactions, and the total amount.
|
|
|
56
87
|
|
|
57
88
|
### 2. Write tools (present, DISARMED by default)
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
`
|
|
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.
|
|
61
97
|
|
|
62
98
|
Each is dry-run by default and returns the exact request it would send. See the
|
|
63
99
|
safety model below.
|
|
@@ -72,8 +108,8 @@ facilitator decision and live rails.
|
|
|
72
108
|
|
|
73
109
|
## Safety model (fail closed)
|
|
74
110
|
|
|
75
|
-
|
|
76
|
-
|
|
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:
|
|
77
113
|
|
|
78
114
|
1. the tool argument `confirm === true`, and
|
|
79
115
|
2. the environment flag `VENLY_MCP_LIVE === "1"`, and
|
|
@@ -84,6 +120,10 @@ it would have sent and never touches the transport. This is proven by
|
|
|
84
120
|
`test/write-tools.test.ts`, including the critical case: `confirm:true` with
|
|
85
121
|
`VENLY_MCP_LIVE` unset still dry-runs and does not call the live client.
|
|
86
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
|
+
|
|
87
127
|
Other invariants:
|
|
88
128
|
|
|
89
129
|
- Credentials are read from env, never logged, never returned in tool output.
|
|
@@ -108,7 +148,7 @@ The fastest path once published to npm - one line in any MCP client config:
|
|
|
108
148
|
```json
|
|
109
149
|
{
|
|
110
150
|
"mcpServers": {
|
|
111
|
-
"venly-
|
|
151
|
+
"venly-finance": {
|
|
112
152
|
"command": "npx",
|
|
113
153
|
"args": ["-y", "@venlyfinance/settlement-mcp"]
|
|
114
154
|
}
|
|
@@ -130,11 +170,12 @@ MCP client config example:
|
|
|
130
170
|
```json
|
|
131
171
|
{
|
|
132
172
|
"mcpServers": {
|
|
133
|
-
"venly-
|
|
173
|
+
"venly-finance": {
|
|
134
174
|
"command": "node",
|
|
135
175
|
"args": ["/absolute/path/to/settlement-mcp/dist/index.js"],
|
|
136
176
|
"env": {
|
|
137
|
-
"
|
|
177
|
+
"VENLY_ENV": "staging",
|
|
178
|
+
"VENLY_FINANCE_BASE_URL": "https://api-staging.venlyfinance.com/v1",
|
|
138
179
|
"VENLY_FUNDFLOW_BASE_URL": "https://api-fundflow-staging.venly.io"
|
|
139
180
|
}
|
|
140
181
|
}
|
|
@@ -149,16 +190,34 @@ Override via env:
|
|
|
149
190
|
|
|
150
191
|
| Env var | Default (staging) |
|
|
151
192
|
|---|---|
|
|
152
|
-
| `
|
|
193
|
+
| `VENLY_ENV` | `staging` for 0.x compatibility; set `mock` explicitly for fixtures |
|
|
194
|
+
| `VENLY_FINANCE_BASE_URL` | `https://api-staging.venlyfinance.com/v1` |
|
|
153
195
|
| `VENLY_FUNDFLOW_BASE_URL` | `https://api-fundflow-staging.venly.io` |
|
|
154
|
-
| `VENLY_TOKEN_URL` | `https://login.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token` |
|
|
196
|
+
| `VENLY_TOKEN_URL` | `https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token` (staging; use `login.venly.io` for production) |
|
|
155
197
|
| `VENLY_CLIENT_ID` | unset (read-only until set) |
|
|
156
198
|
| `VENLY_CLIENT_SECRET` | unset |
|
|
157
199
|
| `VENLY_MCP_LIVE` | unset (writes disarmed) |
|
|
200
|
+
| `VENLY_MCP_PRODUCTION` | unset (production writes disarmed) |
|
|
158
201
|
|
|
159
202
|
Fundflow also exposes a QA sandbox (`https://api-fundflow-qa.venly.io`). If your
|
|
160
203
|
tenant uses different endpoints, override them via the env vars above.
|
|
161
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
|
+
|
|
162
221
|
## Enabling live writes (a deliberate operator decision)
|
|
163
222
|
|
|
164
223
|
Live writes are OFF. To arm them, the operator must:
|
|
@@ -170,6 +229,10 @@ Live writes are OFF. To arm them, the operator must:
|
|
|
170
229
|
Until all three are set, every write tool dry-runs. Arming the flag and
|
|
171
230
|
provisioning credentials is a deliberate, human decision, not a default.
|
|
172
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
|
+
|
|
173
236
|
## x402 position
|
|
174
237
|
|
|
175
238
|
The machine-to-machine agent-payments rail is consolidating on x402 (Cloudflare
|
|
@@ -186,8 +249,10 @@ MCP-consumable workflow docs under `skills/`:
|
|
|
186
249
|
- `reconcile-by-reference-code.md`
|
|
187
250
|
- `four-eyes-approval.md`
|
|
188
251
|
- `stage-and-confirm-transfer.md`
|
|
189
|
-
- `payment-
|
|
252
|
+
- `payment-session-lifecycle.md`
|
|
190
253
|
- `x402-quote-walkthrough.md`
|
|
254
|
+
- `build-international-account.md`
|
|
255
|
+
- `mock-to-staging.md`
|
|
191
256
|
|
|
192
257
|
## Layout
|
|
193
258
|
|
|
@@ -200,11 +265,15 @@ settlement-mcp/
|
|
|
200
265
|
index.ts entry, stdio transport
|
|
201
266
|
server.ts createServer(client, env), registers all tiers
|
|
202
267
|
constants.ts
|
|
203
|
-
types.ts
|
|
268
|
+
types.ts Generated SDK aliases + MCP-owned compatibility types
|
|
204
269
|
safety.ts the write-gate (confirm + env + creds)
|
|
205
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
|
|
206
275
|
client/
|
|
207
|
-
|
|
276
|
+
sdk-client.ts Adapter over @venlyfinance/sdk
|
|
208
277
|
tools/
|
|
209
278
|
read-tools.ts tier 1
|
|
210
279
|
write-tools.ts tier 2 (fail closed)
|
|
@@ -213,10 +282,10 @@ settlement-mcp/
|
|
|
213
282
|
reconcile-by-reference-code.md
|
|
214
283
|
four-eyes-approval.md
|
|
215
284
|
stage-and-confirm-transfer.md
|
|
216
|
-
payment-
|
|
285
|
+
payment-session-lifecycle.md
|
|
217
286
|
x402-quote-walkthrough.md
|
|
218
|
-
|
|
219
|
-
|
|
287
|
+
build-international-account.md
|
|
288
|
+
mock-to-staging.md
|
|
220
289
|
test/
|
|
221
290
|
helpers.ts in-memory MCP harness + mock client
|
|
222
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,13 +1,17 @@
|
|
|
1
1
|
/** Shared constants. Defaults point at STAGING so an accidental run never
|
|
2
2
|
* touches production. Override via env for a real sandbox test. */
|
|
3
|
-
export declare const SERVER_NAME = "venly-
|
|
4
|
-
export declare const SERVER_VERSION = "0.
|
|
3
|
+
export declare const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
+
export declare const SERVER_VERSION = "0.2.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
|
-
* finance https://api.venlyfinance.com/
|
|
9
|
-
export declare const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/
|
|
12
|
+
* finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
|
|
13
|
+
export declare const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
|
|
10
14
|
export declare const DEFAULT_FUNDFLOW_BASE_URL = "https://api-fundflow-staging.venly.io";
|
|
11
|
-
export declare const DEFAULT_TOKEN_URL = "https://login.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
|
|
15
|
+
export declare const DEFAULT_TOKEN_URL = "https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
|
|
12
16
|
/** Cap on serialized tool output length to keep responses readable. */
|
|
13
17
|
export declare const CHARACTER_LIMIT = 30000;
|
package/dist/constants.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/** Shared constants. Defaults point at STAGING so an accidental run never
|
|
2
2
|
* touches production. Override via env for a real sandbox test. */
|
|
3
|
-
export const SERVER_NAME = "venly-
|
|
4
|
-
export const SERVER_VERSION = "0.
|
|
3
|
+
export const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
+
export const SERVER_VERSION = "0.2.0";
|
|
5
|
+
export const ENVIRONMENT_FLAG = "VENLY_ENV";
|
|
6
|
+
export function resolveVenlyEnvironment(env) {
|
|
7
|
+
const value = env[ENVIRONMENT_FLAG] ?? "staging";
|
|
8
|
+
if (value === "mock" || value === "staging" || value === "production") {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
throw new Error(`${ENVIRONMENT_FLAG} must be one of mock, staging, production; received ${JSON.stringify(value)}`);
|
|
12
|
+
}
|
|
5
13
|
/** The env flag that must equal "1" for any write tool to execute live. */
|
|
6
14
|
export const LIVE_FLAG = "VENLY_MCP_LIVE";
|
|
15
|
+
export const PRODUCTION_FLAG = "VENLY_MCP_PRODUCTION";
|
|
7
16
|
/** Default base URLs (STAGING). Production values live in the vendored specs:
|
|
8
|
-
* finance https://api.venlyfinance.com/
|
|
9
|
-
export const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/
|
|
17
|
+
* finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
|
|
18
|
+
export const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
|
|
10
19
|
export const DEFAULT_FUNDFLOW_BASE_URL = "https://api-fundflow-staging.venly.io";
|
|
11
|
-
export const DEFAULT_TOKEN_URL = "https://login.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
|
|
20
|
+
export const DEFAULT_TOKEN_URL = "https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
|
|
12
21
|
/** Cap on serialized tool output length to keep responses readable. */
|
|
13
22
|
export const CHARACTER_LIMIT = 30_000;
|
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 {};
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
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
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
11
|
import { createServer } from "./server.js";
|
|
11
|
-
import {
|
|
12
|
+
import { SdkVenlyClient } from "./client/sdk-client.js";
|
|
12
13
|
import { LIVE_FLAG } from "./constants.js";
|
|
13
14
|
async function main() {
|
|
14
|
-
const client =
|
|
15
|
+
const client = SdkVenlyClient.fromEnv(process.env);
|
|
15
16
|
const server = createServer({ client, env: process.env });
|
|
16
17
|
const transport = new StdioServerTransport();
|
|
17
18
|
await server.connect(transport);
|
|
18
19
|
// Log to stderr only (stdout is the MCP channel). No credentials here.
|
|
19
20
|
const armed = process.env[LIVE_FLAG] === "1";
|
|
20
|
-
process.stderr.write(`venly-
|
|
21
|
+
process.stderr.write(`venly-finance-mcp started in ${client.environment}. write tools ${armed ? "ARMED (VENLY_MCP_LIVE=1)" : "DISARMED (read-only default)"}.\n`);
|
|
21
22
|
}
|
|
22
23
|
main().catch((err) => {
|
|
23
24
|
process.stderr.write(`Fatal: ${err.message}\n`);
|