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.

178 lines (177 loc) 8.47 kB
"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 strict_1 = __importDefault(require("node:assert/strict")); const node_test_1 = require("node:test"); const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js"); const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js"); const notes_1 = require("./notes"); class TestTransport { start() { return __awaiter(this, void 0, void 0, function* () { }); } send(message) { return __awaiter(this, void 0, void 0, function* () { queueMicrotask(() => { var _a, _b; return (_b = (_a = this.peer) === null || _a === void 0 ? void 0 : _a.onmessage) === null || _b === void 0 ? void 0 : _b.call(_a, message); }); }); } close() { return __awaiter(this, void 0, void 0, function* () { var _a; (_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this); }); } } function createTransportPair() { const clientTransport = new TestTransport(); const serverTransport = new TestTransport(); clientTransport.peer = serverTransport; serverTransport.peer = clientTransport; return { clientTransport, serverTransport }; } function parseToolText(result) { var _a; const item = (_a = result.content) === null || _a === void 0 ? void 0 : _a[0]; if (!item || item.type !== "text") { throw new Error("Expected text tool response"); } return JSON.parse(item.text); } function createNoteApi(notes = []) { const calls = { getDocumentNotes: [], createDocumentNote: [], deleteDocumentNote: [], }; const api = { getDocumentNotes: (id) => __awaiter(this, void 0, void 0, function* () { calls.getDocumentNotes.push(id); return notes; }), createDocumentNote: (id, note) => __awaiter(this, void 0, void 0, function* () { calls.createDocumentNote.push([id, note]); return notes; }), deleteDocumentNote: (id, noteId) => __awaiter(this, void 0, void 0, function* () { calls.deleteDocumentNote.push([id, noteId]); return notes; }), }; return { api, calls }; } function withNoteClient(api, run) { return __awaiter(this, void 0, void 0, function* () { const server = new mcp_js_1.McpServer({ name: "paperless-note-test", version: "1.0.0" }); (0, notes_1.registerNoteTools)(server, api); const client = new index_js_1.Client({ name: "paperless-note-test-client", version: "1.0.0", }); const { clientTransport, serverTransport } = createTransportPair(); yield server.connect(serverTransport); yield client.connect(clientTransport); try { yield run(client); } finally { yield client.close(); yield server.close(); } }); } (0, node_test_1.describe)("document note tools", () => { const sampleNotes = [ { id: 5, note: "Paid 2026-06-30", created: "2026-06-30T10:00:00Z", user: { id: 3, username: "nick" }, }, ]; (0, node_test_1.test)("create_document_note posts the note text to the document", () => __awaiter(void 0, void 0, void 0, function* () { const { api, calls } = createNoteApi(sampleNotes); yield withNoteClient(api, (client) => __awaiter(void 0, void 0, void 0, function* () { var _a; const result = (yield client.callTool({ name: "create_document_note", arguments: { id: 1740, note: "Antwort an Finanzamt versendet" }, })); strict_1.default.ok(!result.isError, (_a = parseToolText(result)) === null || _a === void 0 ? void 0 : _a.error); strict_1.default.deepEqual(parseToolText(result), sampleNotes); })); strict_1.default.deepEqual(calls.createDocumentNote, [ [1740, "Antwort an Finanzamt versendet"], ]); })); (0, node_test_1.test)("list_document_notes fetches notes for the document", () => __awaiter(void 0, void 0, void 0, function* () { const { api, calls } = createNoteApi(sampleNotes); yield withNoteClient(api, (client) => __awaiter(void 0, void 0, void 0, function* () { var _a; const result = (yield client.callTool({ name: "list_document_notes", arguments: { id: 42 }, })); strict_1.default.ok(!result.isError, (_a = parseToolText(result)) === null || _a === void 0 ? void 0 : _a.error); strict_1.default.deepEqual(parseToolText(result), sampleNotes); })); strict_1.default.deepEqual(calls.getDocumentNotes, [42]); })); (0, node_test_1.test)("delete_document_note removes a note by its note ID when confirmed", () => __awaiter(void 0, void 0, void 0, function* () { const { api, calls } = createNoteApi([]); yield withNoteClient(api, (client) => __awaiter(void 0, void 0, void 0, function* () { var _a; const result = (yield client.callTool({ name: "delete_document_note", arguments: { id: 42, note_id: 5, confirm: true }, })); strict_1.default.ok(!result.isError, (_a = parseToolText(result)) === null || _a === void 0 ? void 0 : _a.error); })); strict_1.default.deepEqual(calls.deleteDocumentNote, [[42, 5]]); })); (0, node_test_1.test)("delete_document_note refuses to delete without confirmation", () => __awaiter(void 0, void 0, void 0, function* () { const { api, calls } = createNoteApi([]); yield withNoteClient(api, (client) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const result = (yield client.callTool({ name: "delete_document_note", arguments: { id: 42, note_id: 5, confirm: false }, })); strict_1.default.ok(result.isError, "expected an error when confirm is false"); strict_1.default.match((_b = (_a = parseToolText(result)) === null || _a === void 0 ? void 0 : _a.error) !== null && _b !== void 0 ? _b : "", /confirm/i); })); strict_1.default.equal(calls.deleteDocumentNote.length, 0, "no delete should be sent without confirmation"); })); (0, node_test_1.test)("create_document_note rejects an empty note", () => __awaiter(void 0, void 0, void 0, function* () { const { api, calls } = createNoteApi(sampleNotes); yield withNoteClient(api, (client) => __awaiter(void 0, void 0, void 0, function* () { // Depending on the installed MCP SDK version, an input-schema (zod) // violation either rejects with a protocol error (-32602) or resolves // with a CallToolResult carrying isError: true. Accept both so the test // stays correct across SDK versions. let rejected = false; let result; try { result = (yield client.callTool({ name: "create_document_note", arguments: { id: 1, note: "" }, })); } catch (_a) { rejected = true; } strict_1.default.ok(rejected || (result === null || result === void 0 ? void 0 : result.isError), "expected an empty note to be rejected"); })); strict_1.default.equal(calls.createDocumentNote.length, 0, "no note should be created when validation fails"); })); });