@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.
- package/LICENSE +25 -0
- package/README.md +288 -0
- package/SECURITY.md +130 -0
- package/dist/api/client.d.ts +11 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +82 -0
- package/dist/api/client.js.map +1 -0
- package/dist/auth-provider.d.ts +47 -0
- package/dist/auth-provider.d.ts.map +1 -0
- package/dist/auth-provider.js +111 -0
- package/dist/auth-provider.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +103 -0
- package/dist/cli.js.map +1 -0
- package/dist/credential-store.d.ts +25 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +90 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/index.d.ts +189 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +178 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/server.d.ts +32 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +116 -0
- package/dist/server.js.map +1 -0
- package/dist/session-store.d.ts +28 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +50 -0
- package/dist/session-store.js.map +1 -0
- package/dist/setup.d.ts +21 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +104 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools/assets.d.ts +4 -0
- package/dist/tools/assets.d.ts.map +1 -0
- package/dist/tools/assets.js +63 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/budgets.d.ts +4 -0
- package/dist/tools/budgets.d.ts.map +1 -0
- package/dist/tools/budgets.js +63 -0
- package/dist/tools/budgets.js.map +1 -0
- package/dist/tools/categories.d.ts +4 -0
- package/dist/tools/categories.d.ts.map +1 -0
- package/dist/tools/categories.js +105 -0
- package/dist/tools/categories.js.map +1 -0
- package/dist/tools/plaid.d.ts +4 -0
- package/dist/tools/plaid.d.ts.map +1 -0
- package/dist/tools/plaid.js +33 -0
- package/dist/tools/plaid.js.map +1 -0
- package/dist/tools/recurring.d.ts +4 -0
- package/dist/tools/recurring.d.ts.map +1 -0
- package/dist/tools/recurring.js +63 -0
- package/dist/tools/recurring.js.map +1 -0
- package/dist/tools/tags.d.ts +4 -0
- package/dist/tools/tags.d.ts.map +1 -0
- package/dist/tools/tags.js +63 -0
- package/dist/tools/tags.js.map +1 -0
- package/dist/tools/transactions.d.ts +4 -0
- package/dist/tools/transactions.d.ts.map +1 -0
- package/dist/tools/transactions.js +146 -0
- package/dist/tools/transactions.js.map +1 -0
- package/dist/tools/user.d.ts +4 -0
- package/dist/tools/user.d.ts.map +1 -0
- package/dist/tools/user.js +19 -0
- package/dist/tools/user.js.map +1 -0
- package/dist/types/index.d.ts +164 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +8 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +29 -0
- package/dist/utils/errors.js.map +1 -0
- 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
|
+
[](https://www.npmjs.com/package/@infamousendeavors/lunchmoney-mcp)
|
|
4
|
+
[](https://www.npmjs.com/package/@infamousendeavors/lunchmoney-mcp)
|
|
5
|
+
[](https://github.com/infamousendeavors/lunchmoney-mcp/releases)
|
|
6
|
+
[](https://github.com/infamousendeavors/lunchmoney-mcp/actions/workflows/ci.yml)
|
|
7
|
+
[](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
|
+
[](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"}
|