@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.
120 lines (119 loc) • 5.11 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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerDocumentResources = registerDocumentResources;
exports.readDocumentResource = readDocumentResource;
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
function registerDocumentResources(server, api) {
server.resource("paperless-document-resource", new mcp_js_1.ResourceTemplate("paperless://documents/{id}/{resource}", {
list: undefined,
}), (uri, variables) => __awaiter(this, void 0, void 0, function* () { return readDocumentResource(api, uri, variables); }));
server.resource("paperless-document-original-download", new mcp_js_1.ResourceTemplate("paperless://documents/{id}/download{?original}", {
list: undefined,
}), (uri, variables) => __awaiter(this, void 0, void 0, function* () {
return readDocumentDownloadResource(api, uri, parseDocumentId(readVariable(variables, "id")), isTrueQueryValue(readVariable(variables, "original")));
}));
}
function readDocumentResource(api, uri, variables) {
return __awaiter(this, void 0, void 0, function* () {
assertPaperlessDocumentsUri(uri);
const id = parseDocumentId(readVariable(variables, "id"));
const resource = readVariable(variables, "resource").split("?")[0];
switch (resource) {
case "download":
return readDocumentDownloadResource(api, uri, id, isTrueQueryValue(uri.searchParams.get("original") || undefined));
case "thumbnail":
case "thumb":
return readDocumentThumbnailResource(api, uri, id);
default:
throw new Error(`Unsupported Paperless document resource: ${resource}`);
}
});
}
function readDocumentDownloadResource(api, uri, id, original) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield api.downloadDocument(id, original);
return {
contents: [
responseToResourceContents(uri.href, response, "application/octet-stream"),
],
};
});
}
function readDocumentThumbnailResource(api, uri, id) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield api.getThumbnail(id);
return {
contents: [responseToResourceContents(uri.href, response, "image/webp")],
};
});
}
function responseToResourceContents(uri, response, fallbackMimeType) {
const mimeType = getHeader(response, "content-type") || fallbackMimeType;
const data = Buffer.from(response.data);
if (isTextMimeType(mimeType)) {
return {
uri,
mimeType,
text: data.toString("utf8"),
};
}
return {
uri,
mimeType,
blob: data.toString("base64"),
};
}
function assertPaperlessDocumentsUri(uri) {
if (uri.protocol !== "paperless:" || uri.hostname !== "documents") {
throw new Error(`Unsupported Paperless resource URI: ${uri.href}`);
}
}
function parseDocumentId(idValue) {
const id = Number(idValue);
if (!Number.isInteger(id) || id <= 0) {
throw new Error(`Invalid Paperless document id in resource URI: ${idValue}`);
}
return id;
}
function readVariable(variables, name) {
const value = variables[name];
const stringValue = Array.isArray(value) ? value[0] : value;
if (!stringValue) {
throw new Error(`Missing ${name} in Paperless resource URI`);
}
return stringValue;
}
function isTrueQueryValue(value) {
return value === "true" || value === "1";
}
function getHeader(response, headerName) {
const headers = response.headers;
if (typeof headers.get === "function") {
const value = headers.get(headerName);
if (typeof value === "string") {
return value;
}
}
const value = headers[headerName] || headers[headerName.toLowerCase()];
if (Array.isArray(value)) {
return String(value[0]);
}
return typeof value === "string" ? value : undefined;
}
function isTextMimeType(mimeType) {
const normalizedMimeType = mimeType.split(";")[0].trim().toLowerCase();
return (normalizedMimeType.startsWith("text/") ||
normalizedMimeType === "application/json" ||
normalizedMimeType === "application/xml" ||
normalizedMimeType.endsWith("+json") ||
normalizedMimeType.endsWith("+xml"));
}