actual-mcp
Version:
Actual Budget MCP server exposing API functionality
117 lines • 3.9 kB
JavaScript
/**
* MCP Server for Actual Budget
*
* This server exposes your Actual Budget data to LLMs through the Model Context Protocol,
* allowing for natural language interaction with your financial data.
*
* Features:
* - List and view accounts
* - View transactions with filtering
* - Generate financial statistics and analysis
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from 'express';
import { parseArgs } from 'node:util';
import path from 'path';
import { setupPrompts } from './prompts.js';
import { setupResources } from './resources.js';
import { setupTools } from './tools/index.js';
// Configuration
const DEFAULT_DATA_DIR = path.resolve(process.env.HOME || process.env.USERPROFILE || '.', '.actual');
// Initialize the MCP server
const server = new Server({
name: 'Actual Budget',
version: '1.0.0',
}, {
capabilities: {
resources: {},
tools: {},
prompts: {},
logging: {},
},
});
// Argument parsing
const { values: { sse: useSse, port }, } = parseArgs({
options: {
sse: { type: 'boolean', default: false },
port: { type: 'string' },
},
allowPositionals: true,
});
const resolvedPort = port ? parseInt(port, 10) : 3000;
// ----------------------------
// SERVER STARTUP
// ----------------------------
// Start the server
async function main() {
// Validate environment variables
if (!process.env.ACTUAL_DATA_DIR && !process.env.ACTUAL_SERVER_URL) {
console.error('Warning: Neither ACTUAL_DATA_DIR nor ACTUAL_SERVER_URL is set.');
console.error(`Will try to use default data directory: ${DEFAULT_DATA_DIR}`);
}
if (process.env.ACTUAL_SERVER_URL && !process.env.ACTUAL_PASSWORD) {
console.error('Warning: ACTUAL_SERVER_URL is set but ACTUAL_PASSWORD is not.');
console.error('If your server requires authentication, initialization will fail.');
}
if (useSse) {
const app = express();
app.use(express.json());
let transport = null;
// Placeholder for future HTTP transport (stateless)
app.post('/mcp', async (req, res) => {
res.status(501).json({ error: 'HTTP transport not implemented yet' });
});
app.get('/sse', (req, res) => {
transport = new SSEServerTransport('/messages', res);
server.connect(transport);
});
app.post('/messages', async (req, res) => {
if (transport) {
await transport.handlePostMessage(req, res, req.body);
}
else {
res.status(500).json({ error: 'Transport not initialized' });
}
});
app.listen(resolvedPort, (error) => {
if (error) {
console.error('Error:', error);
}
else {
console.error(`Actual Budget MCP Server (SSE) started on port ${resolvedPort}`);
}
});
}
else {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Actual Budget MCP Server (stdio) started');
}
}
setupResources(server);
setupTools(server);
setupPrompts(server);
process.on('SIGINT', () => {
console.error('SIGINT received, shutting down server');
server.close();
process.exit(0);
});
main()
.then(() => {
console.log = (message) => server.sendLoggingMessage({
level: 'info',
message,
});
console.error = (message) => server.sendLoggingMessage({
level: 'error',
message,
});
})
.catch((error) => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map