actual-mcp-server 0.6.50 → 0.6.54

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 CHANGED
@@ -84,12 +84,12 @@ docker run -d \
84
84
  -e ACTUAL_PASSWORD=your_password \
85
85
  -e ACTUAL_BUDGET_SYNC_ID=your_sync_id \
86
86
  -e MCP_SSE_AUTHORIZATION=your_secret_token \
87
- -v actual-mcp-data:/data \ # required, see note below
87
+ -v actual-mcp-data:/app/data \ # required, see note below
88
88
  -v actual-mcp-logs:/app/logs \
89
89
  ghcr.io/agigante80/actual-mcp-server:latest
90
90
  ```
91
91
 
92
- > **Why the `/data` volume is required:** Actual Budget does not expose a REST API. The official `@actual-app/api` library (used internally by this server) works by downloading a local copy of your budget data, running all queries on that local copy, then syncing changes back. The `/data` volume gives the container a persistent, writable place to store that local copy. Without it the container has nowhere to write and will fail on startup. See the [Actual API docs](https://actualbudget.org/docs/api/) for details.
92
+ > **Why the `/app/data` volume is required:** Actual Budget does not expose a REST API. The official `@actual-app/api` library (used internally by this server) works by downloading a local copy of your budget data, running all queries on that local copy, then syncing changes back. The `/app/data` volume gives the container a persistent, writable place to store that local copy (it is the directory the image creates and owns as the runtime user). Without it the container has nowhere to write and will fail on startup. See the [Actual API docs](https://actualbudget.org/docs/api/) for details.
93
93
  >
94
94
  > **actual-mcp does not need to run on the same machine as Actual Budget.** You can have Actual Budget on one server and actual-mcp on another - as long as `ACTUAL_SERVER_URL` points to your Actual Budget instance, everything works.
95
95
 
@@ -121,13 +121,13 @@ git clone https://github.com/agigante80/actual-mcp-server.git
121
121
  cd actual-mcp-server
122
122
  cp .env.example .env # fill in ACTUAL_SERVER_URL, ACTUAL_PASSWORD, ACTUAL_BUDGET_SYNC_ID
123
123
 
124
- docker compose --profile production up -d # Nginx proxy on :3600
124
+ docker compose --profile production up -d # production: MCP server listens on :3600
125
125
  # or
126
126
  docker compose --profile dev up -d # dev mode with hot-reload
127
- # or
128
- docker compose --profile fullstack up -d # includes Actual Budget server on :5006
129
127
  ```
130
128
 
129
+ > The compose file defines only the `dev` and `production` profiles. The MCP server listens on `:3600` directly: there is no bundled reverse proxy and no bundled Actual Budget server, so point `ACTUAL_SERVER_URL` at your own Actual instance. For TLS, enable native HTTPS with `MCP_ENABLE_HTTPS=true` (plus `MCP_HTTPS_CERT` and `MCP_HTTPS_KEY`), or front the server with your own reverse proxy.
130
+
131
131
  ### Option C: npm (HTTP server)
132
132
 
133
133
  ```bash
@@ -147,7 +147,7 @@ npm run build
147
147
  npm run dev -- --http
148
148
  ```
149
149
 
150
- Server starts at `http://localhost:3000/http` (dev) or `http://localhost:3600/http` (Docker).
150
+ Server starts at `http://localhost:3600/http` by default (the listen port is `MCP_BRIDGE_PORT`, default `3600`).
151
151
 
152
152
  ### Option D: stdio (Claude Desktop native, no Docker or HTTP server needed)
153
153
 
@@ -424,25 +424,27 @@ All configuration is via environment variables. Copy `.env.example` to `.env` to
424
424
  | Variable | Default | Required | Description |
425
425
  |----------|---------|----------|-------------|
426
426
  | **Actual Budget Connection** ||||
427
- | `ACTUAL_SERVER_URL` | `http://localhost:5006` | Yes | URL of your Actual Budget server. Use the same URL you type in your browser: `http://localhost:5006` (local), `http://192.168.1.x:5006` (network), `https://actual.yourdomain.com` (domain), or `http://actual:5006` (container name if on the same Docker network) |
427
+ | `ACTUAL_SERVER_URL` | _(none)_ | Yes | URL of your Actual Budget server. Use the same URL you type in your browser: `http://localhost:5006` (local), `http://192.168.1.x:5006` (network), `https://actual.yourdomain.com` (domain), or `http://actual:5006` (container name if on the same Docker network) |
428
428
  | `ACTUAL_PASSWORD` | _(none)_ | Yes | Password for Actual Budget server |
429
429
  | `ACTUAL_BUDGET_SYNC_ID` | _(none)_ | Yes | Budget Sync ID from Actual (Settings then Sync ID) |
430
430
  | `ACTUAL_BUDGET_PASSWORD` | _(none)_ | No | Optional encryption password for encrypted budgets |
431
+ | `ALLOW_INSECURE_UPSTREAM` | `false` | No | Allow an `http://` upstream even when `ACTUAL_BUDGET_PASSWORD` is set (#161). Off by default so a plaintext upstream plus an encryption password is refused |
431
432
  | **MCP Server Settings** ||||
432
- | `MCP_BRIDGE_PORT` | `3000` (dev) / `3600` (Docker) | No | Port for MCP server to listen on |
433
+ | `MCP_BRIDGE_PORT` | `3600` | No | Port for MCP server to listen on |
433
434
  | `MCP_BRIDGE_BIND_HOST` | `0.0.0.0` | No | Host address to bind server to (`0.0.0.0` = all interfaces) |
434
435
  | `MCP_BRIDGE_DATA_DIR` | `./actual-data` | No | Directory to store Actual Budget local data (SQLite). **Required to be a persistent path.** The `@actual-app/api` library downloads a local copy of your budget here to run queries; use a volume mount in Docker to persist it across restarts |
435
436
  | `MCP_BRIDGE_PUBLIC_HOST` | auto-detected | No | Public hostname/IP for server (shown in logs) |
436
437
  | `MCP_BRIDGE_PUBLIC_SCHEME` | auto-detected | No | Public scheme (`http` or `https`) |
437
438
  | `MCP_BRIDGE_USE_TLS` | `false` | No | Set to `true` to advertise `https://` in the server URL (for reverse-proxy setups where TLS is terminated upstream) |
438
439
  | **Transport Configuration** ||||
439
- | `MCP_TRANSPORT_MODE` | `--http` | No | Transport mode: `--http` or `--stdio` |
440
+ | `MCP_TRANSPORT_MODE` | `--http` | No | Transport mode. Only `--http` is a valid value; stdio is selected via the `--stdio` CLI flag, not this var |
440
441
  | `MCP_HTTP_PATH` | `/http` | No | HTTP endpoint routing path |
441
442
  | `MCP_BRIDGE_HTTP_PATH` | same as `MCP_HTTP_PATH` | No | Advertised HTTP path shown to clients (set when a reverse proxy rewrites the path) |
443
+ | `MCP_HTTP_BODY_LIMIT` | `512kb` | No | Maximum accepted JSON-RPC request body size (e.g. `512kb`, `1mb`) |
442
444
  | **Session Management** ||||
443
445
  | `USE_CONNECTION_POOL` | `true` | No | Enable session-based connection pooling |
444
446
  | `MAX_CONCURRENT_SESSIONS` | `15` | No | Maximum concurrent MCP sessions allowed |
445
- | `SESSION_IDLE_TIMEOUT_MINUTES` | `5` (pool) / `2` (HTTP) | No | Minutes before idle session cleanup |
447
+ | `SESSION_IDLE_TIMEOUT_MINUTES` | `5` | No | Minutes before idle session cleanup |
446
448
  | **Security & Authentication** ||||
447
449
  | `AUTH_PROVIDER` | `none` | No | Auth mode: `none` (static Bearer) or `oidc` (JWKS-validated JWT) |
448
450
  | `MCP_SSE_AUTHORIZATION` | _(none)_ | No | Static Bearer token (`AUTH_PROVIDER=none`; highly recommended in production) |
@@ -455,19 +457,21 @@ All configuration is via environment variables. Copy `.env.example` to `.env` to
455
457
  | `MCP_HTTPS_KEY` | _(none)_ | No | Path to PEM private key file (required when `MCP_ENABLE_HTTPS=true`) |
456
458
  | **Logging Configuration** ||||
457
459
  | `MCP_BRIDGE_STORE_LOGS` | `false` | No | Enable file logging (vs console only) |
458
- | `MCP_BRIDGE_LOG_DIR` | `./logs` | No | Directory for log files (if `STORE_LOGS=true`) |
459
- | `MCP_BRIDGE_LOG_LEVEL` | `debug` | No | Log level: `error`, `warn`, `info`, `debug` |
460
+ | `MCP_BRIDGE_LOG_DIR` | `app/logs` (beside the install) | No | Directory for log files (if `STORE_LOGS=true`). `.env.example` and Docker set it explicitly (e.g. `./logs`, `/app/logs`) |
461
+ | `MCP_BRIDGE_LOG_LEVEL` | `debug` (dev) / `info` (prod) | No | Log level: `error`, `warn`, `info`, `debug` |
462
+ | `LOG_FORMAT` | auto | No | Log output format: `json` or `pretty`. Precedence: explicit `LOG_FORMAT` wins, else `NODE_ENV=production` selects `json`, else `pretty` |
463
+ | `MCP_SERVICE_NAME` | `actual-mcp-server` | No | Service name stamped on every structured (json) log record |
460
464
  | **Log Rotation** (when `MCP_BRIDGE_STORE_LOGS=true`) ||||
461
465
  | `MCP_BRIDGE_MAX_FILES` | `14d` | No | Keep rotated logs for N days (e.g., `14d`, `30d`) |
462
466
  | `MCP_BRIDGE_MAX_LOG_SIZE` | `20m` | No | Rotate when file reaches size (e.g., `20m`, `100m`) |
463
467
  | `MCP_BRIDGE_ROTATE_DATEPATTERN` | `YYYY-MM-DD` | No | Date pattern for rotated log filenames |
464
468
  | **Development & Debugging** ||||
465
- | `DEBUG` | `false` | No | Enable debug mode (verbose logging) |
466
- | `LOG_LEVEL` | `info` | No | Log level override: `error`, `warn`, `info`, `debug` |
469
+ | `DEBUG` | _(none)_ | No | Enable debug mode (verbose logging) when set to any truthy value |
470
+ | `LOG_LEVEL` | _(none)_ | No | Debug-detection toggle: set to `debug` to enable extra transport debug output. Distinct from `MCP_BRIDGE_LOG_LEVEL` (the winston level); has no default and is not itself a log level |
467
471
  | `MCP_BRIDGE_DEBUG_TRANSPORT` | `false` | No | Enable transport-level debug logging |
468
472
  | **Advanced/Internal** ||||
469
473
  | `ACTUAL_API_CONCURRENCY` | `5` | No | Max concurrent Actual API operations |
470
- | `NODE_ENV` | `production` | No | Node environment; `production` hides stack traces in error responses |
474
+ | `NODE_ENV` | _(none)_ / `production` | No | Node environment. No app default; the Docker image sets `production`, which selects json logs and hides stack traces in error responses |
471
475
  | `VERSION` | auto-detected | No | Server version (auto-set by build/Docker) |
472
476
  | `TZ` | `UTC` | No | Timezone for timestamps (e.g., `America/New_York`) |
473
477
 
@@ -751,4 +755,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
751
755
 
752
756
  ---
753
757
 
754
- **Version:** 0.6.50 | **Tool Count:** 71 (verified LibreChat-compatible)
758
+ **Version:** 0.6.54 | **Tool Count:** 71 (verified LibreChat-compatible)
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "actual-mcp-server",
3
3
  "displayName": "Actual MCP Server",
4
- "version": "0.6.50",
4
+ "version": "0.6.54",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -30,7 +30,7 @@
30
30
  "verify-tools": "npm run build && node scripts/verify-tools.js",
31
31
  "check:coverage": "node scripts/list-actual-api-methods.mjs",
32
32
  "direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
33
- "test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
33
+ "test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
34
34
  "test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
35
35
  "test:e2e": "npx playwright test",
36
36
  "test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
@@ -55,7 +55,8 @@
55
55
  "release:major": "npm run version:bump -- major",
56
56
  "release:minor": "npm run version:bump -- minor",
57
57
  "release:patch": "npm run version:bump -- patch",
58
- "tool-count": "node scripts/tool-count.mjs"
58
+ "tool-count": "node scripts/tool-count.mjs",
59
+ "config-drift": "npm run build && node scripts/config-drift.mjs"
59
60
  },
60
61
  "dependencies": {
61
62
  "@actual-app/api": "^26.6.0",
@@ -10,7 +10,7 @@ export const configSchema = z.object({
10
10
  // Off by default so a plaintext upstream + encryption password is refused.
11
11
  ALLOW_INSECURE_UPSTREAM: z.string().optional().transform(val => val === 'true'),
12
12
  MCP_BRIDGE_DATA_DIR: z.string().default('./actual-data'),
13
- MCP_BRIDGE_PORT: z.string().default('3000'),
13
+ MCP_BRIDGE_PORT: z.string().default('3600'),
14
14
  MCP_TRANSPORT_MODE: z.enum(['--http']).default('--http'),
15
15
  MCP_SSE_AUTHORIZATION: z.string().optional(),
16
16
  MCP_ENABLE_HTTPS: z.string().optional().transform(val => val === 'true'),
@@ -0,0 +1,102 @@
1
+ // src/lib/config-registry.ts (#231)
2
+ //
3
+ // The single in-code record of every configuration variable that is NOT a Zod
4
+ // schema key in src/config.ts but is still read from process.env somewhere in
5
+ // src/. Together with `Object.keys(configSchema.shape)`, this allowlist is the
6
+ // canonical set of configuration variables the server understands.
7
+ //
8
+ // Why an allowlist instead of promoting everything into the schema: most of these
9
+ // are read before src/config.ts loads (src/logger.ts, see logger.ts:53) or in the
10
+ // deferred-config entry src/index.ts (which reads process.env directly to keep the
11
+ // fast `--help` exit, the decision recorded for #230). Promoting them risks a
12
+ // load-order regression, so they stay raw but DOCUMENTED here, with a reason and
13
+ // read site. Migrating any raw read into the validated config object is deliberately
14
+ // out of scope for #231 (a separate, behaviour-sensitive change).
15
+ //
16
+ // This file is data only. It imports nothing from src/config.ts so it carries no
17
+ // process.env requirement and can be imported by the drift guard and its unit test.
18
+ /**
19
+ * Variables read from process.env in src/ that are NOT configSchema keys.
20
+ * Grouped by reason in the comments; the array is the machine-readable source.
21
+ */
22
+ export const RAW_ENV_ALLOWLIST = [
23
+ // Logger bootstrap: read in src/logger.ts before src/config.ts loads.
24
+ { name: 'MCP_BRIDGE_STORE_LOGS', reason: 'logger bootstrap (before config loads)', readSite: 'logger.ts:14' },
25
+ { name: 'MCP_BRIDGE_LOG_DIR', reason: 'logger bootstrap (before config loads)', readSite: 'logger.ts:15' },
26
+ { name: 'MCP_BRIDGE_ROTATE_DATEPATTERN', reason: 'logger bootstrap (before config loads)', readSite: 'logger.ts:21' },
27
+ { name: 'MCP_BRIDGE_MAX_LOG_SIZE', reason: 'logger bootstrap (before config loads)', readSite: 'logger.ts:22' },
28
+ { name: 'MCP_BRIDGE_MAX_FILES', reason: 'logger bootstrap (before config loads)', readSite: 'logger.ts:23' },
29
+ { name: 'LOG_FORMAT', reason: 'logger bootstrap, read via resolveLogConfig(env)', readSite: 'logger.ts:74' },
30
+ { name: 'MCP_BRIDGE_LOG_LEVEL', reason: 'logger bootstrap, read via resolveLogConfig(env)', readSite: 'logger.ts:77' },
31
+ { name: 'MCP_SERVICE_NAME', reason: 'logger bootstrap, read via resolveLogConfig(env)', readSite: 'logger.ts:78' },
32
+ { name: 'MCP_STDIO_MODE', reason: 'mirrors the --stdio CLI flag, set in-process before the logger import; not a .env knob', readSite: 'logger.ts:345, index.ts', documented: false },
33
+ // Deferred-config entry: read in src/index.ts to preserve the fast --help exit (#230).
34
+ { name: 'MCP_BRIDGE_BIND_HOST', reason: 'deferred-config entry (index.ts reads process.env directly, #230)', readSite: 'index.ts' },
35
+ { name: 'MCP_HTTP_PATH', reason: 'deferred-config entry; the path the server LISTENS on', readSite: 'index.ts:195' },
36
+ { name: 'MCP_BRIDGE_HTTP_PATH', reason: 'deferred-config entry; the path ADVERTISED to clients, falls back to MCP_HTTP_PATH', readSite: 'index.ts:281' },
37
+ { name: 'MCP_BRIDGE_PUBLIC_HOST', reason: 'deferred-config entry; advertised public host', readSite: 'index.ts:269, httpServer.ts:581' },
38
+ { name: 'MCP_BRIDGE_PUBLIC_SCHEME', reason: 'deferred-config entry; advertised scheme override', readSite: 'index.ts' },
39
+ { name: 'MCP_BRIDGE_USE_TLS', reason: 'deprecated alias of MCP_ENABLE_HTTPS; affects only advertised-scheme detection', readSite: 'index.ts:277' },
40
+ { name: 'MCP_BRIDGE_DEBUG_TRANSPORT', reason: 'transport debug flag, read pre-config', readSite: 'index.ts, utils.ts' },
41
+ // Framework / internal: not user-facing config knobs.
42
+ { name: 'NODE_ENV', reason: 'framework var; selects prod log format and prod behaviours', readSite: 'httpServer.ts, logger.ts (resolveLogConfig)' },
43
+ { name: 'DEBUG', reason: 'framework debug toggle', readSite: 'utils.ts, index.ts, httpServer_testing.ts' },
44
+ { name: 'LOG_LEVEL', reason: 'debug-detection toggle (distinct from MCP_BRIDGE_LOG_LEVEL, the winston level)', readSite: 'utils.ts' },
45
+ { name: 'DOTENV_CONFIG_QUIET', reason: 'dotenv internal flag, not an operator knob', readSite: 'index.ts', documented: false },
46
+ { name: 'VERSION', reason: 'build-time version arg, injected by the Docker build, not an operator knob', readSite: 'index.ts, server_info.ts', documented: false },
47
+ // Domain-time: read after config loads; future candidates for schema promotion.
48
+ { name: 'ACTUAL_API_CONCURRENCY', reason: 'adapter concurrency tuning; future schema-promotion candidate', readSite: 'lib/actual-adapter/concurrency.ts' },
49
+ { name: 'SESSION_IDLE_TIMEOUT_MINUTES', reason: 'pool idle timeout; future schema-promotion candidate', readSite: 'lib/ActualConnectionPool.ts' },
50
+ { name: 'USE_CONNECTION_POOL', reason: 'pool toggle; future schema-promotion candidate', readSite: 'actualConnection.ts' },
51
+ ];
52
+ /**
53
+ * Schema keys that are ALSO read directly from process.env at a second site. These
54
+ * are validated by configSchema (so they are canonical via schema membership), but
55
+ * the matrix records their raw read site for completeness. Migrating these reads to
56
+ * config.X is the out-of-scope follow-up.
57
+ */
58
+ export const SCHEMA_VARS_ALSO_READ_RAW = [
59
+ { name: 'MCP_BRIDGE_PORT', readSite: 'index.ts:194' },
60
+ { name: 'MCP_ENABLE_HTTPS', readSite: 'index.ts:277,286' },
61
+ { name: 'MCP_HTTPS_CERT', readSite: 'index.ts:287' },
62
+ { name: 'MCP_HTTPS_KEY', readSite: 'index.ts:288' },
63
+ { name: 'MAX_CONCURRENT_SESSIONS', readSite: 'ActualConnectionPool.ts:60' },
64
+ { name: 'ACTUAL_BUDGET_PASSWORD', readSite: 'actualConnection.ts:33, ActualConnectionPool.ts:255,338' },
65
+ ];
66
+ /**
67
+ * Prefixes of dynamic variable families enumerated at runtime, not declared one by
68
+ * one. `.env.example` documents example members (BUDGET_1_*, BUDGET_2_*, ...); the
69
+ * drift guard collapses any name starting with one of these to the family.
70
+ */
71
+ export const DYNAMIC_ENV_FAMILIES = ['BUDGET_'];
72
+ /**
73
+ * Variables documented for the operator (e.g. in Docker) but read by the OS or
74
+ * runtime, not by the app. The drift guard allows these in docs without requiring a
75
+ * schema/allowlist entry.
76
+ */
77
+ export const OS_LEVEL_ENV = ['TZ'];
78
+ /**
79
+ * The canonical set of configuration variable names the server understands:
80
+ * every Zod schema key united with every raw-read allowlist name. Pass the schema
81
+ * keys in (e.g. `Object.keys(configSchema.shape)`) so this module stays free of any
82
+ * import from src/config.ts and its process.env requirement.
83
+ */
84
+ export function canonicalConfigVars(schemaKeys) {
85
+ return new Set([...schemaKeys, ...RAW_ENV_ALLOWLIST.map((v) => v.name)]);
86
+ }
87
+ /**
88
+ * The subset of canonical vars that MUST appear in the user-facing docs
89
+ * (.env.example and the README env table): every schema key, plus every allowlist
90
+ * entry not flagged `documented: false`. Internal vars (build arg, dotenv flag,
91
+ * CLI-flag mirror) are accounted for by the enumeration but exempt from the docs.
92
+ */
93
+ export function documentedConfigVars(schemaKeys) {
94
+ return new Set([
95
+ ...schemaKeys,
96
+ ...RAW_ENV_ALLOWLIST.filter((v) => v.documented !== false).map((v) => v.name),
97
+ ]);
98
+ }
99
+ /** True if `name` belongs to a dynamic family (e.g. BUDGET_2_NAME). */
100
+ export function isDynamicFamilyVar(name) {
101
+ return DYNAMIC_ENV_FAMILIES.some((prefix) => name.startsWith(prefix));
102
+ }
@@ -62,9 +62,11 @@ export const WRITE_SESSION_DELAY_MS = 100;
62
62
  */
63
63
  export const DEFAULT_BIND_HOST = 'localhost';
64
64
  /**
65
- * Default HTTP port for MCP server
65
+ * Default HTTP port for MCP server. Canonical default is 3600 (#230): it matches
66
+ * the listen-port fallback in src/index.ts, the Dockerfile EXPOSE/HEALTHCHECK, the
67
+ * published image, .env.example, and every deployment guide.
66
68
  */
67
- export const DEFAULT_HTTP_PORT = 3000;
69
+ export const DEFAULT_HTTP_PORT = 3600;
68
70
  /**
69
71
  * Default HTTP path for MCP server endpoint
70
72
  */
@@ -17,8 +17,8 @@ import logger from '../logger.js';
17
17
  * import { createModuleLogger } from '../lib/loggerFactory.js';
18
18
  *
19
19
  * const log = createModuleLogger('HTTP');
20
- * log.info('Server started', { port: 3000 });
21
- * // Output: [HTTP] Server started { port: 3000 }
20
+ * log.info('Server started', { port: 3600 });
21
+ * // Output: [HTTP] Server started { port: 3600 }
22
22
  *
23
23
  * log.error('Connection failed', new Error('Timeout'), { retries: 3 });
24
24
  * // Output: [HTTP] Connection failed { error: 'Timeout', stack: '...', retries: 3 }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "actual-mcp-server",
3
3
  "displayName": "Actual MCP Server",
4
- "version": "0.6.50",
4
+ "version": "0.6.54",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -30,7 +30,7 @@
30
30
  "verify-tools": "npm run build && node scripts/verify-tools.js",
31
31
  "check:coverage": "node scripts/list-actual-api-methods.mjs",
32
32
  "direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
33
- "test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
33
+ "test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
34
34
  "test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
35
35
  "test:e2e": "npx playwright test",
36
36
  "test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
@@ -55,7 +55,8 @@
55
55
  "release:major": "npm run version:bump -- major",
56
56
  "release:minor": "npm run version:bump -- minor",
57
57
  "release:patch": "npm run version:bump -- patch",
58
- "tool-count": "node scripts/tool-count.mjs"
58
+ "tool-count": "node scripts/tool-count.mjs",
59
+ "config-drift": "npm run build && node scripts/config-drift.mjs"
59
60
  },
60
61
  "dependencies": {
61
62
  "@actual-app/api": "^26.6.0",