copilot-money-mcp 1.2.3 → 1.3.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/dist/cli.js CHANGED
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
21
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
22
  for (let key of __getOwnPropNames(mod))
11
23
  if (!__hasOwnProp.call(to, key))
12
24
  __defProp(to, key, {
13
- get: () => mod[key],
25
+ get: __accessProp.bind(mod, key),
14
26
  enumerable: true
15
27
  });
28
+ if (canCache)
29
+ cache.set(mod, to);
16
30
  return to;
17
31
  };
18
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
19
37
  var __export = (target, all) => {
20
38
  for (var name in all)
21
39
  __defProp(target, name, {
22
40
  get: all[name],
23
41
  enumerable: true,
24
42
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
43
+ set: __exportSetter.bind(all, name)
26
44
  });
27
45
  };
28
46
 
@@ -27286,6 +27304,62 @@ class ExperimentalServerTasks {
27286
27304
  requestStream(request, resultSchema, options) {
27287
27305
  return this._server.requestStream(request, resultSchema, options);
27288
27306
  }
27307
+ createMessageStream(params, options) {
27308
+ const clientCapabilities = this._server.getClientCapabilities();
27309
+ if ((params.tools || params.toolChoice) && !clientCapabilities?.sampling?.tools) {
27310
+ throw new Error("Client does not support sampling tools capability.");
27311
+ }
27312
+ if (params.messages.length > 0) {
27313
+ const lastMessage = params.messages[params.messages.length - 1];
27314
+ const lastContent = Array.isArray(lastMessage.content) ? lastMessage.content : [lastMessage.content];
27315
+ const hasToolResults = lastContent.some((c) => c.type === "tool_result");
27316
+ const previousMessage = params.messages.length > 1 ? params.messages[params.messages.length - 2] : undefined;
27317
+ const previousContent = previousMessage ? Array.isArray(previousMessage.content) ? previousMessage.content : [previousMessage.content] : [];
27318
+ const hasPreviousToolUse = previousContent.some((c) => c.type === "tool_use");
27319
+ if (hasToolResults) {
27320
+ if (lastContent.some((c) => c.type !== "tool_result")) {
27321
+ throw new Error("The last message must contain only tool_result content if any is present");
27322
+ }
27323
+ if (!hasPreviousToolUse) {
27324
+ throw new Error("tool_result blocks are not matching any tool_use from the previous message");
27325
+ }
27326
+ }
27327
+ if (hasPreviousToolUse) {
27328
+ const toolUseIds = new Set(previousContent.filter((c) => c.type === "tool_use").map((c) => c.id));
27329
+ const toolResultIds = new Set(lastContent.filter((c) => c.type === "tool_result").map((c) => c.toolUseId));
27330
+ if (toolUseIds.size !== toolResultIds.size || ![...toolUseIds].every((id) => toolResultIds.has(id))) {
27331
+ throw new Error("ids of tool_result blocks and tool_use blocks from previous message do not match");
27332
+ }
27333
+ }
27334
+ }
27335
+ return this.requestStream({
27336
+ method: "sampling/createMessage",
27337
+ params
27338
+ }, CreateMessageResultSchema, options);
27339
+ }
27340
+ elicitInputStream(params, options) {
27341
+ const clientCapabilities = this._server.getClientCapabilities();
27342
+ const mode = params.mode ?? "form";
27343
+ switch (mode) {
27344
+ case "url": {
27345
+ if (!clientCapabilities?.elicitation?.url) {
27346
+ throw new Error("Client does not support url elicitation.");
27347
+ }
27348
+ break;
27349
+ }
27350
+ case "form": {
27351
+ if (!clientCapabilities?.elicitation?.form) {
27352
+ throw new Error("Client does not support form elicitation.");
27353
+ }
27354
+ break;
27355
+ }
27356
+ }
27357
+ const normalizedParams = mode === "form" && params.mode === undefined ? { ...params, mode: "form" } : params;
27358
+ return this.requestStream({
27359
+ method: "elicitation/create",
27360
+ params: normalizedParams
27361
+ }, ElicitResultSchema, options);
27362
+ }
27289
27363
  async getTask(taskId, options) {
27290
27364
  return this._server.getTask({ taskId }, options);
27291
27365
  }
@@ -27764,6 +27838,11 @@ import { existsSync, readdirSync } from "fs";
27764
27838
  import { homedir } from "os";
27765
27839
  import { join } from "path";
27766
27840
 
27841
+ // src/core/decoder.ts
27842
+ import { Worker } from "node:worker_threads";
27843
+ import { fileURLToPath } from "node:url";
27844
+ import path2 from "node:path";
27845
+
27767
27846
  // src/core/leveldb-reader.ts
27768
27847
  import { ClassicLevel } from "classic-level";
27769
27848
  import fs from "node:fs";
@@ -28282,7 +28361,6 @@ async function* iterateDocuments(dbPath, options = {}) {
28282
28361
  key: keyStr,
28283
28362
  collection: parsed.collection,
28284
28363
  documentId: parsed.documentId,
28285
- rawValue: value,
28286
28364
  fields
28287
28365
  };
28288
28366
  count++;
@@ -28506,6 +28584,14 @@ var ItemSchema = exports_external.object({
28506
28584
  last_successful_update: exports_external.string().optional(),
28507
28585
  last_failed_update: exports_external.string().optional(),
28508
28586
  consent_expiration_time: exports_external.string().optional(),
28587
+ status_transactions_last_successful_update: exports_external.string().optional(),
28588
+ status_transactions_last_failed_update: exports_external.string().optional(),
28589
+ status_investments_last_successful_update: exports_external.string().optional(),
28590
+ status_investments_last_failed_update: exports_external.string().optional(),
28591
+ latest_fetch: exports_external.string().optional(),
28592
+ latest_investments_fetch: exports_external.string().optional(),
28593
+ login_required: exports_external.boolean().optional(),
28594
+ disconnected: exports_external.boolean().optional(),
28509
28595
  error_code: exports_external.string().optional(),
28510
28596
  error_message: exports_external.string().optional(),
28511
28597
  error_type: exports_external.string().optional(),
@@ -28518,6 +28604,36 @@ var ItemSchema = exports_external.object({
28518
28604
  updated_at: exports_external.string().optional(),
28519
28605
  webhook: exports_external.string().optional()
28520
28606
  }).passthrough();
28607
+ function getItemDisplayName(item) {
28608
+ return item.institution_name ?? item.institution_id ?? item.item_id;
28609
+ }
28610
+ function isItemHealthy(item) {
28611
+ if (item.connection_status) {
28612
+ if (item.connection_status !== "active") {
28613
+ return false;
28614
+ }
28615
+ }
28616
+ if (item.needs_update === true) {
28617
+ return false;
28618
+ }
28619
+ if (item.error_code && item.error_code !== "ITEM_NO_ERROR") {
28620
+ return false;
28621
+ }
28622
+ return true;
28623
+ }
28624
+ function itemNeedsAttention(item) {
28625
+ if (item.needs_update === true) {
28626
+ return true;
28627
+ }
28628
+ if (item.connection_status === "error" || item.connection_status === "disconnected") {
28629
+ return true;
28630
+ }
28631
+ const loginRequiredCodes = ["ITEM_LOGIN_REQUIRED", "INVALID_CREDENTIALS", "INVALID_MFA"];
28632
+ if (item.error_code && loginRequiredCodes.includes(item.error_code)) {
28633
+ return true;
28634
+ }
28635
+ return false;
28636
+ }
28521
28637
 
28522
28638
  // src/models/category.ts
28523
28639
  var CategorySchema = exports_external.object({
@@ -28634,26 +28750,38 @@ function calculateNextDate(lastDate, frequency) {
28634
28750
  }
28635
28751
  return date6.toISOString().split("T")[0];
28636
28752
  }
28637
- async function decodeTransactions(dbPath) {
28638
- const transactions = [];
28639
- for await (const doc2 of iterateDocuments(dbPath, { collection: "transactions" })) {
28640
- const txn = processTransaction(doc2.fields, doc2.documentId);
28641
- if (txn)
28642
- transactions.push(txn);
28643
- }
28753
+ function deduplicateTransactions(transactions) {
28644
28754
  const seen = new Set;
28645
28755
  const unique = [];
28646
28756
  for (const txn of transactions) {
28647
- const displayName = getTransactionDisplayName(txn);
28648
- const key = `${displayName}|${txn.amount}|${txn.date}`;
28649
- if (!seen.has(key)) {
28650
- seen.add(key);
28757
+ if (!seen.has(txn.transaction_id)) {
28758
+ seen.add(txn.transaction_id);
28651
28759
  unique.push(txn);
28652
28760
  }
28653
28761
  }
28654
- unique.sort((a, b) => a.date > b.date ? -1 : a.date < b.date ? 1 : 0);
28655
28762
  return unique;
28656
28763
  }
28764
+ function reconcilePendingTransactions(transactions) {
28765
+ const supersededPendingIds = new Set;
28766
+ for (const txn of transactions) {
28767
+ if (!txn.pending && txn.pending_transaction_id) {
28768
+ supersededPendingIds.add(txn.pending_transaction_id);
28769
+ }
28770
+ }
28771
+ return transactions.filter((txn) => !(txn.pending && supersededPendingIds.has(txn.transaction_id)));
28772
+ }
28773
+ async function decodeTransactions(dbPath) {
28774
+ const transactions = [];
28775
+ for await (const doc2 of iterateDocuments(dbPath, { collection: "transactions" })) {
28776
+ const txn = processTransaction(doc2.fields, doc2.documentId);
28777
+ if (txn)
28778
+ transactions.push(txn);
28779
+ }
28780
+ const deduped = deduplicateTransactions(transactions);
28781
+ const reconciled = reconcilePendingTransactions(deduped);
28782
+ reconciled.sort((a, b) => a.date > b.date ? -1 : a.date < b.date ? 1 : 0);
28783
+ return reconciled;
28784
+ }
28657
28785
  async function decodeAccounts(dbPath) {
28658
28786
  const accounts = [];
28659
28787
  for await (const doc2 of iterateDocuments(dbPath, { collection: "accounts" })) {
@@ -29326,7 +29454,13 @@ function processItem(fields, docId) {
29326
29454
  "error_type",
29327
29455
  "created_at",
29328
29456
  "updated_at",
29329
- "webhook"
29457
+ "webhook",
29458
+ "status_transactions_last_successful_update",
29459
+ "status_transactions_last_failed_update",
29460
+ "status_investments_last_successful_update",
29461
+ "status_investments_last_failed_update",
29462
+ "latest_fetch",
29463
+ "latest_investments_fetch"
29330
29464
  ];
29331
29465
  for (const field of stringFields) {
29332
29466
  const value = getString(fields, field);
@@ -29336,6 +29470,12 @@ function processItem(fields, docId) {
29336
29470
  const needsUpdateValue = getBoolean(fields, "needs_update");
29337
29471
  if (needsUpdateValue !== undefined)
29338
29472
  itemData.needs_update = needsUpdateValue;
29473
+ const loginRequiredValue = getBoolean(fields, "login_required");
29474
+ if (loginRequiredValue !== undefined)
29475
+ itemData.login_required = loginRequiredValue;
29476
+ const disconnectedValue = getBoolean(fields, "disconnected");
29477
+ if (disconnectedValue !== undefined)
29478
+ itemData.disconnected = disconnectedValue;
29339
29479
  const validated = ItemSchema.safeParse(itemData);
29340
29480
  return validated.success ? validated.data : null;
29341
29481
  }
@@ -29387,219 +29527,42 @@ function processUserAccount(fields, docId, collection) {
29387
29527
  userAccountData.order = order;
29388
29528
  return userAccountData;
29389
29529
  }
29390
- function collectionMatches(collection, target) {
29391
- return collection === target || collection.endsWith(`/${target}`);
29392
- }
29393
- async function decodeAllCollections(dbPath) {
29394
- const rawTransactions = [];
29395
- const rawAccounts = [];
29396
- const rawRecurring = [];
29397
- const rawBudgets = [];
29398
- const rawGoals = [];
29399
- const rawGoalHistory = [];
29400
- const rawInvestmentPrices = [];
29401
- const rawInvestmentSplits = [];
29402
- const rawItems = [];
29403
- const rawCategories = [];
29404
- const rawUserAccounts = [];
29405
- for await (const doc2 of iterateDocuments(dbPath)) {
29406
- const { fields, documentId, collection, key } = doc2;
29407
- if (collection.includes("users/") && collection.endsWith("/accounts")) {
29408
- const userAccount = processUserAccount(fields, documentId, collection);
29409
- if (userAccount)
29410
- rawUserAccounts.push(userAccount);
29411
- } else if (collectionMatches(collection, "transactions")) {
29412
- const txn = processTransaction(fields, documentId);
29413
- if (txn)
29414
- rawTransactions.push(txn);
29415
- } else if (collectionMatches(collection, "accounts")) {
29416
- const acc = processAccount(fields, documentId);
29417
- if (acc)
29418
- rawAccounts.push(acc);
29419
- } else if (collectionMatches(collection, "recurring")) {
29420
- const rec = processRecurring(fields, documentId);
29421
- if (rec)
29422
- rawRecurring.push(rec);
29423
- } else if (collectionMatches(collection, "budgets")) {
29424
- const budget = processBudget(fields, documentId);
29425
- if (budget)
29426
- rawBudgets.push(budget);
29427
- } else if (collectionMatches(collection, "financial_goals")) {
29428
- const goal = processGoal(fields, documentId);
29429
- if (goal)
29430
- rawGoals.push(goal);
29431
- } else if (collection.endsWith("/financial_goal_history")) {
29432
- const history = processGoalHistory(fields, documentId, collection);
29433
- if (history)
29434
- rawGoalHistory.push(history);
29435
- } else if (collectionMatches(collection, "investment_prices") || collection.includes("investment_prices/")) {
29436
- const price = processInvestmentPrice(fields, documentId, key);
29437
- if (price)
29438
- rawInvestmentPrices.push(price);
29439
- } else if (collectionMatches(collection, "investment_splits")) {
29440
- const split = processInvestmentSplit(fields, documentId);
29441
- if (split)
29442
- rawInvestmentSplits.push(split);
29443
- } else if (collectionMatches(collection, "items")) {
29444
- const item = processItem(fields, documentId);
29445
- if (item)
29446
- rawItems.push(item);
29447
- } else if (collectionMatches(collection, "categories")) {
29448
- const category = processCategory(fields, documentId);
29449
- if (category)
29450
- rawCategories.push(category);
29451
- }
29452
- }
29453
- const txnSeen = new Set;
29454
- const transactions = [];
29455
- for (const txn of rawTransactions) {
29456
- const displayName = getTransactionDisplayName(txn);
29457
- const key = `${displayName}|${txn.amount}|${txn.date}`;
29458
- if (!txnSeen.has(key)) {
29459
- txnSeen.add(key);
29460
- transactions.push(txn);
29461
- }
29462
- }
29463
- transactions.sort((a, b) => a.date > b.date ? -1 : a.date < b.date ? 1 : 0);
29464
- const accSeen = new Set;
29465
- const accounts = [];
29466
- for (const acc of rawAccounts) {
29467
- const displayName = getAccountDisplayName(acc);
29468
- const key = `${displayName}|${acc.mask ?? ""}`;
29469
- if (!accSeen.has(key)) {
29470
- accSeen.add(key);
29471
- accounts.push(acc);
29472
- }
29473
- }
29474
- const recSeen = new Set;
29475
- const recurring = [];
29476
- for (const rec of rawRecurring) {
29477
- if (!recSeen.has(rec.recurring_id)) {
29478
- recSeen.add(rec.recurring_id);
29479
- recurring.push(rec);
29480
- }
29481
- }
29482
- const budgetSeen = new Set;
29483
- const budgets = [];
29484
- for (const budget of rawBudgets) {
29485
- if (!budgetSeen.has(budget.budget_id)) {
29486
- budgetSeen.add(budget.budget_id);
29487
- budgets.push(budget);
29488
- }
29489
- }
29490
- const goalSeen = new Set;
29491
- const goals = [];
29492
- for (const goal of rawGoals) {
29493
- if (!goalSeen.has(goal.goal_id)) {
29494
- goalSeen.add(goal.goal_id);
29495
- goals.push(goal);
29496
- }
29497
- }
29498
- const histSeen = new Set;
29499
- const goalHistory = [];
29500
- for (const history of rawGoalHistory) {
29501
- const key = `${history.goal_id}:${history.month}`;
29502
- if (!histSeen.has(key)) {
29503
- histSeen.add(key);
29504
- goalHistory.push(history);
29505
- }
29506
- }
29507
- goalHistory.sort((a, b) => {
29508
- if (a.goal_id !== b.goal_id) {
29509
- return a.goal_id.localeCompare(b.goal_id);
29510
- }
29511
- return b.month.localeCompare(a.month);
29512
- });
29513
- const priceSeen = new Set;
29514
- const investmentPrices = [];
29515
- for (const price of rawInvestmentPrices) {
29516
- const key = `${price.investment_id}-${price.date || price.month || "unknown"}`;
29517
- if (!priceSeen.has(key)) {
29518
- priceSeen.add(key);
29519
- investmentPrices.push(price);
29520
- }
29521
- }
29522
- investmentPrices.sort((a, b) => {
29523
- if (a.investment_id !== b.investment_id) {
29524
- return a.investment_id.localeCompare(b.investment_id);
29525
- }
29526
- const dateA = a.date || a.month || "";
29527
- const dateB = b.date || b.month || "";
29528
- return dateB.localeCompare(dateA);
29529
- });
29530
- const splitSeen = new Set;
29531
- const investmentSplits = [];
29532
- for (const split of rawInvestmentSplits) {
29533
- if (!splitSeen.has(split.split_id)) {
29534
- splitSeen.add(split.split_id);
29535
- investmentSplits.push(split);
29536
- }
29537
- }
29538
- investmentSplits.sort((a, b) => {
29539
- const tickerA = a.ticker_symbol || "";
29540
- const tickerB = b.ticker_symbol || "";
29541
- if (tickerA !== tickerB) {
29542
- return tickerA.localeCompare(tickerB);
29543
- }
29544
- const dateA = a.split_date || "";
29545
- const dateB = b.split_date || "";
29546
- return dateB.localeCompare(dateA);
29547
- });
29548
- const itemSeen = new Set;
29549
- const items = [];
29550
- for (const item of rawItems) {
29551
- if (!itemSeen.has(item.item_id)) {
29552
- itemSeen.add(item.item_id);
29553
- items.push(item);
29554
- }
29555
- }
29556
- items.sort((a, b) => {
29557
- const nameA = a.institution_name || "";
29558
- const nameB = b.institution_name || "";
29559
- if (nameA !== nameB) {
29560
- return nameA.localeCompare(nameB);
29561
- }
29562
- return a.item_id.localeCompare(b.item_id);
29563
- });
29564
- const catSeen = new Set;
29565
- const categories = [];
29566
- for (const category of rawCategories) {
29567
- if (!catSeen.has(category.category_id)) {
29568
- catSeen.add(category.category_id);
29569
- categories.push(category);
29570
- }
29571
- }
29572
- categories.sort((a, b) => {
29573
- const orderA = a.order ?? 999;
29574
- const orderB = b.order ?? 999;
29575
- if (orderA !== orderB) {
29576
- return orderA - orderB;
29577
- }
29578
- const nameA = a.name || "";
29579
- const nameB = b.name || "";
29580
- return nameA.localeCompare(nameB);
29530
+ function decodeAllCollectionsIsolated(dbPath, timeoutMs = 30000) {
29531
+ return new Promise((resolve, reject) => {
29532
+ const selfUrl = import.meta.url;
29533
+ const workerExt = selfUrl.endsWith(".ts") ? ".ts" : ".js";
29534
+ const workerPath = path2.resolve(path2.dirname(fileURLToPath(selfUrl)), "decode-worker" + workerExt);
29535
+ const worker = new Worker(workerPath, {
29536
+ workerData: { dbPath }
29537
+ });
29538
+ let settled = false;
29539
+ const settle = (fn) => {
29540
+ if (!settled) {
29541
+ settled = true;
29542
+ clearTimeout(timer);
29543
+ fn();
29544
+ }
29545
+ };
29546
+ const timer = setTimeout(() => {
29547
+ settle(() => {
29548
+ worker.terminate();
29549
+ reject(new Error(`Decode worker timed out after ${timeoutMs}ms`));
29550
+ });
29551
+ }, timeoutMs);
29552
+ worker.on("message", (msg) => {
29553
+ if (msg.type === "result" && msg.data) {
29554
+ settle(() => resolve(msg.data));
29555
+ } else if (msg.type === "error") {
29556
+ settle(() => reject(new Error(msg.message ?? "Worker decoding failed")));
29557
+ }
29558
+ });
29559
+ worker.on("error", (err) => {
29560
+ settle(() => reject(err));
29561
+ });
29562
+ worker.on("exit", (code) => {
29563
+ settle(() => reject(new Error(code === 0 ? "Decode worker exited without sending result" : `Decode worker exited with code ${code}`)));
29564
+ });
29581
29565
  });
29582
- const userAccSeen = new Set;
29583
- const userAccounts = [];
29584
- for (const userAccount of rawUserAccounts) {
29585
- if (!userAccSeen.has(userAccount.account_id)) {
29586
- userAccSeen.add(userAccount.account_id);
29587
- userAccounts.push(userAccount);
29588
- }
29589
- }
29590
- return {
29591
- transactions,
29592
- accounts,
29593
- recurring,
29594
- budgets,
29595
- goals,
29596
- goalHistory,
29597
- investmentPrices,
29598
- investmentSplits,
29599
- items,
29600
- categories,
29601
- userAccounts
29602
- };
29603
29566
  }
29604
29567
  // src/models/category-full.ts
29605
29568
  var ROOT_CATEGORIES = [
@@ -30796,12 +30759,12 @@ function findCopilotDatabase() {
30796
30759
  }
30797
30760
  } catch {}
30798
30761
  }
30799
- for (const path2 of possiblePaths) {
30762
+ for (const path3 of possiblePaths) {
30800
30763
  try {
30801
- if (existsSync(path2)) {
30802
- const files = readdirSync(path2);
30764
+ if (existsSync(path3)) {
30765
+ const files = readdirSync(path3);
30803
30766
  if (files.some((file2) => file2.endsWith(".ldb") || file2.startsWith("MANIFEST-"))) {
30804
- return path2;
30767
+ return path3;
30805
30768
  }
30806
30769
  }
30807
30770
  } catch {}
@@ -30938,7 +30901,7 @@ class CopilotDatabase {
30938
30901
  await this._loadingAllCollections;
30939
30902
  return;
30940
30903
  }
30941
- this._loadingAllCollections = decodeAllCollections(this.requireDbPath());
30904
+ this._loadingAllCollections = decodeAllCollectionsIsolated(this.requireDbPath());
30942
30905
  try {
30943
30906
  const result = await this._loadingAllCollections;
30944
30907
  this._transactions = result.transactions;
@@ -31935,6 +31898,50 @@ class CopilotMoneyTools {
31935
31898
  accounts
31936
31899
  };
31937
31900
  }
31901
+ async getConnectionStatus() {
31902
+ const items = await this.db.getItems();
31903
+ const connections = items.map((item) => {
31904
+ let status;
31905
+ if (item.disconnected === true || item.connection_status === "disconnected") {
31906
+ status = "disconnected";
31907
+ } else if (item.error_code && item.error_code !== "ITEM_NO_ERROR" || item.connection_status === "error") {
31908
+ status = "error";
31909
+ } else if (item.login_required === true || itemNeedsAttention(item)) {
31910
+ status = "login_required";
31911
+ } else if (!isItemHealthy(item)) {
31912
+ status = "error";
31913
+ } else {
31914
+ status = "connected";
31915
+ }
31916
+ return {
31917
+ item_id: item.item_id,
31918
+ institution_name: getItemDisplayName(item),
31919
+ institution_id: item.institution_id,
31920
+ status,
31921
+ products: item.billed_products ?? [],
31922
+ last_transactions_update: item.status_transactions_last_successful_update ?? null,
31923
+ last_transactions_failed: item.status_transactions_last_failed_update ?? null,
31924
+ last_investments_update: item.status_investments_last_successful_update ?? null,
31925
+ last_investments_failed: item.status_investments_last_failed_update ?? null,
31926
+ latest_fetch: item.latest_fetch ?? null,
31927
+ latest_investments_fetch: item.latest_investments_fetch ?? null,
31928
+ login_required: item.login_required ?? false,
31929
+ disconnected: item.disconnected ?? false,
31930
+ consent_expires: item.consent_expiration_time || null,
31931
+ error_code: item.error_code ?? null,
31932
+ error_message: item.error_message ?? null
31933
+ };
31934
+ });
31935
+ const needsAttention = connections.filter((c) => c.status !== "connected").length;
31936
+ return {
31937
+ connections,
31938
+ summary: {
31939
+ total: connections.length,
31940
+ connected: connections.length - needsAttention,
31941
+ needs_attention: needsAttention
31942
+ }
31943
+ };
31944
+ }
31938
31945
  async getCategories(options = {}) {
31939
31946
  const { view = "list", parent_id, query, type, period } = options;
31940
31947
  let start_date = validateDate(options.start_date, "start_date");
@@ -32593,6 +32600,17 @@ function createToolSchemas() {
32593
32600
  readOnlyHint: true
32594
32601
  }
32595
32602
  },
32603
+ {
32604
+ name: "get_connection_status",
32605
+ description: "Get connection status for all linked financial institutions. " + "Shows per-institution sync health including last successful update timestamps " + "for transactions and investments, login requirements, and error states. " + "Use this to check when accounts were last synced or to identify connections needing attention.",
32606
+ inputSchema: {
32607
+ type: "object",
32608
+ properties: {}
32609
+ },
32610
+ annotations: {
32611
+ readOnlyHint: true
32612
+ }
32613
+ },
32596
32614
  {
32597
32615
  name: "get_categories",
32598
32616
  description: "Unified category retrieval tool. Supports multiple views: " + "list (default) - categories with transaction counts/amounts for a time period; " + "tree - full Plaid category taxonomy as hierarchical tree; " + "search - search categories by keyword. Use parent_id to get subcategories. " + 'For list view, use period (e.g., "this_month") or start_date/end_date to filter by date. ' + "Includes all categories, even those with $0 spent (matching UI behavior).",
@@ -32773,6 +32791,9 @@ class CopilotMoneyServer {
32773
32791
  case "get_accounts":
32774
32792
  result = await this.tools.getAccounts(typedArgs);
32775
32793
  break;
32794
+ case "get_connection_status":
32795
+ result = await this.tools.getConnectionStatus();
32796
+ break;
32776
32797
  case "get_categories":
32777
32798
  result = await this.tools.getCategories(typedArgs || {});
32778
32799
  break;