@blanketbyte/wise-mcp 1.0.7 → 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 +15 -7
- package/dist/wise/service.js +16 -6
- 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) => ({
|
|
@@ -273,6 +273,7 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
273
273
|
(quote.fee ? `, fee ${money(quote.fee.amount, sourceCurrency)}` : "") +
|
|
274
274
|
(sourceCurrency !== recipient.currency ? `, rate ${quote.rate}` : "") +
|
|
275
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" : "") +
|
|
276
277
|
(details.reference ? `, reference "${details.reference}"` : "") +
|
|
277
278
|
(quote.delivery ? `, ${stripHtml(quote.delivery)}` : "");
|
|
278
279
|
const { code, expiresAt } = storePending(sessionKey, {
|
|
@@ -282,7 +283,7 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
282
283
|
targetAccount: recipient.id,
|
|
283
284
|
customerTransactionId: randomUUID(),
|
|
284
285
|
details,
|
|
285
|
-
expected: { amount: quote.target.amount, currency: recipient.currency },
|
|
286
|
+
expected: { amount: quote.target.amount, currency: recipient.currency, options: quote.pay_in_options.map((o) => Number(o.target_amount)) },
|
|
286
287
|
}, summary);
|
|
287
288
|
await log("transfer_prepared", { code, summary });
|
|
288
289
|
return ok({
|
|
@@ -328,11 +329,12 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
328
329
|
try {
|
|
329
330
|
if (a.kind === "transfer") {
|
|
330
331
|
const t = await wise.createTransfer({ targetAccount: a.targetAccount, quoteId: a.quoteId, customerTransactionId: a.customerTransactionId, details: a.details });
|
|
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.
|
|
334
335
|
const got = { amount: Number(t.target.amount), currency: String(t.target.currency ?? "").toUpperCase() };
|
|
335
|
-
|
|
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))) {
|
|
336
338
|
const cancelled = await wise.cancelTransfer(t.id).then((c) => c.status, (err) => `cancel failed: ${err instanceof Error ? err.message : err}`);
|
|
337
339
|
await log("transfer_amount_mismatch", {
|
|
338
340
|
code: confirmation_code,
|
|
@@ -356,7 +358,13 @@ function registerWriteTools(server, wise, { sessionKey, connectionId }, safe) {
|
|
|
356
358
|
transfer_id: t.id,
|
|
357
359
|
wise_status: t.status,
|
|
358
360
|
summary: p.summary,
|
|
359
|
-
|
|
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.",
|
|
360
368
|
});
|
|
361
369
|
}
|
|
362
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,8 +228,7 @@ export class WiseService {
|
|
|
220
228
|
sourceAmount: q.sourceAmount ?? null,
|
|
221
229
|
targetAmount: q.targetAmount ?? null,
|
|
222
230
|
targetAccount: q.targetAccount,
|
|
223
|
-
//
|
|
224
|
-
// though the user pays from their Wise balance in the app. BALANCE goes first and the transfer uses it.
|
|
231
|
+
// The user pays from their Wise balance in the app: list (and show) that price first.
|
|
225
232
|
preferredPayIn: "BALANCE",
|
|
226
233
|
});
|
|
227
234
|
return mapQuote(raw);
|
|
@@ -296,6 +303,8 @@ function mapActivity(a) {
|
|
|
296
303
|
function matches(a, search) {
|
|
297
304
|
return [a.title, a.description, a.type, a.secondary_text, a.amount_text].some((v) => v?.toLowerCase().includes(search));
|
|
298
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.";
|
|
299
308
|
function mapTransfer(t) {
|
|
300
309
|
return {
|
|
301
310
|
id: t.id,
|
|
@@ -322,9 +331,8 @@ function mapRecipient(r) {
|
|
|
322
331
|
}
|
|
323
332
|
function mapQuote(q) {
|
|
324
333
|
const options = (q.paymentOptions ?? []).filter((o) => !o.disabled && (!q.payOut || o.payOut === q.payOut));
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
const o = options.find((x) => q.payIn && x.payIn === q.payIn) ?? options[0];
|
|
334
|
+
// The user pays in the Wise app, usually from the balance: show that price (createQuote lists BALANCE first).
|
|
335
|
+
const o = options.find((x) => x.payIn === "BALANCE") ?? options[0];
|
|
328
336
|
return {
|
|
329
337
|
id: String(q.id),
|
|
330
338
|
source: { amount: o?.sourceAmount ?? q.sourceAmount, currency: q.sourceCurrency },
|
|
@@ -338,6 +346,8 @@ function mapQuote(q) {
|
|
|
338
346
|
estimated_delivery: o?.estimatedDelivery,
|
|
339
347
|
delivery: o?.formattedEstimatedDelivery,
|
|
340
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 })),
|
|
341
351
|
};
|
|
342
352
|
}
|
|
343
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": [
|