actual-mcp-server 0.5.3 → 0.5.5
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 +3 -1
- package/dist/package.json +7 -1
- package/dist/src/config.js +5 -1
- package/dist/src/index.js +30 -32
- package/package.json +7 -1
- package/dist/src/tests/actualToolsTests.js +0 -70
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Actual MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/actual-mcp-server)
|
|
4
|
+
[](https://www.npmjs.com/package/actual-mcp-server)
|
|
3
5
|
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
[](https://nodejs.org/)
|
|
5
7
|
[](https://www.typescriptlang.org/)
|
|
@@ -668,4 +670,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
668
670
|
|
|
669
671
|
---
|
|
670
672
|
|
|
671
|
-
**Version:** 0.5.
|
|
673
|
+
**Version:** 0.5.5 | **Tool Count:** 62 (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.5.
|
|
4
|
+
"version": "0.5.5",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -58,13 +58,18 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@actual-app/api": "^26.4.0",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
|
+
"debug": "^4.4.3",
|
|
61
62
|
"dotenv": "^17.4.1",
|
|
62
63
|
"express": "^5.2.1",
|
|
64
|
+
"jose": "^6.1.3",
|
|
63
65
|
"mcp-auth": "^0.2.0",
|
|
64
66
|
"winston": "^3.18.3",
|
|
65
67
|
"winston-daily-rotate-file": "^5.0.0",
|
|
66
68
|
"zod": "^4.0.0"
|
|
67
69
|
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"prom-client": "*"
|
|
72
|
+
},
|
|
68
73
|
"overrides": {
|
|
69
74
|
"ajv": "8.18.0",
|
|
70
75
|
"qs": "6.14.2"
|
|
@@ -76,6 +81,7 @@
|
|
|
76
81
|
"@playwright/test": "^1.59.1",
|
|
77
82
|
"@types/express": "^5.0.3",
|
|
78
83
|
"@types/node": "^25.6.0",
|
|
84
|
+
"node-fetch": "^3.3.2",
|
|
79
85
|
"tsconfig-paths": "^4.2.0",
|
|
80
86
|
"typescript": "^6.0.2"
|
|
81
87
|
},
|
package/dist/src/config.js
CHANGED
|
@@ -32,7 +32,11 @@ export const configSchema = z.object({
|
|
|
32
32
|
function getConfig() {
|
|
33
33
|
const result = configSchema.safeParse(process.env);
|
|
34
34
|
if (!result.success) {
|
|
35
|
-
|
|
35
|
+
const missing = result.error.issues.map(i => ` • ${i.path.join('.')}`).join('\n');
|
|
36
|
+
console.error(`\n❌ Missing or invalid environment variables:\n${missing}\n\n` +
|
|
37
|
+
`Set them in a .env file in the current directory, or export them before running.\n` +
|
|
38
|
+
`Required: ACTUAL_SERVER_URL, ACTUAL_PASSWORD, ACTUAL_BUDGET_SYNC_ID\n` +
|
|
39
|
+
`See: https://github.com/agigante80/actual-mcp-server\n`);
|
|
36
40
|
process.exit(1);
|
|
37
41
|
}
|
|
38
42
|
return result.data;
|
package/dist/src/index.js
CHANGED
|
@@ -67,32 +67,50 @@ process.on('uncaughtException', (error) => {
|
|
|
67
67
|
});
|
|
68
68
|
// Minimal early help handling before any side-effectful modules (prevents dotenv from running on --help)
|
|
69
69
|
const argsEarly = process.argv.slice(2);
|
|
70
|
+
// dotenv v17 outputs diagnostic text to stdout by default (console.log).
|
|
71
|
+
// Suppress it unconditionally — in stdio mode stdout is the JSON-RPC channel
|
|
72
|
+
// (non-JSON corrupts the framing), and in other modes the diagnostic is noise.
|
|
73
|
+
process.env.DOTENV_CONFIG_QUIET = 'true';
|
|
70
74
|
// Set MCP_STDIO_MODE before the async IIFE so it is in place when src/logger.ts is first
|
|
71
75
|
// imported. The Winston Console transport reads this env var at construction time to decide
|
|
72
76
|
// whether to route all output to stderr (required in stdio mode — stdout writes corrupt JSON-RPC).
|
|
73
77
|
if (argsEarly.includes('--stdio')) {
|
|
74
78
|
process.env.MCP_STDIO_MODE = 'true';
|
|
75
|
-
// dotenv v17 outputs diagnostic text to stdout by default (console.log).
|
|
76
|
-
// In stdio mode stdout is the JSON-RPC channel — any non-JSON output corrupts
|
|
77
|
-
// the framing and causes MCP clients to close the connection immediately.
|
|
78
|
-
process.env.DOTENV_CONFIG_QUIET = 'true';
|
|
79
79
|
}
|
|
80
80
|
// Only load dotenv if we're not just showing help
|
|
81
81
|
// dotenv will be loaded inside the async IIFE below via dynamic import
|
|
82
82
|
// to avoid using require() in ESM and to keep the early --help fast exit.
|
|
83
|
-
const
|
|
84
|
-
|
|
83
|
+
const KNOWN_FLAGS = new Set([
|
|
84
|
+
'--http', '--stdio', '--test-actual-connection', '--test-mcp-client',
|
|
85
|
+
'--debug', '--help', '-h', '--version', '-v',
|
|
86
|
+
]);
|
|
87
|
+
const unknownFlags = argsEarly.filter(a => a.startsWith('-') && !KNOWN_FLAGS.has(a));
|
|
88
|
+
if (unknownFlags.length > 0) {
|
|
89
|
+
console.error(`Unknown option: ${unknownFlags.join(', ')}\nRun \`actual-mcp-server --help\` for usage.`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
if (argsEarly.includes('--help') || argsEarly.includes('-h') ||
|
|
93
|
+
argsEarly.includes('--version') || argsEarly.includes('-v')) {
|
|
94
|
+
const pkg = await import('../package.json', { with: { type: 'json' } });
|
|
95
|
+
const version = pkg.default.version;
|
|
96
|
+
if (argsEarly.includes('--version') || argsEarly.includes('-v')) {
|
|
97
|
+
console.log(version);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(`actual-mcp-server v${version}
|
|
101
|
+
|
|
102
|
+
Usage: actual-mcp-server [--http | --stdio | --test-actual-connection] [--debug] [--help]
|
|
85
103
|
|
|
86
104
|
Options:
|
|
87
105
|
--http Start HTTP MCP server
|
|
88
106
|
--stdio Start stdio MCP server (for Claude Desktop / local clients)
|
|
89
107
|
--test-actual-connection Test connecting to Actual and exit
|
|
90
|
-
--test-actual-tools Test connecting and run all tools, then exit
|
|
91
108
|
--debug Enable debug logging
|
|
92
|
-
--
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
--version, -v Print version and exit
|
|
110
|
+
--help, -h Show this help message
|
|
111
|
+
|
|
112
|
+
Docs & source: https://github.com/agigante80/actual-mcp-server`);
|
|
113
|
+
}
|
|
96
114
|
process.exit(0);
|
|
97
115
|
}
|
|
98
116
|
(async () => {
|
|
@@ -112,9 +130,8 @@ if (argsEarly.includes('--help')) {
|
|
|
112
130
|
console.log('Debug mode enabled: DEBUG=* LOG_LEVEL=debug');
|
|
113
131
|
}
|
|
114
132
|
// dynamic imports to avoid running side effects on module import
|
|
115
|
-
const [{ connectToActual }, {
|
|
133
|
+
const [{ connectToActual }, { ActualMCPConnection }] = await Promise.all([
|
|
116
134
|
import('./actualConnection.js'),
|
|
117
|
-
import('./tests/actualToolsTests.js'),
|
|
118
135
|
import('./lib/ActualMCPConnection.js'),
|
|
119
136
|
]);
|
|
120
137
|
const [{ startHttpServer }, { startStdioServer }, loggerModule, osModule, utilsModule, actualToolsManagerModule,] = await Promise.all([
|
|
@@ -157,12 +174,10 @@ if (argsEarly.includes('--help')) {
|
|
|
157
174
|
const useHttp = args.includes('--http');
|
|
158
175
|
const useStdio = args.includes('--stdio');
|
|
159
176
|
const useTestActualConnection = args.includes('--test-actual-connection');
|
|
160
|
-
const useTestActualTools = args.includes('--test-actual-tools');
|
|
161
177
|
const useTestMcpClient = args.includes('--test-mcp-client');
|
|
162
178
|
const SERVER_DESCRIPTION = 'Bridge MCP server exposing Actual finance API to LibreChat.';
|
|
163
179
|
const SERVER_INSTRUCTIONS = 'Welcome to the Actual MCP server. The tools listed here are only the ones currently confirmed and tested, ' +
|
|
164
180
|
'but the server can proxy any API call supported by Actual. As we expand coverage, more tools will be officially exposed.';
|
|
165
|
-
const usage = usageEarly;
|
|
166
181
|
async function main() {
|
|
167
182
|
// Mutual exclusion — stdio and http are incompatible transports
|
|
168
183
|
if (useHttp && useStdio) {
|
|
@@ -179,23 +194,6 @@ if (argsEarly.includes('--help')) {
|
|
|
179
194
|
logger.info('⚙️ --test-actual-connection specified, connection to Actual Finance successful.');
|
|
180
195
|
process.exit(0);
|
|
181
196
|
}
|
|
182
|
-
if (useTestActualTools) {
|
|
183
|
-
logger.info('⚙️ --test-actual-tools specified, connecting and testing all tools...');
|
|
184
|
-
try {
|
|
185
|
-
await testAllTools();
|
|
186
|
-
logger.info('✅ All tool tests completed.');
|
|
187
|
-
}
|
|
188
|
-
catch (err) {
|
|
189
|
-
if (err instanceof Error) {
|
|
190
|
-
logger.error('❌ Tool tests failed: %s', err.message);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
logger.error('❌ Tool tests failed: %o', err);
|
|
194
|
-
}
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
197
|
-
process.exit(0);
|
|
198
|
-
}
|
|
199
197
|
// Initialize tools before usage
|
|
200
198
|
await actualToolsManager.initialize();
|
|
201
199
|
// Now get implemented tools after initialization
|
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.5.
|
|
4
|
+
"version": "0.5.5",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -58,13 +58,18 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@actual-app/api": "^26.4.0",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
|
+
"debug": "^4.4.3",
|
|
61
62
|
"dotenv": "^17.4.1",
|
|
62
63
|
"express": "^5.2.1",
|
|
64
|
+
"jose": "^6.1.3",
|
|
63
65
|
"mcp-auth": "^0.2.0",
|
|
64
66
|
"winston": "^3.18.3",
|
|
65
67
|
"winston-daily-rotate-file": "^5.0.0",
|
|
66
68
|
"zod": "^4.0.0"
|
|
67
69
|
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"prom-client": "*"
|
|
72
|
+
},
|
|
68
73
|
"overrides": {
|
|
69
74
|
"ajv": "8.18.0",
|
|
70
75
|
"qs": "6.14.2"
|
|
@@ -76,6 +81,7 @@
|
|
|
76
81
|
"@playwright/test": "^1.59.1",
|
|
77
82
|
"@types/express": "^5.0.3",
|
|
78
83
|
"@types/node": "^25.6.0",
|
|
84
|
+
"node-fetch": "^3.3.2",
|
|
79
85
|
"tsconfig-paths": "^4.2.0",
|
|
80
86
|
"typescript": "^6.0.2"
|
|
81
87
|
},
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// src/tests/actualToolsTests.ts
|
|
2
|
-
import logger from '../logger.js';
|
|
3
|
-
import actualToolsManager from '../actualToolsManager.js';
|
|
4
|
-
// Test data mapping for each tool
|
|
5
|
-
const getTestArgs = (toolName) => {
|
|
6
|
-
switch (toolName) {
|
|
7
|
-
case 'actual.accounts.create':
|
|
8
|
-
return { name: 'Test Account', balance: 1000 };
|
|
9
|
-
case 'actual.accounts.update':
|
|
10
|
-
return { id: 'test-account-id', fields: { name: 'Updated Test Account' } };
|
|
11
|
-
case 'actual.accounts.get.balance':
|
|
12
|
-
return { id: 'test-account-id' };
|
|
13
|
-
case 'actual.transactions.create':
|
|
14
|
-
return { accountId: 'test-account-id', amount: 100, payee: 'Test Payee', date: '2025-11-08' };
|
|
15
|
-
case 'actual.transactions.get':
|
|
16
|
-
return { accountId: 'test-account-id', startDate: '2025-11-01', endDate: '2025-11-08' };
|
|
17
|
-
case 'actual.transactions.import':
|
|
18
|
-
return { accountId: 'test-account-id', txs: [{ amount: 50, payee: 'Import Test', date: '2025-11-08' }] };
|
|
19
|
-
case 'actual.categories.create':
|
|
20
|
-
// Try different field names that might be expected
|
|
21
|
-
return { name: 'Test Category', group_id: 'fc3825fd-b982-4b72-b768-5b30844cf832', groupId: 'fc3825fd-b982-4b72-b768-5b30844cf832' };
|
|
22
|
-
case 'actual.payees.create':
|
|
23
|
-
return { name: 'Test Payee' };
|
|
24
|
-
case 'actual.budgets.setAmount':
|
|
25
|
-
// Use an existing category ID (Food category from the budget data)
|
|
26
|
-
return { month: '2025-11', categoryId: '541836f1-e756-4473-a5d0-6c1d3f06c7fa', amount: 500 };
|
|
27
|
-
case 'actual.budgets.getMonth':
|
|
28
|
-
return { month: '2025-11' };
|
|
29
|
-
// These tools don't require parameters
|
|
30
|
-
case 'actual.accounts.list':
|
|
31
|
-
case 'actual.categories.get':
|
|
32
|
-
case 'actual.payees.get':
|
|
33
|
-
case 'actual.budgets.getMonths':
|
|
34
|
-
default:
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
export async function testAllTools() {
|
|
39
|
-
await actualToolsManager.initialize();
|
|
40
|
-
const toolNames = actualToolsManager.getToolNames();
|
|
41
|
-
const results = [];
|
|
42
|
-
for (const name of toolNames) {
|
|
43
|
-
try {
|
|
44
|
-
logger.info(`⚙️ Testing tool: ${name}`);
|
|
45
|
-
const testArgs = getTestArgs(name);
|
|
46
|
-
const result = await actualToolsManager.callTool(name, testArgs);
|
|
47
|
-
logger.info(`✅ Tool ${name} output: ${JSON.stringify(result, null, 2)}`);
|
|
48
|
-
results.push({ name, success: true });
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
const message = err && typeof err?.message === 'string' ? err.message : String(err);
|
|
52
|
-
logger.error(`❌ Tool ${name} test failed: ${message}`);
|
|
53
|
-
results.push({ name, success: false, error: message });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// Summary
|
|
57
|
-
const successful = results.filter(r => r.success).length;
|
|
58
|
-
const failed = results.filter(r => !r.success).length;
|
|
59
|
-
logger.info(`\n📊 Test Summary: ${successful} passed, ${failed} failed`);
|
|
60
|
-
if (failed > 0) {
|
|
61
|
-
logger.error(`❌ Failed tools:`);
|
|
62
|
-
results.filter(r => !r.success).forEach(r => {
|
|
63
|
-
logger.error(` - ${r.name}: ${r.error}`);
|
|
64
|
-
});
|
|
65
|
-
throw new Error(`${failed} tool tests failed`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
logger.info(`🎉 All ${successful} tools passed!`);
|
|
69
|
-
}
|
|
70
|
-
}
|