@sheetlink/mcp 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +47 -20
- package/package.json +1 -1
- package/src/index.ts +44 -24
package/dist/index.js
CHANGED
|
@@ -2,26 +2,49 @@
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
|
|
5
|
+
import https from "https";
|
|
6
|
+
const API_BASE = "api.sheetlink.app";
|
|
6
7
|
const API_KEY = process.env.SHEETLINK_API_KEY;
|
|
7
8
|
if (!API_KEY) {
|
|
8
9
|
console.error("Error: SHEETLINK_API_KEY environment variable is required");
|
|
9
10
|
process.exit(1);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
function apiFetch(path, options = {}) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const bodyData = options.body ?? null;
|
|
16
|
+
const reqOptions = {
|
|
17
|
+
hostname: API_BASE,
|
|
18
|
+
path,
|
|
19
|
+
method: options.method ?? "GET",
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
...(bodyData ? { "Content-Length": Buffer.byteLength(bodyData) } : {}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const req = https.request(reqOptions, (res) => {
|
|
27
|
+
let data = "";
|
|
28
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
29
|
+
res.on("end", () => {
|
|
30
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
31
|
+
reject(new Error(`SheetLink API error ${res.statusCode}: ${data}`));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
try {
|
|
35
|
+
resolve(JSON.parse(data));
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
reject(new Error(`Failed to parse response: ${data}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
req.on("error", reject);
|
|
44
|
+
if (bodyData)
|
|
45
|
+
req.write(bodyData);
|
|
46
|
+
req.end();
|
|
19
47
|
});
|
|
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
48
|
}
|
|
26
49
|
// ── Tool handlers ────────────────────────────────────────────────────────────
|
|
27
50
|
async function listAccounts() {
|
|
@@ -66,8 +89,11 @@ async function listTransactions(args) {
|
|
|
66
89
|
// Filter by category
|
|
67
90
|
if (args.category) {
|
|
68
91
|
const cat = args.category.toUpperCase();
|
|
69
|
-
allTransactions = allTransactions.filter((t) =>
|
|
70
|
-
|
|
92
|
+
allTransactions = allTransactions.filter((t) => {
|
|
93
|
+
const pfc = t.personal_finance_category;
|
|
94
|
+
return ((pfc?.primary ?? "").toUpperCase().includes(cat) ||
|
|
95
|
+
(pfc?.detailed ?? "").toUpperCase().includes(cat));
|
|
96
|
+
});
|
|
71
97
|
}
|
|
72
98
|
// Sort newest first
|
|
73
99
|
allTransactions.sort((a, b) => b.date.localeCompare(a.date));
|
|
@@ -80,8 +106,9 @@ async function listTransactions(args) {
|
|
|
80
106
|
}
|
|
81
107
|
const lines = transactions.map((t) => {
|
|
82
108
|
const amount = typeof t.amount === "number" ? t.amount.toFixed(2) : t.amount;
|
|
83
|
-
const name = t.merchant_name ?? t.
|
|
84
|
-
const
|
|
109
|
+
const name = t.merchant_name ?? t.description_raw ?? "Unknown";
|
|
110
|
+
const pfc = t.personal_finance_category;
|
|
111
|
+
const cat = pfc?.primary ?? "";
|
|
85
112
|
return `${t.date} ${String(name).padEnd(35)} $${String(amount).padStart(8)} ${cat}`;
|
|
86
113
|
});
|
|
87
114
|
const header = `${"Date".padEnd(10)} ${"Merchant".padEnd(35)} ${"Amount".padStart(9)} Category`;
|
|
@@ -122,8 +149,8 @@ async function getSpendingSummary(args) {
|
|
|
122
149
|
const totals = {};
|
|
123
150
|
for (const t of allTransactions) {
|
|
124
151
|
const key = groupBy === "merchant"
|
|
125
|
-
? (t.merchant_name ?? t.
|
|
126
|
-
: (t.
|
|
152
|
+
? (t.merchant_name ?? t.description_raw ?? "Unknown")
|
|
153
|
+
: ((t.personal_finance_category?.primary) ?? "Uncategorized");
|
|
127
154
|
totals[key] = (totals[key] ?? 0) + t.amount;
|
|
128
155
|
}
|
|
129
156
|
// Sort by spend descending
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
CallToolRequestSchema,
|
|
6
6
|
ListToolsRequestSchema,
|
|
7
7
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import https from "https";
|
|
8
9
|
|
|
9
|
-
const API_BASE = "
|
|
10
|
+
const API_BASE = "api.sheetlink.app";
|
|
10
11
|
const API_KEY = process.env.SHEETLINK_API_KEY;
|
|
11
12
|
|
|
12
13
|
if (!API_KEY) {
|
|
@@ -14,22 +15,38 @@ if (!API_KEY) {
|
|
|
14
15
|
process.exit(1);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
function apiFetch(path: string, options: { method?: string; body?: string } = {}): Promise<any> {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const bodyData = options.body ?? null;
|
|
22
|
+
const reqOptions: https.RequestOptions = {
|
|
23
|
+
hostname: API_BASE,
|
|
24
|
+
path,
|
|
25
|
+
method: options.method ?? "GET",
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
...(bodyData ? { "Content-Length": Buffer.byteLength(bodyData) } : {}),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
const req = https.request(reqOptions, (res) => {
|
|
34
|
+
let data = "";
|
|
35
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
36
|
+
res.on("end", () => {
|
|
37
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
38
|
+
reject(new Error(`SheetLink API error ${res.statusCode}: ${data}`));
|
|
39
|
+
} else {
|
|
40
|
+
try { resolve(JSON.parse(data)); }
|
|
41
|
+
catch (e) { reject(new Error(`Failed to parse response: ${data}`)); }
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
req.on("error", reject);
|
|
47
|
+
if (bodyData) req.write(bodyData);
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
// ── Tool handlers ────────────────────────────────────────────────────────────
|
|
@@ -103,11 +120,13 @@ async function listTransactions(args: {
|
|
|
103
120
|
// Filter by category
|
|
104
121
|
if (args.category) {
|
|
105
122
|
const cat = args.category.toUpperCase();
|
|
106
|
-
allTransactions = allTransactions.filter(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
(
|
|
110
|
-
|
|
123
|
+
allTransactions = allTransactions.filter((t) => {
|
|
124
|
+
const pfc = t.personal_finance_category as { primary?: string; detailed?: string } | null;
|
|
125
|
+
return (
|
|
126
|
+
(pfc?.primary ?? "").toUpperCase().includes(cat) ||
|
|
127
|
+
(pfc?.detailed ?? "").toUpperCase().includes(cat)
|
|
128
|
+
);
|
|
129
|
+
});
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
// Sort newest first
|
|
@@ -126,8 +145,9 @@ async function listTransactions(args: {
|
|
|
126
145
|
|
|
127
146
|
const lines = transactions.map((t) => {
|
|
128
147
|
const amount = typeof t.amount === "number" ? t.amount.toFixed(2) : t.amount;
|
|
129
|
-
const name = t.merchant_name ?? t.
|
|
130
|
-
const
|
|
148
|
+
const name = t.merchant_name ?? t.description_raw ?? "Unknown";
|
|
149
|
+
const pfc = t.personal_finance_category as { primary?: string } | null;
|
|
150
|
+
const cat = pfc?.primary ?? "";
|
|
131
151
|
return `${t.date} ${String(name).padEnd(35)} $${String(amount).padStart(8)} ${cat}`;
|
|
132
152
|
});
|
|
133
153
|
|
|
@@ -188,8 +208,8 @@ async function getSpendingSummary(args: {
|
|
|
188
208
|
for (const t of allTransactions) {
|
|
189
209
|
const key =
|
|
190
210
|
groupBy === "merchant"
|
|
191
|
-
? ((t.merchant_name ?? t.
|
|
192
|
-
: ((t.
|
|
211
|
+
? ((t.merchant_name ?? t.description_raw ?? "Unknown") as string)
|
|
212
|
+
: (((t.personal_finance_category as { primary?: string } | null)?.primary) ?? "Uncategorized");
|
|
193
213
|
totals[key] = (totals[key] ?? 0) + (t.amount as number);
|
|
194
214
|
}
|
|
195
215
|
|