UNPKG

@baruchiro/paperless-mcp

Version:

Model Context Protocol (MCP) server for interacting with Paperless-NGX document management system. Enables AI assistants to manage documents, tags, correspondents, and document types through the Paperless-NGX API.

190 lines (189 loc) 8.87 kB
#!/usr/bin/env node "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js"); const express_1 = __importDefault(require("express")); const node_util_1 = require("node:util"); const server_1 = require("./server"); const { version } = require("../package.json"); const { values: { baseUrl, token, http: useHttp, port, publicUrl, "no-auth": noAuth }, } = (0, node_util_1.parseArgs)({ options: { baseUrl: { type: "string" }, token: { type: "string" }, http: { type: "boolean", default: false }, port: { type: "string" }, publicUrl: { type: "string", default: "" }, "no-auth": { type: "boolean", default: false }, }, allowPositionals: true, }); const resolvedBaseUrl = baseUrl || process.env.PAPERLESS_URL; const resolvedToken = token || process.env.PAPERLESS_API_KEY; const resolvedPublicUrl = publicUrl || process.env.PAPERLESS_PUBLIC_URL || resolvedBaseUrl; const resolvedPort = port ? parseInt(port, 10) : 3000; if (!resolvedBaseUrl) { console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>] [--no-auth]"); console.error("Or set PAPERLESS_URL and PAPERLESS_API_KEY environment variables."); process.exit(1); } if (!useHttp && !resolvedToken) { console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>] [--no-auth]"); console.error("Or set PAPERLESS_URL and PAPERLESS_API_KEY environment variables."); process.exit(1); } if (noAuth && !resolvedToken) { console.error("--no-auth allows unauthenticated requests to use the server's Paperless token, " + "but no server token is configured. Provide --token <token> or set PAPERLESS_API_KEY, " + "or drop --no-auth and have clients authenticate with 'Authorization: Bearer <token>'."); process.exit(1); } function buildServer(requestToken) { return (0, server_1.createMcpServer)({ baseUrl: resolvedBaseUrl, token: requestToken, version, publicUrl: resolvedPublicUrl, }); } function main() { return __awaiter(this, void 0, void 0, function* () { if (useHttp) { if (noAuth) { console.log("[paperless-mcp] --no-auth is enabled: requests without an 'Authorization: Bearer' header " + "will use the server's Paperless token. Only use this on a trusted/local network."); } else if (resolvedToken) { console.log("[paperless-mcp] A server token is configured, but unauthenticated requests are rejected. " + "Clients must send 'Authorization: Bearer <paperless-token>'. " + "To use the server token for unauthenticated requests instead, restart with the --no-auth flag " + "(trusted/local networks only)."); } const app = (0, express_1.default)(); app.use(express_1.default.json()); // Store transports for each session const sseTransports = {}; app.post("/mcp", (req, res) => __awaiter(this, void 0, void 0, function* () { const requestToken = (0, server_1.getBearerToken)(req, { fallbackToken: resolvedToken, allowAnonymous: noAuth, }); if (!requestToken) { (0, server_1.sendUnauthorized)(res); return; } try { const server = buildServer(requestToken); const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); res.on("close", () => { transport.close(); }); yield server.connect(transport); yield transport.handleRequest(req, res, req.body); } catch (error) { console.error("Error handling MCP request:", error); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error", }, id: null, }); } } })); app.get("/mcp", (req, res) => __awaiter(this, void 0, void 0, function* () { res.writeHead(405).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed.", }, id: null, })); })); app.delete("/mcp", (req, res) => __awaiter(this, void 0, void 0, function* () { res.writeHead(405).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed.", }, id: null, })); })); app.get("/sse", (req, res) => __awaiter(this, void 0, void 0, function* () { console.log("SSE request received"); const requestToken = (0, server_1.getBearerToken)(req, { fallbackToken: resolvedToken, allowAnonymous: noAuth, }); if (!requestToken) { (0, server_1.sendUnauthorized)(res); return; } try { const server = buildServer(requestToken); const transport = new sse_js_1.SSEServerTransport("/messages", res); sseTransports[transport.sessionId] = transport; res.on("close", () => { delete sseTransports[transport.sessionId]; transport.close(); }); yield server.connect(transport); } catch (error) { console.error("Error handling SSE request:", error); if (!res.headersSent) { res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error", }, id: null, }); } } })); app.post("/messages", (req, res) => __awaiter(this, void 0, void 0, function* () { const sessionId = req.query.sessionId; const transport = sseTransports[sessionId]; if (transport) { yield transport.handlePostMessage(req, res, req.body); } else { res.status(400).send("No transport found for sessionId"); } })); app.listen(resolvedPort, () => { console.log(`MCP Stateless Streamable HTTP Server listening on port ${resolvedPort}`); }); // await new Promise((resolve) => setTimeout(resolve, 1000000)); } else { const server = buildServer(resolvedToken); const transport = new stdio_js_1.StdioServerTransport(); yield server.connect(transport); } }); } main().catch((e) => console.error(e.message));