@venlyfinance/settlement-mcp 0.1.1 → 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 +98 -31
- package/dist/client/sdk-client.d.ts +67 -0
- package/dist/client/sdk-client.js +183 -0
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +11 -2
- 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 +226 -20
- package/dist/types.d.ts +69 -127
- package/dist/types.js +5 -13
- package/package.json +18 -10
- package/scripts/staging-smoke.mjs +13 -0
- package/skills/build-international-account.md +26 -0
- package/skills/mock-to-staging.md +11 -0
- package/dist/client/http-client.d.ts +0 -58
- package/dist/client/http-client.js +0 -163
package/dist/tools/read-tools.js
CHANGED
|
@@ -3,18 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { reconcileByReferenceCode } from "../reconcile.js";
|
|
6
|
-
|
|
7
|
-
function jsonResult(data) {
|
|
8
|
-
return {
|
|
9
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function errorResult(message) {
|
|
13
|
-
return {
|
|
14
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
15
|
-
isError: true,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
6
|
+
import { errorResult, jsonResult } from "../results.js";
|
|
18
7
|
const READ_ONLY = {
|
|
19
8
|
readOnlyHint: true,
|
|
20
9
|
destructiveHint: false,
|
|
@@ -71,6 +60,23 @@ export function registerReadTools(server, client) {
|
|
|
71
60
|
return errorResult(e.message);
|
|
72
61
|
}
|
|
73
62
|
});
|
|
63
|
+
server.registerTool("list_accounts", {
|
|
64
|
+
title: "List accounts",
|
|
65
|
+
description: "List Venly Finance accounts before creating duplicates (finance GET /accounts). Read-only.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
page: z.number().int().min(1).optional(),
|
|
68
|
+
size: z.number().int().min(1).max(200).optional(),
|
|
69
|
+
},
|
|
70
|
+
annotations: READ_ONLY,
|
|
71
|
+
}, async (params) => {
|
|
72
|
+
try {
|
|
73
|
+
const result = await client.listAccounts(params);
|
|
74
|
+
return jsonResult({ count: result.length, accounts: result });
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return errorResult(e.message);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
74
80
|
server.registerTool("get_account", {
|
|
75
81
|
title: "Get account",
|
|
76
82
|
description: "Fetch a settlement account (finance GET /accounts/{accountId}). Read-only.",
|
|
@@ -84,6 +90,24 @@ export function registerReadTools(server, client) {
|
|
|
84
90
|
return errorResult(e.message);
|
|
85
91
|
}
|
|
86
92
|
});
|
|
93
|
+
server.registerTool("list_wallets", {
|
|
94
|
+
title: "List account wallets",
|
|
95
|
+
description: "List wallets auto-provisioned for a Finance account (finance GET /accounts/{accountId}/wallets). Read-only.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
accountId: z.string().describe("Account UUID"),
|
|
98
|
+
page: z.number().int().min(1).optional(),
|
|
99
|
+
size: z.number().int().min(1).max(200).optional(),
|
|
100
|
+
},
|
|
101
|
+
annotations: READ_ONLY,
|
|
102
|
+
}, async ({ accountId, page, size }) => {
|
|
103
|
+
try {
|
|
104
|
+
const result = await client.listWallets(accountId, { page, size });
|
|
105
|
+
return jsonResult({ count: result.length, wallets: result });
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
return errorResult(e.message);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
87
111
|
server.registerTool("list_virtual_bank_accounts", {
|
|
88
112
|
title: "List virtual bank accounts",
|
|
89
113
|
description: "List the EUR vIBANs on an account, each with its reconciliation " +
|
|
@@ -99,6 +123,22 @@ export function registerReadTools(server, client) {
|
|
|
99
123
|
return errorResult(e.message);
|
|
100
124
|
}
|
|
101
125
|
});
|
|
126
|
+
server.registerTool("get_virtual_bank_account", {
|
|
127
|
+
title: "Get virtual bank account",
|
|
128
|
+
description: "Fetch receiving-account details including status, IBAN/BIC and referenceCode. Read-only.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
accountId: z.string().describe("Account UUID"),
|
|
131
|
+
virtualBankAccountId: z.string().describe("Virtual bank account UUID"),
|
|
132
|
+
},
|
|
133
|
+
annotations: READ_ONLY,
|
|
134
|
+
}, async ({ accountId, virtualBankAccountId }) => {
|
|
135
|
+
try {
|
|
136
|
+
return jsonResult(await client.getVirtualBankAccount(accountId, virtualBankAccountId));
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return errorResult(e.message);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
102
142
|
server.registerTool("reconcile_by_reference_code", {
|
|
103
143
|
title: "Reconcile by referenceCode",
|
|
104
144
|
description: "Match observed incoming bank transactions on an account's EUR vIBANs to " +
|
|
@@ -132,6 +172,24 @@ export function registerReadTools(server, client) {
|
|
|
132
172
|
return errorResult(e.message);
|
|
133
173
|
}
|
|
134
174
|
});
|
|
175
|
+
server.registerTool("list_transfers", {
|
|
176
|
+
title: "List transfers",
|
|
177
|
+
description: "List transfer history for an account (finance GET /accounts/{accountId}/transfers). Read-only.",
|
|
178
|
+
inputSchema: {
|
|
179
|
+
accountId: z.string().describe("Account UUID"),
|
|
180
|
+
page: z.number().int().min(1).optional(),
|
|
181
|
+
size: z.number().int().min(1).max(200).optional(),
|
|
182
|
+
},
|
|
183
|
+
annotations: READ_ONLY,
|
|
184
|
+
}, async ({ accountId, page, size }) => {
|
|
185
|
+
try {
|
|
186
|
+
const result = await client.listTransfers(accountId, { page, size });
|
|
187
|
+
return jsonResult({ count: result.length, transfers: result });
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
return errorResult(e.message);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
135
193
|
server.registerTool("get_transfer", {
|
|
136
194
|
title: "Get transfer",
|
|
137
195
|
description: "Fetch a transfer by id (finance GET /accounts/{accountId}/transfers/{transferId}). Read-only.",
|
|
@@ -165,6 +223,19 @@ export function registerReadTools(server, client) {
|
|
|
165
223
|
return errorResult(e.message);
|
|
166
224
|
}
|
|
167
225
|
});
|
|
226
|
+
server.registerTool("get_party", {
|
|
227
|
+
title: "Get party",
|
|
228
|
+
description: "Fetch an individual or organisation party, including KYC/KYB state when present. Read-only.",
|
|
229
|
+
inputSchema: { partyId: z.string().describe("Party UUID") },
|
|
230
|
+
annotations: READ_ONLY,
|
|
231
|
+
}, async ({ partyId }) => {
|
|
232
|
+
try {
|
|
233
|
+
return jsonResult(await client.getParty(partyId));
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
return errorResult(e.message);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
168
239
|
server.registerTool("get_reference_data", {
|
|
169
240
|
title: "Get reference data",
|
|
170
241
|
description: "Fetch settlement reference data: supported chains, fiat currencies, " +
|
|
@@ -8,16 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { buildDryRun, evaluateWriteGate } from "../safety.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
isError: true,
|
|
20
|
-
};
|
|
11
|
+
import { errorResult, jsonResult } from "../results.js";
|
|
12
|
+
import { normalizeLegacyFiatTransfer } from "../client/sdk-client.js";
|
|
13
|
+
function executionResult(gate, result) {
|
|
14
|
+
return jsonResult({
|
|
15
|
+
mode: gate.environment === "mock" ? "mock" : "live",
|
|
16
|
+
environment: gate.environment,
|
|
17
|
+
result,
|
|
18
|
+
});
|
|
21
19
|
}
|
|
22
20
|
const WRITE_ANNOTATIONS = {
|
|
23
21
|
readOnlyHint: false,
|
|
@@ -30,18 +28,215 @@ const confirmField = z
|
|
|
30
28
|
.default(false)
|
|
31
29
|
.describe("Must be true to attempt a live call. Even then, VENLY_MCP_LIVE=1 and " +
|
|
32
30
|
"credentials are also required, otherwise the tool dry-runs.");
|
|
31
|
+
const addressSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
addressLine1: z.string().optional(),
|
|
34
|
+
addressLine2: z.string().optional(),
|
|
35
|
+
city: z.string().optional(),
|
|
36
|
+
state: z.string().optional(),
|
|
37
|
+
postalCode: z.string().optional(),
|
|
38
|
+
country: z.string().regex(/^[A-Z]{2}$/).optional(),
|
|
39
|
+
})
|
|
40
|
+
.optional();
|
|
41
|
+
const partySchema = z.object({
|
|
42
|
+
partyType: z.enum(["INDIVIDUAL", "ORGANISATION"]),
|
|
43
|
+
externalId: z.string().optional(),
|
|
44
|
+
firstName: z.string().optional(),
|
|
45
|
+
lastName: z.string().optional(),
|
|
46
|
+
name: z.string().optional(),
|
|
47
|
+
vatNumber: z.string().optional(),
|
|
48
|
+
address: addressSchema,
|
|
49
|
+
});
|
|
50
|
+
function partyValidationError(party) {
|
|
51
|
+
if (party.partyType === "INDIVIDUAL" && (!party.firstName || !party.lastName)) {
|
|
52
|
+
return "INDIVIDUAL parties require firstName and lastName";
|
|
53
|
+
}
|
|
54
|
+
if (party.partyType === "ORGANISATION" && !party.name) {
|
|
55
|
+
return "ORGANISATION parties require name";
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
33
59
|
export function registerWriteTools(server, client, env) {
|
|
60
|
+
server.registerTool("create_party", {
|
|
61
|
+
title: "Create a customer or organisation party",
|
|
62
|
+
description: "Create a Finance party. This creates the party record; it does not complete KYC/KYB. Dry-run by default outside explicit mock mode.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
partyType: z.enum(["INDIVIDUAL", "ORGANISATION"]),
|
|
65
|
+
externalId: z.string().optional(),
|
|
66
|
+
firstName: z.string().optional(),
|
|
67
|
+
lastName: z.string().optional(),
|
|
68
|
+
name: z.string().optional(),
|
|
69
|
+
vatNumber: z.string().optional(),
|
|
70
|
+
address: addressSchema,
|
|
71
|
+
confirm: confirmField,
|
|
72
|
+
},
|
|
73
|
+
annotations: WRITE_ANNOTATIONS,
|
|
74
|
+
}, async ({ confirm, ...party }) => {
|
|
75
|
+
const validationError = partyValidationError(party);
|
|
76
|
+
if (validationError)
|
|
77
|
+
return errorResult(validationError);
|
|
78
|
+
const gate = evaluateWriteGate(confirm, env);
|
|
79
|
+
if (!gate.armed) {
|
|
80
|
+
return jsonResult(buildDryRun("create_party", "POST", "finance", "/parties", party, gate));
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return executionResult(gate, await client.createParty(party));
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
return errorResult(e.message);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
server.registerTool("create_account", {
|
|
90
|
+
title: "Create an account and provision its wallet",
|
|
91
|
+
description: "Create a Finance account. Venly auto-provisions its wallet on the selected chain. Supply partyId or an inline party. Dry-run by default.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
externalId: z.string().min(1),
|
|
94
|
+
name: z.string().optional(),
|
|
95
|
+
chain: z.enum(["AVALANCHE", "BASE", "POLYGON"]),
|
|
96
|
+
address: z.string().optional().describe("Required for SELF_CUSTODY tenants"),
|
|
97
|
+
partyId: z.string().optional(),
|
|
98
|
+
party: partySchema.optional(),
|
|
99
|
+
confirm: confirmField,
|
|
100
|
+
},
|
|
101
|
+
annotations: WRITE_ANNOTATIONS,
|
|
102
|
+
}, async ({ confirm, ...body }) => {
|
|
103
|
+
if (!body.partyId && !body.party) {
|
|
104
|
+
return errorResult("create_account requires partyId or an inline party");
|
|
105
|
+
}
|
|
106
|
+
if (body.party) {
|
|
107
|
+
const validationError = partyValidationError(body.party);
|
|
108
|
+
if (validationError)
|
|
109
|
+
return errorResult(validationError);
|
|
110
|
+
}
|
|
111
|
+
const gate = evaluateWriteGate(confirm, env);
|
|
112
|
+
if (!gate.armed) {
|
|
113
|
+
return jsonResult(buildDryRun("create_account", "POST", "finance", "/accounts", body, gate));
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return executionResult(gate, await client.createAccount(body));
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
return errorResult(e.message);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
server.registerTool("create_virtual_bank_account", {
|
|
123
|
+
title: "Create a EUR receiving account",
|
|
124
|
+
description: "Provision a EUR SEPA virtual bank account and conversion target. Outside mock mode the Finance account must have KYC status VERIFIED. Dry-run by default.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
accountId: z.string(),
|
|
127
|
+
name: z.string().min(1),
|
|
128
|
+
inCurrency: z.literal("EUR"),
|
|
129
|
+
targetCryptocurrency: z.enum(["USDC", "EURC", "USDT", "USDS"]),
|
|
130
|
+
idempotencyKey: z.string().min(1).optional(),
|
|
131
|
+
confirm: confirmField,
|
|
132
|
+
},
|
|
133
|
+
annotations: WRITE_ANNOTATIONS,
|
|
134
|
+
}, async ({ accountId, confirm, ...input }) => {
|
|
135
|
+
const body = {
|
|
136
|
+
...input,
|
|
137
|
+
idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
|
|
138
|
+
};
|
|
139
|
+
const gate = evaluateWriteGate(confirm, env);
|
|
140
|
+
if (!gate.armed) {
|
|
141
|
+
const dryRun = buildDryRun("create_virtual_bank_account", "POST", "finance", `/accounts/${accountId}/virtual-bank-accounts`, body, gate);
|
|
142
|
+
dryRun.note += " The account must have KYC status VERIFIED before live provisioning.";
|
|
143
|
+
return jsonResult(dryRun);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return executionResult(gate, await client.createVirtualBankAccount(accountId, body));
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return errorResult(e.message);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
server.registerTool("create_fiat_transfer", {
|
|
153
|
+
title: "Create a fiat-denominated internal transfer",
|
|
154
|
+
description: "Create an account-to-account transfer using the current Finance OpenAPI fields. Dry-run by default outside explicit mock mode.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
senderAccountId: z.string(),
|
|
157
|
+
receiverAccountId: z.string().optional(),
|
|
158
|
+
receiverExternalId: z.string().optional(),
|
|
159
|
+
currency: z.enum(["EUR", "GBP", "USD"]),
|
|
160
|
+
amount: z.number(),
|
|
161
|
+
description: z.string().optional(),
|
|
162
|
+
merchantReference: z.string().optional(),
|
|
163
|
+
idempotencyKey: z.string().min(1).optional(),
|
|
164
|
+
confirm: confirmField,
|
|
165
|
+
},
|
|
166
|
+
annotations: WRITE_ANNOTATIONS,
|
|
167
|
+
}, async ({ senderAccountId, confirm, ...input }) => {
|
|
168
|
+
if (!input.receiverAccountId && !input.receiverExternalId) {
|
|
169
|
+
return errorResult("A receiverAccountId or receiverExternalId is required");
|
|
170
|
+
}
|
|
171
|
+
const body = {
|
|
172
|
+
...input,
|
|
173
|
+
idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
|
|
174
|
+
};
|
|
175
|
+
const gate = evaluateWriteGate(confirm, env);
|
|
176
|
+
if (!gate.armed) {
|
|
177
|
+
return jsonResult(buildDryRun("create_fiat_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/fiat`, body, gate));
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
return executionResult(gate, await client.createCurrentFiatTransfer(senderAccountId, body));
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
return errorResult(e.message);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
server.registerTool("create_crypto_transfer", {
|
|
187
|
+
title: "Create a crypto-denominated internal transfer",
|
|
188
|
+
description: "Create an account-to-account asset transfer using the current Finance OpenAPI fields. Dry-run by default outside explicit mock mode.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
senderAccountId: z.string(),
|
|
191
|
+
receiverAccountId: z.string().optional(),
|
|
192
|
+
receiverExternalId: z.string().optional(),
|
|
193
|
+
chain: z.enum(["AVALANCHE", "BASE", "POLYGON"]),
|
|
194
|
+
asset: z.string().min(1),
|
|
195
|
+
amount: z.number(),
|
|
196
|
+
description: z.string().optional(),
|
|
197
|
+
merchantReference: z.string().optional(),
|
|
198
|
+
idempotencyKey: z.string().min(1).optional(),
|
|
199
|
+
confirm: confirmField,
|
|
200
|
+
},
|
|
201
|
+
annotations: WRITE_ANNOTATIONS,
|
|
202
|
+
}, async ({ senderAccountId, confirm, ...input }) => {
|
|
203
|
+
if (!input.receiverAccountId && !input.receiverExternalId) {
|
|
204
|
+
return errorResult("A receiverAccountId or receiverExternalId is required");
|
|
205
|
+
}
|
|
206
|
+
const body = {
|
|
207
|
+
...input,
|
|
208
|
+
idempotencyKey: input.idempotencyKey ?? crypto.randomUUID(),
|
|
209
|
+
};
|
|
210
|
+
const gate = evaluateWriteGate(confirm, env);
|
|
211
|
+
if (!gate.armed) {
|
|
212
|
+
return jsonResult(buildDryRun("create_crypto_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/crypto`, body, gate));
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
return executionResult(gate, await client.createCryptoTransfer(senderAccountId, body));
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return errorResult(e.message);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
34
221
|
server.registerTool("stage_transfer", {
|
|
35
222
|
title: "Stage a fiat transfer (dry-run by default)",
|
|
36
223
|
description: "Stage a fiat-to-crypto transfer (finance POST /accounts/{senderAccountId}/transfers/fiat). " +
|
|
37
|
-
"
|
|
38
|
-
"
|
|
224
|
+
"Legacy fiatAmount/fiatCurrency inputs are normalized to the current OpenAPI fields; " +
|
|
225
|
+
"the dry-run shows the exact normalized request. DISARMED by default: returns that " +
|
|
226
|
+
"request without sending unless confirm:true AND VENLY_MCP_LIVE=1 AND credentials are present.",
|
|
39
227
|
inputSchema: {
|
|
40
228
|
senderAccountId: z.string().describe("Account initiating the transfer"),
|
|
41
229
|
receiverAccountId: z.string(),
|
|
42
|
-
fiatAmount: z
|
|
230
|
+
fiatAmount: z
|
|
231
|
+
.string()
|
|
232
|
+
.refine((value) => value.trim() !== "" && Number.isFinite(Number(value)), "fiatAmount must be a numeric decimal string")
|
|
233
|
+
.describe("Decimal string, e.g. \"1000.00\""),
|
|
43
234
|
fiatCurrency: z.string().describe("e.g. EUR"),
|
|
44
|
-
cryptocurrency: z
|
|
235
|
+
cryptocurrency: z
|
|
236
|
+
.string()
|
|
237
|
+
.optional()
|
|
238
|
+
.describe("Retired: rejected with guidance. The current contract resolves the fiat " +
|
|
239
|
+
"amount to the account's settlement asset; use create_crypto_transfer instead."),
|
|
45
240
|
description: z.string().optional(),
|
|
46
241
|
merchantReference: z.string().optional(),
|
|
47
242
|
confirm: confirmField,
|
|
@@ -49,20 +244,31 @@ export function registerWriteTools(server, client, env) {
|
|
|
49
244
|
annotations: WRITE_ANNOTATIONS,
|
|
50
245
|
}, async ({ senderAccountId, confirm, ...rest }) => {
|
|
51
246
|
const gate = evaluateWriteGate(confirm, env);
|
|
52
|
-
const
|
|
247
|
+
const legacyInput = {
|
|
53
248
|
receiverAccountId: rest.receiverAccountId,
|
|
54
249
|
fiatAmount: rest.fiatAmount,
|
|
55
250
|
fiatCurrency: rest.fiatCurrency,
|
|
56
251
|
cryptocurrency: rest.cryptocurrency,
|
|
57
252
|
description: rest.description,
|
|
58
253
|
merchantReference: rest.merchantReference,
|
|
254
|
+
idempotencyKey: crypto.randomUUID(),
|
|
59
255
|
};
|
|
256
|
+
// Normalize BEFORE the gate branch so the dry-run preview is byte-for-byte
|
|
257
|
+
// the request a live call would send (and the retired cryptocurrency field
|
|
258
|
+
// is rejected instead of silently dropped).
|
|
259
|
+
let body;
|
|
260
|
+
try {
|
|
261
|
+
body = normalizeLegacyFiatTransfer(legacyInput);
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
return errorResult(e.message);
|
|
265
|
+
}
|
|
60
266
|
if (!gate.armed) {
|
|
61
267
|
return jsonResult(buildDryRun("stage_transfer", "POST", "finance", `/accounts/${senderAccountId}/transfers/fiat`, body, gate));
|
|
62
268
|
}
|
|
63
269
|
try {
|
|
64
|
-
const result = await client.createFiatTransfer(senderAccountId,
|
|
65
|
-
return
|
|
270
|
+
const result = await client.createFiatTransfer(senderAccountId, legacyInput);
|
|
271
|
+
return executionResult(gate, result);
|
|
66
272
|
}
|
|
67
273
|
catch (e) {
|
|
68
274
|
return errorResult(e.message);
|
|
@@ -91,7 +297,7 @@ export function registerWriteTools(server, client, env) {
|
|
|
91
297
|
}
|
|
92
298
|
try {
|
|
93
299
|
const result = await client.approveRampRequest(id, body);
|
|
94
|
-
return
|
|
300
|
+
return executionResult(gate, result);
|
|
95
301
|
}
|
|
96
302
|
catch (e) {
|
|
97
303
|
return errorResult(e.message);
|
|
@@ -118,7 +324,7 @@ export function registerWriteTools(server, client, env) {
|
|
|
118
324
|
}
|
|
119
325
|
try {
|
|
120
326
|
const result = await client.rejectRampRequest(id, body);
|
|
121
|
-
return
|
|
327
|
+
return executionResult(gate, result);
|
|
122
328
|
}
|
|
123
329
|
catch (e) {
|
|
124
330
|
return errorResult(e.message);
|
|
@@ -162,7 +368,7 @@ export function registerWriteTools(server, client, env) {
|
|
|
162
368
|
}
|
|
163
369
|
try {
|
|
164
370
|
const result = await client.createPayInSession(accountId, body);
|
|
165
|
-
return
|
|
371
|
+
return executionResult(gate, result);
|
|
166
372
|
}
|
|
167
373
|
catch (e) {
|
|
168
374
|
return errorResult(e.message);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,108 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Generated API contracts + the injectable VenlyClient interface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* echo are modeled. Fields are intentionally loose (optional) because this is a
|
|
9
|
-
* thin wrapper, not a full SDK.
|
|
10
|
-
*
|
|
11
|
-
* TRANSPORT NOTE: the bundled HttpVenlyClient is a deliberately minimal fetch
|
|
12
|
-
* transport (see client/http-client.ts). A future release replaces it with a
|
|
13
|
-
* thin adapter over `@venlyfinance/sdk` with no change to this interface;
|
|
14
|
-
* until then the minimal transport is what ships. When that lands, replace
|
|
15
|
-
* HttpVenlyClient with a thin adapter over it and delete the vendored transport.
|
|
4
|
+
* Finance and Fundflow resources and requests are aliases to the types exported
|
|
5
|
+
* by `@venlyfinance/sdk`. Only MCP-owned inputs and compatibility shapes are
|
|
6
|
+
* declared locally. This prevents the MCP from silently drifting away from the
|
|
7
|
+
* vendored OpenAPI specifications.
|
|
16
8
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cryptoAmount?: number;
|
|
44
|
-
fiatFeeAmount?: number;
|
|
45
|
-
exchangeRate?: number;
|
|
46
|
-
feePercentage?: number;
|
|
47
|
-
paymentReference?: string;
|
|
48
|
-
paymentReceived?: boolean;
|
|
49
|
-
blockchainTransactionHash?: string;
|
|
50
|
-
createdAt?: string;
|
|
51
|
-
createdBy?: string;
|
|
52
|
-
version?: number;
|
|
53
|
-
}
|
|
54
|
-
/** Finance Account (finance getAccount). */
|
|
55
|
-
export interface Account {
|
|
56
|
-
id: string;
|
|
57
|
-
status?: string;
|
|
58
|
-
reference?: string;
|
|
59
|
-
createdAt?: string;
|
|
60
|
-
updatedAt?: string;
|
|
61
|
-
[key: string]: unknown;
|
|
62
|
-
}
|
|
63
|
-
/** Finance VirtualBankAccount. `referenceCode` is the reconciliation key. */
|
|
64
|
-
export interface VirtualBankAccount {
|
|
65
|
-
id: string;
|
|
66
|
-
accountId?: string;
|
|
67
|
-
bankAccountType?: string;
|
|
68
|
-
name?: string;
|
|
69
|
-
status?: string;
|
|
70
|
-
currency?: string;
|
|
71
|
-
targetCryptocurrency?: string;
|
|
72
|
-
iban?: string;
|
|
73
|
-
bic?: string;
|
|
74
|
-
bankName?: string;
|
|
75
|
-
beneficiaryName?: string;
|
|
76
|
-
referenceCode?: string;
|
|
77
|
-
createdAt?: string;
|
|
78
|
-
updatedAt?: string;
|
|
79
|
-
}
|
|
80
|
-
/** Finance Transfer (finance getTransfer). */
|
|
81
|
-
export interface Transfer {
|
|
82
|
-
id: string;
|
|
83
|
-
status?: string;
|
|
84
|
-
fiatAmount?: string | number;
|
|
85
|
-
fiatCurrency?: string;
|
|
86
|
-
cryptocurrency?: string;
|
|
87
|
-
createdAt?: string;
|
|
88
|
-
[key: string]: unknown;
|
|
89
|
-
}
|
|
90
|
-
/** Finance Party (finance listParties). */
|
|
91
|
-
export interface Party {
|
|
92
|
-
id: string;
|
|
93
|
-
type?: string;
|
|
94
|
-
status?: string;
|
|
95
|
-
[key: string]: unknown;
|
|
96
|
-
}
|
|
97
|
-
/** A fiat-to-crypto payment session (finance PaymentSession). */
|
|
98
|
-
export interface PaymentSession {
|
|
99
|
-
id: string;
|
|
100
|
-
accountId?: string;
|
|
101
|
-
paymentUrl?: string;
|
|
102
|
-
externalRef?: string;
|
|
103
|
-
status?: string;
|
|
104
|
-
[key: string]: unknown;
|
|
105
|
-
}
|
|
9
|
+
import type { FinanceComponents, FundflowComponents } from "@venlyfinance/sdk";
|
|
10
|
+
import type { VenlyEnvironment } from "./constants.js";
|
|
11
|
+
type FinanceSchemas = FinanceComponents["schemas"];
|
|
12
|
+
type FundflowSchemas = FundflowComponents["schemas"];
|
|
13
|
+
export type AddressInput = FinanceSchemas["Address"];
|
|
14
|
+
export type Party = FinanceSchemas["Party"];
|
|
15
|
+
export type CreatePartyInput = FinanceSchemas["CreatePartyRequest"];
|
|
16
|
+
export type Account = FinanceSchemas["Account"];
|
|
17
|
+
export type CreateAccountInput = FinanceSchemas["CreateAccountRequest"];
|
|
18
|
+
export type Wallet = FinanceSchemas["Wallet"];
|
|
19
|
+
export type VirtualBankAccount = FinanceSchemas["VirtualBankAccount"];
|
|
20
|
+
export type CreateVirtualBankAccountInput = FinanceSchemas["CreateVirtualBankAccountRequest"];
|
|
21
|
+
export type PaymentSession = FinanceSchemas["PaymentSession"];
|
|
22
|
+
export type CreatePayInSessionRequest = FinanceSchemas["CreatePayInSessionRequest"];
|
|
23
|
+
export type Transfer = FinanceSchemas["Transfer"];
|
|
24
|
+
export type CurrentCreateFiatTransferInput = FinanceSchemas["CreateFiatTransferInput"];
|
|
25
|
+
export type CreateCryptoTransferInput = FinanceSchemas["CreateCryptoTransferInput"];
|
|
26
|
+
export type RampRequestDto = FundflowSchemas["RampRequestDto"];
|
|
27
|
+
export type RampRequestListItem = FundflowSchemas["RampRequestListItem"];
|
|
28
|
+
export type OptimisticLockingBody = FundflowSchemas["UpdateWithOptimisticLockingRequest"];
|
|
29
|
+
export type SupportedChains = FundflowSchemas["SupportedChainsDto"];
|
|
30
|
+
export type FiatCurrency = FundflowSchemas["FiatCurrencyDto"];
|
|
31
|
+
export type CryptoCurrency = FundflowSchemas["CryptoCurrencyDto"];
|
|
32
|
+
export type VenlyFee = FundflowSchemas["FeeDto"];
|
|
33
|
+
export type RampStatus = NonNullable<RampRequestDto["status"]>;
|
|
34
|
+
export type RampType = NonNullable<RampRequestDto["rampType"]>;
|
|
106
35
|
/**
|
|
107
36
|
* An observed incoming bank transaction on a vIBAN. This is operator- or
|
|
108
37
|
* bank-feed-supplied data (there is no list-vIBAN-transactions endpoint in the
|
|
@@ -127,54 +56,67 @@ export interface ListRampRequestsParams {
|
|
|
127
56
|
page?: number;
|
|
128
57
|
size?: number;
|
|
129
58
|
}
|
|
130
|
-
/**
|
|
59
|
+
/** Legacy stage_transfer body, normalized to the current finance
|
|
60
|
+
* CreateFiatTransferInput before any call (see normalizeLegacyFiatTransfer). */
|
|
131
61
|
export interface CreateFiatTransferInput {
|
|
132
62
|
receiverAccountId: string;
|
|
133
63
|
receiverExternalId?: string;
|
|
134
64
|
fiatAmount: string;
|
|
135
65
|
fiatCurrency: string;
|
|
66
|
+
/** Retired: the current contract has no such field. Normalization rejects it
|
|
67
|
+
* instead of silently dropping it. */
|
|
136
68
|
cryptocurrency?: string;
|
|
137
69
|
description?: string;
|
|
138
70
|
merchantReference?: string;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
export interface OptimisticLockingBody {
|
|
142
|
-
version: number;
|
|
143
|
-
}
|
|
144
|
-
/** Body for the payment session POST (finance CreatePayInSessionRequest). */
|
|
145
|
-
export interface CreatePayInSessionRequest {
|
|
146
|
-
inAmount: string;
|
|
147
|
-
inCurrency: string;
|
|
148
|
-
outCryptocurrency: string;
|
|
149
|
-
callbackUrl: string;
|
|
150
|
-
idempotencyKey: string;
|
|
151
|
-
successRedirectUrl?: string;
|
|
152
|
-
failureRedirectUrl?: string;
|
|
153
|
-
externalRef?: string;
|
|
154
|
-
metadata?: Record<string, string>;
|
|
71
|
+
/** Preserved across the dry-run preview and the live call when supplied. */
|
|
72
|
+
idempotencyKey?: string;
|
|
155
73
|
}
|
|
156
74
|
/**
|
|
157
|
-
* The injectable Venly
|
|
158
|
-
* implementation; tests inject a mock. The MCP layer depends
|
|
159
|
-
*
|
|
160
|
-
*
|
|
75
|
+
* The injectable Venly client contract. SdkVenlyClient is the production
|
|
76
|
+
* implementation; tests inject a lightweight mock. The MCP layer depends only
|
|
77
|
+
* on this interface, keeping the fail-closed write path testable without a
|
|
78
|
+
* network.
|
|
161
79
|
*/
|
|
162
80
|
export interface VenlyClient {
|
|
81
|
+
/** The environment this client actually targets. When present, createServer
|
|
82
|
+
* refuses to start if it disagrees with the VENLY_ENV the write gate reads –
|
|
83
|
+
* the mock gate auto-arms writes, so the two must never diverge. */
|
|
84
|
+
readonly environment?: VenlyEnvironment;
|
|
163
85
|
listRampRequests(params?: ListRampRequestsParams): Promise<RampRequestListItem[]>;
|
|
164
86
|
getRampRequest(id: string): Promise<RampRequestDto>;
|
|
87
|
+
listAccounts(params?: {
|
|
88
|
+
page?: number;
|
|
89
|
+
size?: number;
|
|
90
|
+
}): Promise<Account[]>;
|
|
165
91
|
getAccount(accountId: string): Promise<Account>;
|
|
92
|
+
listWallets(accountId: string, params?: {
|
|
93
|
+
page?: number;
|
|
94
|
+
size?: number;
|
|
95
|
+
}): Promise<Wallet[]>;
|
|
166
96
|
listVirtualBankAccounts(accountId: string): Promise<VirtualBankAccount[]>;
|
|
97
|
+
getVirtualBankAccount(accountId: string, virtualBankAccountId: string): Promise<VirtualBankAccount>;
|
|
98
|
+
listTransfers(accountId: string, params?: {
|
|
99
|
+
page?: number;
|
|
100
|
+
size?: number;
|
|
101
|
+
}): Promise<Transfer[]>;
|
|
167
102
|
getTransfer(accountId: string, transferId: string): Promise<Transfer>;
|
|
168
103
|
listParties(params?: {
|
|
169
104
|
page?: number;
|
|
170
105
|
size?: number;
|
|
171
106
|
}): Promise<Party[]>;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
107
|
+
getParty(partyId: string): Promise<Party>;
|
|
108
|
+
getSupportedChains(): Promise<SupportedChains[]>;
|
|
109
|
+
getFiatCurrencies(): Promise<FiatCurrency[]>;
|
|
110
|
+
getCryptocurrencies(): Promise<CryptoCurrency[]>;
|
|
111
|
+
getCompanyFees(): Promise<VenlyFee[]>;
|
|
112
|
+
createParty(body: CreatePartyInput): Promise<Party>;
|
|
113
|
+
createAccount(body: CreateAccountInput): Promise<Account>;
|
|
114
|
+
createVirtualBankAccount(accountId: string, body: CreateVirtualBankAccountInput): Promise<VirtualBankAccount>;
|
|
176
115
|
createFiatTransfer(senderAccountId: string, body: CreateFiatTransferInput): Promise<Transfer>;
|
|
116
|
+
createCurrentFiatTransfer(senderAccountId: string, body: CurrentCreateFiatTransferInput): Promise<Transfer>;
|
|
117
|
+
createCryptoTransfer(senderAccountId: string, body: CreateCryptoTransferInput): Promise<Transfer>;
|
|
177
118
|
approveRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
|
|
178
119
|
rejectRampRequest(id: string, body: OptimisticLockingBody): Promise<RampRequestDto>;
|
|
179
120
|
createPayInSession(accountId: string, body: CreatePayInSessionRequest): Promise<PaymentSession>;
|
|
180
121
|
}
|
|
122
|
+
export {};
|