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.
Files changed (56) hide show
  1. package/README.md +174 -0
  2. package/dist/cache/dedup.d.ts +1 -0
  3. package/dist/cache/dedup.js +10 -0
  4. package/dist/cache/dedup.js.map +1 -0
  5. package/dist/cache/ttl-cache.d.ts +6 -0
  6. package/dist/cache/ttl-cache.js +20 -0
  7. package/dist/cache/ttl-cache.js.map +1 -0
  8. package/dist/client/base.d.ts +5 -0
  9. package/dist/client/base.js +25 -0
  10. package/dist/client/base.js.map +1 -0
  11. package/dist/client/corporate.d.ts +26 -0
  12. package/dist/client/corporate.js +70 -0
  13. package/dist/client/corporate.js.map +1 -0
  14. package/dist/client/personal.d.ts +10 -0
  15. package/dist/client/personal.js +34 -0
  16. package/dist/client/personal.js.map +1 -0
  17. package/dist/client/retry.d.ts +1 -0
  18. package/dist/client/retry.js +32 -0
  19. package/dist/client/retry.js.map +1 -0
  20. package/dist/client/signing.d.ts +6 -0
  21. package/dist/client/signing.js +24 -0
  22. package/dist/client/signing.js.map +1 -0
  23. package/dist/errors/index.d.ts +12 -0
  24. package/dist/errors/index.js +25 -0
  25. package/dist/errors/index.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +14 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/server.d.ts +2 -0
  30. package/dist/server.js +89 -0
  31. package/dist/server.js.map +1 -0
  32. package/dist/tools/account.d.ts +4 -0
  33. package/dist/tools/account.js +75 -0
  34. package/dist/tools/account.js.map +1 -0
  35. package/dist/tools/corporate-auth.d.ts +3 -0
  36. package/dist/tools/corporate-auth.js +71 -0
  37. package/dist/tools/corporate-auth.js.map +1 -0
  38. package/dist/tools/corporate-settings.d.ts +3 -0
  39. package/dist/tools/corporate-settings.js +62 -0
  40. package/dist/tools/corporate-settings.js.map +1 -0
  41. package/dist/tools/currency.d.ts +4 -0
  42. package/dist/tools/currency.js +51 -0
  43. package/dist/tools/currency.js.map +1 -0
  44. package/dist/tools/jars.d.ts +4 -0
  45. package/dist/tools/jars.js +59 -0
  46. package/dist/tools/jars.js.map +1 -0
  47. package/dist/tools/statement.d.ts +4 -0
  48. package/dist/tools/statement.js +159 -0
  49. package/dist/tools/statement.js.map +1 -0
  50. package/dist/tools/webhook.d.ts +4 -0
  51. package/dist/tools/webhook.js +101 -0
  52. package/dist/tools/webhook.js.map +1 -0
  53. package/dist/types/monobank.d.ts +58 -0
  54. package/dist/types/monobank.js +13 -0
  55. package/dist/types/monobank.js.map +1 -0
  56. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ <p align="center">
2
+ <img src="https://upload.wikimedia.org/wikipedia/commons/d/db/Monobank_logo.svg" alt="Monobank" width="280" />
3
+ </p>
4
+
5
+ <h1 align="center">monobank-mcp</h1>
6
+
7
+ <p align="center">
8
+ MCP server for Monobank Open API — accounts, transactions, exchange rates, jars & webhooks for Claude and other LLM clients.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/monobank-mcp"><img src="https://img.shields.io/npm/v/monobank-mcp" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/monobank-mcp"><img src="https://img.shields.io/npm/dm/monobank-mcp" alt="npm downloads" /></a>
14
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
15
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen?logo=node.js&logoColor=white" alt="Node.js" /></a>
16
+ <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white" alt="TypeScript" /></a>
17
+ <a href="https://modelcontextprotocol.io"><img src="https://badge.mcpx.dev?type=server&features=tools,resources,prompts" alt="MCP" /></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://insiders.vscode.dev/redirect/mcp/install?name=monobank&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22monobank-mcp%22%5D%2C%22env%22%3A%7B%22MONOBANK_TOKEN%22%3A%22%24%7Binput%3Amonobank-token%7D%22%7D%7D"><img src="https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white" alt="Install in VS Code" /></a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## Features
27
+
28
+ - **8 Personal API tools** — accounts, statements, exchange rates, jars, webhooks
29
+ - **4 Corporate API tools** — ECDSA signing, multi-user auth flow, zero rate limits
30
+ - **Smart caching** — respects Monobank's 1 req/60s limit automatically
31
+ - **Retry with backoff** — exponential retry on transient failures
32
+ - **Request deduplication** — concurrent identical calls merged into one HTTP request
33
+ - **Resources & Prompts** — exchange rates as context, spending analysis template
34
+ - **English-first docs** — full API reference, agent setup guide, corporate guide
35
+
36
+ ## Quick Start
37
+
38
+ Get your token at **https://api.monobank.ua/** (scan QR with Monobank app), then:
39
+
40
+ ```bash
41
+ npx -y monobank-mcp
42
+ ```
43
+
44
+ ## Configuration
45
+
46
+ ### Claude Desktop
47
+
48
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "monobank": {
54
+ "command": "npx",
55
+ "args": ["-y", "monobank-mcp"],
56
+ "env": {
57
+ "MONOBANK_TOKEN": "your_token_here"
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ### VS Code / Cursor
65
+
66
+ Add to `.vscode/mcp.json` or `.cursor/mcp.json`:
67
+
68
+ ```json
69
+ {
70
+ "servers": {
71
+ "monobank": {
72
+ "command": "npx",
73
+ "args": ["-y", "monobank-mcp"],
74
+ "env": {
75
+ "MONOBANK_TOKEN": "your_token_here"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Claude Code
83
+
84
+ ```bash
85
+ claude mcp add monobank -- npx -y monobank-mcp
86
+ ```
87
+
88
+ Then set `MONOBANK_TOKEN` in your environment.
89
+
90
+ ## Tools
91
+
92
+ ### Personal API
93
+
94
+ | Tool | Description |
95
+ |------|-------------|
96
+ | `get_client_info` | Client profile, all accounts and jars with balances, IBANs |
97
+ | `get_statement` | Transaction history for any date range (max 31 days) |
98
+ | `get_recent_transactions` | Latest N transactions from the last X minutes |
99
+ | `get_exchange_rates` | Current UAH/USD/EUR and 100+ exchange rate pairs |
100
+ | `get_jars` | Savings jars with balances, goals, and progress |
101
+ | `set_webhook` | Register a URL for real-time transaction events |
102
+ | `delete_webhook` | Stop receiving webhook notifications |
103
+ | `get_webhook_status` | Check currently registered webhook URL |
104
+
105
+ ### Corporate API
106
+
107
+ Available when `MONOBANK_AUTH_MODE=corporate`. See [Corporate API Setup](docs/CORPORATE_API.md).
108
+
109
+ | Tool | Description |
110
+ |------|-------------|
111
+ | `initiate_authorization` | Start user auth flow, returns Monobank app URL |
112
+ | `check_authorization` | Check if user approved the auth request |
113
+ | `get_corp_settings` | Get corporate app settings (key, name, webhook) |
114
+ | `set_corp_webhook` | Set webhook URL for the corporate application |
115
+
116
+ ## Resources
117
+
118
+ | Resource | URI | Description |
119
+ |----------|-----|-------------|
120
+ | Exchange Rates | `monobank://exchange-rates` | Current exchange rates as background context |
121
+
122
+ ## Prompts
123
+
124
+ | Prompt | Description |
125
+ |--------|-------------|
126
+ | `spending-analysis` | Analyze spending patterns for a given period (week / month / quarter) |
127
+
128
+ ## Rate Limits
129
+
130
+ | Endpoint | Personal API | Corporate API |
131
+ |----------|-------------|---------------|
132
+ | `client-info` | 1 req / 60s | Unlimited |
133
+ | `statement` | 1 req / 60s | Unlimited |
134
+ | `currency` | No limit | No limit |
135
+ | `webhook` | No limit | No limit |
136
+
137
+ The server caches responses automatically (59s for rate-limited endpoints, 5 min for exchange rates). Retry with exponential backoff handles transient failures.
138
+
139
+ ## Environment Variables
140
+
141
+ | Variable | Required | Description |
142
+ |----------|----------|-------------|
143
+ | `MONOBANK_TOKEN` | Personal mode | Token from https://api.monobank.ua/ |
144
+ | `MONOBANK_AUTH_MODE` | No | `personal` (default) or `corporate` |
145
+ | `MONOBANK_KEY_ID` | Corporate mode | SHA1 hash of your public key |
146
+ | `MONOBANK_PRIVATE_KEY_PATH` | Corporate mode | Path to ECDSA secp256k1 private key |
147
+ | `MONOBANK_PRIVATE_KEY_PEM` | Corporate mode | PEM string (alternative to file path) |
148
+
149
+ ## Docker
150
+
151
+ ```bash
152
+ docker build -t monobank-mcp .
153
+ docker run -e MONOBANK_TOKEN=your_token monobank-mcp
154
+ ```
155
+
156
+ ## Development
157
+
158
+ ```bash
159
+ git clone https://github.com/serhiizghama/monobank-mcp.git
160
+ cd monobank-mcp
161
+ npm install
162
+ npm run build
163
+ npm test
164
+ ```
165
+
166
+ ## Documentation
167
+
168
+ - [API Reference](docs/API_REFERENCE.md) — full tool/resource/prompt reference with examples
169
+ - [Corporate API Setup](docs/CORPORATE_API.md) — ECDSA key generation, auth flow
170
+ - [Agent Setup Guide](docs/AGENT_SETUP.md) — instructions for AI agents to auto-install
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1 @@
1
+ export declare function dedup<T>(key: string, fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,10 @@
1
+ const inflight = new Map();
2
+ export async function dedup(key, fn) {
3
+ const existing = inflight.get(key);
4
+ if (existing)
5
+ return existing;
6
+ const promise = fn().finally(() => inflight.delete(key));
7
+ inflight.set(key, promise);
8
+ return promise;
9
+ }
10
+ //# sourceMappingURL=dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/cache/dedup.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,GAAW,EAAE,EAAoB;IAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,QAAQ;QAAE,OAAO,QAAsB,CAAC;IAE5C,MAAM,OAAO,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare class TtlCache {
2
+ private store;
3
+ get<T>(key: string): T | undefined;
4
+ set<T>(key: string, data: T, ttlSeconds: number): void;
5
+ invalidate(key: string): void;
6
+ }
@@ -0,0 +1,20 @@
1
+ export class TtlCache {
2
+ store = new Map();
3
+ get(key) {
4
+ const entry = this.store.get(key);
5
+ if (!entry)
6
+ return undefined;
7
+ if (Date.now() > entry.expiresAt) {
8
+ this.store.delete(key);
9
+ return undefined;
10
+ }
11
+ return entry.data;
12
+ }
13
+ set(key, data, ttlSeconds) {
14
+ this.store.set(key, { data, expiresAt: Date.now() + ttlSeconds * 1000 });
15
+ }
16
+ invalidate(key) {
17
+ this.store.delete(key);
18
+ }
19
+ }
20
+ //# sourceMappingURL=ttl-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttl-cache.js","sourceRoot":"","sources":["../../src/cache/ttl-cache.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,QAAQ;IACX,KAAK,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEvD,GAAG,CAAI,GAAW;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,IAAS,CAAC;IACzB,CAAC;IAED,GAAG,CAAI,GAAW,EAAE,IAAO,EAAE,UAAkB;QAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export declare function apiFetch<T>(path: string, options?: {
2
+ headers?: Record<string, string>;
3
+ method?: string;
4
+ body?: Record<string, unknown>;
5
+ }): Promise<T>;
@@ -0,0 +1,25 @@
1
+ import { AuthError, MonobankError, RateLimitError } from '../errors/index.js';
2
+ const BASE_URL = 'https://api.monobank.ua';
3
+ export async function apiFetch(path, options = {}) {
4
+ const response = await fetch(`${BASE_URL}${path}`, {
5
+ method: options.method ?? 'GET',
6
+ headers: { 'Content-Type': 'application/json', ...options.headers },
7
+ body: options.body ? JSON.stringify(options.body) : undefined,
8
+ });
9
+ if (response.ok) {
10
+ const text = await response.text();
11
+ return text ? JSON.parse(text) : undefined;
12
+ }
13
+ if (response.status === 401)
14
+ throw new AuthError();
15
+ if (response.status === 403)
16
+ throw new AuthError('Access denied. Token may lack required permissions.');
17
+ if (response.status === 429) {
18
+ const retryAfter = parseInt(response.headers.get('Retry-After') ?? '60', 10);
19
+ throw new RateLimitError(retryAfter);
20
+ }
21
+ const errorBody = await response.text().catch(() => '');
22
+ const message = errorBody || `HTTP ${response.status}`;
23
+ throw new MonobankError('server', message, undefined, response.status);
24
+ }
25
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/client/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,UAII,EAAE;IAEN,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,GAAG,IAAI,EAAE,EAAE;QACjD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;QACnE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9D,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAO,CAAC,CAAC,CAAE,SAAe,CAAC;IAC3D,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,MAAM,IAAI,SAAS,EAAE,CAAC;IACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;QAAE,MAAM,IAAI,SAAS,CAAC,qDAAqD,CAAC,CAAC;IAExG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAC7E,MAAM,IAAI,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,SAAS,IAAI,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;IACvD,MAAM,IAAI,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ClientInfo, ExchangeRate, StatementItem } from '../types/monobank.js';
2
+ export interface CorpSettings {
3
+ pubkey: string;
4
+ name: string;
5
+ permission: string;
6
+ logo: string;
7
+ webhook: string | null;
8
+ }
9
+ export declare class CorporateClient {
10
+ private readonly signingConfig;
11
+ constructor(keyId: string, privateKeyPem: string);
12
+ private userHeaders;
13
+ private corpHeaders;
14
+ getClientInfo(requestId: string): Promise<ClientInfo>;
15
+ getStatement(requestId: string, accountId: string, from: number, to?: number): Promise<StatementItem[]>;
16
+ initiateAuthorization(callbackUrl: string, permissions?: string): Promise<{
17
+ tokenRequestId: string;
18
+ acceptUrl: string;
19
+ }>;
20
+ checkAuthorization(requestId: string): Promise<{
21
+ status: string;
22
+ }>;
23
+ getCorpSettings(): Promise<CorpSettings>;
24
+ setCorpWebhook(url: string): Promise<void>;
25
+ getExchangeRates(): Promise<ExchangeRate[]>;
26
+ }
@@ -0,0 +1,70 @@
1
+ import { dedup } from '../cache/dedup.js';
2
+ import { apiFetch } from './base.js';
3
+ import { withRetry } from './retry.js';
4
+ import { signRequest } from './signing.js';
5
+ export class CorporateClient {
6
+ signingConfig;
7
+ constructor(keyId, privateKeyPem) {
8
+ this.signingConfig = { keyId, privateKeyPem };
9
+ }
10
+ userHeaders(path, requestId) {
11
+ return {
12
+ ...signRequest(this.signingConfig, path, requestId),
13
+ 'X-Request-Id': requestId,
14
+ };
15
+ }
16
+ corpHeaders(path) {
17
+ return signRequest(this.signingConfig, path, '');
18
+ }
19
+ // --- Per-user endpoints (require requestId from auth flow) ---
20
+ async getClientInfo(requestId) {
21
+ const path = '/personal/client-info';
22
+ return dedup(`corp:client-info:${requestId}`, () => withRetry(() => apiFetch(path, { headers: this.userHeaders(path, requestId) })));
23
+ }
24
+ async getStatement(requestId, accountId, from, to) {
25
+ const path = to
26
+ ? `/personal/statement/${accountId}/${from}/${to}`
27
+ : `/personal/statement/${accountId}/${from}`;
28
+ return dedup(`corp:statement:${path}:${requestId}`, () => withRetry(() => apiFetch(path, { headers: this.userHeaders(path, requestId) })));
29
+ }
30
+ // --- Auth flow endpoints ---
31
+ async initiateAuthorization(callbackUrl, permissions = 'sp') {
32
+ const path = '/personal/auth/request';
33
+ const sigHeaders = signRequest(this.signingConfig, path, permissions);
34
+ return withRetry(() => apiFetch(path, {
35
+ method: 'POST',
36
+ headers: {
37
+ ...sigHeaders,
38
+ 'X-Callback': callbackUrl,
39
+ 'X-Permissions': permissions,
40
+ },
41
+ }));
42
+ }
43
+ async checkAuthorization(requestId) {
44
+ const path = '/personal/auth/request';
45
+ const sigHeaders = signRequest(this.signingConfig, path, requestId);
46
+ return withRetry(() => apiFetch(path, {
47
+ headers: {
48
+ ...sigHeaders,
49
+ 'X-Request-Id': requestId,
50
+ },
51
+ }));
52
+ }
53
+ // --- Corp-level endpoints (no user context) ---
54
+ async getCorpSettings() {
55
+ const path = '/personal/corp/settings';
56
+ return withRetry(() => apiFetch(path, { headers: this.corpHeaders(path) }));
57
+ }
58
+ async setCorpWebhook(url) {
59
+ const path = '/personal/corp/webhook';
60
+ await withRetry(() => apiFetch(path, {
61
+ method: 'POST',
62
+ headers: this.corpHeaders(path),
63
+ body: { webHookUrl: url },
64
+ }));
65
+ }
66
+ async getExchangeRates() {
67
+ return dedup('exchange-rates', () => withRetry(() => apiFetch('/bank/currency')));
68
+ }
69
+ }
70
+ //# sourceMappingURL=corporate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"corporate.js","sourceRoot":"","sources":["../../src/client/corporate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ClientInfo, ExchangeRate, StatementItem } from '../types/monobank.js';
2
+ export declare class PersonalClient {
3
+ private readonly token;
4
+ constructor(token: string);
5
+ private headers;
6
+ getClientInfo(): Promise<ClientInfo>;
7
+ getStatement(accountId: string, from: number, to?: number): Promise<StatementItem[]>;
8
+ setWebhook(url: string): Promise<void>;
9
+ getExchangeRates(): Promise<ExchangeRate[]>;
10
+ }
@@ -0,0 +1,34 @@
1
+ import { dedup } from '../cache/dedup.js';
2
+ import { apiFetch } from './base.js';
3
+ import { withRetry } from './retry.js';
4
+ export class PersonalClient {
5
+ token;
6
+ constructor(token) {
7
+ this.token = token;
8
+ }
9
+ headers() {
10
+ return { 'X-Token': this.token };
11
+ }
12
+ async getClientInfo() {
13
+ return dedup('client-info', () => withRetry(() => apiFetch('/personal/client-info', {
14
+ headers: this.headers(),
15
+ })));
16
+ }
17
+ async getStatement(accountId, from, to) {
18
+ const path = to
19
+ ? `/personal/statement/${accountId}/${from}/${to}`
20
+ : `/personal/statement/${accountId}/${from}`;
21
+ return dedup(`statement:${path}`, () => withRetry(() => apiFetch(path, { headers: this.headers() })));
22
+ }
23
+ async setWebhook(url) {
24
+ await withRetry(() => apiFetch('/personal/webhook', {
25
+ method: 'POST',
26
+ headers: this.headers(),
27
+ body: { webHookUrl: url },
28
+ }));
29
+ }
30
+ async getExchangeRates() {
31
+ return dedup('exchange-rates', () => withRetry(() => apiFetch('/bank/currency')));
32
+ }
33
+ }
34
+ //# sourceMappingURL=personal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personal.js","sourceRoot":"","sources":["../../src/client/personal.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,OAAO,cAAc;IACI;IAA7B,YAA6B,KAAa;QAAb,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAEtC,OAAO;QACb,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,OAAO,KAAK,CAAC,aAAa,EAAE,GAAG,EAAE,CAC/B,SAAS,CAAC,GAAG,EAAE,CACb,QAAQ,CAAa,uBAAuB,EAAE;YAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;SACxB,CAAC,CACH,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,SAAiB,EACjB,IAAY,EACZ,EAAW;QAEX,MAAM,IAAI,GAAG,EAAE;YACb,CAAC,CAAC,uBAAuB,SAAS,IAAI,IAAI,IAAI,EAAE,EAAE;YAClD,CAAC,CAAC,uBAAuB,SAAS,IAAI,IAAI,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,GAAG,EAAE,CACrC,SAAS,CAAC,GAAG,EAAE,CACb,QAAQ,CAAkB,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAC7D,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAW;QAC1B,MAAM,SAAS,CAAC,GAAG,EAAE,CACnB,QAAQ,CAAO,mBAAmB,EAAE;YAClC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;YACvB,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE;SAC1B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,OAAO,KAAK,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAClC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAiB,gBAAgB,CAAC,CAAC,CAC5D,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export declare function withRetry<T>(fn: () => Promise<T>, maxAttempts?: number): Promise<T>;
@@ -0,0 +1,32 @@
1
+ import { AuthError, MonobankError, RateLimitError } from '../errors/index.js';
2
+ const BACKOFF_DELAYS_MS = [2000, 4000, 8000, 16000, 32000];
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+ export async function withRetry(fn, maxAttempts = 3) {
7
+ let lastError;
8
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
9
+ try {
10
+ return await fn();
11
+ }
12
+ catch (err) {
13
+ lastError = err;
14
+ if (err instanceof AuthError)
15
+ throw err;
16
+ if (err instanceof MonobankError && err.category === 'validation')
17
+ throw err;
18
+ if (err instanceof RateLimitError) {
19
+ const delay = err.retryAfterSeconds
20
+ ? err.retryAfterSeconds * 1000
21
+ : (BACKOFF_DELAYS_MS[attempt] ?? 32000);
22
+ await sleep(delay);
23
+ continue;
24
+ }
25
+ if (attempt < maxAttempts - 1) {
26
+ await sleep(BACKOFF_DELAYS_MS[attempt] ?? 32000);
27
+ }
28
+ }
29
+ }
30
+ throw lastError;
31
+ }
32
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/client/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3D,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,WAAW,GAAG,CAAC;IAEf,IAAI,SAAkB,CAAC;IAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,GAAG,GAAG,CAAC;YAEhB,IAAI,GAAG,YAAY,SAAS;gBAAE,MAAM,GAAG,CAAC;YACxC,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY;gBAAE,MAAM,GAAG,CAAC;YAE7E,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;gBAClC,MAAM,KAAK,GAAG,GAAG,CAAC,iBAAiB;oBACjC,CAAC,CAAC,GAAG,CAAC,iBAAiB,GAAG,IAAI;oBAC9B,CAAC,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;gBAC1C,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;gBACnB,SAAS;YACX,CAAC;YAED,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,SAAS,CAAC;AAClB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface SigningConfig {
2
+ keyId: string;
3
+ privateKeyPem: string;
4
+ }
5
+ export declare function signRequest(config: SigningConfig, requestPath: string, secondIngredient?: string): Record<string, string>;
6
+ export declare function loadPrivateKey(): string;
@@ -0,0 +1,24 @@
1
+ import { createSign } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ export function signRequest(config, requestPath, secondIngredient = '') {
4
+ const timestamp = Math.floor(Date.now() / 1000);
5
+ const dataToSign = `${timestamp}${secondIngredient}${requestPath}`;
6
+ const signer = createSign('SHA256');
7
+ signer.update(dataToSign);
8
+ const signature = signer.sign(config.privateKeyPem, 'base64');
9
+ return {
10
+ 'X-Key-Id': config.keyId,
11
+ 'X-Time': timestamp.toString(),
12
+ 'X-Sign': signature,
13
+ };
14
+ }
15
+ export function loadPrivateKey() {
16
+ if (process.env.MONOBANK_PRIVATE_KEY_PEM) {
17
+ return process.env.MONOBANK_PRIVATE_KEY_PEM.replace(/\\n/g, '\n');
18
+ }
19
+ const path = process.env.MONOBANK_PRIVATE_KEY_PATH;
20
+ if (path)
21
+ return readFileSync(path, 'utf-8');
22
+ throw new Error('Corporate API requires MONOBANK_PRIVATE_KEY_PEM or MONOBANK_PRIVATE_KEY_PATH');
23
+ }
24
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.js","sourceRoot":"","sources":["../../src/client/signing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAOvC,MAAM,UAAU,WAAW,CACzB,MAAqB,EACrB,WAAmB,EACnB,mBAA2B,EAAE;IAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,EAAE,CAAC;IAEnE,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IAE9D,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,KAAK;QACxB,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE;QAC9B,QAAQ,EAAE,SAAS;KACpB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACnD,IAAI,IAAI;QAAE,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;AAClG,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare class MonobankError extends Error {
2
+ readonly category: 'auth' | 'rate_limit' | 'validation' | 'not_found' | 'server';
3
+ readonly retryAfterSeconds?: number | undefined;
4
+ readonly statusCode?: number | undefined;
5
+ constructor(category: 'auth' | 'rate_limit' | 'validation' | 'not_found' | 'server', message: string, retryAfterSeconds?: number | undefined, statusCode?: number | undefined);
6
+ }
7
+ export declare class RateLimitError extends MonobankError {
8
+ constructor(retryAfterSeconds: number);
9
+ }
10
+ export declare class AuthError extends MonobankError {
11
+ constructor(message?: string);
12
+ }
@@ -0,0 +1,25 @@
1
+ export class MonobankError extends Error {
2
+ category;
3
+ retryAfterSeconds;
4
+ statusCode;
5
+ constructor(category, message, retryAfterSeconds, statusCode) {
6
+ super(message);
7
+ this.category = category;
8
+ this.retryAfterSeconds = retryAfterSeconds;
9
+ this.statusCode = statusCode;
10
+ this.name = 'MonobankError';
11
+ }
12
+ }
13
+ export class RateLimitError extends MonobankError {
14
+ constructor(retryAfterSeconds) {
15
+ super('rate_limit', `Rate limit exceeded. Retry after ${retryAfterSeconds} seconds.`, retryAfterSeconds);
16
+ this.name = 'RateLimitError';
17
+ }
18
+ }
19
+ export class AuthError extends MonobankError {
20
+ constructor(message = 'Invalid or missing token. Get your token at https://api.monobank.ua/') {
21
+ super('auth', message, undefined, 401);
22
+ this.name = 'AuthError';
23
+ }
24
+ }
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEpB;IAEA;IACA;IAJlB,YACkB,QAAuE,EACvF,OAAe,EACC,iBAA0B,EAC1B,UAAmB;QAEnC,KAAK,CAAC,OAAO,CAAC,CAAC;QALC,aAAQ,GAAR,QAAQ,CAA+D;QAEvE,sBAAiB,GAAjB,iBAAiB,CAAS;QAC1B,eAAU,GAAV,UAAU,CAAS;QAGnC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,aAAa;IAC/C,YAAY,iBAAyB;QACnC,KAAK,CACH,YAAY,EACZ,oCAAoC,iBAAiB,WAAW,EAChE,iBAAiB,CAClB,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,SAAU,SAAQ,aAAa;IAC1C,YAAY,OAAO,GAAG,sEAAsE;QAC1F,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createServer } from './server.js';
4
+ async function main() {
5
+ const transport = new StdioServerTransport();
6
+ const server = createServer();
7
+ await server.connect(transport);
8
+ console.error('Monobank MCP server running on stdio');
9
+ }
10
+ main().catch((error) => {
11
+ console.error('Fatal error:', error);
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createServer(): McpServer;