@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
JavaScript
;
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));