@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 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 Settlement MCP server
1
+ # Venly Finance MCP
2
2
 
3
- A human-gated operator surface for Venly settlement, exposed as a Model Context
4
- Protocol (MCP) server over stdio. An operator, or a supervised agent, can check a
5
- ramp's four-eyes approval state, reconcile a EUR vIBAN by referenceCode, or stage
6
- a transfer, without hand-writing API calls. It pairs that with a position on
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
- The differentiator: the MCP is the human-gated operator surface; x402 is the
10
- machine-to-machine rail. Read-only by default, write tools fail closed.
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
- Status: v0.1.0. Read tools work against staging out of the box once credentials
13
- are set; write tools are shipped disarmed and fail closed (see the safety model).
14
- The x402 tool is a stub that states a position, it moves no funds.
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
- >= 18, stdio transport.
21
+ >= 20, stdio transport.
20
22
  - A thin layer over the Venly Finance API (`finance.yaml`, servers
21
- `https://api.venlyfinance.com/api/v1`) and the Fundflow API (`fundflow.yaml`,
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
- `HttpVenlyClient` is a minimal fetch-based implementation; tests inject a mock.
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
- ### Transport note
50
+ Then select the `build_international_account` prompt or ask the agent to read:
28
51
 
29
- The built-in HTTP transport is minimal by design (OAuth2 client credentials,
30
- lazy token fetch, no logging of secrets) and is fully covered by the test
31
- suite. A future release adopts [`@venlyfinance/sdk`](../) as the transport
32
- layer, with no change to the tool interface.
52
+ - `venly://capabilities`
53
+ - `venly://safety`
54
+ - `venly://workflows/international-account`
55
+ - `venly://workflows/mock-to-staging`
33
56
 
34
- ## The three tool tiers
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
- `stage_transfer`, `approve_ramp_request`, `reject_ramp_request`,
60
- `create_payment_link`.
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
- Read-only is the default posture. A write tool executes a live call ONLY when
76
- ALL THREE hold:
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-settlement": {
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-settlement": {
173
+ "venly-finance": {
134
174
  "command": "node",
135
175
  "args": ["/absolute/path/to/settlement-mcp/dist/index.js"],
136
176
  "env": {
137
- "VENLY_FINANCE_BASE_URL": "https://api-staging.venlyfinance.com/api/v1",
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
- | `VENLY_FINANCE_BASE_URL` | `https://api-staging.venlyfinance.com/api/v1` |
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-link-lifecycle.md`
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 VenlyClient interface + domain types
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
- http-client.ts HttpVenlyClient (fetch, OAuth, vendored transport)
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-link-lifecycle.md
285
+ payment-session-lifecycle.md
217
286
  x402-quote-walkthrough.md
218
- four-eyes-approval.md
219
- stage-and-confirm-transfer.md
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
+ }
@@ -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-settlement-mcp-server";
4
- export declare const SERVER_VERSION = "0.1.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/api/v1, fundflow https://api-fundflow.venly.io */
9
- export declare const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/api/v1";
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-settlement-mcp-server";
4
- export const SERVER_VERSION = "0.1.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/api/v1, fundflow https://api-fundflow.venly.io */
9
- export const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/api/v1";
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 a real HttpVenlyClient (from env) and
4
- * connects it over stdio. Read-only by default; write tools stay disarmed
5
- * unless VENLY_MCP_LIVE=1 and credentials are set.
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 HttpVenlyClient and never logged.
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 a real HttpVenlyClient (from env) and
4
- * connects it over stdio. Read-only by default; write tools stay disarmed
5
- * unless VENLY_MCP_LIVE=1 and credentials are set.
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 HttpVenlyClient and never logged.
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 { HttpVenlyClient } from "./client/http-client.js";
12
+ import { SdkVenlyClient } from "./client/sdk-client.js";
12
13
  import { LIVE_FLAG } from "./constants.js";
13
14
  async function main() {
14
- const client = HttpVenlyClient.fromEnv();
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-settlement-mcp started. write tools ${armed ? "ARMED (VENLY_MCP_LIVE=1)" : "DISARMED (read-only default)"}.\n`);
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`);