@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
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joe Garcia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Based on lunch-money-mcp by Gilbert Pellegrom (https://github.com/gilbitron/lunch-money-mcp), MIT License.
package/README.md ADDED
@@ -0,0 +1,288 @@
1
+ # lunchmoney-mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@infamousendeavors/lunchmoney-mcp.svg)](https://www.npmjs.com/package/@infamousendeavors/lunchmoney-mcp)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@infamousendeavors/lunchmoney-mcp.svg)](https://www.npmjs.com/package/@infamousendeavors/lunchmoney-mcp)
5
+ [![GitHub downloads](https://img.shields.io/github/downloads/infamousendeavors/lunchmoney-mcp/total.svg)](https://github.com/infamousendeavors/lunchmoney-mcp/releases)
6
+ [![CI](https://github.com/infamousendeavors/lunchmoney-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/infamousendeavors/lunchmoney-mcp/actions/workflows/ci.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ The definitive MCP server for [Lunch Money](https://lunchmoney.app) -- manage your finances through any AI assistant that supports the Model Context Protocol.
10
+
11
+ 37 tools covering every Lunch Money API endpoint. Runs locally via stdio or remotely over HTTP with OAuth 2.1. Credentials never touch disk in plain text.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ npx lunchmoney-mcp setup # Store your API token in the OS keychain
17
+ npx lunchmoney-mcp # Start the MCP server (stdio)
18
+ ```
19
+
20
+ Then add it to your MCP client. For Claude Desktop, add this to your config:
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "lunchmoney": {
26
+ "command": "npx",
27
+ "args": ["lunchmoney-mcp"]
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ That's it. Ask your AI assistant to "show my recent transactions" and you're off.
34
+
35
+ ## Features
36
+
37
+ - **37 tools** -- full CRUD for transactions, categories, tags, budgets, recurring items, assets, and Plaid accounts
38
+ - **Two transport modes** -- stdio for local AI clients (Claude Desktop, Cursor) or HTTP for remote/multi-user deployments
39
+ - **Secure credential storage** -- API tokens in the OS keychain (macOS Keychain, GNOME Keyring, Windows Credential Manager); OAuth session tokens encrypted with AES-256-GCM
40
+ - **4 OAuth providers** -- Google, GitHub, CyberArk Identity, or any custom OAuth 2.1 provider
41
+ - **Deploy anywhere** -- Docker, systemd, Railway, Render, Fly.io with one-click configs included
42
+ - **278 tests** -- comprehensive test suite with >90% coverage
43
+
44
+ ## Setup
45
+
46
+ ### Local (stdio mode)
47
+
48
+ Best for single-user setups with Claude Desktop, Cursor, or other local MCP clients.
49
+
50
+ ```bash
51
+ # Install globally (optional -- npx works too)
52
+ npm install -g lunchmoney-mcp
53
+
54
+ # Run the setup wizard to store your token securely
55
+ lunchmoney-mcp setup
56
+
57
+ # Start the server
58
+ lunchmoney-mcp
59
+ ```
60
+
61
+ Your API token is stored in the OS keychain and never written to disk. Get a token at [my.lunchmoney.app/developers](https://my.lunchmoney.app/developers).
62
+
63
+ Alternatively, you can configure the token from within your AI assistant using the `configureLunchMoneyToken` tool -- no terminal required.
64
+
65
+ ### Remote (HTTP mode)
66
+
67
+ Best for multi-user deployments, shared teams, or running on a server.
68
+
69
+ ```bash
70
+ # Set required environment variables
71
+ export LUNCH_MONEY_API_TOKEN="your-token"
72
+ export AUTH_PROVIDER="google" # or github, cyberark, custom
73
+ export GOOGLE_CLIENT_ID="..."
74
+ export GOOGLE_CLIENT_SECRET="..."
75
+ export BASE_URL="https://your-domain.com"
76
+
77
+ # Start the server
78
+ lunchmoney-mcp --http --port 8080
79
+ ```
80
+
81
+ HTTP mode requires OAuth 2.1 for authentication. See [Authentication](#authentication) below.
82
+
83
+ ### Environment Variable Fallback
84
+
85
+ For containers and CI where no OS keychain is available:
86
+
87
+ ```bash
88
+ export LUNCH_MONEY_API_TOKEN="your-token"
89
+ lunchmoney-mcp
90
+ ```
91
+
92
+ ## Authentication
93
+
94
+ HTTP mode supports four OAuth providers. Set `AUTH_PROVIDER` and the corresponding credentials:
95
+
96
+ ### Google
97
+
98
+ ```bash
99
+ AUTH_PROVIDER=google
100
+ GOOGLE_CLIENT_ID=your-client-id
101
+ GOOGLE_CLIENT_SECRET=your-client-secret
102
+ ```
103
+
104
+ ### GitHub
105
+
106
+ ```bash
107
+ AUTH_PROVIDER=github
108
+ GITHUB_CLIENT_ID=your-client-id
109
+ GITHUB_CLIENT_SECRET=your-client-secret
110
+ ```
111
+
112
+ ### CyberArk Identity
113
+
114
+ ```bash
115
+ AUTH_PROVIDER=cyberark
116
+ CYBERARK_TENANT_URL=https://abc1234.id.cyberark.cloud
117
+ CYBERARK_CLIENT_ID=your-client-id # or OAUTH_CLIENT_ID
118
+ CYBERARK_CLIENT_SECRET=your-secret # or OAUTH_CLIENT_SECRET
119
+ ```
120
+
121
+ ### Custom OAuth
122
+
123
+ ```bash
124
+ AUTH_PROVIDER=custom
125
+ OAUTH_CLIENT_ID=your-client-id
126
+ OAUTH_CLIENT_SECRET=your-client-secret
127
+ OAUTH_AUTH_URL=https://provider.com/authorize
128
+ OAUTH_TOKEN_URL=https://provider.com/token
129
+ OAUTH_SCOPES=openid,email # optional, defaults to openid,email
130
+ ```
131
+
132
+ ## Deployment
133
+
134
+ Pre-built deployment configs are included in the repository.
135
+
136
+ ### Docker
137
+
138
+ ```bash
139
+ docker build -t lunchmoney-mcp .
140
+ docker run -p 8080:8080 \
141
+ -e LUNCH_MONEY_API_TOKEN="your-token" \
142
+ -e AUTH_PROVIDER=google \
143
+ -e GOOGLE_CLIENT_ID="..." \
144
+ -e GOOGLE_CLIENT_SECRET="..." \
145
+ -e BASE_URL="https://your-domain.com" \
146
+ lunchmoney-mcp
147
+ ```
148
+
149
+ Or with Docker Compose:
150
+
151
+ ```bash
152
+ docker compose up
153
+ ```
154
+
155
+ ### systemd
156
+
157
+ A systemd service file is included at `deploy/lunchmoney-mcp.service`. Copy it to `/etc/systemd/system/` and configure the environment variables.
158
+
159
+ ### Railway
160
+
161
+ [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template)
162
+
163
+ The included `railway.json` configures the build and start commands automatically. Set your environment variables in the Railway dashboard.
164
+
165
+ ### Render
166
+
167
+ The included `render.yaml` defines the service as a private worker. Set your environment variables in the Render dashboard.
168
+
169
+ ### Fly.io
170
+
171
+ ```bash
172
+ fly launch
173
+ fly secrets set LUNCH_MONEY_API_TOKEN="your-token"
174
+ fly secrets set AUTH_PROVIDER=google GOOGLE_CLIENT_ID="..." GOOGLE_CLIENT_SECRET="..."
175
+ fly deploy
176
+ ```
177
+
178
+ ## Tools Reference
179
+
180
+ ### Setup (1 tool)
181
+
182
+ | Tool | Description |
183
+ |------|-------------|
184
+ | `configureLunchMoneyToken` | Configure and validate your Lunch Money API token |
185
+
186
+ ### User (1 tool)
187
+
188
+ | Tool | Description |
189
+ |------|-------------|
190
+ | `getUser` | Get account details including email, name, and currency preferences |
191
+
192
+ ### Categories (7 tools)
193
+
194
+ | Tool | Description |
195
+ |------|-------------|
196
+ | `getCategories` | List all categories including groups and parent categories |
197
+ | `getCategory` | Get a single category by ID |
198
+ | `createCategory` | Create a new spending or income category |
199
+ | `updateCategory` | Update an existing category's properties |
200
+ | `deleteCategory` | Delete a category by ID |
201
+ | `createCategoryGroup` | Create a new category group with optional category IDs |
202
+ | `addToGroup` | Add existing categories to a category group |
203
+
204
+ ### Tags (4 tools)
205
+
206
+ | Tool | Description |
207
+ |------|-------------|
208
+ | `getTags` | List all transaction tags |
209
+ | `createTag` | Create a new tag for categorizing transactions |
210
+ | `updateTag` | Update an existing tag's name |
211
+ | `deleteTag` | Delete a tag by ID |
212
+
213
+ ### Transactions (10 tools)
214
+
215
+ | Tool | Description |
216
+ |------|-------------|
217
+ | `getTransactions` | List transactions with filtering (date range, category, tags, status) |
218
+ | `getTransaction` | Get a single transaction by ID |
219
+ | `createTransaction` | Create a new transaction (expense, income, or transfer) |
220
+ | `updateTransaction` | Update an existing transaction's properties |
221
+ | `deleteTransaction` | Delete a transaction by ID |
222
+ | `bulkUpdateTransactions` | Bulk update multiple transactions with the same changes |
223
+ | `getTransactionGroup` | Retrieve a transaction group with all child transactions |
224
+ | `createTransactionGroup` | Group multiple transactions under a single parent |
225
+ | `deleteTransactionGroup` | Delete a group, restoring individual transactions |
226
+ | `unsplitTransactions` | Reverse a split, merging child transactions back into parent |
227
+
228
+ ### Recurring Items (4 tools)
229
+
230
+ | Tool | Description |
231
+ |------|-------------|
232
+ | `getRecurringItems` | List all recurring expense and income items |
233
+ | `createRecurringItem` | Create a new recurring expense or income item |
234
+ | `updateRecurringItem` | Update an existing recurring item's properties |
235
+ | `deleteRecurringItem` | Delete a recurring item by ID |
236
+
237
+ ### Budgets (4 tools)
238
+
239
+ | Tool | Description |
240
+ |------|-------------|
241
+ | `getBudgets` | List all budgets with category assignments and date ranges |
242
+ | `createBudget` | Create a new budget for a category with amount and date range |
243
+ | `updateBudget` | Update an existing budget's amount, category, or date range |
244
+ | `deleteBudget` | Delete a budget by ID |
245
+
246
+ ### Assets (4 tools)
247
+
248
+ | Tool | Description |
249
+ |------|-------------|
250
+ | `getAssets` | List all manually-managed assets |
251
+ | `createAsset` | Create a new manually-managed asset |
252
+ | `updateAsset` | Update an existing asset's properties including balance |
253
+ | `deleteAsset` | Delete an asset by ID |
254
+
255
+ ### Plaid Accounts (2 tools)
256
+
257
+ | Tool | Description |
258
+ |------|-------------|
259
+ | `getPlaidAccounts` | List all Plaid-connected accounts with balances |
260
+ | `fetchPlaidAccounts` | Trigger a Plaid sync to update account balances |
261
+
262
+ ## CLI Reference
263
+
264
+ ```
265
+ lunchmoney-mcp # Start in stdio mode (default)
266
+ lunchmoney-mcp --http # Start in HTTP mode with OAuth
267
+ lunchmoney-mcp --http --port 3000 # HTTP mode on custom port
268
+ lunchmoney-mcp setup # Run the interactive setup wizard
269
+ lunchmoney-mcp --version # Print version
270
+ ```
271
+
272
+ The server also reads the `PORT` environment variable when `--port` is not specified.
273
+
274
+ ## Security
275
+
276
+ Credentials are stored in your OS keychain and never written to disk in plain text. OAuth session tokens are encrypted with AES-256-GCM, with the encryption key stored in the keychain.
277
+
278
+ For full details on the two-tier credential architecture, threat model, and deployment security, see [SECURITY.md](SECURITY.md).
279
+
280
+ ## Contributing
281
+
282
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing guidelines, and pull request requirements.
283
+
284
+ ## License
285
+
286
+ [MIT](LICENSE) -- Copyright (c) 2026 Joe Garcia
287
+
288
+ Based on [lunch-money-mcp](https://github.com/gilbitron/lunch-money-mcp) by Gilbert Pellegrom, MIT License.
package/SECURITY.md ADDED
@@ -0,0 +1,130 @@
1
+ # Security
2
+
3
+ ## Credential Architecture
4
+
5
+ lunchmoney-mcp uses a two-tier credential architecture designed to keep your financial data secure.
6
+
7
+ ### Tier 1: OS Keychain (Long-lived Secrets)
8
+
9
+ Your Lunch Money API token and OAuth client credentials are stored in your operating system's native credential manager:
10
+
11
+ - **macOS:** Keychain Access
12
+ - **Linux:** Secret Service (GNOME Keyring / KDE Wallet)
13
+ - **Windows:** Windows Credential Manager
14
+
15
+ These credentials are:
16
+ - Encrypted by the OS using your login password
17
+ - Never written to disk in plain text
18
+ - Stored under the service namespace `lunchmoney-mcp`
19
+ - Accessible only to processes running as your user
20
+
21
+ ### Tier 2: Encrypted Disk Store (OAuth Session Tokens)
22
+
23
+ When running in HTTP mode with OAuth, session tokens (access tokens, refresh tokens, authorization codes) are stored on disk for persistence across server restarts.
24
+
25
+ - **Encryption:** AES-256-GCM (authenticated encryption)
26
+ - **Encryption key:** Stored in the OS keychain (Tier 1), or supplied via the `ENCRYPTION_KEY` env var when no keychain is available (see below) -- the encrypted files are useless without it
27
+ - **Location:** Platform-native data directory
28
+ - macOS: `~/Library/Application Support/lunchmoney-mcp/`
29
+ - Linux: `~/.local/share/lunchmoney-mcp/`
30
+ - Windows: `%APPDATA%/lunchmoney-mcp/`
31
+ - **TTL:** Tokens expire automatically and are cleaned up periodically
32
+
33
+ ### ENV Variable Fallback
34
+
35
+ For containerized deployments (Docker, Kubernetes) where no OS keychain is available, credentials can be passed via environment variables:
36
+
37
+ - `LUNCH_MONEY_API_TOKEN` -- your Lunch Money API key
38
+ - `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` -- for Google OAuth
39
+ - `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` -- for GitHub OAuth
40
+ - `CYBERARK_TENANT_URL` / `CYBERARK_CLIENT_ID` / `CYBERARK_CLIENT_SECRET` -- for CyberArk Identity OAuth
41
+ - `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET` / `OAUTH_AUTH_URL` / `OAUTH_TOKEN_URL` -- for custom OAuth
42
+
43
+ **Important:** When using ENV vars, ensure your deployment platform encrypts environment variables at rest (Railway, Render, and Fly.io all do this by default).
44
+
45
+ ### Encryption Key (`ENCRYPTION_KEY`)
46
+
47
+ In HTTP/OAuth mode the session store is encrypted with a stable key. The key is resolved in this order:
48
+
49
+ 1. **OS keychain** -- generated and persisted automatically on first run.
50
+ 2. **`ENCRYPTION_KEY` env var** -- used when the keychain is unavailable (containers, headless hosts).
51
+
52
+ Requirements and behavior (as of 0.3.0):
53
+
54
+ - **Format:** exactly 64 lowercase hexadecimal characters (32 bytes). An invalid value is a hard error -- the server refuses to start rather than run with a misconfigured key.
55
+ - **Generate one:** `openssl rand -hex 32`
56
+ - **Refuse, don't drift:** if the keychain is unavailable *and* `ENCRYPTION_KEY` is unset, the server **refuses to start in HTTP/OAuth mode**. Earlier versions silently generated an ephemeral key, which invalidated every stored session on each restart. Stdio mode has no persisted sessions, so it still allows an ephemeral key.
57
+ - **Rotation:** in container deployments, set a new `ENCRYPTION_KEY` and restart. In keychain deployments, delete the `encryption-key` entry under the `lunchmoney-mcp` service in your OS credential manager and restart (a fresh key is generated). Rotating the key invalidates existing encrypted sessions; users re-authenticate on next use. Rotate if you suspect the key was exposed.
58
+
59
+ ## Threat Model
60
+
61
+ | Threat | Mitigation |
62
+ |--------|-----------|
63
+ | API token stolen from disk | Token stored in OS keychain, never in plain text |
64
+ | Session tokens stolen from disk | Encrypted with AES-256-GCM; encryption key in keychain |
65
+ | Keychain compromised | Requires OS-level compromise (user login password) |
66
+ | Token in chat history (configureLunchMoneyToken) | Optional -- users can use `npx lunchmoney-mcp setup` CLI instead |
67
+ | ENV var exposure in containers | Use platform-provided secret management; never log ENV vars |
68
+ | Man-in-the-middle on API calls | All API calls use HTTPS; the OAuth flow uses PKCE (see note below) |
69
+ | Unauthorized MCP access (HTTP mode) | OAuth 2.1 with PKCE required for all authenticated endpoints (see note below) |
70
+ | Silent/phishing OAuth grant | Consent required on every first-time grant (Google consent screen not suppressed) |
71
+ | Lost encryption key in containers | Server refuses to start without a valid `ENCRYPTION_KEY` in HTTP/OAuth mode (no silent ephemeral key) |
72
+
73
+ > **Note on PKCE:** the OAuth authorization flow (including PKCE / `code_challenge`) is implemented by FastMCP, not by this server directly. We rely on the behavior of the pinned `fastmcp` version (currently `3.35.0`) and do not override `code_challenge_method`. Treat the PKCE guarantee as transitive through that dependency; it is re-verified whenever FastMCP is bumped.
74
+
75
+ ## Supply Chain Security
76
+
77
+ MCP servers are high-value targets — they sit between AI assistants and your data. A compromised dependency could turn this server into a data exfiltration tool. We take this seriously.
78
+
79
+ ### What We Do
80
+
81
+ | Protection | How |
82
+ |-----------|-----|
83
+ | **Pinned dependencies** | All versions in `package.json` are exact (no `^` or `~`). A compromised new release won't auto-install. |
84
+ | **Lockfile integrity** | `package-lock.json` is committed and CI uses `npm ci` (not `npm install`), ensuring the exact dependency tree is reproduced. |
85
+ | **npm provenance** | Every release is published with `--provenance` from GitHub Actions, creating a cryptographic attestation linking the npm package to its source commit. Verify with `npm audit signatures`. |
86
+ | **Automated vulnerability scanning** | `npm audit --audit-level=high` runs on every CI build. Known vulnerabilities block merges. |
87
+ | **Signature verification** | `npm audit signatures` runs in CI to verify all installed packages have valid registry signatures. |
88
+ | **Dependabot** | GitHub Dependabot monitors for vulnerable dependencies and opens PRs weekly. Major version bumps require manual review. |
89
+ | **Minimal dependency surface** | Only 5 production dependencies. Dev dependencies (vitest, typescript, etc.) are not shipped in the npm package. |
90
+ | **GitHub Actions pinned** | CI workflow actions are pinned to specific versions and monitored by Dependabot. |
91
+
92
+ ### What You Can Do
93
+
94
+ **Verify provenance** of any installed version:
95
+ ```bash
96
+ npm audit signatures
97
+ ```
98
+
99
+ **Verify the package source** matches the GitHub repo:
100
+ ```bash
101
+ # Check provenance attestation on npm
102
+ npm view @infamousendeavors/lunchmoney-mcp --json | jq '.dist.attestations'
103
+ ```
104
+
105
+ **Pin your install** to a specific version:
106
+ ```bash
107
+ npm install -g @infamousendeavors/lunchmoney-mcp@0.3.0
108
+ ```
109
+
110
+ **Audit before running:**
111
+ ```bash
112
+ npx @infamousendeavors/lunchmoney-mcp --version # check version
113
+ npm audit # check for known vulnerabilities
114
+ ```
115
+
116
+ ### Production Dependencies (5 total)
117
+
118
+ | Package | Purpose | Why We Trust It |
119
+ |---------|---------|----------------|
120
+ | `fastmcp` | MCP protocol framework | Active development, MCP ecosystem standard |
121
+ | `zod` | Input validation | 25k+ GitHub stars, widely audited |
122
+ | `keytar` | OS keychain access | Community-maintained, native OS keychain binding |
123
+ | `dotenv` | ENV file parsing | 30M+ weekly downloads, minimal surface area |
124
+ | `env-paths` | Platform data dirs | Zero dependencies, 50 lines of code |
125
+
126
+ Dev dependencies (`vitest`, `typescript`, `tsx`, `@types/node`, `@vitest/coverage-v8`) are NOT included in the published npm package — they are excluded via the `files` field in `package.json`.
127
+
128
+ ## Reporting Vulnerabilities
129
+
130
+ If you discover a security vulnerability, please email joe at joe-garcia dot com instead of opening a public issue.
@@ -0,0 +1,11 @@
1
+ export declare class LunchMoneyClient {
2
+ private baseURL;
3
+ private accessToken;
4
+ constructor(accessToken: string);
5
+ private request;
6
+ get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T>;
7
+ post<T>(endpoint: string, body?: unknown): Promise<T>;
8
+ put<T>(endpoint: string, body?: unknown): Promise<T>;
9
+ delete<T>(endpoint: string): Promise<T>;
10
+ }
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAEA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;YAQjB,OAAO;IAwDf,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBtE,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAOrD,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAOpD,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;CAG9C"}
@@ -0,0 +1,82 @@
1
+ import { LunchMoneyAPIError, handleAPIError } from "../utils/errors.js";
2
+ export class LunchMoneyClient {
3
+ baseURL;
4
+ accessToken;
5
+ constructor(accessToken) {
6
+ if (!accessToken) {
7
+ throw new Error("Lunch Money API token is required");
8
+ }
9
+ this.baseURL = "https://dev.lunchmoney.app/v1";
10
+ this.accessToken = accessToken;
11
+ }
12
+ async request(endpoint, options = {}) {
13
+ const url = `${this.baseURL}${endpoint}`;
14
+ const headers = {
15
+ Authorization: `Bearer ${this.accessToken}`,
16
+ "Content-Type": "application/json",
17
+ ...options.headers,
18
+ };
19
+ try {
20
+ const response = await fetch(url, {
21
+ ...options,
22
+ headers,
23
+ });
24
+ if (!response.ok) {
25
+ let errorMessage = `API request failed: ${response.statusText}`;
26
+ try {
27
+ const errorData = await response.json();
28
+ if (typeof errorData === "object" &&
29
+ errorData !== null &&
30
+ "error" in errorData &&
31
+ typeof errorData.error === "string") {
32
+ errorMessage = errorData.error;
33
+ }
34
+ }
35
+ catch {
36
+ // If response is not JSON, use status text
37
+ }
38
+ throw new LunchMoneyAPIError(errorMessage, response.status, await response.text().catch(() => undefined));
39
+ }
40
+ // Handle empty responses
41
+ const contentType = response.headers.get("content-type");
42
+ if (contentType && contentType.includes("application/json")) {
43
+ const data = await response.json();
44
+ return data;
45
+ }
46
+ return {};
47
+ }
48
+ catch (error) {
49
+ if (error instanceof LunchMoneyAPIError) {
50
+ throw error;
51
+ }
52
+ handleAPIError(error);
53
+ }
54
+ }
55
+ async get(endpoint, params) {
56
+ const queryString = params
57
+ ? `?${new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => {
58
+ if (value !== undefined && value !== null) {
59
+ acc[key] = String(value);
60
+ }
61
+ return acc;
62
+ }, {})).toString()}`
63
+ : "";
64
+ return this.request(`${endpoint}${queryString}`, { method: "GET" });
65
+ }
66
+ async post(endpoint, body) {
67
+ return this.request(endpoint, {
68
+ method: "POST",
69
+ body: body ? JSON.stringify(body) : undefined,
70
+ });
71
+ }
72
+ async put(endpoint, body) {
73
+ return this.request(endpoint, {
74
+ method: "PUT",
75
+ body: body ? JSON.stringify(body) : undefined,
76
+ });
77
+ }
78
+ async delete(endpoint) {
79
+ return this.request(endpoint, { method: "DELETE" });
80
+ }
81
+ }
82
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { GoogleProvider, GitHubProvider, OAuthProvider } from "fastmcp";
2
+ import type { TokenStorage } from "fastmcp/auth";
3
+ export type AuthProviderType = "google" | "github" | "cyberark" | "custom";
4
+ export interface AuthProviderOptions {
5
+ provider: AuthProviderType;
6
+ baseUrl: string;
7
+ clientId: string;
8
+ clientSecret: string;
9
+ /** CyberArk tenant URL (e.g. https://abc1234.id.cyberark.cloud) - required for cyberark provider */
10
+ cyberarkTenantUrl?: string;
11
+ /** OAuth authorization endpoint URL - required for custom provider */
12
+ authorizationEndpoint?: string;
13
+ /** OAuth token endpoint URL - required for custom provider */
14
+ tokenEndpoint?: string;
15
+ /** OAuth scopes - optional for custom provider (defaults to ['openid', 'email']) */
16
+ scopes?: string[];
17
+ /** Token storage backend for persisting OAuth sessions across restarts */
18
+ tokenStorage?: TokenStorage;
19
+ /** Encryption key for token storage (set to false to disable if already encrypted, or provide a hex string) */
20
+ encryptionKey?: false | string;
21
+ }
22
+ /**
23
+ * Create an OAuth auth provider for use with FastMCP HTTP transport.
24
+ * Supports Google, GitHub, CyberArk Identity, and custom OAuth providers.
25
+ */
26
+ export declare function createAuthProvider(options: AuthProviderOptions): GoogleProvider | GitHubProvider | OAuthProvider<import("fastmcp").OAuthSession>;
27
+ /**
28
+ * Additional environment-based config for providers that need extra fields.
29
+ */
30
+ export interface AuthProviderEnvConfig {
31
+ clientId: string;
32
+ clientSecret: string;
33
+ /** CyberArk tenant URL - present only for cyberark provider */
34
+ cyberarkTenantUrl?: string;
35
+ /** OAuth authorization endpoint - present only for custom provider */
36
+ authorizationEndpoint?: string;
37
+ /** OAuth token endpoint - present only for custom provider */
38
+ tokenEndpoint?: string;
39
+ /** OAuth scopes - present only for custom provider */
40
+ scopes?: string[];
41
+ }
42
+ /**
43
+ * Read OAuth credentials from environment variables for the given provider.
44
+ * Throws if required env vars are missing.
45
+ */
46
+ export declare function getAuthCredentialsFromEnv(provider: AuthProviderType): AuthProviderEnvConfig;
47
+ //# sourceMappingURL=auth-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider.d.ts","sourceRoot":"","sources":["../src/auth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,+GAA+G;IAC/G,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,mFAqE9D;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,qBAAqB,CAuD3F"}