@blanketbyte/wise-mcp 1.0.6 → 1.0.8
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/dist/tools.js +21 -11
- package/dist/wise/service.js +16 -2
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -3,7 +3,7 @@ import { appendFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { config } from "./config.js";
|
|
6
|
-
import { ACTIVITY_TYPES } from "./wise/service.js";
|
|
6
|
+
import { ACTIVITY_TYPES, UNPAID_PRICE_NOTE } from "./wise/service.js";
|
|
7
7
|
import { WiseAccessRevokedError } from "./wise/client.js";
|
|
8
8
|
import { storePending, takePending, discardPending } from "./pending.js";
|
|
9
9
|
const ok = (data) => ({
|
|
@@ -272,6 +272,8 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
272
272
|
`${recipient.name} (${recipient.account ?? recipient.type}) receives ${money(quote.target.amount, recipient.currency)}` +
|
|
273
273
|
(quote.fee ? `, fee ${money(quote.fee.amount, sourceCurrency)}` : "") +
|
|
274
274
|
(sourceCurrency !== recipient.currency ? `, rate ${quote.rate}` : "") +
|
|
275
|
+
(quote.pay_in && quote.pay_in !== "BALANCE" ? `, priced for paying by ${quote.pay_in}` : "") +
|
|
276
|
+
(quote.pay_in === "BALANCE" ? ", when paid from your Wise balance" : "") +
|
|
275
277
|
(details.reference ? `, reference "${details.reference}"` : "") +
|
|
276
278
|
(quote.delivery ? `, ${stripHtml(quote.delivery)}` : "");
|
|
277
279
|
const { code, expiresAt } = storePending(sessionKey, {
|
|
@@ -281,7 +283,7 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
281
283
|
targetAccount: recipient.id,
|
|
282
284
|
customerTransactionId: randomUUID(),
|
|
283
285
|
details,
|
|
284
|
-
expected: { amount: quote.
|
|
286
|
+
expected: { amount: quote.target.amount, currency: recipient.currency, options: quote.pay_in_options.map((o) => Number(o.target_amount)) },
|
|
285
287
|
}, summary);
|
|
286
288
|
await log("transfer_prepared", { code, summary });
|
|
287
289
|
return ok({
|
|
@@ -290,7 +292,7 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
290
292
|
quote,
|
|
291
293
|
balance_check: available >= quote.source.amount
|
|
292
294
|
? `OK: ${money(available, sourceCurrency)} available`
|
|
293
|
-
: `Not enough ${sourceCurrency} balance (${money(available, sourceCurrency)})
|
|
295
|
+
: `Not enough ${sourceCurrency} balance (${money(available, sourceCurrency)}): the price assumes paying from the Wise balance, so tell the user to top it up first (paying another way in the app can cost more)`,
|
|
294
296
|
confirmation_code: code,
|
|
295
297
|
expires_at: expiresAt.toISOString(),
|
|
296
298
|
next_step: "Show the summary. Only call confirm_action if the user explicitly approves. Afterwards they pay it in the Wise app.",
|
|
@@ -327,10 +329,12 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
327
329
|
try {
|
|
328
330
|
if (a.kind === "transfer") {
|
|
329
331
|
const t = await wise.createTransfer({ targetAccount: a.targetAccount, quoteId: a.quoteId, customerTransactionId: a.customerTransactionId, details: a.details });
|
|
330
|
-
// Wise
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
// Wise shows a new (unpaid) transfer at the bank-transfer price of its quote and re-prices it when it's paid
|
|
333
|
+
// from the balance. Anything that isn't one of this quote's prices is a transfer the user didn't approve:
|
|
334
|
+
// it's still unpaid, so cancel it and say what happened.
|
|
335
|
+
const got = { amount: Number(t.target.amount), currency: String(t.target.currency ?? "").toUpperCase() };
|
|
336
|
+
const close = (x) => Math.abs(got.amount - x) <= 0.005;
|
|
337
|
+
if (got.currency !== a.expected.currency.toUpperCase() || !(close(a.expected.amount) || a.expected.options.some(close))) {
|
|
334
338
|
const cancelled = await wise.cancelTransfer(t.id).then((c) => c.status, (err) => `cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
335
339
|
await log("transfer_amount_mismatch", {
|
|
336
340
|
code: confirmation_code,
|
|
@@ -341,12 +345,12 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
341
345
|
got,
|
|
342
346
|
cancelled,
|
|
343
347
|
});
|
|
344
|
-
return fail(`Wise created transfer ${t.id}
|
|
345
|
-
`${money(a.expected.amount, a.expected.currency)} (quote ${a.quoteId}). ` +
|
|
348
|
+
return fail(`Wise created transfer ${t.id} where the recipient gets ${money(got.amount, got.currency)} instead of the approved ` +
|
|
349
|
+
`${money(a.expected.amount, a.expected.currency)} (quote ${a.quoteId}), so the price differs from the summary. ` +
|
|
346
350
|
(cancelled === "cancelled"
|
|
347
351
|
? "It was cancelled automatically, so nothing will be paid. "
|
|
348
352
|
: `Automatic cancellation didn't succeed (${cancelled}): tell the user to cancel it in the Wise app and NOT pay it. `) +
|
|
349
|
-
"Don't retry; tell the user
|
|
353
|
+
"Don't retry; tell the user.");
|
|
350
354
|
}
|
|
351
355
|
await log("transfer_created", { code: confirmation_code, summary: p.summary, transfer_id: t.id, status: t.status });
|
|
352
356
|
return ok({
|
|
@@ -354,7 +358,13 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
354
358
|
transfer_id: t.id,
|
|
355
359
|
wise_status: t.status,
|
|
356
360
|
summary: p.summary,
|
|
357
|
-
|
|
361
|
+
...(close(a.expected.amount)
|
|
362
|
+
? {}
|
|
363
|
+
: {
|
|
364
|
+
wise_shows_until_paid: `${money(got.amount, got.currency)} to the recipient (Wise's bank-transfer price)`,
|
|
365
|
+
note: `${UNPAID_PRICE_NOTE} Paid from the balance, the recipient gets the approved ${money(a.expected.amount, a.expected.currency)}.`,
|
|
366
|
+
}),
|
|
367
|
+
next_step: "Open the Wise app (or wise.com) and pay it from your Wise balance. No money has moved yet.",
|
|
358
368
|
});
|
|
359
369
|
}
|
|
360
370
|
if (a.kind === "recipient") {
|
package/dist/wise/service.js
CHANGED
|
@@ -189,7 +189,15 @@ export class WiseService {
|
|
|
189
189
|
async getTransfer(id) {
|
|
190
190
|
const t = await this.client.get(`/v1/transfers/${id}`);
|
|
191
191
|
const recipient = t.targetAccount ? await this.getRecipient(t.targetAccount).catch(() => undefined) : undefined;
|
|
192
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
...mapTransfer(t),
|
|
194
|
+
recipient,
|
|
195
|
+
rate: t.rate,
|
|
196
|
+
quote_id: t.quoteUuid ?? t.quote,
|
|
197
|
+
has_active_issues: t.hasActiveIssues,
|
|
198
|
+
raw_status: t.status,
|
|
199
|
+
note: t.status === "incoming_payment_waiting" ? UNPAID_PRICE_NOTE : undefined,
|
|
200
|
+
};
|
|
193
201
|
}
|
|
194
202
|
// --- Recipients -----------------------------------------------------------------
|
|
195
203
|
async listRecipients(profiles, currency) {
|
|
@@ -220,6 +228,8 @@ export class WiseService {
|
|
|
220
228
|
sourceAmount: q.sourceAmount ?? null,
|
|
221
229
|
targetAmount: q.targetAmount ?? null,
|
|
222
230
|
targetAccount: q.targetAccount,
|
|
231
|
+
// The user pays from their Wise balance in the app: list (and show) that price first.
|
|
232
|
+
preferredPayIn: "BALANCE",
|
|
223
233
|
});
|
|
224
234
|
return mapQuote(raw);
|
|
225
235
|
}
|
|
@@ -293,6 +303,8 @@ function mapActivity(a) {
|
|
|
293
303
|
function matches(a, search) {
|
|
294
304
|
return [a.title, a.description, a.type, a.secondary_text, a.amount_text].some((v) => v?.toLowerCase().includes(search));
|
|
295
305
|
}
|
|
306
|
+
/** Wise prices a transfer made via the API as a bank-transfer pay-in until it's paid (seen: 1 EUR → 0.05 EUR, fee 0.95). */
|
|
307
|
+
export const UNPAID_PRICE_NOTE = "Amounts of an unpaid transfer are Wise's bank-transfer price (with its fee). Paying it from the Wise balance in the app re-prices it, e.g. without that fee.";
|
|
296
308
|
function mapTransfer(t) {
|
|
297
309
|
return {
|
|
298
310
|
id: t.id,
|
|
@@ -319,7 +331,7 @@ function mapRecipient(r) {
|
|
|
319
331
|
}
|
|
320
332
|
function mapQuote(q) {
|
|
321
333
|
const options = (q.paymentOptions ?? []).filter((o) => !o.disabled && (!q.payOut || o.payOut === q.payOut));
|
|
322
|
-
//
|
|
334
|
+
// The user pays in the Wise app, usually from the balance: show that price (createQuote lists BALANCE first).
|
|
323
335
|
const o = options.find((x) => x.payIn === "BALANCE") ?? options[0];
|
|
324
336
|
return {
|
|
325
337
|
id: String(q.id),
|
|
@@ -334,6 +346,8 @@ function mapQuote(q) {
|
|
|
334
346
|
estimated_delivery: o?.estimatedDelivery,
|
|
335
347
|
delivery: o?.formattedEstimatedDelivery,
|
|
336
348
|
notices: (q.notices ?? []).map((n) => stripTags(n.text)).filter(Boolean),
|
|
349
|
+
// Price per pay-in method: an unpaid transfer shows the BANK_TRANSFER one until it's paid.
|
|
350
|
+
pay_in_options: options.map((x) => ({ pay_in: x.payIn, fee: x.fee?.total, target_amount: x.targetAmount })),
|
|
337
351
|
};
|
|
338
352
|
}
|
|
339
353
|
function mapRequirementField(g) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blanketbyte/wise-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "MCP server for Wise: read balances, card payments, transfers and recipients, and prepare transfers you approve twice. Runs locally (npx, for Claude, Codex, Cursor…) or as a remote connector for Claude and ChatGPT.",
|
|
5
5
|
"mcpName": "io.github.BlanketByte/wise-mcp",
|
|
6
6
|
"keywords": [
|