@venlyfinance/settlement-mcp 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -9,16 +9,18 @@ x402, the machine-to-machine agent-payments rail.
9
9
  The differentiator: the MCP is the human-gated operator surface; x402 is the
10
10
  machine-to-machine rail. Read-only by default, write tools fail closed.
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
+ 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.
15
17
 
16
18
  ## What it is
17
19
 
18
20
  - Built on the official MCP TypeScript SDK (`@modelcontextprotocol/sdk`), Node
19
21
  >= 18, 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`).
@@ -57,7 +59,7 @@ matched vIBAN, the matched transactions, and the total amount.
57
59
  ### 2. Write tools (present, DISARMED by default)
58
60
 
59
61
  `stage_transfer`, `approve_ramp_request`, `reject_ramp_request`,
60
- `create_payment_link`.
62
+ `create_payment_session`.
61
63
 
62
64
  Each is dry-run by default and returns the exact request it would send. See the
63
65
  safety model below.
@@ -134,7 +136,7 @@ MCP client config example:
134
136
  "command": "node",
135
137
  "args": ["/absolute/path/to/settlement-mcp/dist/index.js"],
136
138
  "env": {
137
- "VENLY_FINANCE_BASE_URL": "https://api-staging.venlyfinance.com/api/v1",
139
+ "VENLY_FINANCE_BASE_URL": "https://api-staging.venlyfinance.com/v1",
138
140
  "VENLY_FUNDFLOW_BASE_URL": "https://api-fundflow-staging.venly.io"
139
141
  }
140
142
  }
@@ -149,9 +151,9 @@ Override via env:
149
151
 
150
152
  | Env var | Default (staging) |
151
153
  |---|---|
152
- | `VENLY_FINANCE_BASE_URL` | `https://api-staging.venlyfinance.com/api/v1` |
154
+ | `VENLY_FINANCE_BASE_URL` | `https://api-staging.venlyfinance.com/v1` |
153
155
  | `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` |
156
+ | `VENLY_TOKEN_URL` | `https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token` (staging; use `login.venly.io` for production) |
155
157
  | `VENLY_CLIENT_ID` | unset (read-only until set) |
156
158
  | `VENLY_CLIENT_SECRET` | unset |
157
159
  | `VENLY_MCP_LIVE` | unset (writes disarmed) |
@@ -186,7 +188,7 @@ MCP-consumable workflow docs under `skills/`:
186
188
  - `reconcile-by-reference-code.md`
187
189
  - `four-eyes-approval.md`
188
190
  - `stage-and-confirm-transfer.md`
189
- - `payment-link-lifecycle.md`
191
+ - `payment-session-lifecycle.md`
190
192
  - `x402-quote-walkthrough.md`
191
193
 
192
194
  ## Layout
@@ -213,7 +215,7 @@ settlement-mcp/
213
215
  reconcile-by-reference-code.md
214
216
  four-eyes-approval.md
215
217
  stage-and-confirm-transfer.md
216
- payment-link-lifecycle.md
218
+ payment-session-lifecycle.md
217
219
  x402-quote-walkthrough.md
218
220
  four-eyes-approval.md
219
221
  stage-and-confirm-transfer.md
@@ -15,7 +15,7 @@
15
15
  * when a write method is invoked, and write methods are invoked only after
16
16
  * the gate in safety.ts is armed. Read-only by default is enforced upstream.
17
17
  */
18
- import type { Account, CreateFiatTransferInput, CreatePaymentLinkRequest, ListRampRequestsParams, OptimisticLockingBody, Party, PaymentLink, RampRequestDto, RampRequestListItem, Transfer, VenlyClient, VirtualBankAccount } from "../types.js";
18
+ import type { Account, CreateFiatTransferInput, CreatePayInSessionRequest, ListRampRequestsParams, OptimisticLockingBody, Party, PaymentSession, RampRequestDto, RampRequestListItem, Transfer, VenlyClient, VirtualBankAccount } from "../types.js";
19
19
  export interface HttpVenlyClientConfig {
20
20
  financeBaseUrl?: string;
21
21
  fundflowBaseUrl?: string;
@@ -54,5 +54,5 @@ export declare class HttpVenlyClient implements VenlyClient {
54
54
  createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
55
55
  approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
56
56
  rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
57
- createPaymentLink(accountId: string, body: CreatePaymentLinkRequest): Promise<PaymentLink>;
57
+ createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
58
58
  }
@@ -157,7 +157,7 @@ export class HttpVenlyClient {
157
157
  body,
158
158
  });
159
159
  }
160
- createPaymentLink(accountId, body) {
161
- return this.request(this.financeBaseUrl, "POST", `/accounts/${accountId}/fiat-to-crypto/payment-links`, { body });
160
+ createPayInSession(accountId, body) {
161
+ return this.request(this.financeBaseUrl, "POST", `/accounts/${accountId}/fiat-to-crypto/payment-sessions`, { body });
162
162
  }
163
163
  }
@@ -1,13 +1,13 @@
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
3
  export declare const SERVER_NAME = "venly-settlement-mcp-server";
4
- export declare const SERVER_VERSION = "0.1.0";
4
+ export declare const SERVER_VERSION = "0.1.1";
5
5
  /** The env flag that must equal "1" for any write tool to execute live. */
6
6
  export declare const LIVE_FLAG = "VENLY_MCP_LIVE";
7
7
  /** 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";
8
+ * finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
9
+ export declare const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
10
10
  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";
11
+ export declare const DEFAULT_TOKEN_URL = "https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
12
12
  /** Cap on serialized tool output length to keep responses readable. */
13
13
  export declare const CHARACTER_LIMIT = 30000;
package/dist/constants.js CHANGED
@@ -1,13 +1,13 @@
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
3
  export const SERVER_NAME = "venly-settlement-mcp-server";
4
- export const SERVER_VERSION = "0.1.0";
4
+ export const SERVER_VERSION = "0.1.1";
5
5
  /** The env flag that must equal "1" for any write tool to execute live. */
6
6
  export const LIVE_FLAG = "VENLY_MCP_LIVE";
7
7
  /** 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";
8
+ * finance https://api.venlyfinance.com/v1, fundflow https://api-fundflow.venly.io */
9
+ export const DEFAULT_FINANCE_BASE_URL = "https://api-staging.venlyfinance.com/v1";
10
10
  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";
11
+ export const DEFAULT_TOKEN_URL = "https://login-staging.venly.io/auth/realms/VenlyFinance/protocol/openid-connect/token";
12
12
  /** Cap on serialized tool output length to keep responses readable. */
13
13
  export const CHARACTER_LIMIT = 30_000;
@@ -124,17 +124,24 @@ export function registerWriteTools(server, client, env) {
124
124
  return errorResult(e.message);
125
125
  }
126
126
  });
127
- server.registerTool("create_payment_link", {
128
- title: "Create a fiat-to-crypto payment link (dry-run by default)",
129
- description: "Create a pay-in link (finance POST /accounts/{accountId}/fiat-to-crypto/payment-links). " +
130
- "DISARMED by default.",
127
+ server.registerTool("create_payment_session", {
128
+ title: "Create a fiat-to-crypto payment session (dry-run by default)",
129
+ description: "Create a hosted pay-in session (finance POST " +
130
+ "/accounts/{accountId}/fiat-to-crypto/payment-sessions); redirect the " +
131
+ "payer to the returned paymentUrl. DISARMED by default.",
131
132
  inputSchema: {
132
133
  accountId: z.string(),
133
134
  inAmount: z.string().describe("Decimal string, e.g. \"250.00\""),
134
135
  inCurrency: z.string().describe("e.g. EUR"),
135
- outCryptocurrency: z.string().optional().describe("e.g. USDC"),
136
- redirectUrl: z.string().optional(),
136
+ outCryptocurrency: z.string().describe("e.g. USDC"),
137
+ callbackUrl: z.string().describe("Webhook URL notified on completion"),
138
+ successRedirectUrl: z.string().optional(),
139
+ failureRedirectUrl: z.string().optional(),
137
140
  externalRef: z.string().optional(),
141
+ idempotencyKey: z
142
+ .string()
143
+ .optional()
144
+ .describe("UUID; generated when omitted"),
138
145
  confirm: confirmField,
139
146
  },
140
147
  annotations: WRITE_ANNOTATIONS,
@@ -144,14 +151,17 @@ export function registerWriteTools(server, client, env) {
144
151
  inAmount: rest.inAmount,
145
152
  inCurrency: rest.inCurrency,
146
153
  outCryptocurrency: rest.outCryptocurrency,
147
- redirectUrl: rest.redirectUrl,
154
+ callbackUrl: rest.callbackUrl,
155
+ successRedirectUrl: rest.successRedirectUrl,
156
+ failureRedirectUrl: rest.failureRedirectUrl,
148
157
  externalRef: rest.externalRef,
158
+ idempotencyKey: rest.idempotencyKey ?? crypto.randomUUID(),
149
159
  };
150
160
  if (!gate.armed) {
151
- return jsonResult(buildDryRun("create_payment_link", "POST", "finance", `/accounts/${accountId}/fiat-to-crypto/payment-links`, body, gate));
161
+ return jsonResult(buildDryRun("create_payment_session", "POST", "finance", `/accounts/${accountId}/fiat-to-crypto/payment-sessions`, body, gate));
152
162
  }
153
163
  try {
154
- const result = await client.createPaymentLink(accountId, body);
164
+ const result = await client.createPayInSession(accountId, body);
155
165
  return jsonResult({ mode: "live", result });
156
166
  }
157
167
  catch (e) {
package/dist/types.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Domain types + the injectable VenlyClient interface.
3
3
  *
4
- * These shapes are a minimal projection of the vendored OpenAPI specs at
5
- * projects/venly-docs-rebuild/api-reference/finance.yaml (servers:
6
- * https://api.venlyfinance.com/api/v1) and fundflow.yaml (servers:
4
+ * These shapes are a minimal projection of the published OpenAPI specs
5
+ * vendored in this repository under `specs/` – finance.yaml (servers:
6
+ * https://api.venlyfinance.com/v1) and fundflow.yaml (servers:
7
7
  * https://api-fundflow.venly.io). Only the fields the tools actually read or
8
8
  * echo are modeled. Fields are intentionally loose (optional) because this is a
9
9
  * thin wrapper, not a full SDK.
@@ -94,8 +94,8 @@ export interface Party {
94
94
  status?: string;
95
95
  [key: string]: unknown;
96
96
  }
97
- /** A payment link (finance PaymentLink). */
98
- export interface PaymentLink {
97
+ /** A fiat-to-crypto payment session (finance PaymentSession). */
98
+ export interface PaymentSession {
99
99
  id: string;
100
100
  accountId?: string;
101
101
  paymentUrl?: string;
@@ -141,12 +141,15 @@ export interface CreateFiatTransferInput {
141
141
  export interface OptimisticLockingBody {
142
142
  version: number;
143
143
  }
144
- /** Body for the payment link POST (finance CreatePaymentLinkRequest). */
145
- export interface CreatePaymentLinkRequest {
144
+ /** Body for the payment session POST (finance CreatePayInSessionRequest). */
145
+ export interface CreatePayInSessionRequest {
146
146
  inAmount: string;
147
147
  inCurrency: string;
148
- outCryptocurrency?: string;
149
- redirectUrl?: string;
148
+ outCryptocurrency: string;
149
+ callbackUrl: string;
150
+ idempotencyKey: string;
151
+ successRedirectUrl?: string;
152
+ failureRedirectUrl?: string;
150
153
  externalRef?: string;
151
154
  metadata?: Record<string, string>;
152
155
  }
@@ -173,5 +176,5 @@ export interface VenlyClient {
173
176
  createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
174
177
  approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
175
178
  rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
176
- createPaymentLink(accountId: string, body: CreatePaymentLinkRequest): Promise<PaymentLink>;
179
+ createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
177
180
  }
package/dist/types.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Domain types + the injectable VenlyClient interface.
3
3
  *
4
- * These shapes are a minimal projection of the vendored OpenAPI specs at
5
- * projects/venly-docs-rebuild/api-reference/finance.yaml (servers:
6
- * https://api.venlyfinance.com/api/v1) and fundflow.yaml (servers:
4
+ * These shapes are a minimal projection of the published OpenAPI specs
5
+ * vendored in this repository under `specs/` – finance.yaml (servers:
6
+ * https://api.venlyfinance.com/v1) and fundflow.yaml (servers:
7
7
  * https://api-fundflow.venly.io). Only the fields the tools actually read or
8
8
  * echo are modeled. Fields are intentionally loose (optional) because this is a
9
9
  * thin wrapper, not a full SDK.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venlyfinance/settlement-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Venly Settlement MCP server. Human-gated operator surface over the Venly Finance and Fundflow APIs, plus an x402 machine-to-machine quote stub. Read-only by default, write tools fail closed.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
- # Skill: Create and track a payment link
1
+ # Skill: Create and track a fiat-to-crypto payment session
2
2
 
3
- Stand up a fiat-to-crypto payment link for an account, hand the URL to a payer,
3
+ Stand up a hosted pay-in session for an account, hand the URL to a payer,
4
4
  and follow the payment through to settlement.
5
5
 
6
6
  ## When to use
@@ -13,7 +13,7 @@ collections, top-ups.
13
13
 
14
14
  - `get_account` (read)
15
15
  - `list_virtual_bank_accounts` (read)
16
- - `create_payment_link` (write, disarmed by default)
16
+ - `create_payment_session` (write, disarmed by default)
17
17
  - `get_transfer` (read)
18
18
 
19
19
  ## Steps
@@ -21,10 +21,11 @@ collections, top-ups.
21
21
  1. `get_account` for the collecting account. Confirm `status: "ACTIVE"` - an
22
22
  unverified or suspended account cannot collect.
23
23
  2. Optional context: `list_virtual_bank_accounts` shows the account's existing
24
- collection surfaces (IBAN + referenceCode); a payment link is the hosted
24
+ collection surfaces (IBAN + referenceCode); a payment session is the hosted
25
25
  alternative for payers who won't do a bank transfer.
26
- 3. `create_payment_link` with the `accountId` and an `externalRef` your own
27
- system can reconcile on later.
26
+ 3. `create_payment_session` with the `accountId`, a `callbackUrl` your system
27
+ will receive the completion webhook on, and an `externalRef` you can
28
+ reconcile on later. An `idempotencyKey` is generated when you don't pass one.
28
29
  - By default the tool returns a dry-run object with the exact POST it would
29
30
  send. Review it.
30
31
  - Live execution needs all three: `confirm: true`, `VENLY_MCP_LIVE=1`, and
@@ -36,7 +37,7 @@ collections, top-ups.
36
37
 
37
38
  ## Notes
38
39
 
39
- - Payment links expire (`expiresAt`); a link that was never paid ends at
40
+ - Payment sessions expire (`expiresAt`); a session that was never paid ends at
40
41
  `EXPIRED`, not `FAILED`.
41
42
  - Statuses walk `CREATED → PENDING_PAYMENT → PAYMENT_RECEIVED → CONVERTING →
42
43
  MINTING → COMPLETED`, with `FAILED`, `CANCELLED`, `REFUNDING`, `REFUNDED` as