@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 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 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.1 (realigned to the live published OpenAPI specs: staging base URL
13
- and auth host corrected, `create_payment_link` became `create_payment_session`
14
- to match the live API). Read tools work against staging out of the box once
15
- credentials are set; write tools are shipped disarmed and fail closed (see the
16
- safety model). 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.
17
17
 
18
18
  ## What it is
19
19
 
20
20
  - Built on the official MCP TypeScript SDK (`@modelcontextprotocol/sdk`), Node
21
- >= 18, stdio transport.
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
- `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
+ ```
28
49
 
29
- ### Transport note
50
+ Then select the `build_international_account` prompt or ask the agent to read:
30
51
 
31
- The built-in HTTP transport is minimal by design (OAuth2 client credentials,
32
- lazy token fetch, no logging of secrets) and is fully covered by the test
33
- suite. A future release adopts [`@venlyfinance/sdk`](../) as the transport
34
- 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`
35
56
 
36
- ## 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
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
- `stage_transfer`, `approve_ramp_request`, `reject_ramp_request`,
62
- `create_payment_session`.
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
- Read-only is the default posture. A write tool executes a live call ONLY when
78
- 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:
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-settlement": {
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-settlement": {
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 VenlyClient interface + domain types
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
- http-client.ts HttpVenlyClient (fetch, OAuth, vendored transport)
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
- four-eyes-approval.md
221
- stage-and-confirm-transfer.md
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
+ }
@@ -1,9 +1,13 @@
1
- /** Shared constants. Defaults point at STAGING so an accidental run never
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.1";
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. Defaults point at STAGING so an accidental run never
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.1";
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 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 {};