UNPKG

@baruchiro/actual-mcp

Version:

Actual Budget MCP server exposing API functionality

105 lines (104 loc) 3.6 kB
#!/usr/bin/env node /** * 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: {}, }, }); // 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.log(`Actual Budget MCP Server (SSE) started on port ${resolvedPort}`); } }); } else { const transport = new StdioServerTransport(); await server.connect(transport); console.log("Actual Budget MCP Server (stdio) started"); } } setupResources(server); setupTools(server); setupPrompts(server); process.on("SIGINT", () => { console.log("SIGINT received, shutting down server"); server.close(); process.exit(0); }); main().catch((error) => { console.error("Server error:", error); process.exit(1); });