@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.
Files changed (3) hide show
  1. package/dist/index.js +47 -20
  2. package/package.json +1 -1
  3. 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
- const API_BASE = "https://api.sheetlink.app";
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
- 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
- },
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) => (t.category_primary ?? "").toUpperCase().includes(cat) ||
70
- (t.category_detailed ?? "").toUpperCase().includes(cat));
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.description ?? t.description_raw ?? "Unknown";
84
- const cat = t.category_primary ?? "";
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.description ?? "Unknown")
126
- : (t.category_primary ?? "Uncategorized");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sheetlink/mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MCP server for SheetLink — connect Claude to your bank transactions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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 = "https://api.sheetlink.app";
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
- 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
- });
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
- if (!res.ok) {
28
- const text = await res.text();
29
- throw new Error(`SheetLink API error ${res.status}: ${text}`);
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
- return res.json();
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
- (t) =>
108
- ((t.category_primary as string) ?? "").toUpperCase().includes(cat) ||
109
- ((t.category_detailed as string) ?? "").toUpperCase().includes(cat)
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.description ?? t.description_raw ?? "Unknown";
130
- const cat = t.category_primary ?? "";
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.description ?? "Unknown") as string)
192
- : ((t.category_primary ?? "Uncategorized") as string);
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