actual-mcp-server 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +663 -0
  3. package/bin/actual-mcp-server.js +3 -0
  4. package/dist/generated/actual-client/types.js +5 -0
  5. package/dist/package.json +88 -0
  6. package/dist/src/actualConnection.js +157 -0
  7. package/dist/src/actualToolsManager.js +211 -0
  8. package/dist/src/auth/budget-acl.js +143 -0
  9. package/dist/src/auth/setup.js +58 -0
  10. package/dist/src/config.js +41 -0
  11. package/dist/src/index.js +313 -0
  12. package/dist/src/lib/ActualConnectionPool.js +343 -0
  13. package/dist/src/lib/ActualMCPConnection.js +125 -0
  14. package/dist/src/lib/actual-adapter.js +1228 -0
  15. package/dist/src/lib/actual-schema.js +222 -0
  16. package/dist/src/lib/budget-registry.js +64 -0
  17. package/dist/src/lib/constants.js +121 -0
  18. package/dist/src/lib/errors.js +19 -0
  19. package/dist/src/lib/loggerFactory.js +72 -0
  20. package/dist/src/lib/node-polyfills.js +20 -0
  21. package/dist/src/lib/query-validator.js +221 -0
  22. package/dist/src/lib/retry.js +26 -0
  23. package/dist/src/lib/schemas/common.js +203 -0
  24. package/dist/src/lib/toolFactory.js +109 -0
  25. package/dist/src/logger.js +127 -0
  26. package/dist/src/observability.js +58 -0
  27. package/dist/src/prompts/showLargeTransactions.js +6 -0
  28. package/dist/src/resources/accountsSummary.js +13 -0
  29. package/dist/src/server/httpServer.js +540 -0
  30. package/dist/src/server/httpServer_testing.js +401 -0
  31. package/dist/src/server/stdioServer.js +52 -0
  32. package/dist/src/server/streamable-http.js +148 -0
  33. package/dist/src/tests/actualToolsTests.js +70 -0
  34. package/dist/src/tests/observability.smoke.test.js +18 -0
  35. package/dist/src/tests/testMcpClient.js +170 -0
  36. package/dist/src/tests_adapter_runner.js +86 -0
  37. package/dist/src/tools/accounts_close.js +16 -0
  38. package/dist/src/tools/accounts_create.js +27 -0
  39. package/dist/src/tools/accounts_delete.js +16 -0
  40. package/dist/src/tools/accounts_get_balance.js +40 -0
  41. package/dist/src/tools/accounts_list.js +16 -0
  42. package/dist/src/tools/accounts_reopen.js +16 -0
  43. package/dist/src/tools/accounts_update.js +52 -0
  44. package/dist/src/tools/bank_sync.js +22 -0
  45. package/dist/src/tools/budget_updates_batch.js +77 -0
  46. package/dist/src/tools/budgets_getMonth.js +14 -0
  47. package/dist/src/tools/budgets_getMonths.js +14 -0
  48. package/dist/src/tools/budgets_get_all.js +13 -0
  49. package/dist/src/tools/budgets_holdForNextMonth.js +19 -0
  50. package/dist/src/tools/budgets_list_available.js +20 -0
  51. package/dist/src/tools/budgets_resetHold.js +16 -0
  52. package/dist/src/tools/budgets_setAmount.js +26 -0
  53. package/dist/src/tools/budgets_setCarryover.js +18 -0
  54. package/dist/src/tools/budgets_switch.js +27 -0
  55. package/dist/src/tools/budgets_transfer.js +64 -0
  56. package/dist/src/tools/categories_create.js +65 -0
  57. package/dist/src/tools/categories_delete.js +16 -0
  58. package/dist/src/tools/categories_get.js +14 -0
  59. package/dist/src/tools/categories_update.js +22 -0
  60. package/dist/src/tools/category_groups_create.js +18 -0
  61. package/dist/src/tools/category_groups_delete.js +26 -0
  62. package/dist/src/tools/category_groups_get.js +13 -0
  63. package/dist/src/tools/category_groups_update.js +21 -0
  64. package/dist/src/tools/get_id_by_name.js +36 -0
  65. package/dist/src/tools/index.js +63 -0
  66. package/dist/src/tools/payee_rules_get.js +27 -0
  67. package/dist/src/tools/payees_create.js +25 -0
  68. package/dist/src/tools/payees_delete.js +16 -0
  69. package/dist/src/tools/payees_get.js +14 -0
  70. package/dist/src/tools/payees_merge.js +17 -0
  71. package/dist/src/tools/payees_update.js +59 -0
  72. package/dist/src/tools/query_run.js +78 -0
  73. package/dist/src/tools/rules_create.js +129 -0
  74. package/dist/src/tools/rules_create_or_update.js +191 -0
  75. package/dist/src/tools/rules_delete.js +26 -0
  76. package/dist/src/tools/rules_get.js +13 -0
  77. package/dist/src/tools/rules_update.js +120 -0
  78. package/dist/src/tools/schedules_create.js +54 -0
  79. package/dist/src/tools/schedules_delete.js +41 -0
  80. package/dist/src/tools/schedules_get.js +13 -0
  81. package/dist/src/tools/schedules_update.js +40 -0
  82. package/dist/src/tools/server_get_version.js +22 -0
  83. package/dist/src/tools/server_info.js +86 -0
  84. package/dist/src/tools/session_close.js +100 -0
  85. package/dist/src/tools/session_list.js +24 -0
  86. package/dist/src/tools/transactions_create.js +50 -0
  87. package/dist/src/tools/transactions_delete.js +20 -0
  88. package/dist/src/tools/transactions_filter.js +73 -0
  89. package/dist/src/tools/transactions_get.js +23 -0
  90. package/dist/src/tools/transactions_import.js +21 -0
  91. package/dist/src/tools/transactions_search_by_amount.js +126 -0
  92. package/dist/src/tools/transactions_search_by_category.js +137 -0
  93. package/dist/src/tools/transactions_search_by_month.js +142 -0
  94. package/dist/src/tools/transactions_search_by_payee.js +142 -0
  95. package/dist/src/tools/transactions_summary_by_category.js +80 -0
  96. package/dist/src/tools/transactions_summary_by_payee.js +72 -0
  97. package/dist/src/tools/transactions_uncategorized.js +66 -0
  98. package/dist/src/tools/transactions_update.js +34 -0
  99. package/dist/src/tools/transactions_update_batch.js +60 -0
  100. package/dist/src/utils.js +63 -0
  101. package/package.json +88 -0
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ export const configSchema = z.object({
3
+ ACTUAL_SERVER_URL: z.string().url(),
4
+ ACTUAL_PASSWORD: z.string().default(''),
5
+ ACTUAL_BUDGET_SYNC_ID: z.string().min(1),
6
+ // Optional per-budget encryption password (leave unset for unencrypted budgets)
7
+ ACTUAL_BUDGET_PASSWORD: z.string().optional(),
8
+ MCP_BRIDGE_DATA_DIR: z.string().default('./actual-data'),
9
+ MCP_BRIDGE_PORT: z.string().default('3000'),
10
+ MCP_TRANSPORT_MODE: z.enum(['--http']).default('--http'),
11
+ MCP_SSE_AUTHORIZATION: z.string().optional(),
12
+ MCP_ENABLE_HTTPS: z.string().optional().transform(val => val === 'true'),
13
+ MCP_HTTPS_CERT: z.string().optional(),
14
+ MCP_HTTPS_KEY: z.string().optional(),
15
+ MAX_CONCURRENT_SESSIONS: z.string().default('15').transform(val => parseInt(val, 10)),
16
+ // --- OIDC / mcp-auth (CF-5) ---
17
+ // Set AUTH_PROVIDER=oidc to enable JWT validation via mcp-auth.
18
+ // When 'none' (default), the legacy MCP_SSE_AUTHORIZATION static Bearer token is used.
19
+ AUTH_PROVIDER: z.enum(['none', 'oidc']).default('none'),
20
+ // OIDC issuer URL (e.g. https://auth.example.com/realms/myrealm). Required when AUTH_PROVIDER=oidc.
21
+ OIDC_ISSUER: z.string().optional(),
22
+ // This server's resource identifier URL (e.g. https://actual-mcp.example.com). Required when AUTH_PROVIDER=oidc.
23
+ OIDC_RESOURCE: z.string().optional(),
24
+ // Comma-separated required scopes (e.g. "read,write"). Optional.
25
+ OIDC_SCOPES: z.string().optional(),
26
+ // JSON map of principal → budget sync-ID list for per-user budget ACL.
27
+ // Keys: email, sub, or "group:<name>". Values: array of sync IDs or ["*"] for all.
28
+ // Example: {"alice@example.com":["budget-1"],"group:admin":["*"]}
29
+ // Leave unset to allow all authenticated users to access all budgets.
30
+ AUTH_BUDGET_ACL: z.string().optional(),
31
+ });
32
+ function getConfig() {
33
+ const result = configSchema.safeParse(process.env);
34
+ if (!result.success) {
35
+ console.error('Invalid or missing environment variables:', result.error.issues);
36
+ process.exit(1);
37
+ }
38
+ return result.data;
39
+ }
40
+ const config = getConfig();
41
+ export default config;
@@ -0,0 +1,313 @@
1
+ import { z } from 'zod';
2
+ // Add global error handlers
3
+ let isHandlingQueryError = false;
4
+ process.on('unhandledRejection', (reason, promise) => {
5
+ console.error('=== UNHANDLED REJECTION ===');
6
+ console.error('Promise:', promise);
7
+ console.error('Reason:', reason);
8
+ if (reason instanceof Error) {
9
+ console.error('Stack:', reason.stack);
10
+ }
11
+ console.error('===========================');
12
+ // Check if this is a known domain-level error from @actual-app/api
13
+ // These indicate invalid user input, not server bugs — the error is properly
14
+ // returned to the caller; if it somehow escapes as an unhandled rejection
15
+ // we should log but NOT crash the server.
16
+ const reasonStr = String(reason);
17
+ const reasonObj = reason;
18
+ if (reasonStr.includes('does not exist in table') ||
19
+ (reasonStr.includes('Field') && reasonStr.includes('does not exist')) ||
20
+ reasonStr.includes('Expression stack') ||
21
+ reasonStr.includes('Date is required') ||
22
+ reasonStr.includes('date condition is required') ||
23
+ reasonStr.includes('Cannot create schedules with the same name') ||
24
+ reasonStr.includes('Schedule') && reasonStr.includes('not found') ||
25
+ reasonStr.includes('is system-managed and not user-editable') ||
26
+ reasonStr.includes('is not an expense category') ||
27
+ // Bank sync errors from GoCardless/SimpleFIN/Nordigen surface as unhandled
28
+ // rejections from within the @actual-app/api SDK worker. These are non-fatal:
29
+ // the caller already received a proper error response (or the retry will).
30
+ reasonObj?.type === 'BankSyncError' ||
31
+ reasonStr.includes('BankSyncError') ||
32
+ reasonStr.includes('NORDIGEN_ERROR') ||
33
+ reasonStr.includes('RATE_LIMIT_EXCEEDED') ||
34
+ reasonStr.includes('Rate limit exceeded') ||
35
+ reasonStr.includes('Failed syncing account') ||
36
+ reasonStr.includes('GoCardless') ||
37
+ reasonStr.includes('SimpleFIN')) {
38
+ console.error('⚠️ Known Actual API domain error escaped to unhandledRejection:');
39
+ console.error('⚠️ ' + reasonStr);
40
+ console.error('⚠️ Server will continue running. The caller received an error response.');
41
+ return;
42
+ }
43
+ // For all other unhandled rejections, exit
44
+ process.exit(1);
45
+ });
46
+ process.on('uncaughtException', (error) => {
47
+ const errMsg = String(error?.message || error);
48
+ const errType = error?.type;
49
+ // Bank sync errors from GoCardless/SimpleFIN can surface as uncaughtException
50
+ // from within the @actual-app/api SDK when the provider API returns an error
51
+ // asynchronously (e.g. RATE_LIMIT_EXCEEDED from GoCardless/Nordigen).
52
+ // These are non-fatal — the server should survive and continue serving requests.
53
+ if (errType === 'BankSyncError' ||
54
+ errMsg.includes('BankSyncError') ||
55
+ errMsg.includes('NORDIGEN_ERROR') ||
56
+ errMsg.includes('RATE_LIMIT_EXCEEDED') ||
57
+ errMsg.includes('Rate limit exceeded') ||
58
+ errMsg.includes('Failed syncing account') ||
59
+ errMsg.includes('GoCardless') ||
60
+ errMsg.includes('SimpleFIN')) {
61
+ console.error('⚠️ [BANK SYNC] Non-fatal bank sync error surfaced as uncaughtException (server continues):');
62
+ console.error('⚠️ ' + errMsg);
63
+ return;
64
+ }
65
+ console.error('Uncaught Exception:', error);
66
+ process.exit(1);
67
+ });
68
+ // Minimal early help handling before any side-effectful modules (prevents dotenv from running on --help)
69
+ const argsEarly = process.argv.slice(2);
70
+ // Set MCP_STDIO_MODE before the async IIFE so it is in place when src/logger.ts is first
71
+ // imported. The Winston Console transport reads this env var at construction time to decide
72
+ // whether to route all output to stderr (required in stdio mode — stdout writes corrupt JSON-RPC).
73
+ if (argsEarly.includes('--stdio')) {
74
+ process.env.MCP_STDIO_MODE = 'true';
75
+ }
76
+ // Only load dotenv if we're not just showing help
77
+ // dotenv will be loaded inside the async IIFE below via dynamic import
78
+ // to avoid using require() in ESM and to keep the early --help fast exit.
79
+ const usageEarly = `
80
+ Usage: npm run dev -- [--http | --stdio | --test-actual-connection | --test-actual-tools] [--debug] [--help]
81
+
82
+ Options:
83
+ --http Start HTTP MCP server
84
+ --stdio Start stdio MCP server (for Claude Desktop / local clients)
85
+ --test-actual-connection Test connecting to Actual and exit
86
+ --test-actual-tools Test connecting and run all tools, then exit
87
+ --debug Enable debug logging
88
+ --help Show this help message
89
+ `;
90
+ if (argsEarly.includes('--help')) {
91
+ console.log(usageEarly);
92
+ process.exit(0);
93
+ }
94
+ (async () => {
95
+ // Load dotenv here (dynamic import) only when not running with --help
96
+ if (!argsEarly.includes('--help')) {
97
+ const dotenv = await import('dotenv');
98
+ dotenv.config();
99
+ }
100
+ // Enable verbose debug output when --debug is passed
101
+ if (argsEarly.includes('--debug')) {
102
+ // enable "debug" package logs (many libs use this)
103
+ process.env.DEBUG = process.env.DEBUG || '*';
104
+ // enable structured logger at debug level
105
+ process.env.LOG_LEVEL = process.env.LOG_LEVEL || 'debug';
106
+ // optional flag your code can check for even more verbose transport logging
107
+ process.env.MCP_BRIDGE_DEBUG_TRANSPORT = 'true';
108
+ console.log('Debug mode enabled: DEBUG=* LOG_LEVEL=debug');
109
+ }
110
+ // dynamic imports to avoid running side effects on module import
111
+ const [{ connectToActual }, { testAllTools }, { ActualMCPConnection }] = await Promise.all([
112
+ import('./actualConnection.js'),
113
+ import('./tests/actualToolsTests.js'),
114
+ import('./lib/ActualMCPConnection.js'),
115
+ ]);
116
+ const [{ startHttpServer }, { startStdioServer }, loggerModule, osModule, utilsModule, actualToolsManagerModule,] = await Promise.all([
117
+ import('./server/httpServer.js'),
118
+ import('./server/stdioServer.js'),
119
+ import('./logger.js'),
120
+ import('os'),
121
+ import('./utils.js'),
122
+ import('./actualToolsManager.js'),
123
+ ]);
124
+ const logger = loggerModule.default;
125
+ const os = osModule;
126
+ const { getLocalIp } = utilsModule;
127
+ const actualToolsManager = actualToolsManagerModule.default;
128
+ // Load version from environment (Docker build-time) or package.json (local dev)
129
+ let VERSION = process.env.VERSION;
130
+ if (!VERSION || VERSION === 'unknown') {
131
+ const packageJson = await import('../package.json', { with: { type: 'json' } });
132
+ VERSION = packageJson.default.version;
133
+ // Append git commit hash for development builds
134
+ try {
135
+ const { execSync } = await import('child_process');
136
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
137
+ const commitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim();
138
+ if (branch === 'develop' || branch !== 'main') {
139
+ VERSION = `${VERSION}-dev-${commitHash}`;
140
+ }
141
+ }
142
+ catch (err) {
143
+ // Git not available or not in a git repo, use version as-is
144
+ logger.debug('Could not determine git commit hash:', err);
145
+ }
146
+ }
147
+ // Ensure VERSION is always a string (fallback to 0.1.0 if somehow still undefined)
148
+ const version = VERSION || '0.1.0';
149
+ // now continue with the original logic (args, flags, usage, etc.)
150
+ const PORT = process.env.MCP_BRIDGE_PORT ? Number(process.env.MCP_BRIDGE_PORT) : 3600;
151
+ const HTTP_PATH = process.env.MCP_HTTP_PATH || '/http';
152
+ const args = process.argv.slice(2);
153
+ const useHttp = args.includes('--http');
154
+ const useStdio = args.includes('--stdio');
155
+ const useTestActualConnection = args.includes('--test-actual-connection');
156
+ const useTestActualTools = args.includes('--test-actual-tools');
157
+ const useTestMcpClient = args.includes('--test-mcp-client');
158
+ const SERVER_DESCRIPTION = 'Bridge MCP server exposing Actual finance API to LibreChat.';
159
+ const SERVER_INSTRUCTIONS = 'Welcome to the Actual MCP server. The tools listed here are only the ones currently confirmed and tested, ' +
160
+ 'but the server can proxy any API call supported by Actual. As we expand coverage, more tools will be officially exposed.';
161
+ const usage = usageEarly;
162
+ async function main() {
163
+ // Mutual exclusion — stdio and http are incompatible transports
164
+ if (useHttp && useStdio) {
165
+ logger.error('❌ --http and --stdio are mutually exclusive. Pick one.');
166
+ process.exit(1);
167
+ }
168
+ logger.info(`🚀 Starting Actual MCP Server v${VERSION}`);
169
+ // NOTE: Persistent connection disabled - using init/shutdown per operation pattern
170
+ // This ensures tombstone=0 for all created entities (they appear in UI)
171
+ // await connectToActual();
172
+ if (useTestActualConnection) {
173
+ // For test connection mode only, we still need to connect
174
+ await connectToActual();
175
+ logger.info('⚙️ --test-actual-connection specified, connection to Actual Finance successful.');
176
+ process.exit(0);
177
+ }
178
+ if (useTestActualTools) {
179
+ logger.info('⚙️ --test-actual-tools specified, connecting and testing all tools...');
180
+ try {
181
+ await testAllTools();
182
+ logger.info('✅ All tool tests completed.');
183
+ }
184
+ catch (err) {
185
+ if (err instanceof Error) {
186
+ logger.error('❌ Tool tests failed: %s', err.message);
187
+ }
188
+ else {
189
+ logger.error('❌ Tool tests failed: %o', err);
190
+ }
191
+ process.exit(1);
192
+ }
193
+ process.exit(0);
194
+ }
195
+ // Initialize tools before usage
196
+ await actualToolsManager.initialize();
197
+ // Now get implemented tools after initialization
198
+ const implementedTools = actualToolsManager.getToolNames();
199
+ // Extract schemas map with tool name → JSON schema
200
+ const toolSchemas = {};
201
+ for (const toolName of implementedTools) {
202
+ const tool = actualToolsManager.getTool(toolName);
203
+ if (tool?.inputSchema) {
204
+ toolSchemas[toolName] = z.toJSONSchema(tool.inputSchema);
205
+ }
206
+ }
207
+ // Capabilities object with your fixed capabilities values
208
+ const rawCapabilities = {
209
+ tools: true,
210
+ logging: false,
211
+ events: false,
212
+ prompts: false,
213
+ };
214
+ // Convert to MCP format (object for each capability)
215
+ const capabilities = {};
216
+ for (const [key, enabled] of Object.entries(rawCapabilities)) {
217
+ if (enabled) {
218
+ capabilities[key] = {};
219
+ }
220
+ }
221
+ // Create one ActualMCPConnection instance here
222
+ const mcp = new ActualMCPConnection();
223
+ // determine advertised host (what clients should use to reach this server)
224
+ // - MCP_BRIDGE_PUBLIC_HOST: force a public host/IP (e.g. 192.168.33.11)
225
+ // - getLocalIp(): falls back to machine LAN IP
226
+ // - final fallback: localhost
227
+ const advertisedHost = process.env.MCP_BRIDGE_PUBLIC_HOST || (getLocalIp && getLocalIp()) || 'localhost';
228
+ // determine scheme/protocol:
229
+ // - MCP_BRIDGE_PUBLIC_SCHEME can override (e.g. "https")
230
+ // - otherwise use http/https based on TLS setting
231
+ const schemeOverride = process.env.MCP_BRIDGE_PUBLIC_SCHEME;
232
+ let scheme = schemeOverride;
233
+ if (!scheme) {
234
+ scheme = (process.env.MCP_BRIDGE_USE_TLS === 'true' || process.env.MCP_ENABLE_HTTPS === 'true') ? 'https' : 'http';
235
+ }
236
+ // choose advertised path based on transport type
237
+ const advertisedPath = process.env.MCP_BRIDGE_HTTP_PATH || HTTP_PATH;
238
+ const advertisedUrl = `${scheme}://${advertisedHost}:${PORT}${advertisedPath}`;
239
+ // Fail fast if native TLS is enabled but cert/key paths are missing or unreadable
240
+ if (process.env.MCP_ENABLE_HTTPS === 'true') {
241
+ const certPath = process.env.MCP_HTTPS_CERT;
242
+ const keyPath = process.env.MCP_HTTPS_KEY;
243
+ if (!certPath || !keyPath) {
244
+ logger.error('MCP_ENABLE_HTTPS=true requires both MCP_HTTPS_CERT and MCP_HTTPS_KEY to be set');
245
+ process.exit(1);
246
+ }
247
+ const { existsSync } = await import('node:fs');
248
+ for (const [label, p] of [['MCP_HTTPS_CERT', certPath], ['MCP_HTTPS_KEY', keyPath]]) {
249
+ if (!existsSync(p)) {
250
+ logger.error(`${label} path not found: ${p}`);
251
+ process.exit(1);
252
+ }
253
+ }
254
+ }
255
+ // If requested, run the MCP client-side tests now that all variables are ready
256
+ if (useTestMcpClient) {
257
+ logger.info('⚙️ --test-mcp-client specified, starting HTTP server and running client-side MCP tests...');
258
+ // start server in http mode (bind to configured PORT)
259
+ await startHttpServer(mcp, PORT, HTTP_PATH, capabilities, implementedTools, SERVER_DESCRIPTION, SERVER_INSTRUCTIONS, toolSchemas, version, process.env.MCP_BRIDGE_BIND_HOST || 'localhost', advertisedUrl);
260
+ // dynamic import to avoid circular at top
261
+ const { testMcpClient } = await import('./tests/testMcpClient.js');
262
+ try {
263
+ await testMcpClient(advertisedUrl, PORT, HTTP_PATH);
264
+ logger.info('✅ MCP client-side tests passed');
265
+ process.exit(0);
266
+ }
267
+ catch (err) {
268
+ // Log the full error object and stack so we get useful debug info
269
+ if (err instanceof Error) {
270
+ logger.error('❌ MCP client-side tests failed: %s', err.message);
271
+ if (err.stack)
272
+ logger.error(err.stack);
273
+ }
274
+ else {
275
+ logger.error('❌ MCP client-side tests failed: %o', err);
276
+ }
277
+ process.exit(1);
278
+ }
279
+ }
280
+ // Startup banner — skip in stdio mode (stdout writes corrupt JSON-RPC framing)
281
+ if (!useStdio) {
282
+ logger.info('---------');
283
+ logger.info('🟡 MCP SERVER INFO');
284
+ logger.info(`• Server Description: ${SERVER_DESCRIPTION}`);
285
+ logger.info(`• OAuth Required: false`);
286
+ logger.info(`• Capabilities: ${Object.keys(capabilities).join(', ')}`);
287
+ logger.info(`• Tools: ${implementedTools.join(', ') || 'none'}`);
288
+ logger.info(`• Server Instructions: ${SERVER_INSTRUCTIONS}`);
289
+ logger.info(`• MCP endpoint (advertised): ${advertisedUrl}`);
290
+ logger.info('---------');
291
+ }
292
+ if (useHttp) {
293
+ logger.info('Mode: HTTP');
294
+ await startHttpServer(mcp, PORT, HTTP_PATH, capabilities, implementedTools, SERVER_DESCRIPTION, SERVER_INSTRUCTIONS, toolSchemas, version,
295
+ // bind host (ensure server accepts connections on that interface)
296
+ process.env.MCP_BRIDGE_BIND_HOST || '0.0.0.0',
297
+ // advertised URL shown to clients
298
+ advertisedUrl);
299
+ }
300
+ else if (useStdio) {
301
+ logger.debug('Mode: stdio');
302
+ await startStdioServer(mcp, capabilities, implementedTools, SERVER_DESCRIPTION, SERVER_INSTRUCTIONS, toolSchemas, version);
303
+ }
304
+ else {
305
+ logger.error('❌ Please specify a transport mode: --http or --stdio');
306
+ process.exit(1);
307
+ }
308
+ }
309
+ main().catch((err) => {
310
+ logger.error('Failed to start Actual MCP bridge:', err.message || String(err));
311
+ process.exit(1);
312
+ });
313
+ })();