@mintline/mcp 1.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 ADDED
@@ -0,0 +1,79 @@
1
+ # Mintline MCP Server
2
+
3
+ Connect AI assistants to your Mintline receipts and transactions via the [Model Context Protocol](https://modelcontextprotocol.io).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @mintline/mcp
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### 1. Get your API key
14
+
15
+ Create an API key at [mintline.ai/app/settings/api-keys](https://mintline.ai/app/settings/api-keys)
16
+
17
+ ### 2. Configure Claude Desktop
18
+
19
+ Add to your `~/.claude/claude_desktop_config.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "mintline": {
25
+ "command": "mintline-mcp",
26
+ "env": {
27
+ "MINTLINE_API_KEY": "ml_live_your_api_key_here"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### 3. Restart Claude Desktop
35
+
36
+ The Mintline tools will now be available.
37
+
38
+ ## Available Tools
39
+
40
+ | Tool | Description |
41
+ |------|-------------|
42
+ | `list_receipts` | Search and filter receipts by vendor, status |
43
+ | `get_receipt` | Get receipt details with line items |
44
+ | `list_transactions` | Search and filter bank transactions |
45
+ | `get_transaction` | Get transaction details |
46
+ | `list_statements` | List uploaded bank statements |
47
+ | `list_matches` | View proposed receipt-transaction matches |
48
+ | `confirm_match` | Confirm a proposed match |
49
+ | `reject_match` | Reject a proposed match |
50
+
51
+ ## Example Prompts
52
+
53
+ - "Show me my unmatched receipts"
54
+ - "Find receipts from Amazon"
55
+ - "What transactions need matching?"
56
+ - "Confirm the top match"
57
+ - "Show details for receipt rcpt_01abc123"
58
+
59
+ ## Environment Variables
60
+
61
+ | Variable | Required | Description |
62
+ |----------|----------|-------------|
63
+ | `MINTLINE_API_KEY` | Yes | Your Mintline API key |
64
+ | `MINTLINE_API_URL` | No | API URL (default: https://api.mintline.ai) |
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ git clone https://github.com/mintlineai/mintline-mcp.git
70
+ cd mintline-mcp
71
+ npm install
72
+
73
+ # Run locally
74
+ MINTLINE_API_KEY=ml_live_... node src/index.js
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./index.js";
package/dist/index.js ADDED
@@ -0,0 +1,382 @@
1
+ // src/index.js
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
+ // src/client.js
10
+ var BASE_URL = process.env.MINTLINE_API_URL || "https://api.mintline.ai";
11
+ function createClient(apiKey2) {
12
+ if (!apiKey2) {
13
+ throw new Error("MINTLINE_API_KEY is required");
14
+ }
15
+ async function request(method, path, body) {
16
+ const res = await fetch(`${BASE_URL}${path}`, {
17
+ method,
18
+ headers: {
19
+ "Authorization": `Bearer ${apiKey2}`,
20
+ "Content-Type": "application/json"
21
+ },
22
+ body: body ? JSON.stringify(body) : void 0
23
+ });
24
+ const data = await res.json();
25
+ if (!data.success) {
26
+ throw new Error(data.error?.message || "Request failed");
27
+ }
28
+ return data;
29
+ }
30
+ return {
31
+ // Receipts
32
+ async listReceipts(params = {}) {
33
+ const query = new URLSearchParams();
34
+ if (params.limit) query.set("limit", params.limit);
35
+ if (params.offset) query.set("offset", params.offset);
36
+ if (params.search) query.set("q", params.search);
37
+ if (params.status) query.set("status", params.status);
38
+ const qs = query.toString();
39
+ return request("GET", `/api/receipts${qs ? `?${qs}` : ""}`);
40
+ },
41
+ async getReceipt(id) {
42
+ return request("GET", `/api/receipts/${id}`);
43
+ },
44
+ // Transactions
45
+ async listTransactions(params = {}) {
46
+ const query = new URLSearchParams();
47
+ if (params.limit) query.set("limit", params.limit);
48
+ if (params.offset) query.set("offset", params.offset);
49
+ if (params.search) query.set("q", params.search);
50
+ if (params.status) query.set("status", params.status);
51
+ if (params.statementId) query.set("statementId", params.statementId);
52
+ const qs = query.toString();
53
+ return request("GET", `/api/transactions${qs ? `?${qs}` : ""}`);
54
+ },
55
+ async getTransaction(id) {
56
+ return request("GET", `/api/transactions/${id}`);
57
+ },
58
+ // Statements
59
+ async listStatements(params = {}) {
60
+ const query = new URLSearchParams();
61
+ if (params.limit) query.set("limit", params.limit);
62
+ if (params.offset) query.set("offset", params.offset);
63
+ const qs = query.toString();
64
+ return request("GET", `/api/statements${qs ? `?${qs}` : ""}`);
65
+ },
66
+ // Matches
67
+ async listMatches(params = {}) {
68
+ const query = new URLSearchParams();
69
+ if (params.limit) query.set("limit", params.limit);
70
+ if (params.offset) query.set("offset", params.offset);
71
+ if (params.status) query.set("status", params.status);
72
+ const qs = query.toString();
73
+ return request("GET", `/api/matches${qs ? `?${qs}` : ""}`);
74
+ },
75
+ async confirmMatch(id) {
76
+ return request("POST", `/api/matches/${id}/confirm`);
77
+ },
78
+ async rejectMatch(id, reason) {
79
+ return request("POST", `/api/matches/${id}/reject`, { reason });
80
+ }
81
+ };
82
+ }
83
+
84
+ // src/tools.js
85
+ var tools = [
86
+ {
87
+ name: "list_receipts",
88
+ description: "List receipts with optional filtering. Use this to find receipts by vendor name, date range, or match status.",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ search: {
93
+ type: "string",
94
+ description: "Search by vendor name"
95
+ },
96
+ status: {
97
+ type: "string",
98
+ enum: ["all", "matched", "unmatched", "hidden"],
99
+ description: "Filter by match status"
100
+ },
101
+ limit: {
102
+ type: "number",
103
+ description: "Max results to return (default 20)"
104
+ }
105
+ }
106
+ }
107
+ },
108
+ {
109
+ name: "get_receipt",
110
+ description: "Get detailed information about a specific receipt including line items and matched transaction.",
111
+ inputSchema: {
112
+ type: "object",
113
+ properties: {
114
+ id: {
115
+ type: "string",
116
+ description: "Receipt ID (e.g., rcpt_01abc123)"
117
+ }
118
+ },
119
+ required: ["id"]
120
+ }
121
+ },
122
+ {
123
+ name: "list_transactions",
124
+ description: "List bank transactions with optional filtering. Use this to find transactions by description or match status.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {
128
+ search: {
129
+ type: "string",
130
+ description: "Search by transaction description"
131
+ },
132
+ status: {
133
+ type: "string",
134
+ enum: ["all", "matched", "unmatched", "hidden"],
135
+ description: "Filter by match status"
136
+ },
137
+ statementId: {
138
+ type: "string",
139
+ description: "Filter by bank statement ID"
140
+ },
141
+ limit: {
142
+ type: "number",
143
+ description: "Max results to return (default 20)"
144
+ }
145
+ }
146
+ }
147
+ },
148
+ {
149
+ name: "get_transaction",
150
+ description: "Get detailed information about a specific bank transaction.",
151
+ inputSchema: {
152
+ type: "object",
153
+ properties: {
154
+ id: {
155
+ type: "string",
156
+ description: "Transaction ID (e.g., btxn_01abc123)"
157
+ }
158
+ },
159
+ required: ["id"]
160
+ }
161
+ },
162
+ {
163
+ name: "list_statements",
164
+ description: "List uploaded bank statements.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ limit: {
169
+ type: "number",
170
+ description: "Max results to return (default 20)"
171
+ }
172
+ }
173
+ }
174
+ },
175
+ {
176
+ name: "list_matches",
177
+ description: "List proposed matches between receipts and transactions. Use this to review matches that need confirmation.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ status: {
182
+ type: "string",
183
+ enum: ["proposed", "confirmed", "rejected"],
184
+ description: "Filter by match status (default: proposed)"
185
+ },
186
+ limit: {
187
+ type: "number",
188
+ description: "Max results to return (default 20)"
189
+ }
190
+ }
191
+ }
192
+ },
193
+ {
194
+ name: "confirm_match",
195
+ description: "Confirm a proposed match between a receipt and transaction. This links them together permanently.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ id: {
200
+ type: "string",
201
+ description: "Match ID to confirm"
202
+ }
203
+ },
204
+ required: ["id"]
205
+ }
206
+ },
207
+ {
208
+ name: "reject_match",
209
+ description: "Reject a proposed match. The receipt and transaction will not be suggested as a match again.",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ id: {
214
+ type: "string",
215
+ description: "Match ID to reject"
216
+ },
217
+ reason: {
218
+ type: "string",
219
+ description: "Optional reason for rejection"
220
+ }
221
+ },
222
+ required: ["id"]
223
+ }
224
+ }
225
+ ];
226
+ async function handleTool(client2, name, args) {
227
+ switch (name) {
228
+ case "list_receipts": {
229
+ const result = await client2.listReceipts({
230
+ search: args.search,
231
+ status: args.status,
232
+ limit: args.limit || 20
233
+ });
234
+ return formatReceipts(result.data);
235
+ }
236
+ case "get_receipt": {
237
+ const result = await client2.getReceipt(args.id);
238
+ return formatReceiptDetail(result.data);
239
+ }
240
+ case "list_transactions": {
241
+ const result = await client2.listTransactions({
242
+ search: args.search,
243
+ status: args.status,
244
+ statementId: args.statementId,
245
+ limit: args.limit || 20
246
+ });
247
+ return formatTransactions(result.data);
248
+ }
249
+ case "get_transaction": {
250
+ const result = await client2.getTransaction(args.id);
251
+ return formatTransaction(result.data);
252
+ }
253
+ case "list_statements": {
254
+ const result = await client2.listStatements({
255
+ limit: args.limit || 20
256
+ });
257
+ return formatStatements(result.data);
258
+ }
259
+ case "list_matches": {
260
+ const result = await client2.listMatches({
261
+ status: args.status || "proposed",
262
+ limit: args.limit || 20
263
+ });
264
+ return formatMatches(result.data);
265
+ }
266
+ case "confirm_match": {
267
+ await client2.confirmMatch(args.id);
268
+ return `Match ${args.id} confirmed successfully.`;
269
+ }
270
+ case "reject_match": {
271
+ await client2.rejectMatch(args.id, args.reason);
272
+ return `Match ${args.id} rejected.${args.reason ? ` Reason: ${args.reason}` : ""}`;
273
+ }
274
+ default:
275
+ throw new Error(`Unknown tool: ${name}`);
276
+ }
277
+ }
278
+ function formatReceipts(receipts) {
279
+ if (!receipts?.length) return "No receipts found.";
280
+ return receipts.map(
281
+ (r) => `\u2022 ${r.id}: ${r.vendor?.name || r.vendorName || "Unknown"} - ${r.totalAmount || "?"} ${r.currency || ""} (${r.purchaseDate ? new Date(r.purchaseDate).toLocaleDateString() : "no date"})`
282
+ ).join("\n");
283
+ }
284
+ function formatReceiptDetail(r) {
285
+ let text = `Receipt: ${r.id}
286
+ `;
287
+ text += `Vendor: ${r.vendor?.name || r.vendorName || "Unknown"}
288
+ `;
289
+ text += `Date: ${r.purchaseDate ? new Date(r.purchaseDate).toLocaleDateString() : "N/A"}
290
+ `;
291
+ text += `Total: ${r.totalAmount || "?"} ${r.currency || ""}
292
+ `;
293
+ if (r.items?.length) {
294
+ text += `
295
+ Line Items:
296
+ `;
297
+ r.items.forEach((item) => {
298
+ text += ` \u2022 ${item.description}: ${item.totalPrice}
299
+ `;
300
+ });
301
+ }
302
+ if (r.matchedTransaction) {
303
+ text += `
304
+ Matched to: ${r.matchedTransaction.id} - ${r.matchedTransaction.description}`;
305
+ }
306
+ return text;
307
+ }
308
+ function formatTransactions(transactions) {
309
+ if (!transactions?.length) return "No transactions found.";
310
+ return transactions.map(
311
+ (t) => `\u2022 ${t.id}: ${t.description} - ${t.amount} ${t.currency || ""} (${t.transactionDate ? new Date(t.transactionDate).toLocaleDateString() : "no date"})`
312
+ ).join("\n");
313
+ }
314
+ function formatTransaction(t) {
315
+ let text = `Transaction: ${t.id}
316
+ `;
317
+ text += `Description: ${t.description}
318
+ `;
319
+ text += `Amount: ${t.amount} ${t.currency || ""}
320
+ `;
321
+ text += `Date: ${t.transactionDate ? new Date(t.transactionDate).toLocaleDateString() : "N/A"}
322
+ `;
323
+ text += `Type: ${t.transactionType || "N/A"}`;
324
+ return text;
325
+ }
326
+ function formatStatements(statements) {
327
+ if (!statements?.length) return "No statements found.";
328
+ return statements.map(
329
+ (s) => `\u2022 ${s.id}: ${s.institutionName} - ${s.statementDate ? new Date(s.statementDate).toLocaleDateString() : "no date"} (${s.transactionCount || 0} transactions)`
330
+ ).join("\n");
331
+ }
332
+ function formatMatches(matches) {
333
+ if (!matches?.length) return "No matches found.";
334
+ return matches.map(
335
+ (m) => `\u2022 ${m.id}: Receipt ${m.receiptId} \u2194 Transaction ${m.transactionId} (${Math.round(m.confidenceScore * 100)}% confidence) [${m.status}]`
336
+ ).join("\n");
337
+ }
338
+
339
+ // src/index.js
340
+ var apiKey = process.env.MINTLINE_API_KEY;
341
+ if (!apiKey) {
342
+ console.error("Error: MINTLINE_API_KEY environment variable is required");
343
+ process.exit(1);
344
+ }
345
+ var client = createClient(apiKey);
346
+ var server = new Server(
347
+ {
348
+ name: "mintline-mcp",
349
+ version: "1.0.0"
350
+ },
351
+ {
352
+ capabilities: {
353
+ tools: {}
354
+ }
355
+ }
356
+ );
357
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
358
+ return { tools };
359
+ });
360
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
361
+ const { name, arguments: args } = request.params;
362
+ try {
363
+ const result = await handleTool(client, name, args || {});
364
+ return {
365
+ content: [{ type: "text", text: result }]
366
+ };
367
+ } catch (error) {
368
+ return {
369
+ content: [{ type: "text", text: `Error: ${error.message}` }],
370
+ isError: true
371
+ };
372
+ }
373
+ });
374
+ async function main() {
375
+ const transport = new StdioServerTransport();
376
+ await server.connect(transport);
377
+ console.error("Mintline MCP server running");
378
+ }
379
+ main().catch((error) => {
380
+ console.error("Fatal error:", error);
381
+ process.exit(1);
382
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@mintline/mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Mintline - connect AI assistants to your receipts and transactions",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "mintline-mcp": "./dist/cli.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/sdk && node scripts/build-cli.js",
12
+ "prepublishOnly": "npm run build",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "mcp",
20
+ "mintline",
21
+ "receipts",
22
+ "ai",
23
+ "claude"
24
+ ],
25
+ "author": "Mintline",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=18"
32
+ },
33
+ "devDependencies": {
34
+ "esbuild": "^0.27.2"
35
+ }
36
+ }