actual-mcp-server 0.22.14 → 0.22.18
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/README.md +57 -11
- package/dist/src/actualConnection.js +7 -1
- package/dist/src/actualToolsManager.js +53 -1
- package/dist/src/config.js +29 -0
- package/dist/src/index.js +3 -2
- package/dist/src/lib/ActualMCPConnection.js +1 -1
- package/dist/src/lib/actual-adapter.js +284 -14
- package/dist/src/lib/errors.js +20 -0
- package/dist/src/lib/schemas/common.js +1 -1
- package/dist/src/lib/schemas/recur.js +8 -8
- package/dist/src/lib/schemas/rules.js +133 -0
- package/dist/src/lib/session-owners.js +41 -0
- package/dist/src/lib/tool-annotations.js +5 -3
- package/dist/src/lib/toolsets.js +302 -0
- package/dist/src/server/httpServer.js +44 -5
- package/dist/src/tools/budget_updates_batch.js +35 -50
- package/dist/src/tools/index.js +1 -0
- package/dist/src/tools/query_run.js +11 -47
- package/dist/src/tools/rules_create.js +8 -131
- package/dist/src/tools/rules_create_batch.js +47 -0
- package/dist/src/tools/rules_create_or_update.js +16 -38
- package/dist/src/tools/rules_update.js +15 -31
- package/dist/src/tools/schedules_create.js +10 -10
- package/dist/src/tools/schedules_update.js +12 -12
- package/dist/src/tools/server_info.js +5 -1
- package/dist/src/tools/session_close.js +15 -10
- package/dist/src/tools/session_list.js +7 -2
- package/dist/src/tools/transactions_update_batch.js +15 -24
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ Actual MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/
|
|
|
24
24
|
┌─────────────┐ MCP/HTTP ┌──────────────────┐ Actual API ┌──────────────┐
|
|
25
25
|
│ LibreChat │ ◄───────────► │ Actual MCP │ ◄───────────► │ Actual │
|
|
26
26
|
│ LobeChat │ │ Server │ │ Budget │
|
|
27
|
-
│ (remote) │ │ (
|
|
27
|
+
│ (remote) │ │ (83 tools) │ │ Server │
|
|
28
28
|
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
29
29
|
|
|
30
30
|
┌─────────────┐ MCP/stdio ┌──────────────────┐ Actual API ┌──────────────┐
|
|
31
31
|
│ Claude │ ◄───────────► │ Actual MCP │ ◄───────────► │ Actual │
|
|
32
32
|
│ Desktop │ │ Server │ │ Budget │
|
|
33
|
-
│ (local) │ │ (
|
|
33
|
+
│ (local) │ │ (83 tools) │ │ Server │
|
|
34
34
|
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -38,14 +38,14 @@ Actual MCP Server is a [Model Context Protocol](https://modelcontextprotocol.io/
|
|
|
38
38
|
|
|
39
39
|
Most Actual Budget MCP implementations are simple stdio bridges designed for single-user, local use with Claude Desktop. This project goes further:
|
|
40
40
|
|
|
41
|
-
- **
|
|
41
|
+
- **83 tools, the most comprehensive coverage available.** Accounts, transactions, categories, payees, tags, notes, rules, budgets, batch operations, bank sync, and more. Covers the reachable Actual Budget API with no genuine gaps.
|
|
42
42
|
- **HTTP and stdio transport.** Runs as a real remote server for LibreChat/LobeChat (`--http`), or as a direct local process for Claude Desktop (`--stdio`). No Docker or HTTP server is needed for local use.
|
|
43
43
|
- **6 exclusive ActualQL-powered tools.** Search and summarise transactions by month, amount, category, or payee using Actual Budget's native query engine. Aggregated results, no raw data dumped into the AI context window.
|
|
44
44
|
- **Multi-budget switching at runtime.** Configure multiple budget files and let the AI switch between them mid-conversation with `actual_budgets_switch`. Works on both transports: HTTP keys the active budget to the MCP session, and stdio (Claude Desktop, Claude Code, Cursor) gets a synthetic per-process session so a switch is scoped to that process rather than shared globally (#348).
|
|
45
45
|
- **Multi-user ready with OIDC.** Secure every session with JWKS-validated JWTs and per-user budget ACLs. No shared tokens required.
|
|
46
46
|
- **Production-grade reliability on both transports.** HTTP connection pooling (up to 15 concurrent sessions), and a long-lived stdio process that logs in ONCE and reuses that connection for every tool call instead of re-authenticating per call, so Claude Desktop and Claude Code stay fast and a burst of calls no longer risks a per-call login storm against the upstream limiter. Automatic retry with exponential backoff, and a full test suite (unit + E2E + integration).
|
|
47
47
|
|
|
48
|
-
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All
|
|
48
|
+
> **Verified working** with [LibreChat](https://www.librechat.ai/), [LobeChat](https://lobehub.com/home), and [Claude Desktop](https://claude.ai/download). All 83 tools tested end-to-end. Any MCP-compatible client should work.
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
@@ -182,7 +182,7 @@ Add to `claude_desktop_config.json` (see [docs/guides/MCP_CLIENTS_SETUP.md](docs
|
|
|
182
182
|
}
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
> **No token needed.** stdio runs as a local process owned by your user. The transport itself is the security boundary. All
|
|
185
|
+
> **No token needed.** stdio runs as a local process owned by your user. The transport itself is the security boundary. All 83 tools are available.
|
|
186
186
|
>
|
|
187
187
|
> **`MCP_BRIDGE_DATA_DIR` should be an absolute path.** Without one, the data directory resolves relative to wherever the client spawns the process, which can be unpredictable. The directory is created automatically on first run.
|
|
188
188
|
|
|
@@ -308,7 +308,7 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
308
308
|
|
|
309
309
|
## Available Tools
|
|
310
310
|
|
|
311
|
-
**
|
|
311
|
+
**83 tools** across all categories. All tools use the `actual_<category>_<action>` naming convention.
|
|
312
312
|
|
|
313
313
|
### Accounts (12)
|
|
314
314
|
|
|
@@ -425,13 +425,14 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
425
425
|
| `actual_budgets_export` | Export the active budget as a `.zip` into `ACTUAL_EXPORT_DIR`; returns path, byte size and sha256, never the file contents |
|
|
426
426
|
| `actual_budgets_import` | Restore a budget from an Actual `.zip` or a YNAB4/YNAB5 export. **Destructive:** the budget id comes from the archive, so re-importing an export *replaces* that budget's data rather than making a copy. Also loads the imported budget, changing the session's active budget |
|
|
427
427
|
|
|
428
|
-
### Rules (
|
|
428
|
+
### Rules (6)
|
|
429
429
|
|
|
430
430
|
`actual_rules_get` · `actual_rules_create` · `actual_rules_update` · `actual_rules_delete`
|
|
431
431
|
|
|
432
432
|
| Tool | Description |
|
|
433
433
|
|------|-------------|
|
|
434
434
|
| `actual_rules_create_or_update` | Idempotent upsert: create a rule, or update the existing one that matches the same conditions, in a single call. Use this instead of get-then-create when re-running a categorisation setup, so repeated runs do not pile up duplicate rules |
|
|
435
|
+
| `actual_rules_create_batch` | Create 1 to 50 rules in one call, each exactly the input of `actual_rules_create`. Best-effort, not atomic (see Batch Operations): a rule that fails its own checks, names a category, payee or account that does not exist, or is rejected by Actual is reported with its index while the rest continue |
|
|
435
436
|
|
|
436
437
|
### Schedules (4)
|
|
437
438
|
|
|
@@ -451,7 +452,12 @@ For Claude Desktop (stdio), restart Claude after upgrading.
|
|
|
451
452
|
|
|
452
453
|
### Batch Operations (1)
|
|
453
454
|
|
|
454
|
-
|
|
455
|
+
Four tools take many items in one call, for clients that allow only a few tool calls per turn. They share ONE contract: each item is applied independently and **not atomically**. There is no rollback, so items applied before a failure stay applied; a bad item is reported by its index and the rest continue; an infrastructure failure (lost connection, timeout) fails the whole call, and then you must read the data back before retrying anything. A bulk path never retries a create on its own.
|
|
456
|
+
|
|
457
|
+
- `actual_transactions_update_batch` (counted under Transactions): update up to 50 transactions, `{id, fields}` each. Result: `{ succeeded: [{id}], failed: [{id, error}], total, successCount, failureCount }`.
|
|
458
|
+
- `actual_transactions_import` (counted under Transactions): bulk create with import semantics (deduplication and reconciliation against existing transactions, rules optionally applied). Use it to add many transactions at once.
|
|
459
|
+
- `actual_budget_updates_batch`: set budget amounts and/or carryover flags for 1 to 100 month/category pairs, and the way to rebalance categories: read the month with `actual_budgets_getMonth`, then send one batch of absolute amounts. Each operation needs at least one of `amount` (integer cents) or `carryover`. Result: `{ succeeded: [{index, month, categoryId}], failed: [{index, month, categoryId, error}], total, successCount, failureCount }`. An unknown category, an out-of-range month or a carryover on an income category fails only that item. After a timeout, read the months back with `actual_budgets_getMonth` before retrying.
|
|
460
|
+
- `actual_rules_create_batch` (counted under Rules): create 1 to 50 rules, each exactly the input of `actual_rules_create`. Result: `{ succeeded: [{index, id}], failed: [{index, error}], total, successCount, failureCount }`. Items marked "not attempted" were never sent. Re-read with `actual_rules_get` before retrying any item.
|
|
455
461
|
|
|
456
462
|
### Server Information & Lookup (5)
|
|
457
463
|
|
|
@@ -498,6 +504,10 @@ All configuration is via environment variables. Copy `.env.example` to `.env` to
|
|
|
498
504
|
| `MCP_HTTP_PATH` | `/http` | No | HTTP endpoint routing path |
|
|
499
505
|
| `MCP_BRIDGE_HTTP_PATH` | same as `MCP_HTTP_PATH` | No | Advertised HTTP path shown to clients (set when a reverse proxy rewrites the path) |
|
|
500
506
|
| `MCP_HTTP_BODY_LIMIT` | `512kb` | No | Maximum accepted JSON-RPC request body size (e.g. `512kb`, `1mb`, `1048576`). Validated at startup: an invalid, zero or overflowing size refuses to start naming the variable (#466) |
|
|
507
|
+
| **Tool Surface** ||||
|
|
508
|
+
| `MCP_TOOLSETS` | `all` | No | Comma-separated toolset groups (`context`, `transactions`, `analysis`, `budget`, `rules`, `schedules`, `structure`, `query`, `admin`) and presets (`chat`) to publish; `all` publishes every tool. Unknown names refuse to start. See [Reducing the tool surface](#reducing-the-tool-surface) |
|
|
509
|
+
| `MCP_TOOLS` | _(empty)_ | No | Extra tools to add on top of `MCP_TOOLSETS`, by full registered name (e.g. `actual_accounts_list`). Unknown names refuse to start |
|
|
510
|
+
| `MCP_READ_ONLY` | `false` | No | `true` hides and refuses the 47 write-capable tools. Only `true` or `false` (case-insensitive) are accepted; any other value refuses to start |
|
|
501
511
|
| **Session Management** ||||
|
|
502
512
|
| `USE_CONNECTION_POOL` | `true` | No | Enable session-based connection pooling |
|
|
503
513
|
| `MAX_CONCURRENT_SESSIONS` | `15` | No | Maximum concurrent MCP sessions allowed |
|
|
@@ -544,6 +554,42 @@ All configuration is via environment variables. Copy `.env.example` to `.env` to
|
|
|
544
554
|
| `VERSION` | auto-detected | No | Server version (auto-set by build/Docker) |
|
|
545
555
|
| `TZ` | `UTC` | No | Timezone for timestamps (e.g., `America/New_York`) |
|
|
546
556
|
|
|
557
|
+
### Reducing the tool surface
|
|
558
|
+
|
|
559
|
+
All 83 tools are published by default. Chat clients with a small tool budget (Gemini, LibreChat) do better with fewer, so the surface can be narrowed at startup. A tool that is not published is also refused if a client calls it by name, and the refusal names the setting responsible.
|
|
560
|
+
|
|
561
|
+
| Toolset | Tools | Contents |
|
|
562
|
+
|---------|-------|----------|
|
|
563
|
+
| `context` | 14 | Orientation, lookups and server information |
|
|
564
|
+
| `transactions` | 13 | Transaction reads, create, update, delete, import and transfers |
|
|
565
|
+
| `analysis` | 7 | Summaries, aggregates and balances |
|
|
566
|
+
| `budget` | 10 | Budget months, amounts, carryover, holds, transfers, batch updates and notes |
|
|
567
|
+
| `rules` | 7 | Rules and payee rules, including `actual_rules_create_batch` |
|
|
568
|
+
| `schedules` | 4 | Scheduled transactions |
|
|
569
|
+
| `structure` | 21 | Accounts, account groups, categories, category groups, payees and tags |
|
|
570
|
+
| `query` | 1 | `actual_query_run` (SELECT only) |
|
|
571
|
+
| `admin` | 6 | Budget switching, import, export, bank sync and session management |
|
|
572
|
+
|
|
573
|
+
Each tool is in exactly one group. The `chat` preset cuts across them and publishes 12 tools: `actual_get_context`, `actual_query_run`, `actual_transactions_create`, `actual_transactions_update`, `actual_transactions_delete`, `actual_transactions_update_batch`, `actual_rules_create`, `actual_rules_create_batch`, `actual_budget_updates_batch`, `actual_budgets_transfer`, `actual_budgets_setAmount` and `actual_budgets_getMonth`. Measured on the `tools/list` payload, all 83 tools are 108,164 bytes and the `chat` preset is 25,949 bytes (about 6.5k tokens saved, a 76% cut).
|
|
574
|
+
|
|
575
|
+
`MCP_READ_ONLY=true` additionally hides and refuses every write-capable tool (47 of the 83, including `actual_bank_sync`, `actual_budgets_export`, `actual_budgets_switch` and `actual_session_close`), leaving 36. `actual_session_list` stays available under read-only. See [Security and Privacy](docs/SECURITY_AND_PRIVACY.md#tool-surface-mcp_toolsets-and-mcp_read_only).
|
|
576
|
+
|
|
577
|
+
Example: a chat assistant.
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
MCP_TOOLSETS=chat
|
|
581
|
+
MCP_TOOLS=actual_accounts_list,actual_payees_get
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Example: a read-only household assistant.
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
MCP_TOOLSETS=context,analysis,query
|
|
588
|
+
MCP_READ_ONLY=true
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
A configuration that resolves to zero tools refuses to start.
|
|
592
|
+
|
|
547
593
|
---
|
|
548
594
|
|
|
549
595
|
## Multi-Budget Switching
|
|
@@ -602,7 +648,7 @@ stdio is the simplest way to connect Claude Desktop directly to Actual Budget. T
|
|
|
602
648
|
- No auth token. Process ownership is the security boundary.
|
|
603
649
|
- All logs go to stderr so they never corrupt the JSON-RPC framing on stdout
|
|
604
650
|
- The process exits when stdin closes (Claude Desktop shutting down)
|
|
605
|
-
- All
|
|
651
|
+
- All 83 tools are available, identical to HTTP mode
|
|
606
652
|
|
|
607
653
|
**Start manually to verify:**
|
|
608
654
|
|
|
@@ -705,7 +751,7 @@ See [AI Client Setup, OIDC](docs/guides/AI_CLIENT_SETUP.md#oidc-authentication-m
|
|
|
705
751
|
| Command | What It Tests | Requires Live Server |
|
|
706
752
|
|---------|---------------|---------------------|
|
|
707
753
|
| `npm run build` | TypeScript compilation | No |
|
|
708
|
-
| `npm run test:unit-js` |
|
|
754
|
+
| `npm run test:unit-js` | 83-tool smoke, schema validation, auth ACL | No |
|
|
709
755
|
| `npm run test:adapter` | Adapter, retry logic, concurrency | No |
|
|
710
756
|
| `npm run test:e2e` | MCP protocol compliance (Playwright) | No |
|
|
711
757
|
| `npm run test:e2e:docker:full` | Full stack integration | Yes (Docker) |
|
|
@@ -789,7 +835,7 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
789
835
|
|
|
790
836
|
---
|
|
791
837
|
|
|
792
|
-
**Version:** 0.22.
|
|
838
|
+
**Version:** 0.22.18 | **Tool Count:** 83 (verified LibreChat-compatible)
|
|
793
839
|
|
|
794
840
|
## Sponsor
|
|
795
841
|
|
|
@@ -165,6 +165,10 @@ export async function shutdownActualForSession(sessionId) {
|
|
|
165
165
|
logger.error(`Error shutting down Actual for session ${sessionId}:`, err);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
function poolSummary() {
|
|
169
|
+
const { sessions: _sessions, ...summary } = connectionPool.getStats();
|
|
170
|
+
return summary;
|
|
171
|
+
}
|
|
168
172
|
export function getConnectionState() {
|
|
169
173
|
// When using connection pooling, consider server initialized if pool is ready
|
|
170
174
|
// (even with 0 active connections, pool being initialized means server is ready to accept sessions)
|
|
@@ -173,7 +177,9 @@ export function getConnectionState() {
|
|
|
173
177
|
return {
|
|
174
178
|
initialized: effectivelyInitialized,
|
|
175
179
|
initializationError,
|
|
176
|
-
|
|
180
|
+
// Aggregate figures only. This feeds the unauthenticated /health route, so the
|
|
181
|
+
// per-session list (full session ids) is left out on purpose.
|
|
182
|
+
connectionPool: useConnectionPool ? poolSummary() : null,
|
|
177
183
|
idleTimeoutMinutes: useConnectionPool ? connectionPool.getIdleTimeoutMinutes() : null,
|
|
178
184
|
};
|
|
179
185
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/actualToolsManager.ts
|
|
2
2
|
import logger from './logger.js';
|
|
3
3
|
import { formatZodError } from './lib/zod-error-format.js';
|
|
4
|
+
import config from './config.js';
|
|
5
|
+
import { createModuleLogger } from './lib/loggerFactory.js';
|
|
6
|
+
import { resolvePublishedToolNames, unavailableError } from './lib/toolsets.js';
|
|
7
|
+
const toolsetLog = createModuleLogger('TOOLSETS');
|
|
4
8
|
// ✅ List of tools already implemented in this class.
|
|
5
9
|
// Adding the tool name here is considered fully implemented.
|
|
6
10
|
const IMPLEMENTED_TOOLS = [
|
|
@@ -46,6 +50,7 @@ const IMPLEMENTED_TOOLS = [
|
|
|
46
50
|
'actual_payee_rules_get',
|
|
47
51
|
'actual_query_run',
|
|
48
52
|
'actual_rules_create',
|
|
53
|
+
'actual_rules_create_batch',
|
|
49
54
|
'actual_rules_create_or_update',
|
|
50
55
|
'actual_rules_delete',
|
|
51
56
|
'actual_rules_get',
|
|
@@ -135,8 +140,20 @@ const API_TOOL_MAP = {
|
|
|
135
140
|
};
|
|
136
141
|
class ActualToolsManager {
|
|
137
142
|
tools = new Map();
|
|
143
|
+
// Resolved ONCE in initialize() (#483) and never recomputed. Null before that, and every
|
|
144
|
+
// accessor below throws rather than fall back to "all tools", so a caller that runs
|
|
145
|
+
// before initialize() fails closed instead of publishing a surface the operator hid.
|
|
146
|
+
policy = null;
|
|
138
147
|
constructor() { }
|
|
139
|
-
|
|
148
|
+
/**
|
|
149
|
+
* @param settings Overrides the validated config. Production passes nothing; the unit
|
|
150
|
+
* tests pass explicit settings so they never depend on process.env.
|
|
151
|
+
*/
|
|
152
|
+
async initialize(settings = {
|
|
153
|
+
toolsets: config.MCP_TOOLSETS,
|
|
154
|
+
tools: config.MCP_TOOLS,
|
|
155
|
+
readOnly: config.MCP_READ_ONLY,
|
|
156
|
+
}) {
|
|
140
157
|
// Dynamically import all tool modules from src/tools/index.ts
|
|
141
158
|
const toolModules = (await import('./tools/index.js'));
|
|
142
159
|
let count = 0;
|
|
@@ -148,6 +165,33 @@ class ActualToolsManager {
|
|
|
148
165
|
}
|
|
149
166
|
}
|
|
150
167
|
logger.info(`🔗 Loaded ${count} tool modules from src/tools`);
|
|
168
|
+
// Throws on an unknown name or an empty result: startup fails loudly (#483).
|
|
169
|
+
this.policy = resolvePublishedToolNames(this.getToolNames(), settings);
|
|
170
|
+
toolsetLog.info(`Publishing ${this.policy.published.length} of ${this.policy.registered} registered tools`, {
|
|
171
|
+
published: this.policy.published.length,
|
|
172
|
+
registered: this.policy.registered,
|
|
173
|
+
toolsets: this.policy.settings.toolsets,
|
|
174
|
+
extraTools: this.policy.settings.tools,
|
|
175
|
+
readOnly: this.policy.settings.readOnly,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
requirePolicy() {
|
|
179
|
+
if (!this.policy) {
|
|
180
|
+
throw new Error('Tool publication policy is not resolved: actualToolsManager.initialize() has not run');
|
|
181
|
+
}
|
|
182
|
+
return this.policy;
|
|
183
|
+
}
|
|
184
|
+
/** The tools this process publishes (tools/list) and dispatches, in registry order. */
|
|
185
|
+
getPublishedToolNames() {
|
|
186
|
+
return [...this.requirePolicy().published];
|
|
187
|
+
}
|
|
188
|
+
isPublished(name) {
|
|
189
|
+
return !this.requirePolicy().hidden.has(name) && this.tools.has(name);
|
|
190
|
+
}
|
|
191
|
+
/** The resolved MCP_TOOLSETS / MCP_TOOLS / MCP_READ_ONLY, for server_info. */
|
|
192
|
+
getToolPolicy() {
|
|
193
|
+
const { toolsets, tools, readOnly } = this.requirePolicy().settings;
|
|
194
|
+
return { toolsets: [...toolsets], tools: [...tools], readOnly };
|
|
151
195
|
}
|
|
152
196
|
getToolNames() {
|
|
153
197
|
return Array.from(this.tools.keys());
|
|
@@ -156,9 +200,17 @@ class ActualToolsManager {
|
|
|
156
200
|
return this.tools.get(name);
|
|
157
201
|
}
|
|
158
202
|
async callTool(name, args) {
|
|
203
|
+
const policy = this.requirePolicy();
|
|
204
|
+
// Registration is checked BEFORE publication: an unregistered name stays "not found".
|
|
159
205
|
const tool = this.getTool(name);
|
|
160
206
|
if (!tool)
|
|
161
207
|
throw new Error(`Tool not found: ${name}`);
|
|
208
|
+
// Hide AND refuse (#483): omitting a tool from tools/list is not access control.
|
|
209
|
+
const unavailable = unavailableError(policy, name);
|
|
210
|
+
if (unavailable) {
|
|
211
|
+
toolsetLog.warn('Refused a call to an unpublished tool', { tool: name, setting: unavailable.setting });
|
|
212
|
+
throw unavailable;
|
|
213
|
+
}
|
|
162
214
|
try {
|
|
163
215
|
const result = await tool.call(args);
|
|
164
216
|
// Force-serialize/deserialize to ensure result is JSON-safe (no circular refs,
|
package/dist/src/config.js
CHANGED
|
@@ -119,6 +119,35 @@ export const configSchema = z.object({
|
|
|
119
119
|
MCP_HTTP_BODY_LIMIT: z.string().default('512kb').refine((v) => parseStrictByteSize(v) !== null, 'MCP_HTTP_BODY_LIMIT must be a positive byte size such as 512kb, 1mb or 1048576 (units b, kb, mb, gb, tb, pb; ' +
|
|
120
120
|
'no spaces around it, no separators; at least 1 byte and at most 9007199254740991 bytes) (#466)'),
|
|
121
121
|
MAX_CONCURRENT_SESSIONS: z.string().default('15').transform(val => parseInt(val, 10)),
|
|
122
|
+
// --- Published tool surface (#483) ---
|
|
123
|
+
// Which tools this process publishes AND dispatches. Names are validated, and an unknown
|
|
124
|
+
// one aborts startup, by resolvePublishedToolNames (src/lib/toolsets.ts) in
|
|
125
|
+
// actualToolsManager.initialize(), because the registry is only known there.
|
|
126
|
+
// MCP_TOOLSETS comma list of toolsets and presets ('all' by default = every tool).
|
|
127
|
+
// MCP_TOOLS extra individual tool names published on top of MCP_TOOLSETS.
|
|
128
|
+
MCP_TOOLSETS: z.string().default('all'),
|
|
129
|
+
MCP_TOOLS: z.string().default(''),
|
|
130
|
+
// MCP_READ_ONLY drops every write-capable tool, and operators read it as a safety
|
|
131
|
+
// promise, so it is parsed STRICTLY and in this one place: trimmed and case-insensitive
|
|
132
|
+
// 'true' or 'false'; unset or empty is false; anything else ('1', 'yes', 'on', a typo)
|
|
133
|
+
// refuses startup naming the value. The usual `=== 'true'` idiom is wrong here: it
|
|
134
|
+
// fails SAFE for a flag that enables risk, but would fail OPEN for this one, silently
|
|
135
|
+
// publishing every writer when an operator wrote MCP_READ_ONLY=1.
|
|
136
|
+
MCP_READ_ONLY: z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.transform((raw, ctx) => {
|
|
140
|
+
const v = (raw ?? '').trim().toLowerCase();
|
|
141
|
+
if (v === '' || v === 'false')
|
|
142
|
+
return false;
|
|
143
|
+
if (v === 'true')
|
|
144
|
+
return true;
|
|
145
|
+
ctx.addIssue({
|
|
146
|
+
code: 'custom',
|
|
147
|
+
message: `MCP_READ_ONLY must be "true" or "false" (case-insensitive; unset or empty means false), got ${JSON.stringify(raw)}.`,
|
|
148
|
+
});
|
|
149
|
+
return z.NEVER;
|
|
150
|
+
}),
|
|
122
151
|
// --- OIDC / mcp-auth (CF-5) ---
|
|
123
152
|
// Set AUTH_PROVIDER=oidc to enable JWT validation via mcp-auth.
|
|
124
153
|
// When 'none' (default), the legacy MCP_SSE_AUTHORIZATION static Bearer token is used.
|
package/dist/src/index.js
CHANGED
|
@@ -238,8 +238,9 @@ Docs & source: https://github.com/agigante80/actual-mcp-server`);
|
|
|
238
238
|
}
|
|
239
239
|
// Initialize tools before usage
|
|
240
240
|
await actualToolsManager.initialize();
|
|
241
|
-
//
|
|
242
|
-
|
|
241
|
+
// The PUBLISHED tools (#483). This is the one name list every tools/list path receives
|
|
242
|
+
// (HTTP SDK handler, the no-session LobeChat path, the expired-session shim, stdio).
|
|
243
|
+
const implementedTools = actualToolsManager.getPublishedToolNames();
|
|
243
244
|
// Extract schemas map with tool name → JSON schema
|
|
244
245
|
const toolSchemas = {};
|
|
245
246
|
for (const toolName of implementedTools) {
|
|
@@ -45,7 +45,7 @@ export class ActualMCPConnection extends EventEmitter {
|
|
|
45
45
|
// If actualToolsManager is not ready, return demo tools
|
|
46
46
|
let tools;
|
|
47
47
|
try {
|
|
48
|
-
const toolNames = actualToolsManager.
|
|
48
|
+
const toolNames = actualToolsManager.getPublishedToolNames();
|
|
49
49
|
tools = toolNames.map((name) => {
|
|
50
50
|
const tool = actualToolsManager.getTool(name);
|
|
51
51
|
if (!tool) {
|