monobank-mcp 2.0.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/README.md +174 -0
- package/dist/cache/dedup.d.ts +1 -0
- package/dist/cache/dedup.js +10 -0
- package/dist/cache/dedup.js.map +1 -0
- package/dist/cache/ttl-cache.d.ts +6 -0
- package/dist/cache/ttl-cache.js +20 -0
- package/dist/cache/ttl-cache.js.map +1 -0
- package/dist/client/base.d.ts +5 -0
- package/dist/client/base.js +25 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/corporate.d.ts +26 -0
- package/dist/client/corporate.js +70 -0
- package/dist/client/corporate.js.map +1 -0
- package/dist/client/personal.d.ts +10 -0
- package/dist/client/personal.js +34 -0
- package/dist/client/personal.js.map +1 -0
- package/dist/client/retry.d.ts +1 -0
- package/dist/client/retry.js +32 -0
- package/dist/client/retry.js.map +1 -0
- package/dist/client/signing.d.ts +6 -0
- package/dist/client/signing.js +24 -0
- package/dist/client/signing.js.map +1 -0
- package/dist/errors/index.d.ts +12 -0
- package/dist/errors/index.js +25 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +89 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/account.d.ts +4 -0
- package/dist/tools/account.js +75 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/corporate-auth.d.ts +3 -0
- package/dist/tools/corporate-auth.js +71 -0
- package/dist/tools/corporate-auth.js.map +1 -0
- package/dist/tools/corporate-settings.d.ts +3 -0
- package/dist/tools/corporate-settings.js +62 -0
- package/dist/tools/corporate-settings.js.map +1 -0
- package/dist/tools/currency.d.ts +4 -0
- package/dist/tools/currency.js +51 -0
- package/dist/tools/currency.js.map +1 -0
- package/dist/tools/jars.d.ts +4 -0
- package/dist/tools/jars.js +59 -0
- package/dist/tools/jars.js.map +1 -0
- package/dist/tools/statement.d.ts +4 -0
- package/dist/tools/statement.js +159 -0
- package/dist/tools/statement.js.map +1 -0
- package/dist/tools/webhook.d.ts +4 -0
- package/dist/tools/webhook.js +101 -0
- package/dist/tools/webhook.js.map +1 -0
- package/dist/types/monobank.d.ts +58 -0
- package/dist/types/monobank.js +13 -0
- package/dist/types/monobank.js.map +1 -0
- package/package.json +40 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { TtlCache } from './cache/ttl-cache.js';
|
|
4
|
+
import { CorporateClient } from './client/corporate.js';
|
|
5
|
+
import { PersonalClient } from './client/personal.js';
|
|
6
|
+
import { apiFetch } from './client/base.js';
|
|
7
|
+
import { withRetry } from './client/retry.js';
|
|
8
|
+
import { loadPrivateKey } from './client/signing.js';
|
|
9
|
+
import { registerAccountTools } from './tools/account.js';
|
|
10
|
+
import { registerCorporateAuthTools } from './tools/corporate-auth.js';
|
|
11
|
+
import { registerCorporateSettingsTools } from './tools/corporate-settings.js';
|
|
12
|
+
import { registerCurrencyTools } from './tools/currency.js';
|
|
13
|
+
import { registerJarTools } from './tools/jars.js';
|
|
14
|
+
import { registerStatementTools } from './tools/statement.js';
|
|
15
|
+
import { registerWebhookTools } from './tools/webhook.js';
|
|
16
|
+
export function createServer() {
|
|
17
|
+
const mode = (process.env.MONOBANK_AUTH_MODE ?? 'personal');
|
|
18
|
+
const server = new McpServer({ name: 'monobank-mcp', version: '1.0.0' }, {
|
|
19
|
+
capabilities: {
|
|
20
|
+
logging: {},
|
|
21
|
+
},
|
|
22
|
+
instructions: 'Monobank banking MCP server. Provides access to Ukrainian bank account balances, ' +
|
|
23
|
+
'transaction history (statements), currency exchange rates, savings jars, and webhook management. ' +
|
|
24
|
+
(mode === 'personal'
|
|
25
|
+
? 'Rate limit: 1 request per 60 seconds for personal API endpoints (client-info, statement). '
|
|
26
|
+
: 'Corporate mode: no rate limits. Per-user endpoints require a requestId from the auth flow. ') +
|
|
27
|
+
'Results are cached automatically. Use get_client_info first to discover available accounts.',
|
|
28
|
+
});
|
|
29
|
+
const cache = new TtlCache();
|
|
30
|
+
if (mode === 'corporate') {
|
|
31
|
+
const keyId = process.env.MONOBANK_KEY_ID;
|
|
32
|
+
if (!keyId)
|
|
33
|
+
throw new Error('MONOBANK_KEY_ID is required for corporate mode');
|
|
34
|
+
const privateKeyPem = loadPrivateKey();
|
|
35
|
+
const client = new CorporateClient(keyId, privateKeyPem);
|
|
36
|
+
registerCorporateAuthTools(server, client);
|
|
37
|
+
registerCorporateSettingsTools(server, client);
|
|
38
|
+
registerCurrencyTools(server, client, cache);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const token = process.env.MONOBANK_TOKEN;
|
|
42
|
+
if (!token)
|
|
43
|
+
throw new Error('MONOBANK_TOKEN is required for personal mode');
|
|
44
|
+
const client = new PersonalClient(token);
|
|
45
|
+
registerAccountTools(server, client, cache);
|
|
46
|
+
registerStatementTools(server, client, cache);
|
|
47
|
+
registerCurrencyTools(server, client, cache);
|
|
48
|
+
registerWebhookTools(server, client, cache);
|
|
49
|
+
registerJarTools(server, client, cache);
|
|
50
|
+
}
|
|
51
|
+
registerResources(server, mode, cache);
|
|
52
|
+
registerPrompts(server);
|
|
53
|
+
return server;
|
|
54
|
+
}
|
|
55
|
+
function registerResources(server, _mode, cache) {
|
|
56
|
+
server.registerResource('exchange-rates', 'monobank://exchange-rates', {
|
|
57
|
+
description: 'Current Monobank exchange rates for all currency pairs',
|
|
58
|
+
mimeType: 'application/json',
|
|
59
|
+
}, async (uri) => {
|
|
60
|
+
const cacheKey = 'resource:exchange-rates';
|
|
61
|
+
const cached = cache.get(cacheKey);
|
|
62
|
+
if (cached) {
|
|
63
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: cached }] };
|
|
64
|
+
}
|
|
65
|
+
const rates = await withRetry(() => apiFetch('/bank/currency'));
|
|
66
|
+
const text = JSON.stringify(rates, null, 2);
|
|
67
|
+
cache.set(cacheKey, text, 300);
|
|
68
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text }] };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function registerPrompts(server) {
|
|
72
|
+
server.registerPrompt('spending-analysis', {
|
|
73
|
+
title: 'Analyze Spending',
|
|
74
|
+
description: 'Analyze spending patterns for a given time period',
|
|
75
|
+
argsSchema: {
|
|
76
|
+
period: z.enum(['week', 'month', 'quarter']).describe('Time period to analyze'),
|
|
77
|
+
account_id: z.string().optional().describe('Specific account ID (optional, defaults to main card)'),
|
|
78
|
+
},
|
|
79
|
+
}, async ({ period, account_id }) => ({
|
|
80
|
+
messages: [{
|
|
81
|
+
role: 'user',
|
|
82
|
+
content: {
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: `Analyze my spending for the last ${period}. ${account_id ? `Focus on account ${account_id}.` : 'Use the default card.'} Steps:\n1. Use get_statement to fetch transactions for the period\n2. Categorize transactions by MCC code\n3. Calculate totals per category\n4. Identify the largest expenses\n5. Note any unusual spending patterns`,
|
|
85
|
+
},
|
|
86
|
+
}],
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TtlCache } from '../cache/ttl-cache.js';
|
|
3
|
+
import type { PersonalClient } from '../client/personal.js';
|
|
4
|
+
export declare function registerAccountTools(server: McpServer, client: PersonalClient, cache: TtlCache): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { AuthError, RateLimitError } from '../errors/index.js';
|
|
2
|
+
import { CURRENCY_SYMBOLS, formatAmount } from '../types/monobank.js';
|
|
3
|
+
export function registerAccountTools(server, client, cache) {
|
|
4
|
+
server.registerTool('get_client_info', {
|
|
5
|
+
title: 'Get Client Info',
|
|
6
|
+
description: 'Get client profile, all bank accounts and savings jars. Includes balances, IBANs, and account types. Results are cached for 59 seconds.',
|
|
7
|
+
annotations: {
|
|
8
|
+
readOnlyHint: true,
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
idempotentHint: true,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
},
|
|
13
|
+
}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
const cacheKey = 'client-info';
|
|
16
|
+
const cached = cache.get(cacheKey);
|
|
17
|
+
if (cached) {
|
|
18
|
+
return { content: [{ type: 'text', text: formatClientInfo(cached) }] };
|
|
19
|
+
}
|
|
20
|
+
const info = await client.getClientInfo();
|
|
21
|
+
cache.set(cacheKey, info, 59);
|
|
22
|
+
return { content: [{ type: 'text', text: formatClientInfo(info) }] };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return handleToolError(err);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function formatClientInfo(info) {
|
|
30
|
+
const formatted = {
|
|
31
|
+
name: info.name,
|
|
32
|
+
accounts: (info.accounts ?? []).map((a) => ({
|
|
33
|
+
id: a.id,
|
|
34
|
+
iban: a.iban,
|
|
35
|
+
type: a.type,
|
|
36
|
+
currency: CURRENCY_SYMBOLS[a.currencyCode] ?? a.currencyCode,
|
|
37
|
+
balance: formatAmount(a.balance),
|
|
38
|
+
creditLimit: a.creditLimit > 0 ? formatAmount(a.creditLimit) : undefined,
|
|
39
|
+
cashback: a.cashbackType,
|
|
40
|
+
})),
|
|
41
|
+
jars: (info.jars ?? []).map((j) => ({
|
|
42
|
+
id: j.id,
|
|
43
|
+
title: j.title,
|
|
44
|
+
balance: formatAmount(j.balance),
|
|
45
|
+
goal: j.goal > 0 ? formatAmount(j.goal) : undefined,
|
|
46
|
+
currency: CURRENCY_SYMBOLS[j.currencyCode] ?? j.currencyCode,
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
return JSON.stringify(formatted, null, 2);
|
|
50
|
+
}
|
|
51
|
+
function handleToolError(err) {
|
|
52
|
+
if (err instanceof AuthError) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: `Authentication failed: ${err.message}` }],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (err instanceof RateLimitError) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: `Rate limit hit. Monobank allows 1 request per 60 seconds. Retry in ${err.retryAfterSeconds ?? 60}s. Cached results are served automatically.`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/tools/account.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAmB,gBAAgB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvF,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,MAAsB,EACtB,KAAe;IAEf,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EACT,yIAAyI;QAC3I,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,aAAa,CAAC;YAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAa,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAClF,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;YAC1C,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAE9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAgB;IACxC,MAAM,SAAS,GAAG;QAChB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,YAAY;YAC5D,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;YAChC,WAAW,EAAE,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;YACxE,QAAQ,EAAE,CAAC,CAAC,YAAY;SACzB,CAAC,CAAC;QACH,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;YAChC,IAAI,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACnD,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,YAAY;SAC7D,CAAC,CAAC;KACJ,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,0BAA0B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;QAClC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,sEAAsE,GAAG,CAAC,iBAAiB,IAAI,EAAE,6CAA6C;iBACrJ;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError } from '../errors/index.js';
|
|
3
|
+
export function registerCorporateAuthTools(server, client) {
|
|
4
|
+
server.registerTool('initiate_authorization', {
|
|
5
|
+
title: 'Initiate Authorization',
|
|
6
|
+
description: '[Corporate API only] Start the user authorization flow. Returns a URL the user must open in their Monobank app to approve access. Poll check_authorization with the returned requestId to confirm approval.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
callback_url: z.string().url()
|
|
9
|
+
.describe('Publicly accessible HTTPS URL where Monobank will POST the authorization result'),
|
|
10
|
+
permissions: z.string().optional().default('sp')
|
|
11
|
+
.describe("Permission flags: 's' = statements, 'p' = personal info. Default: 'sp' (both)"),
|
|
12
|
+
},
|
|
13
|
+
annotations: {
|
|
14
|
+
readOnlyHint: false,
|
|
15
|
+
destructiveHint: false,
|
|
16
|
+
idempotentHint: false,
|
|
17
|
+
openWorldHint: true,
|
|
18
|
+
},
|
|
19
|
+
}, async ({ callback_url, permissions }) => {
|
|
20
|
+
try {
|
|
21
|
+
const result = await client.initiateAuthorization(callback_url, permissions);
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: 'text',
|
|
25
|
+
text: `Authorization initiated.\n\nAsk the user to open this URL in their Monobank app:\n${result.acceptUrl}\n\nRequest ID: ${result.tokenRequestId}\n\nUse check_authorization with this ID to confirm approval.`,
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return handleError(err);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
server.registerTool('check_authorization', {
|
|
34
|
+
title: 'Check Authorization',
|
|
35
|
+
description: '[Corporate API only] Check if a user has approved the authorization request.',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
request_id: z.string()
|
|
38
|
+
.describe('The tokenRequestId returned by initiate_authorization'),
|
|
39
|
+
},
|
|
40
|
+
annotations: {
|
|
41
|
+
readOnlyHint: true,
|
|
42
|
+
destructiveHint: false,
|
|
43
|
+
idempotentHint: true,
|
|
44
|
+
openWorldHint: true,
|
|
45
|
+
},
|
|
46
|
+
}, async ({ request_id }) => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await client.checkAuthorization(request_id);
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: `Status: ${result.status}` }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return handleError(err);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function handleError(err) {
|
|
59
|
+
if (err instanceof AuthError) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: `Authentication failed: ${err.message}` }],
|
|
62
|
+
isError: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=corporate-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corporate-auth.js","sourceRoot":"","sources":["../../src/tools/corporate-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,UAAU,0BAA0B,CACxC,MAAiB,EACjB,MAAuB;IAEvB,MAAM,CAAC,YAAY,CACjB,wBAAwB,EACxB;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,6MAA6M;QAC/M,WAAW,EAAE;YACX,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;iBAC3B,QAAQ,CAAC,iFAAiF,CAAC;YAC9F,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;iBAC7C,QAAQ,CAAC,+EAA+E,CAAC;SAC7F;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;YAC7E,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,qFAAqF,MAAM,CAAC,SAAS,mBAAmB,MAAM,CAAC,cAAc,+DAA+D;qBACnN,CAAC;aACH,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,8EAA8E;QAChF,WAAW,EAAE;YACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;iBACnB,QAAQ,CAAC,uDAAuD,CAAC;SACrE;QACD,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;YAC3D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;aACvE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,GAAY;IAC/B,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,0BAA0B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError } from '../errors/index.js';
|
|
3
|
+
export function registerCorporateSettingsTools(server, client) {
|
|
4
|
+
server.registerTool('get_corp_settings', {
|
|
5
|
+
title: 'Get Corporate Settings',
|
|
6
|
+
description: '[Corporate API only] Get corporate application settings including registered public key, app name, permissions, and webhook URL.',
|
|
7
|
+
annotations: {
|
|
8
|
+
readOnlyHint: true,
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
idempotentHint: true,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
},
|
|
13
|
+
}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
const settings = await client.getCorpSettings();
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: JSON.stringify(settings, null, 2) }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return handleError(err);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
server.registerTool('set_corp_webhook', {
|
|
25
|
+
title: 'Set Corporate Webhook',
|
|
26
|
+
description: '[Corporate API only] Set or update the webhook URL for the corporate application.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
url: z.string().url()
|
|
29
|
+
.describe('Publicly accessible HTTPS URL to receive events'),
|
|
30
|
+
},
|
|
31
|
+
annotations: {
|
|
32
|
+
readOnlyHint: false,
|
|
33
|
+
destructiveHint: false,
|
|
34
|
+
idempotentHint: true,
|
|
35
|
+
openWorldHint: true,
|
|
36
|
+
},
|
|
37
|
+
}, async ({ url }) => {
|
|
38
|
+
try {
|
|
39
|
+
await client.setCorpWebhook(url);
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: 'text', text: `Corporate webhook set to: ${url}` }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return handleError(err);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function handleError(err) {
|
|
50
|
+
if (err instanceof AuthError) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: `Authentication failed: ${err.message}` }],
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=corporate-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corporate-settings.js","sourceRoot":"","sources":["../../src/tools/corporate-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,UAAU,8BAA8B,CAC5C,MAAiB,EACjB,MAAuB;IAEvB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,kIAAkI;QACpI,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;YAChD,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aAC9E,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,mFAAmF;QACrF,WAAW,EAAE;YACX,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;iBAClB,QAAQ,CAAC,iDAAiD,CAAC;SAC/D;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;QAChB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YACjC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,6BAA6B,GAAG,EAAE,EAAE,CAAC;aAC/E,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,GAAY;IAC/B,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,0BAA0B,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;YACnF,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC;QAC/D,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TtlCache } from '../cache/ttl-cache.js';
|
|
3
|
+
import type { PersonalClient } from '../client/personal.js';
|
|
4
|
+
export declare function registerCurrencyTools(server: McpServer, client: PersonalClient, cache: TtlCache): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CURRENCY_SYMBOLS } from '../types/monobank.js';
|
|
2
|
+
const CACHE_TTL_SECONDS = 300; // 5 minutes — Monobank updates rates every 5 min
|
|
3
|
+
export function registerCurrencyTools(server, client, cache) {
|
|
4
|
+
server.registerTool('get_exchange_rates', {
|
|
5
|
+
title: 'Get Exchange Rates',
|
|
6
|
+
description: 'Get current Monobank exchange rates (UAH/USD/EUR and 100+ other pairs). Public endpoint, no auth required. Cached for 5 minutes.',
|
|
7
|
+
annotations: {
|
|
8
|
+
readOnlyHint: true,
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
idempotentHint: true,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
},
|
|
13
|
+
}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
const cacheKey = 'exchange-rates';
|
|
16
|
+
const cached = cache.get(cacheKey);
|
|
17
|
+
if (cached) {
|
|
18
|
+
return { content: [{ type: 'text', text: formatRates(cached) }] };
|
|
19
|
+
}
|
|
20
|
+
const rates = await client.getExchangeRates();
|
|
21
|
+
cache.set(cacheKey, rates, CACHE_TTL_SECONDS);
|
|
22
|
+
return { content: [{ type: 'text', text: formatRates(rates) }] };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: 'text', text: `Error fetching exchange rates: ${message}` }],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function formatRates(rates) {
|
|
34
|
+
const formatted = rates.map((r) => {
|
|
35
|
+
const from = CURRENCY_SYMBOLS[r.currencyCodeA] ?? r.currencyCodeA;
|
|
36
|
+
const to = CURRENCY_SYMBOLS[r.currencyCodeB] ?? r.currencyCodeB;
|
|
37
|
+
const result = {
|
|
38
|
+
pair: `${from}/${to}`,
|
|
39
|
+
date: new Date(r.date * 1000).toISOString(),
|
|
40
|
+
};
|
|
41
|
+
if (r.rateBuy != null)
|
|
42
|
+
result.buy = r.rateBuy;
|
|
43
|
+
if (r.rateSell != null)
|
|
44
|
+
result.sell = r.rateSell;
|
|
45
|
+
if (r.rateCross != null)
|
|
46
|
+
result.cross = r.rateCross;
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
return JSON.stringify(formatted, null, 2);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=currency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.js","sourceRoot":"","sources":["../../src/tools/currency.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE3E,MAAM,iBAAiB,GAAG,GAAG,CAAC,CAAC,iDAAiD;AAEhF,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,MAAsB,EACtB,KAAe;IAEf,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,kIAAkI;QACpI,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,IAAI;SACpB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,gBAAgB,CAAC;YAClC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAiB,QAAQ,CAAC,CAAC;YACnD,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7E,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC9C,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC;YAE9C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kCAAkC,OAAO,EAAE,EAAE,CAAC;gBACvF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAqB;IACxC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC;QAClE,MAAM,EAAE,GAAG,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC;QAChE,MAAM,MAAM,GAA4B;YACtC,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,EAAE;YACrB,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;SAC5C,CAAC;QACF,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI;YAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC;QAC9C,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI;YAAE,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;QACjD,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI;YAAE,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC;QACpD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TtlCache } from '../cache/ttl-cache.js';
|
|
3
|
+
import type { PersonalClient } from '../client/personal.js';
|
|
4
|
+
export declare function registerJarTools(server: McpServer, client: PersonalClient, cache: TtlCache): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AuthError, RateLimitError } from '../errors/index.js';
|
|
2
|
+
import { CURRENCY_SYMBOLS, formatAmount } from '../types/monobank.js';
|
|
3
|
+
export function registerJarTools(server, client, cache) {
|
|
4
|
+
server.registerTool('get_jars', {
|
|
5
|
+
title: 'Get Jars',
|
|
6
|
+
description: 'Get all savings jars (копилки) with current balances and goals.',
|
|
7
|
+
annotations: {
|
|
8
|
+
readOnlyHint: true,
|
|
9
|
+
destructiveHint: false,
|
|
10
|
+
idempotentHint: true,
|
|
11
|
+
openWorldHint: true,
|
|
12
|
+
},
|
|
13
|
+
}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
const cacheKey = 'client-info';
|
|
16
|
+
let info = cache.get(cacheKey);
|
|
17
|
+
if (!info) {
|
|
18
|
+
info = await client.getClientInfo();
|
|
19
|
+
cache.set(cacheKey, info, 59);
|
|
20
|
+
}
|
|
21
|
+
if (!info.jars || info.jars.length === 0) {
|
|
22
|
+
return { content: [{ type: 'text', text: 'No savings jars found.' }] };
|
|
23
|
+
}
|
|
24
|
+
const jars = info.jars.map((j) => ({
|
|
25
|
+
id: j.id,
|
|
26
|
+
title: j.title,
|
|
27
|
+
description: j.description || undefined,
|
|
28
|
+
balance: formatAmount(j.balance),
|
|
29
|
+
goal: j.goal > 0 ? formatAmount(j.goal) : 'No goal set',
|
|
30
|
+
progress: j.goal > 0 ? `${((j.balance / j.goal) * 100).toFixed(1)}%` : undefined,
|
|
31
|
+
currency: CURRENCY_SYMBOLS[j.currencyCode] ?? j.currencyCode,
|
|
32
|
+
}));
|
|
33
|
+
return { content: [{ type: 'text', text: JSON.stringify(jars, null, 2) }] };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof AuthError) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: 'text', text: `Authentication failed: ${err.message}` }],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (err instanceof RateLimitError) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: `Rate limit hit. Retry in ${err.retryAfterSeconds ?? 60}s.`,
|
|
47
|
+
}],
|
|
48
|
+
isError: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=jars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jars.js","sourceRoot":"","sources":["../../src/tools/jars.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TtlCache } from '../cache/ttl-cache.js';
|
|
3
|
+
import type { PersonalClient } from '../client/personal.js';
|
|
4
|
+
export declare function registerStatementTools(server: McpServer, client: PersonalClient, cache: TtlCache): void;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError, RateLimitError } from '../errors/index.js';
|
|
3
|
+
import { CURRENCY_SYMBOLS, formatAmount } from '../types/monobank.js';
|
|
4
|
+
const MAX_RANGE_SECONDS = 31 * 24 * 60 * 60; // 31 days
|
|
5
|
+
export function registerStatementTools(server, client, cache) {
|
|
6
|
+
server.registerTool('get_statement', {
|
|
7
|
+
title: 'Get Statement',
|
|
8
|
+
description: 'Get transaction history for a date range (max 31 days). Cached for 59 seconds.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
account_id: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.default('0')
|
|
14
|
+
.describe("Account ID from get_client_info. Use '0' for the default (black) card"),
|
|
15
|
+
from_date: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe("Start date in ISO 8601 format (e.g. '2024-01-01') or Unix timestamp in seconds"),
|
|
18
|
+
to_date: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('End date in ISO 8601 format. Defaults to now. Max range: 31 days'),
|
|
22
|
+
limit: z
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(500)
|
|
27
|
+
.optional()
|
|
28
|
+
.default(50)
|
|
29
|
+
.describe('Maximum number of transactions to return (default: 50, max: 500)'),
|
|
30
|
+
},
|
|
31
|
+
annotations: {
|
|
32
|
+
readOnlyHint: true,
|
|
33
|
+
destructiveHint: false,
|
|
34
|
+
idempotentHint: true,
|
|
35
|
+
openWorldHint: true,
|
|
36
|
+
},
|
|
37
|
+
}, async ({ account_id, from_date, to_date, limit }) => {
|
|
38
|
+
try {
|
|
39
|
+
const from = parseTimestamp(from_date);
|
|
40
|
+
const to = to_date ? parseTimestamp(to_date) : Math.floor(Date.now() / 1000);
|
|
41
|
+
if (to - from > MAX_RANGE_SECONDS) {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: 'text', text: 'Error: Date range exceeds 31 days. Monobank limits statement queries to 31 days maximum.' }],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (from > to) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: 'text', text: 'Error: from_date must be before to_date.' }],
|
|
50
|
+
isError: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const cacheKey = `statement:${account_id}:${from}:${to}`;
|
|
54
|
+
const cached = cache.get(cacheKey);
|
|
55
|
+
if (cached) {
|
|
56
|
+
const sliced = cached.slice(0, limit);
|
|
57
|
+
return { content: [{ type: 'text', text: JSON.stringify(sliced, null, 2) }] };
|
|
58
|
+
}
|
|
59
|
+
const items = await client.getStatement(account_id, from, to);
|
|
60
|
+
const formatted = formatStatementItems(items);
|
|
61
|
+
cache.set(cacheKey, formatted, 59);
|
|
62
|
+
const sliced = formatted.slice(0, limit);
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: sliced.length === 0
|
|
67
|
+
? 'No transactions in this period.'
|
|
68
|
+
: JSON.stringify(sliced, null, 2),
|
|
69
|
+
}],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return handleToolError(err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
server.registerTool('get_recent_transactions', {
|
|
77
|
+
title: 'Get Recent Transactions',
|
|
78
|
+
description: 'Get the most recent transactions from an account. Use this to check for new activity, monitor spending, or find a specific payment.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
account_id: z.string().optional().default('0')
|
|
81
|
+
.describe("Account ID. Use '0' for default card. Get IDs via get_client_info"),
|
|
82
|
+
minutes: z.number().int().min(1).max(43200).optional().default(60)
|
|
83
|
+
.describe('How many minutes back to look (default: 60, max: 43200 = 30 days)'),
|
|
84
|
+
limit: z.number().int().min(1).max(100).optional().default(20)
|
|
85
|
+
.describe('Max transactions to return (default: 20)'),
|
|
86
|
+
},
|
|
87
|
+
annotations: {
|
|
88
|
+
readOnlyHint: true,
|
|
89
|
+
destructiveHint: false,
|
|
90
|
+
idempotentHint: true,
|
|
91
|
+
openWorldHint: true,
|
|
92
|
+
},
|
|
93
|
+
}, async ({ account_id, minutes, limit }) => {
|
|
94
|
+
try {
|
|
95
|
+
const to = Math.floor(Date.now() / 1000);
|
|
96
|
+
const from = to - minutes * 60;
|
|
97
|
+
const items = await client.getStatement(account_id, from, to);
|
|
98
|
+
const formatted = formatStatementItems(items).slice(0, limit);
|
|
99
|
+
return {
|
|
100
|
+
content: [{
|
|
101
|
+
type: 'text',
|
|
102
|
+
text: formatted.length === 0
|
|
103
|
+
? 'No transactions in this period.'
|
|
104
|
+
: JSON.stringify(formatted, null, 2),
|
|
105
|
+
}],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return handleToolError(err);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function parseTimestamp(value) {
|
|
114
|
+
const asNumber = Number(value);
|
|
115
|
+
if (!isNaN(asNumber) && asNumber > 1_000_000_000 && asNumber < 10_000_000_000) {
|
|
116
|
+
return Math.floor(asNumber);
|
|
117
|
+
}
|
|
118
|
+
const date = new Date(value);
|
|
119
|
+
if (isNaN(date.getTime())) {
|
|
120
|
+
throw new Error(`Invalid date: "${value}". Use ISO 8601 format (e.g. "2024-01-01") or Unix timestamp in seconds.`);
|
|
121
|
+
}
|
|
122
|
+
return Math.floor(date.getTime() / 1000);
|
|
123
|
+
}
|
|
124
|
+
function formatStatementItems(items) {
|
|
125
|
+
return items.map((item) => ({
|
|
126
|
+
time: new Date(item.time * 1000).toISOString(),
|
|
127
|
+
description: item.description,
|
|
128
|
+
amount: `${formatAmount(item.amount)} ${CURRENCY_SYMBOLS[item.currencyCode] ?? item.currencyCode}`,
|
|
129
|
+
balance: formatAmount(item.balance),
|
|
130
|
+
mcc: item.mcc,
|
|
131
|
+
hold: item.hold,
|
|
132
|
+
...(item.comment ? { comment: item.comment } : {}),
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
function handleToolError(err) {
|
|
136
|
+
if (err instanceof AuthError) {
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: 'text', text: `Authentication failed: ${err.message}` }],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (err instanceof RateLimitError) {
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: 'text',
|
|
147
|
+
text: `Rate limit hit. Monobank allows 1 request per 60 seconds. Retry in ${err.retryAfterSeconds ?? 60}s.`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
isError: true,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=statement.js.map
|