@infamousendeavors/lunchmoney-mcp 0.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.
Files changed (80) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +288 -0
  3. package/SECURITY.md +130 -0
  4. package/dist/api/client.d.ts +11 -0
  5. package/dist/api/client.d.ts.map +1 -0
  6. package/dist/api/client.js +82 -0
  7. package/dist/api/client.js.map +1 -0
  8. package/dist/auth-provider.d.ts +47 -0
  9. package/dist/auth-provider.d.ts.map +1 -0
  10. package/dist/auth-provider.js +111 -0
  11. package/dist/auth-provider.js.map +1 -0
  12. package/dist/cli.d.ts +13 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +103 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/credential-store.d.ts +25 -0
  17. package/dist/credential-store.d.ts.map +1 -0
  18. package/dist/credential-store.js +90 -0
  19. package/dist/credential-store.js.map +1 -0
  20. package/dist/index.d.ts +11 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +8 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/schemas/index.d.ts +189 -0
  25. package/dist/schemas/index.d.ts.map +1 -0
  26. package/dist/schemas/index.js +178 -0
  27. package/dist/schemas/index.js.map +1 -0
  28. package/dist/server.d.ts +32 -0
  29. package/dist/server.d.ts.map +1 -0
  30. package/dist/server.js +116 -0
  31. package/dist/server.js.map +1 -0
  32. package/dist/session-store.d.ts +28 -0
  33. package/dist/session-store.d.ts.map +1 -0
  34. package/dist/session-store.js +50 -0
  35. package/dist/session-store.js.map +1 -0
  36. package/dist/setup.d.ts +21 -0
  37. package/dist/setup.d.ts.map +1 -0
  38. package/dist/setup.js +104 -0
  39. package/dist/setup.js.map +1 -0
  40. package/dist/tools/assets.d.ts +4 -0
  41. package/dist/tools/assets.d.ts.map +1 -0
  42. package/dist/tools/assets.js +63 -0
  43. package/dist/tools/assets.js.map +1 -0
  44. package/dist/tools/budgets.d.ts +4 -0
  45. package/dist/tools/budgets.d.ts.map +1 -0
  46. package/dist/tools/budgets.js +63 -0
  47. package/dist/tools/budgets.js.map +1 -0
  48. package/dist/tools/categories.d.ts +4 -0
  49. package/dist/tools/categories.d.ts.map +1 -0
  50. package/dist/tools/categories.js +105 -0
  51. package/dist/tools/categories.js.map +1 -0
  52. package/dist/tools/plaid.d.ts +4 -0
  53. package/dist/tools/plaid.d.ts.map +1 -0
  54. package/dist/tools/plaid.js +33 -0
  55. package/dist/tools/plaid.js.map +1 -0
  56. package/dist/tools/recurring.d.ts +4 -0
  57. package/dist/tools/recurring.d.ts.map +1 -0
  58. package/dist/tools/recurring.js +63 -0
  59. package/dist/tools/recurring.js.map +1 -0
  60. package/dist/tools/tags.d.ts +4 -0
  61. package/dist/tools/tags.d.ts.map +1 -0
  62. package/dist/tools/tags.js +63 -0
  63. package/dist/tools/tags.js.map +1 -0
  64. package/dist/tools/transactions.d.ts +4 -0
  65. package/dist/tools/transactions.d.ts.map +1 -0
  66. package/dist/tools/transactions.js +146 -0
  67. package/dist/tools/transactions.js.map +1 -0
  68. package/dist/tools/user.d.ts +4 -0
  69. package/dist/tools/user.d.ts.map +1 -0
  70. package/dist/tools/user.js +19 -0
  71. package/dist/tools/user.js.map +1 -0
  72. package/dist/types/index.d.ts +164 -0
  73. package/dist/types/index.d.ts.map +1 -0
  74. package/dist/types/index.js +2 -0
  75. package/dist/types/index.js.map +1 -0
  76. package/dist/utils/errors.d.ts +8 -0
  77. package/dist/utils/errors.d.ts.map +1 -0
  78. package/dist/utils/errors.js +29 -0
  79. package/dist/utils/errors.js.map +1 -0
  80. package/package.json +58 -0
@@ -0,0 +1,178 @@
1
+ import { z } from "zod";
2
+ // Lunch Money calendar-date fields are always YYYY-MM-DD. Constrain them once
3
+ // and reuse, so malformed input fails fast here instead of as a confusing
4
+ // upstream API error. (balance_as_of is intentionally NOT this -- LM accepts a
5
+ // full ISO 8601 datetime there.)
6
+ const dateString = z
7
+ .string()
8
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD");
9
+ // Transaction filter schema
10
+ export const transactionFilterSchema = z.object({
11
+ start_date: dateString.optional(),
12
+ end_date: dateString.optional(),
13
+ category_id: z.number().optional(),
14
+ tag_id: z.number().optional(),
15
+ account_id: z.number().optional(),
16
+ debit_as_negative: z.boolean().optional(),
17
+ pending: z.boolean().optional(),
18
+ status: z.enum(["cleared", "uncleared", "recurring", "recurring_suggested"]).optional(),
19
+ offset: z.number().int().min(0).optional(),
20
+ limit: z.number().int().min(1).max(1000).optional(),
21
+ });
22
+ // Category schemas
23
+ export const createCategorySchema = z.object({
24
+ name: z.string().min(1),
25
+ description: z.string().optional(),
26
+ is_income: z.boolean().optional(),
27
+ exclude_budget: z.boolean().optional(),
28
+ exclude_from_totals: z.boolean().optional(),
29
+ category_group_id: z.number().optional(),
30
+ parent_category_id: z.number().optional(),
31
+ });
32
+ export const updateCategorySchema = z.object({
33
+ name: z.string().min(1).optional(),
34
+ description: z.string().optional(),
35
+ is_income: z.boolean().optional(),
36
+ exclude_budget: z.boolean().optional(),
37
+ exclude_from_totals: z.boolean().optional(),
38
+ archived: z.boolean().optional(),
39
+ category_group_id: z.number().optional(),
40
+ parent_category_id: z.number().optional(),
41
+ });
42
+ // Tag schemas
43
+ export const createTagSchema = z.object({
44
+ name: z.string().min(1),
45
+ });
46
+ export const updateTagSchema = z.object({
47
+ name: z.string().min(1),
48
+ });
49
+ // Transaction schemas
50
+ export const createTransactionSchema = z.object({
51
+ date: dateString,
52
+ amount: z.string(),
53
+ payee: z.string().optional(),
54
+ currency: z.string().optional(),
55
+ notes: z.string().optional(),
56
+ category_id: z.number().optional(),
57
+ account_id: z.number(),
58
+ tags: z.array(z.number()).optional(),
59
+ status: z.enum(["cleared", "uncleared"]).optional(),
60
+ external_id: z.string().optional(),
61
+ original_name: z.string().optional(),
62
+ type: z.enum(["expense", "income", "transfer"]).optional(),
63
+ });
64
+ export const updateTransactionSchema = z.object({
65
+ date: dateString.optional(),
66
+ amount: z.string().optional(),
67
+ payee: z.string().optional(),
68
+ currency: z.string().optional(),
69
+ notes: z.string().optional(),
70
+ category_id: z.number().nullable().optional(),
71
+ account_id: z.number().optional(),
72
+ tags: z.array(z.number()).optional(),
73
+ status: z.enum(["cleared", "uncleared"]).optional(),
74
+ external_id: z.string().optional(),
75
+ original_name: z.string().optional(),
76
+ type: z.enum(["expense", "income", "transfer"]).optional(),
77
+ });
78
+ export const bulkUpdateTransactionsSchema = z.object({
79
+ transaction_ids: z.array(z.number()).min(1),
80
+ category_id: z.number().nullable().optional(),
81
+ tags: z.array(z.number()).optional(),
82
+ notes: z.string().optional(),
83
+ status: z.enum(["cleared", "uncleared"]).optional(),
84
+ });
85
+ // Recurring item schemas
86
+ export const createRecurringItemSchema = z.object({
87
+ payee: z.string().optional(),
88
+ amount: z.string(),
89
+ currency: z.string().optional(),
90
+ category_id: z.number().optional(),
91
+ notes: z.string().optional(),
92
+ account_id: z.number().optional(),
93
+ tag_id: z.number().optional(),
94
+ frequency: z.string().optional(),
95
+ flow: z.enum(["inflow", "outflow"]).optional(),
96
+ start_date: dateString.optional(),
97
+ end_date: dateString.optional(),
98
+ });
99
+ export const updateRecurringItemSchema = z.object({
100
+ payee: z.string().optional(),
101
+ amount: z.string().optional(),
102
+ currency: z.string().optional(),
103
+ category_id: z.number().nullable().optional(),
104
+ notes: z.string().optional(),
105
+ account_id: z.number().optional(),
106
+ tag_id: z.number().nullable().optional(),
107
+ frequency: z.string().optional(),
108
+ flow: z.enum(["inflow", "outflow"]).optional(),
109
+ start_date: dateString.optional(),
110
+ end_date: dateString.optional(),
111
+ });
112
+ // Budget schemas
113
+ export const createBudgetSchema = z.object({
114
+ category_id: z.number().optional(),
115
+ amount: z.string(),
116
+ currency: z.string().optional(),
117
+ start_date: dateString,
118
+ end_date: dateString,
119
+ });
120
+ export const updateBudgetSchema = z.object({
121
+ category_id: z.number().nullable().optional(),
122
+ amount: z.string().optional(),
123
+ currency: z.string().optional(),
124
+ start_date: dateString.optional(),
125
+ end_date: dateString.optional(),
126
+ });
127
+ // Asset schemas
128
+ export const createAssetSchema = z.object({
129
+ type_name: z.string().min(1),
130
+ type_name_override: z.string().optional(),
131
+ name: z.string().min(1),
132
+ balance: z.string(),
133
+ balance_as_of: z.string().optional(),
134
+ currency: z.string().optional(),
135
+ institution_name: z.string().optional(),
136
+ });
137
+ export const updateAssetSchema = z.object({
138
+ type_name: z.string().optional(),
139
+ type_name_override: z.string().optional(),
140
+ name: z.string().optional(),
141
+ balance: z.string().optional(),
142
+ balance_as_of: z.string().optional(),
143
+ currency: z.string().optional(),
144
+ institution_name: z.string().optional(),
145
+ });
146
+ // Transaction group schemas
147
+ export const createTransactionGroupSchema = z.object({
148
+ date: dateString,
149
+ payee: z.string(),
150
+ transactions: z.array(z.number()).min(2),
151
+ category_id: z.number().optional(),
152
+ notes: z.string().optional(),
153
+ tags: z.array(z.number()).optional(),
154
+ });
155
+ export const unsplitTransactionsSchema = z.object({
156
+ parent_ids: z.array(z.number()).min(1),
157
+ remove_parents: z.boolean().optional(),
158
+ });
159
+ // Category group schemas
160
+ export const createCategoryGroupSchema = z.object({
161
+ name: z.string().min(1),
162
+ description: z.string().optional(),
163
+ is_income: z.boolean().optional(),
164
+ exclude_from_budget: z.boolean().optional(),
165
+ exclude_from_totals: z.boolean().optional(),
166
+ category_ids: z.array(z.number()).optional(),
167
+ new_categories: z.array(z.string()).optional(),
168
+ });
169
+ export const addToGroupSchema = z.object({
170
+ group_id: z.number().int().positive(),
171
+ category_ids: z.array(z.number()).optional(),
172
+ new_categories: z.array(z.string()).optional(),
173
+ });
174
+ // ID parameter schemas
175
+ export const idSchema = z.object({
176
+ id: z.number().int().positive(),
177
+ });
178
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/schemas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,0EAA0E;AAC1E,+EAA+E;AAC/E,iCAAiC;AACjC,MAAM,UAAU,GAAG,CAAC;KACjB,MAAM,EAAE;KACR,KAAK,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,CAAC;AAEvD,4BAA4B;AAC5B,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAAC,CAAC,QAAQ,EAAE;IACvF,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;CACpD,CAAC,CAAC;AAEH,mBAAmB;AACnB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACtC,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC3C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACtC,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC3C,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAChC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC;AAEH,cAAc;AACd,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACxB,CAAC,CAAC;AAEH,sBAAsB;AACtB,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC3D,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE;IAC3B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC3D,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE;CACpD,CAAC,CAAC;AAEH,yBAAyB;AACzB,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC9C,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC9C,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,iBAAiB;AACjB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,UAAU,EAAE,UAAU;IACtB,QAAQ,EAAE,UAAU;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC7C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE;IACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,gBAAgB;AAChB,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAEH,4BAA4B;AAC5B,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAEH,yBAAyB;AACzB,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC3C,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC3C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC5C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC/C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACrC,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC5C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC/C,CAAC,CAAC;AAEH,uBAAuB;AACvB,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import "dotenv/config";
2
+ import { FastMCP, GoogleProvider, GitHubProvider, OAuthProvider } from "fastmcp";
3
+ import { CredentialStore } from "./credential-store.js";
4
+ /** Union type for supported auth provider instances */
5
+ export type AuthProviderInstance = InstanceType<typeof GoogleProvider> | InstanceType<typeof GitHubProvider> | InstanceType<typeof OAuthProvider>;
6
+ export interface CreateServerOptions {
7
+ /** OAuth auth provider for HTTP transport mode */
8
+ auth?: AuthProviderInstance;
9
+ /** Enable health endpoint (default: true when using HTTP transport) */
10
+ health?: boolean;
11
+ }
12
+ export declare const TOKEN_NOT_CONFIGURED_MSG = "Lunch Money API token not configured. Use the configureLunchMoneyToken tool to set your token, or run: npx lunchmoney-mcp setup";
13
+ /**
14
+ * Execute logic for the configureLunchMoneyToken tool.
15
+ * Exported for testing.
16
+ */
17
+ export declare function executeConfigureToken(args: {
18
+ token: string;
19
+ }, credentialStore: CredentialStore): Promise<string>;
20
+ /**
21
+ * Execute logic for the stub getUser tool (when no token is configured).
22
+ * Exported for testing.
23
+ */
24
+ export declare function executeStubGetUser(): Promise<string>;
25
+ export declare function createServer(options?: CreateServerOptions): Promise<FastMCP>;
26
+ export interface StartServerOptions {
27
+ transportType: "stdio" | "httpStream";
28
+ port?: number;
29
+ authProvider?: string;
30
+ }
31
+ export declare function startServer(server: FastMCP, transportType: "stdio" | "httpStream", port?: number, authProvider?: string): Promise<void>;
32
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGjF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAYxD,uDAAuD;AACvD,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG,YAAY,CAAC,OAAO,aAAa,CAAC,CAAC;AAElJ,MAAM,WAAW,mBAAmB;IAClC,kDAAkD;IAClD,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,wBAAwB,oIAC8F,CAAC;AAEpI;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EACvB,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1D;AAED,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CA0DlF;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,GAAG,YAAY,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GAAG,YAAY,EACrC,IAAI,CAAC,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAoBf"}
package/dist/server.js ADDED
@@ -0,0 +1,116 @@
1
+ import "dotenv/config";
2
+ import { FastMCP } from "fastmcp";
3
+ import { z } from "zod";
4
+ import { LunchMoneyClient } from "./api/client.js";
5
+ import { CredentialStore } from "./credential-store.js";
6
+ import { registerUserTools } from "./tools/user.js";
7
+ import { registerCategoryTools } from "./tools/categories.js";
8
+ import { registerTagTools } from "./tools/tags.js";
9
+ import { registerTransactionTools } from "./tools/transactions.js";
10
+ import { registerRecurringTools } from "./tools/recurring.js";
11
+ import { registerBudgetTools } from "./tools/budgets.js";
12
+ import { registerAssetTools } from "./tools/assets.js";
13
+ import { registerPlaidTools } from "./tools/plaid.js";
14
+ import { formatErrorForMCP } from "./utils/errors.js";
15
+ export const TOKEN_NOT_CONFIGURED_MSG = "Lunch Money API token not configured. Use the configureLunchMoneyToken tool to set your token, or run: npx lunchmoney-mcp setup";
16
+ /**
17
+ * Execute logic for the configureLunchMoneyToken tool.
18
+ * Exported for testing.
19
+ */
20
+ export async function executeConfigureToken(args, credentialStore) {
21
+ try {
22
+ // Validate the token by calling GET /me
23
+ const testClient = new LunchMoneyClient(args.token);
24
+ const user = await testClient.get("/me");
25
+ // Token is valid — store it
26
+ await credentialStore.setApiToken(args.token);
27
+ return `Token validated and stored successfully! Welcome, ${user.name} (${user.email}). Restart the MCP server to use the new token.`;
28
+ }
29
+ catch (error) {
30
+ if (error instanceof Error && error.message === "Lunch Money API token is required") {
31
+ return "Error: Token cannot be empty.";
32
+ }
33
+ return `Failed to validate token: ${formatErrorForMCP(error)}. Please check your token and try again. Get a token at https://my.lunchmoney.app/developers`;
34
+ }
35
+ }
36
+ /**
37
+ * Execute logic for the stub getUser tool (when no token is configured).
38
+ * Exported for testing.
39
+ */
40
+ export async function executeStubGetUser() {
41
+ return TOKEN_NOT_CONFIGURED_MSG;
42
+ }
43
+ export async function createServer(options) {
44
+ const credentialStore = new CredentialStore();
45
+ const token = await credentialStore.getApiToken();
46
+ const serverConfig = {
47
+ name: "Lunch Money MCP",
48
+ version: "0.1.0",
49
+ instructions: "This MCP server provides full integration with the Lunch Money API. " +
50
+ "You can manage user accounts, categories, tags, transactions, recurring items, budgets, and assets. " +
51
+ "All operations support full CRUD capabilities with proper validation.",
52
+ };
53
+ // Add auth provider if specified (for HTTP transport with OAuth)
54
+ if (options?.auth) {
55
+ serverConfig.auth = options.auth;
56
+ }
57
+ // Enable health endpoint (useful for HTTP transport)
58
+ if (options?.health !== false) {
59
+ serverConfig.health = { enabled: true };
60
+ }
61
+ const server = new FastMCP(serverConfig);
62
+ // Register the configureLunchMoneyToken tool — works without a token
63
+ server.addTool({
64
+ name: "configureLunchMoneyToken",
65
+ description: "Configure your Lunch Money API token. Validates the token by checking your account, then stores it securely in the system keychain.",
66
+ parameters: z.object({
67
+ token: z.string().min(1, "API token is required"),
68
+ }),
69
+ execute: async (args) => executeConfigureToken(args, credentialStore),
70
+ });
71
+ // Only register API tools if we have a token
72
+ if (token) {
73
+ const client = new LunchMoneyClient(token);
74
+ registerUserTools(server, client);
75
+ registerCategoryTools(server, client);
76
+ registerTagTools(server, client);
77
+ registerTransactionTools(server, client);
78
+ registerRecurringTools(server, client);
79
+ registerBudgetTools(server, client);
80
+ registerAssetTools(server, client);
81
+ registerPlaidTools(server, client);
82
+ }
83
+ else {
84
+ // Register a stub getUser tool that tells the user to configure their token
85
+ server.addTool({
86
+ name: "getUser",
87
+ description: "Get the current user's account details (requires API token to be configured)",
88
+ parameters: z.object({}),
89
+ execute: executeStubGetUser,
90
+ });
91
+ }
92
+ return server;
93
+ }
94
+ export async function startServer(server, transportType, port, authProvider) {
95
+ if (transportType === "httpStream") {
96
+ const effectivePort = port || 8080;
97
+ server.start({
98
+ transportType: "httpStream",
99
+ httpStream: {
100
+ port: effectivePort,
101
+ },
102
+ });
103
+ console.log(`Lunch Money MCP server started on port ${effectivePort} (HTTP)`);
104
+ if (authProvider) {
105
+ console.log(`OAuth provider: ${authProvider}`);
106
+ }
107
+ console.log(`Health check: http://localhost:${effectivePort}/health`);
108
+ }
109
+ else {
110
+ server.start({
111
+ transportType: "stdio",
112
+ });
113
+ // Don't log to stdout in stdio mode — it would interfere with the protocol
114
+ }
115
+ }
116
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type TokenStorage } from "fastmcp/auth";
2
+ import type { CredentialStore } from "./credential-store.js";
3
+ /**
4
+ * Get the platform-native data directory for session storage.
5
+ * - macOS: ~/Library/Application Support/lunchmoney-mcp
6
+ * - Linux: ~/.local/share/lunchmoney-mcp
7
+ * - Windows: %LOCALAPPDATA%/lunchmoney-mcp/Data
8
+ */
9
+ export declare function getDataDirectory(): string;
10
+ export interface CreateSessionStoreOptions {
11
+ /** Override the data directory (useful for testing) */
12
+ directory?: string;
13
+ /** Cleanup interval in milliseconds (default: 60000) */
14
+ cleanupIntervalMs?: number;
15
+ }
16
+ /**
17
+ * Create an encrypted, disk-backed session store for OAuth tokens.
18
+ *
19
+ * Sessions are persisted to the platform-native data directory as JSON files,
20
+ * encrypted at rest with AES-256-GCM. The encryption key is stored in the
21
+ * OS keychain via the credential store, never on the filesystem.
22
+ *
23
+ * @param credentialStore - Used to retrieve the encryption key from the OS keychain
24
+ * @param options - Optional overrides for directory and cleanup interval
25
+ * @returns An EncryptedTokenStorage wrapping a DiskStore
26
+ */
27
+ export declare function createSessionStore(credentialStore: CredentialStore, options?: CreateSessionStoreOptions): Promise<TokenStorage>;
28
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAS7D;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,MAAM,WAAW,yBAAyB;IACxC,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,eAAe,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
@@ -0,0 +1,50 @@
1
+ import { mkdirSync } from "fs";
2
+ import envPaths from "env-paths";
3
+ import { DiskStore, EncryptedTokenStorage } from "fastmcp/auth";
4
+ const APP_NAME = "lunchmoney-mcp";
5
+ /**
6
+ * Default cleanup interval for expired tokens (60 seconds).
7
+ */
8
+ const DEFAULT_CLEANUP_INTERVAL_MS = 60_000;
9
+ /**
10
+ * Get the platform-native data directory for session storage.
11
+ * - macOS: ~/Library/Application Support/lunchmoney-mcp
12
+ * - Linux: ~/.local/share/lunchmoney-mcp
13
+ * - Windows: %LOCALAPPDATA%/lunchmoney-mcp/Data
14
+ */
15
+ export function getDataDirectory() {
16
+ return envPaths(APP_NAME).data;
17
+ }
18
+ /**
19
+ * Create an encrypted, disk-backed session store for OAuth tokens.
20
+ *
21
+ * Sessions are persisted to the platform-native data directory as JSON files,
22
+ * encrypted at rest with AES-256-GCM. The encryption key is stored in the
23
+ * OS keychain via the credential store, never on the filesystem.
24
+ *
25
+ * @param credentialStore - Used to retrieve the encryption key from the OS keychain
26
+ * @param options - Optional overrides for directory and cleanup interval
27
+ * @returns An EncryptedTokenStorage wrapping a DiskStore
28
+ */
29
+ export async function createSessionStore(credentialStore, options) {
30
+ const directory = options?.directory ?? getDataDirectory();
31
+ const cleanupIntervalMs = options?.cleanupIntervalMs ?? DEFAULT_CLEANUP_INTERVAL_MS;
32
+ // Ensure the data directory exists
33
+ mkdirSync(directory, { recursive: true });
34
+ // Retrieve the encryption key. This store persists OAuth sessions to disk,
35
+ // so an ephemeral key is not acceptable: require a stable key (keychain or
36
+ // a valid ENCRYPTION_KEY), and refuse to start otherwise.
37
+ const encryptionKey = await credentialStore.getEncryptionKey({
38
+ requirePersistent: true,
39
+ });
40
+ // Create the disk-backed store
41
+ const diskStore = new DiskStore({
42
+ directory,
43
+ cleanupIntervalMs,
44
+ fileExtension: ".json",
45
+ });
46
+ // Wrap with AES-256-GCM encryption
47
+ const encryptedStore = new EncryptedTokenStorage(diskStore, encryptionKey);
48
+ return encryptedStore;
49
+ }
50
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAqB,MAAM,cAAc,CAAC;AAGnF,MAAM,QAAQ,GAAG,gBAAgB,CAAC;AAElC;;GAEG;AACH,MAAM,2BAA2B,GAAG,MAAM,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,eAAgC,EAChC,OAAmC;IAEnC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,CAAC;IAC3D,MAAM,iBAAiB,GAAG,OAAO,EAAE,iBAAiB,IAAI,2BAA2B,CAAC;IAEpF,mCAAmC;IACnC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,2EAA2E;IAC3E,2EAA2E;IAC3E,0DAA0D;IAC1D,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,gBAAgB,CAAC;QAC3D,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAC;IAEH,+BAA+B;IAC/B,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,SAAS;QACT,iBAAiB;QACjB,aAAa,EAAE,OAAO;KACvB,CAAC,CAAC;IAEH,mCAAmC;IACnC,MAAM,cAAc,GAAG,IAAI,qBAAqB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAE3E,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { CredentialStore } from "./credential-store.js";
2
+ import type { User } from "./types/index.js";
3
+ export interface ReadlineInterface {
4
+ question(query: string, callback: (answer: string) => void): void;
5
+ close(): void;
6
+ }
7
+ export declare function createReadlineInterface(): ReadlineInterface;
8
+ /**
9
+ * Suppress the terminal echo of typed input on a readline interface, so a
10
+ * secret (the API token) is not printed as it is typed. Returns an `unmask`
11
+ * function that restores the original behavior.
12
+ *
13
+ * Works by swapping the interface's internal `_writeToOutput`: while masked,
14
+ * only newlines pass through (so Enter still breaks the line) and every echoed
15
+ * keystroke is swallowed. If the interface does not expose `_writeToOutput`
16
+ * (a test mock, or a non-TTY stream), this is a no-op.
17
+ */
18
+ export declare function installInputMask(rl: ReadlineInterface): () => void;
19
+ export declare function validateToken(token: string): Promise<User>;
20
+ export declare function runSetupWizard(rlFactory?: () => ReadlineInterface, credentialStore?: CredentialStore, log?: (msg: string) => void): Promise<void>;
21
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,uBAAuB,IAAI,iBAAiB,CAK3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAelE;AAqBD,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhE;AAED,wBAAsB,cAAc,CAClC,SAAS,CAAC,EAAE,MAAM,iBAAiB,EACnC,eAAe,CAAC,EAAE,eAAe,EACjC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
package/dist/setup.js ADDED
@@ -0,0 +1,104 @@
1
+ import * as readline from "readline";
2
+ import { LunchMoneyClient } from "./api/client.js";
3
+ import { CredentialStore } from "./credential-store.js";
4
+ export function createReadlineInterface() {
5
+ return readline.createInterface({
6
+ input: process.stdin,
7
+ output: process.stdout,
8
+ });
9
+ }
10
+ /**
11
+ * Suppress the terminal echo of typed input on a readline interface, so a
12
+ * secret (the API token) is not printed as it is typed. Returns an `unmask`
13
+ * function that restores the original behavior.
14
+ *
15
+ * Works by swapping the interface's internal `_writeToOutput`: while masked,
16
+ * only newlines pass through (so Enter still breaks the line) and every echoed
17
+ * keystroke is swallowed. If the interface does not expose `_writeToOutput`
18
+ * (a test mock, or a non-TTY stream), this is a no-op.
19
+ */
20
+ export function installInputMask(rl) {
21
+ const internal = rl;
22
+ const original = internal._writeToOutput;
23
+ if (typeof original !== "function") {
24
+ return () => { };
25
+ }
26
+ internal._writeToOutput = (stringToWrite) => {
27
+ if (stringToWrite === "\n" || stringToWrite === "\r\n" || stringToWrite === "\r") {
28
+ original.call(internal, stringToWrite);
29
+ }
30
+ // otherwise: swallow the echoed character
31
+ };
32
+ return () => {
33
+ internal._writeToOutput = original;
34
+ };
35
+ }
36
+ function askQuestion(rl, query, options = {}) {
37
+ return new Promise((resolve) => {
38
+ let unmask;
39
+ rl.question(query, (answer) => {
40
+ unmask?.();
41
+ resolve(answer.trim());
42
+ });
43
+ // Install the mask after the prompt is written so the prompt stays visible
44
+ // but the typed token does not echo.
45
+ if (options.mask) {
46
+ unmask = installInputMask(rl);
47
+ }
48
+ });
49
+ }
50
+ export async function validateToken(token) {
51
+ const client = new LunchMoneyClient(token);
52
+ return client.get("/me");
53
+ }
54
+ export async function runSetupWizard(rlFactory, credentialStore, log) {
55
+ const print = log || console.log;
56
+ const store = credentialStore || new CredentialStore();
57
+ const rl = rlFactory ? rlFactory() : createReadlineInterface();
58
+ try {
59
+ print("");
60
+ print("=== Lunch Money MCP Server Setup ===");
61
+ print("");
62
+ print("This wizard will help you configure your Lunch Money API token.");
63
+ print("You can get a token at: https://my.lunchmoney.app/developers");
64
+ print("");
65
+ const token = await askQuestion(rl, "Enter your Lunch Money API token (input hidden): ", {
66
+ mask: true,
67
+ });
68
+ if (!token) {
69
+ print("No token provided. Setup cancelled.");
70
+ return;
71
+ }
72
+ print("");
73
+ print("Validating token...");
74
+ try {
75
+ const user = await validateToken(token);
76
+ await store.setApiToken(token);
77
+ print("");
78
+ print(`Token validated and stored successfully!`);
79
+ print(` Name: ${user.name}`);
80
+ print(` Email: ${user.email}`);
81
+ print(` Currency: ${user.currency}`);
82
+ print("");
83
+ print("You can now start the MCP server:");
84
+ print(" npx lunchmoney-mcp (stdio mode)");
85
+ print(" npx lunchmoney-mcp --http (HTTP mode)");
86
+ print("");
87
+ }
88
+ catch (error) {
89
+ print("");
90
+ if (error instanceof Error) {
91
+ print(`Token validation failed: ${error.message}`);
92
+ }
93
+ else {
94
+ print("Token validation failed. Please check your token and try again.");
95
+ }
96
+ print("Get a new token at: https://my.lunchmoney.app/developers");
97
+ print("");
98
+ }
99
+ }
100
+ finally {
101
+ rl.close();
102
+ }
103
+ }
104
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { FastMCP } from "fastmcp";
2
+ import { LunchMoneyClient } from "../api/client.js";
3
+ export declare function registerAssetTools(server: FastMCP, client: LunchMoneyClient): void;
4
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/tools/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AASpD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,gBAAgB,QA6DzB"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { formatErrorForMCP } from "../utils/errors.js";
3
+ import { createAssetSchema, updateAssetSchema, idSchema, } from "../schemas/index.js";
4
+ export function registerAssetTools(server, client) {
5
+ server.addTool({
6
+ name: "getAssets",
7
+ description: "List all manually-managed assets",
8
+ parameters: z.object({}),
9
+ execute: async () => {
10
+ try {
11
+ const response = await client.get("/assets");
12
+ return JSON.stringify(response, null, 2);
13
+ }
14
+ catch (error) {
15
+ return formatErrorForMCP(error);
16
+ }
17
+ },
18
+ });
19
+ server.addTool({
20
+ name: "createAsset",
21
+ description: "Create a new manually-managed asset",
22
+ parameters: createAssetSchema,
23
+ execute: async (args) => {
24
+ try {
25
+ const asset = await client.post("/assets", args);
26
+ return JSON.stringify(asset, null, 2);
27
+ }
28
+ catch (error) {
29
+ return formatErrorForMCP(error);
30
+ }
31
+ },
32
+ });
33
+ server.addTool({
34
+ name: "updateAsset",
35
+ description: "Update an existing asset's properties including balance and metadata",
36
+ parameters: idSchema.merge(updateAssetSchema),
37
+ execute: async (args) => {
38
+ try {
39
+ const { id, ...updateData } = args;
40
+ const asset = await client.put(`/assets/${id}`, updateData);
41
+ return JSON.stringify(asset, null, 2);
42
+ }
43
+ catch (error) {
44
+ return formatErrorForMCP(error);
45
+ }
46
+ },
47
+ });
48
+ server.addTool({
49
+ name: "deleteAsset",
50
+ description: "Delete an asset by ID",
51
+ parameters: idSchema,
52
+ execute: async (args) => {
53
+ try {
54
+ await client.delete(`/assets/${args.id}`);
55
+ return `Asset ${args.id} deleted successfully`;
56
+ }
57
+ catch (error) {
58
+ return formatErrorForMCP(error);
59
+ }
60
+ },
61
+ });
62
+ }
63
+ //# sourceMappingURL=assets.js.map