@sheetlink/mcp 0.1.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 ADDED
@@ -0,0 +1,79 @@
1
+ # SheetLink MCP Server
2
+
3
+ Connect Claude to your bank transactions via SheetLink.
4
+
5
+ ## What it does
6
+
7
+ Exposes three tools to Claude:
8
+
9
+ - **`list_accounts`** — lists your connected bank accounts
10
+ - **`list_transactions`** — fetches transactions with optional date, account, and category filters
11
+ - **`get_spending_summary`** — aggregates spending by category or merchant for a date range
12
+
13
+ ## Requirements
14
+
15
+ - SheetLink MAX tier (for API key access)
16
+ - Node.js 18+
17
+
18
+ ## Setup
19
+
20
+ ### 1. Get your API key
21
+
22
+ Log in to [sheetlink.app/dashboard/api-keys](https://sheetlink.app/dashboard/api-keys) and create an API key.
23
+
24
+ ### 2. Configure Claude Desktop
25
+
26
+ Add to your `claude_desktop_config.json`:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "sheetlink": {
32
+ "command": "npx",
33
+ "args": ["-y", "@sheetlink/mcp"],
34
+ "env": {
35
+ "SHEETLINK_API_KEY": "sl_your_api_key_here"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### 3. Restart Claude Desktop
43
+
44
+ That's it. Ask Claude things like:
45
+
46
+ - *"What did I spend on restaurants last month?"*
47
+ - *"Show me my top spending categories for Q1"*
48
+ - *"List all transactions over $100 from Chase this week"*
49
+ - *"Build me a P&L for March using my bank data"*
50
+
51
+ ## Tools
52
+
53
+ ### `list_accounts`
54
+ Lists all connected bank accounts with institution name and last sync time.
55
+
56
+ ### `list_transactions`
57
+ | Parameter | Type | Description |
58
+ |---|---|---|
59
+ | `item_id` | string | Filter to a specific bank (from `list_accounts`) |
60
+ | `start_date` | string | YYYY-MM-DD |
61
+ | `end_date` | string | YYYY-MM-DD |
62
+ | `category` | string | Partial match on Plaid category (e.g. `FOOD_AND_DRINK`) |
63
+ | `limit` | number | Max results (default 100) |
64
+
65
+ ### `get_spending_summary`
66
+ | Parameter | Type | Description |
67
+ |---|---|---|
68
+ | `item_id` | string | Limit to one bank |
69
+ | `start_date` | string | YYYY-MM-DD |
70
+ | `end_date` | string | YYYY-MM-DD |
71
+ | `group_by` | `category` \| `merchant` | Default: `category` |
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ npm install
77
+ npm run build
78
+ SHEETLINK_API_KEY=sl_your_key node dist/index.js
79
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ const API_BASE = "https://api.sheetlink.app";
6
+ const API_KEY = process.env.SHEETLINK_API_KEY;
7
+ if (!API_KEY) {
8
+ console.error("Error: SHEETLINK_API_KEY environment variable is required");
9
+ process.exit(1);
10
+ }
11
+ async function apiFetch(path, options = {}) {
12
+ const res = await fetch(`${API_BASE}${path}`, {
13
+ ...options,
14
+ headers: {
15
+ Authorization: `Bearer ${API_KEY}`,
16
+ "Content-Type": "application/json",
17
+ ...options.headers,
18
+ },
19
+ });
20
+ if (!res.ok) {
21
+ const text = await res.text();
22
+ throw new Error(`SheetLink API error ${res.status}: ${text}`);
23
+ }
24
+ return res.json();
25
+ }
26
+ // ── Tool handlers ────────────────────────────────────────────────────────────
27
+ async function listAccounts() {
28
+ const data = await apiFetch("/api/items");
29
+ const items = data.items;
30
+ if (!items.length) {
31
+ return "No bank accounts connected. Visit sheetlink.app to connect a bank.";
32
+ }
33
+ return items
34
+ .map((item) => `• ${item.institution_name} (item_id: ${item.item_id}, last synced: ${item.last_synced_at ?? "never"})`)
35
+ .join("\n");
36
+ }
37
+ async function listTransactions(args) {
38
+ // Get items to sync
39
+ const itemsData = await apiFetch("/api/items");
40
+ const items = itemsData.items;
41
+ if (!items.length) {
42
+ return "No bank accounts connected.";
43
+ }
44
+ const targetItems = args.item_id
45
+ ? items.filter((i) => i.item_id === args.item_id)
46
+ : items;
47
+ if (!targetItems.length) {
48
+ return `No item found with item_id: ${args.item_id}`;
49
+ }
50
+ // Sync each item and collect transactions
51
+ let allTransactions = [];
52
+ for (const item of targetItems) {
53
+ const syncData = await apiFetch("/api/sync", {
54
+ method: "POST",
55
+ body: JSON.stringify({ item_id: item.item_id }),
56
+ });
57
+ allTransactions.push(...(syncData.transactions ?? []));
58
+ }
59
+ // Filter by date range
60
+ if (args.start_date) {
61
+ allTransactions = allTransactions.filter((t) => t.date >= args.start_date);
62
+ }
63
+ if (args.end_date) {
64
+ allTransactions = allTransactions.filter((t) => t.date <= args.end_date);
65
+ }
66
+ // Filter by category
67
+ if (args.category) {
68
+ const cat = args.category.toUpperCase();
69
+ allTransactions = allTransactions.filter((t) => (t.category_primary ?? "").toUpperCase().includes(cat) ||
70
+ (t.category_detailed ?? "").toUpperCase().includes(cat));
71
+ }
72
+ // Sort newest first
73
+ allTransactions.sort((a, b) => b.date.localeCompare(a.date));
74
+ // Apply limit
75
+ const limit = args.limit ?? 100;
76
+ const truncated = allTransactions.length > limit;
77
+ const transactions = allTransactions.slice(0, limit);
78
+ if (!transactions.length) {
79
+ return "No transactions found matching the given filters.";
80
+ }
81
+ const lines = transactions.map((t) => {
82
+ const amount = typeof t.amount === "number" ? t.amount.toFixed(2) : t.amount;
83
+ const name = t.merchant_name ?? t.description ?? t.description_raw ?? "Unknown";
84
+ const cat = t.category_primary ?? "";
85
+ return `${t.date} ${String(name).padEnd(35)} $${String(amount).padStart(8)} ${cat}`;
86
+ });
87
+ const header = `${"Date".padEnd(10)} ${"Merchant".padEnd(35)} ${"Amount".padStart(9)} Category`;
88
+ const separator = "─".repeat(header.length);
89
+ const result = [header, separator, ...lines].join("\n");
90
+ return truncated
91
+ ? `${result}\n\n(Showing first ${limit} of ${allTransactions.length} transactions. Use a narrower date range or increase limit.)`
92
+ : result;
93
+ }
94
+ async function getSpendingSummary(args) {
95
+ const itemsData = await apiFetch("/api/items");
96
+ const items = itemsData.items;
97
+ if (!items.length)
98
+ return "No bank accounts connected.";
99
+ const targetItems = args.item_id
100
+ ? items.filter((i) => i.item_id === args.item_id)
101
+ : items;
102
+ let allTransactions = [];
103
+ for (const item of targetItems) {
104
+ const syncData = await apiFetch("/api/sync", {
105
+ method: "POST",
106
+ body: JSON.stringify({ item_id: item.item_id }),
107
+ });
108
+ allTransactions.push(...(syncData.transactions ?? []));
109
+ }
110
+ // Filter by date range
111
+ if (args.start_date) {
112
+ allTransactions = allTransactions.filter((t) => t.date >= args.start_date);
113
+ }
114
+ if (args.end_date) {
115
+ allTransactions = allTransactions.filter((t) => t.date <= args.end_date);
116
+ }
117
+ if (!allTransactions.length) {
118
+ return "No transactions found in the given date range.";
119
+ }
120
+ // Group and sum
121
+ const groupBy = args.group_by ?? "category";
122
+ const totals = {};
123
+ for (const t of allTransactions) {
124
+ const key = groupBy === "merchant"
125
+ ? (t.merchant_name ?? t.description ?? "Unknown")
126
+ : (t.category_primary ?? "Uncategorized");
127
+ totals[key] = (totals[key] ?? 0) + t.amount;
128
+ }
129
+ // Sort by spend descending
130
+ const sorted = Object.entries(totals).sort((a, b) => b[1] - a[1]);
131
+ const totalSpend = sorted.reduce((sum, [, v]) => sum + v, 0);
132
+ const dateRange = args.start_date && args.end_date
133
+ ? `${args.start_date} to ${args.end_date}`
134
+ : args.start_date
135
+ ? `from ${args.start_date}`
136
+ : args.end_date
137
+ ? `through ${args.end_date}`
138
+ : "all time";
139
+ const lines = sorted.map(([key, amt]) => `${key.padEnd(40)} $${amt.toFixed(2).padStart(10)}`);
140
+ return [
141
+ `Spending summary by ${groupBy} (${dateRange})`,
142
+ `Total: $${totalSpend.toFixed(2)}`,
143
+ "─".repeat(55),
144
+ ...lines,
145
+ ].join("\n");
146
+ }
147
+ // ── MCP Server ───────────────────────────────────────────────────────────────
148
+ const server = new Server({ name: "sheetlink", version: "0.1.0" }, { capabilities: { tools: {} } });
149
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
150
+ tools: [
151
+ {
152
+ name: "list_accounts",
153
+ description: "List all bank accounts connected to SheetLink, including institution names and last sync time.",
154
+ inputSchema: {
155
+ type: "object",
156
+ properties: {},
157
+ required: [],
158
+ },
159
+ },
160
+ {
161
+ name: "list_transactions",
162
+ description: "Fetch bank transactions from SheetLink. Optionally filter by account, date range, or spending category. Returns date, merchant, amount, and category for each transaction.",
163
+ inputSchema: {
164
+ type: "object",
165
+ properties: {
166
+ item_id: {
167
+ type: "string",
168
+ description: "Filter to a specific bank account (item_id from list_accounts). Omit to fetch all accounts.",
169
+ },
170
+ start_date: {
171
+ type: "string",
172
+ description: "Start date filter in YYYY-MM-DD format (inclusive).",
173
+ },
174
+ end_date: {
175
+ type: "string",
176
+ description: "End date filter in YYYY-MM-DD format (inclusive).",
177
+ },
178
+ category: {
179
+ type: "string",
180
+ description: "Filter by spending category (e.g. FOOD_AND_DRINK, TRANSPORTATION, SHOPPING). Partial match, case-insensitive.",
181
+ },
182
+ limit: {
183
+ type: "number",
184
+ description: "Maximum number of transactions to return (default: 100).",
185
+ },
186
+ },
187
+ required: [],
188
+ },
189
+ },
190
+ {
191
+ name: "get_spending_summary",
192
+ description: "Get a spending summary aggregated by category or merchant for a given date range. Useful for answering questions like 'how much did I spend on food last month?' or 'what are my top spending categories?'",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {
196
+ item_id: {
197
+ type: "string",
198
+ description: "Limit summary to a specific bank account. Omit for all accounts.",
199
+ },
200
+ start_date: {
201
+ type: "string",
202
+ description: "Start date in YYYY-MM-DD format.",
203
+ },
204
+ end_date: {
205
+ type: "string",
206
+ description: "End date in YYYY-MM-DD format.",
207
+ },
208
+ group_by: {
209
+ type: "string",
210
+ enum: ["category", "merchant"],
211
+ description: "Group spending by category (default) or merchant.",
212
+ },
213
+ },
214
+ required: [],
215
+ },
216
+ },
217
+ ],
218
+ }));
219
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
220
+ const { name, arguments: args = {} } = request.params;
221
+ try {
222
+ let result;
223
+ if (name === "list_accounts") {
224
+ result = await listAccounts();
225
+ }
226
+ else if (name === "list_transactions") {
227
+ result = await listTransactions(args);
228
+ }
229
+ else if (name === "get_spending_summary") {
230
+ result = await getSpendingSummary(args);
231
+ }
232
+ else {
233
+ throw new Error(`Unknown tool: ${name}`);
234
+ }
235
+ return { content: [{ type: "text", text: result }] };
236
+ }
237
+ catch (err) {
238
+ const message = err instanceof Error ? err.message : String(err);
239
+ return {
240
+ content: [{ type: "text", text: `Error: ${message}` }],
241
+ isError: true,
242
+ };
243
+ }
244
+ });
245
+ const transport = new StdioServerTransport();
246
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@sheetlink/mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for SheetLink — connect Claude to your bank transactions",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "sheetlink-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.3.0",
20
+ "@types/node": "^20.0.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=18"
24
+ }
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ const API_BASE = "https://api.sheetlink.app";
10
+ const API_KEY = process.env.SHEETLINK_API_KEY;
11
+
12
+ if (!API_KEY) {
13
+ console.error("Error: SHEETLINK_API_KEY environment variable is required");
14
+ process.exit(1);
15
+ }
16
+
17
+ async function apiFetch(path: string, options: RequestInit = {}) {
18
+ const res = await fetch(`${API_BASE}${path}`, {
19
+ ...options,
20
+ headers: {
21
+ Authorization: `Bearer ${API_KEY}`,
22
+ "Content-Type": "application/json",
23
+ ...options.headers,
24
+ },
25
+ });
26
+
27
+ if (!res.ok) {
28
+ const text = await res.text();
29
+ throw new Error(`SheetLink API error ${res.status}: ${text}`);
30
+ }
31
+
32
+ return res.json();
33
+ }
34
+
35
+ // ── Tool handlers ────────────────────────────────────────────────────────────
36
+
37
+ async function listAccounts() {
38
+ const data = await apiFetch("/api/items");
39
+ const items = data.items as Array<{
40
+ item_id: string;
41
+ institution_name: string;
42
+ last_synced_at: string | null;
43
+ }>;
44
+
45
+ if (!items.length) {
46
+ return "No bank accounts connected. Visit sheetlink.app to connect a bank.";
47
+ }
48
+
49
+ return items
50
+ .map(
51
+ (item) =>
52
+ `• ${item.institution_name} (item_id: ${item.item_id}, last synced: ${item.last_synced_at ?? "never"})`
53
+ )
54
+ .join("\n");
55
+ }
56
+
57
+ async function listTransactions(args: {
58
+ item_id?: string;
59
+ start_date?: string;
60
+ end_date?: string;
61
+ category?: string;
62
+ limit?: number;
63
+ }) {
64
+ // Get items to sync
65
+ const itemsData = await apiFetch("/api/items");
66
+ const items = itemsData.items as Array<{ item_id: string; institution_name: string }>;
67
+
68
+ if (!items.length) {
69
+ return "No bank accounts connected.";
70
+ }
71
+
72
+ const targetItems = args.item_id
73
+ ? items.filter((i) => i.item_id === args.item_id)
74
+ : items;
75
+
76
+ if (!targetItems.length) {
77
+ return `No item found with item_id: ${args.item_id}`;
78
+ }
79
+
80
+ // Sync each item and collect transactions
81
+ let allTransactions: Array<Record<string, unknown>> = [];
82
+
83
+ for (const item of targetItems) {
84
+ const syncData = await apiFetch("/api/sync", {
85
+ method: "POST",
86
+ body: JSON.stringify({ item_id: item.item_id }),
87
+ });
88
+ allTransactions.push(...(syncData.transactions ?? []));
89
+ }
90
+
91
+ // Filter by date range
92
+ if (args.start_date) {
93
+ allTransactions = allTransactions.filter(
94
+ (t) => (t.date as string) >= args.start_date!
95
+ );
96
+ }
97
+ if (args.end_date) {
98
+ allTransactions = allTransactions.filter(
99
+ (t) => (t.date as string) <= args.end_date!
100
+ );
101
+ }
102
+
103
+ // Filter by category
104
+ if (args.category) {
105
+ const cat = args.category.toUpperCase();
106
+ allTransactions = allTransactions.filter(
107
+ (t) =>
108
+ ((t.category_primary as string) ?? "").toUpperCase().includes(cat) ||
109
+ ((t.category_detailed as string) ?? "").toUpperCase().includes(cat)
110
+ );
111
+ }
112
+
113
+ // Sort newest first
114
+ allTransactions.sort((a, b) =>
115
+ (b.date as string).localeCompare(a.date as string)
116
+ );
117
+
118
+ // Apply limit
119
+ const limit = args.limit ?? 100;
120
+ const truncated = allTransactions.length > limit;
121
+ const transactions = allTransactions.slice(0, limit);
122
+
123
+ if (!transactions.length) {
124
+ return "No transactions found matching the given filters.";
125
+ }
126
+
127
+ const lines = transactions.map((t) => {
128
+ const amount = typeof t.amount === "number" ? t.amount.toFixed(2) : t.amount;
129
+ const name = t.merchant_name ?? t.description ?? t.description_raw ?? "Unknown";
130
+ const cat = t.category_primary ?? "";
131
+ return `${t.date} ${String(name).padEnd(35)} $${String(amount).padStart(8)} ${cat}`;
132
+ });
133
+
134
+ const header = `${"Date".padEnd(10)} ${"Merchant".padEnd(35)} ${"Amount".padStart(9)} Category`;
135
+ const separator = "─".repeat(header.length);
136
+ const result = [header, separator, ...lines].join("\n");
137
+
138
+ return truncated
139
+ ? `${result}\n\n(Showing first ${limit} of ${allTransactions.length} transactions. Use a narrower date range or increase limit.)`
140
+ : result;
141
+ }
142
+
143
+ async function getSpendingSummary(args: {
144
+ item_id?: string;
145
+ start_date?: string;
146
+ end_date?: string;
147
+ group_by?: "category" | "merchant";
148
+ }) {
149
+ const itemsData = await apiFetch("/api/items");
150
+ const items = itemsData.items as Array<{ item_id: string }>;
151
+
152
+ if (!items.length) return "No bank accounts connected.";
153
+
154
+ const targetItems = args.item_id
155
+ ? items.filter((i) => i.item_id === args.item_id)
156
+ : items;
157
+
158
+ let allTransactions: Array<Record<string, unknown>> = [];
159
+
160
+ for (const item of targetItems) {
161
+ const syncData = await apiFetch("/api/sync", {
162
+ method: "POST",
163
+ body: JSON.stringify({ item_id: item.item_id }),
164
+ });
165
+ allTransactions.push(...(syncData.transactions ?? []));
166
+ }
167
+
168
+ // Filter by date range
169
+ if (args.start_date) {
170
+ allTransactions = allTransactions.filter(
171
+ (t) => (t.date as string) >= args.start_date!
172
+ );
173
+ }
174
+ if (args.end_date) {
175
+ allTransactions = allTransactions.filter(
176
+ (t) => (t.date as string) <= args.end_date!
177
+ );
178
+ }
179
+
180
+ if (!allTransactions.length) {
181
+ return "No transactions found in the given date range.";
182
+ }
183
+
184
+ // Group and sum
185
+ const groupBy = args.group_by ?? "category";
186
+ const totals: Record<string, number> = {};
187
+
188
+ for (const t of allTransactions) {
189
+ const key =
190
+ groupBy === "merchant"
191
+ ? ((t.merchant_name ?? t.description ?? "Unknown") as string)
192
+ : ((t.category_primary ?? "Uncategorized") as string);
193
+ totals[key] = (totals[key] ?? 0) + (t.amount as number);
194
+ }
195
+
196
+ // Sort by spend descending
197
+ const sorted = Object.entries(totals).sort((a, b) => b[1] - a[1]);
198
+
199
+ const totalSpend = sorted.reduce((sum, [, v]) => sum + v, 0);
200
+ const dateRange =
201
+ args.start_date && args.end_date
202
+ ? `${args.start_date} to ${args.end_date}`
203
+ : args.start_date
204
+ ? `from ${args.start_date}`
205
+ : args.end_date
206
+ ? `through ${args.end_date}`
207
+ : "all time";
208
+
209
+ const lines = sorted.map(
210
+ ([key, amt]) =>
211
+ `${key.padEnd(40)} $${amt.toFixed(2).padStart(10)}`
212
+ );
213
+
214
+ return [
215
+ `Spending summary by ${groupBy} (${dateRange})`,
216
+ `Total: $${totalSpend.toFixed(2)}`,
217
+ "─".repeat(55),
218
+ ...lines,
219
+ ].join("\n");
220
+ }
221
+
222
+ // ── MCP Server ───────────────────────────────────────────────────────────────
223
+
224
+ const server = new Server(
225
+ { name: "sheetlink", version: "0.1.0" },
226
+ { capabilities: { tools: {} } }
227
+ );
228
+
229
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
230
+ tools: [
231
+ {
232
+ name: "list_accounts",
233
+ description:
234
+ "List all bank accounts connected to SheetLink, including institution names and last sync time.",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {},
238
+ required: [],
239
+ },
240
+ },
241
+ {
242
+ name: "list_transactions",
243
+ description:
244
+ "Fetch bank transactions from SheetLink. Optionally filter by account, date range, or spending category. Returns date, merchant, amount, and category for each transaction.",
245
+ inputSchema: {
246
+ type: "object",
247
+ properties: {
248
+ item_id: {
249
+ type: "string",
250
+ description:
251
+ "Filter to a specific bank account (item_id from list_accounts). Omit to fetch all accounts.",
252
+ },
253
+ start_date: {
254
+ type: "string",
255
+ description: "Start date filter in YYYY-MM-DD format (inclusive).",
256
+ },
257
+ end_date: {
258
+ type: "string",
259
+ description: "End date filter in YYYY-MM-DD format (inclusive).",
260
+ },
261
+ category: {
262
+ type: "string",
263
+ description:
264
+ "Filter by spending category (e.g. FOOD_AND_DRINK, TRANSPORTATION, SHOPPING). Partial match, case-insensitive.",
265
+ },
266
+ limit: {
267
+ type: "number",
268
+ description: "Maximum number of transactions to return (default: 100).",
269
+ },
270
+ },
271
+ required: [],
272
+ },
273
+ },
274
+ {
275
+ name: "get_spending_summary",
276
+ description:
277
+ "Get a spending summary aggregated by category or merchant for a given date range. Useful for answering questions like 'how much did I spend on food last month?' or 'what are my top spending categories?'",
278
+ inputSchema: {
279
+ type: "object",
280
+ properties: {
281
+ item_id: {
282
+ type: "string",
283
+ description:
284
+ "Limit summary to a specific bank account. Omit for all accounts.",
285
+ },
286
+ start_date: {
287
+ type: "string",
288
+ description: "Start date in YYYY-MM-DD format.",
289
+ },
290
+ end_date: {
291
+ type: "string",
292
+ description: "End date in YYYY-MM-DD format.",
293
+ },
294
+ group_by: {
295
+ type: "string",
296
+ enum: ["category", "merchant"],
297
+ description: "Group spending by category (default) or merchant.",
298
+ },
299
+ },
300
+ required: [],
301
+ },
302
+ },
303
+ ],
304
+ }));
305
+
306
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
307
+ const { name, arguments: args = {} } = request.params;
308
+
309
+ try {
310
+ let result: string;
311
+
312
+ if (name === "list_accounts") {
313
+ result = await listAccounts();
314
+ } else if (name === "list_transactions") {
315
+ result = await listTransactions(args as Parameters<typeof listTransactions>[0]);
316
+ } else if (name === "get_spending_summary") {
317
+ result = await getSpendingSummary(args as Parameters<typeof getSpendingSummary>[0]);
318
+ } else {
319
+ throw new Error(`Unknown tool: ${name}`);
320
+ }
321
+
322
+ return { content: [{ type: "text", text: result }] };
323
+ } catch (err) {
324
+ const message = err instanceof Error ? err.message : String(err);
325
+ return {
326
+ content: [{ type: "text", text: `Error: ${message}` }],
327
+ isError: true,
328
+ };
329
+ }
330
+ });
331
+
332
+ const transport = new StdioServerTransport();
333
+ await server.connect(transport);
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src"]
14
+ }