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.

271 lines (270 loc) 9.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DOCUMENT_QUERY_PAPERLESS_FILTER_KEYS = exports.SEARCH_DOCUMENTS_ARGS_SHAPE = exports.QUERY_DOCUMENTS_ARGS_SHAPE = exports.LIST_DOCUMENTS_ARGS_SHAPE = exports.paperlessFiltersSchema = exports.paperlessFilterValueSchema = exports.customFieldQuerySchema = void 0; exports.buildDocumentQueryString = buildDocumentQueryString; const zod_1 = require("zod"); const CUSTOM_FIELD_QUERY_GROUP_OPERATORS = ["AND", "OR"]; const customFieldQueryPrimitiveSchema = zod_1.z.union([ zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), zod_1.z.null(), ]); const customFieldQueryValueSchema = zod_1.z.union([ customFieldQueryPrimitiveSchema, zod_1.z.array(customFieldQueryPrimitiveSchema), ]); function isCustomFieldQueryPrimitive(value) { return (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null); } function isCustomFieldQueryValue(value) { if (isCustomFieldQueryPrimitive(value)) { return true; } return (Array.isArray(value) && value.every((item) => isCustomFieldQueryPrimitive(item))); } function isCustomFieldQueryGroupOperator(value) { return (typeof value === "string" && CUSTOM_FIELD_QUERY_GROUP_OPERATORS.includes(value)); } function isCustomFieldQuery(value) { if (!Array.isArray(value)) { return false; } if (value.length === 3 && (typeof value[0] === "string" || typeof value[0] === "number") && !isCustomFieldQueryGroupOperator(value[0]) && typeof value[1] === "string" && isCustomFieldQueryValue(value[2])) { return true; } if (value.length === 2 && isCustomFieldQueryGroupOperator(value[0]) && Array.isArray(value[1]) && value[1].length >= 1) { return value[1].every((item) => isCustomFieldQuery(item)); } return false; } exports.customFieldQuerySchema = zod_1.z .array(zod_1.z.unknown()) .superRefine((value, ctx) => { if (!isCustomFieldQuery(value)) { ctx.addIssue({ code: zod_1.z.ZodIssueCode.custom, message: "Invalid custom_field_query. Use [field_name_or_id, operator, value] or ['AND'|'OR', [clause1, clause2]].", }); } }); const paperlessFilterScalarSchema = zod_1.z.union([ zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), ]); exports.paperlessFilterValueSchema = zod_1.z.union([ paperlessFilterScalarSchema, zod_1.z.array(paperlessFilterScalarSchema), ]); exports.paperlessFiltersSchema = zod_1.z.record(exports.paperlessFilterValueSchema); const DOCUMENT_QUERY_BASE_ARGS_SHAPE = { page: zod_1.z.number().optional(), page_size: zod_1.z.number().optional(), search: zod_1.z.string().optional(), correspondent: zod_1.z.number().optional(), document_type: zod_1.z.number().optional(), tag: zod_1.z.number().optional(), storage_path: zod_1.z.number().optional(), created__date__gte: zod_1.z.string().optional(), created__date__lte: zod_1.z.string().optional(), ordering: zod_1.z.string().optional(), archive_serial_number: zod_1.z.number().optional(), archive_serial_number__isnull: zod_1.z.boolean().optional(), custom_fields__icontains: zod_1.z.string().min(1).optional(), }; exports.LIST_DOCUMENTS_ARGS_SHAPE = Object.assign(Object.assign({}, DOCUMENT_QUERY_BASE_ARGS_SHAPE), { custom_field_query: zod_1.z.string().min(1).optional() }); exports.QUERY_DOCUMENTS_ARGS_SHAPE = Object.assign(Object.assign({}, DOCUMENT_QUERY_BASE_ARGS_SHAPE), { query: zod_1.z.string().optional(), more_like_id: zod_1.z.number().optional(), custom_field_query: exports.customFieldQuerySchema .optional() .describe("Paperless custom field query. Use [field_name_or_id, operator, value] for a single clause or ['AND'|'OR', [clause1, clause2]] for grouped clauses."), paperless_filters: exports.paperlessFiltersSchema .optional() .describe("Additional documented /api/documents/ Paperless filters. Keys must match Paperless query parameter names exactly. Prefer first-class arguments when available.") }); exports.SEARCH_DOCUMENTS_ARGS_SHAPE = { query: zod_1.z.string(), }; const FIRST_CLASS_QUERY_PARAM_MAP = { page: "page", page_size: "page_size", ordering: "ordering", query: "query", search: "search", more_like_id: "more_like_id", correspondent: "correspondent__id", document_type: "document_type__id", tag: "tags__id", storage_path: "storage_path__id", created__date__gte: "created__date__gte", created__date__lte: "created__date__lte", archive_serial_number: "archive_serial_number", archive_serial_number__isnull: "archive_serial_number__isnull", custom_fields__icontains: "custom_fields__icontains", }; // Derived from the documented /api/documents/ query parameters in Paperless_ngx_REST_API.yaml. exports.DOCUMENT_QUERY_PAPERLESS_FILTER_KEYS = [ "added__date__gt", "added__date__gte", "added__date__lt", "added__date__lte", "added__day", "added__gt", "added__gte", "added__lt", "added__lte", "added__month", "added__year", "archive_serial_number", "archive_serial_number__gt", "archive_serial_number__gte", "archive_serial_number__isnull", "archive_serial_number__lt", "archive_serial_number__lte", "checksum__icontains", "checksum__iendswith", "checksum__iexact", "checksum__istartswith", "content__icontains", "content__iendswith", "content__iexact", "content__istartswith", "correspondent__id", "correspondent__id__in", "correspondent__id__none", "correspondent__isnull", "correspondent__name__icontains", "correspondent__name__iendswith", "correspondent__name__iexact", "correspondent__name__istartswith", "created__date__gt", "created__date__gte", "created__date__lt", "created__date__lte", "created__day", "created__gt", "created__gte", "created__lt", "created__lte", "created__month", "created__year", "custom_field_query", "custom_fields__icontains", "custom_fields__id__all", "custom_fields__id__in", "custom_fields__id__none", "document_type__id", "document_type__id__in", "document_type__id__none", "document_type__isnull", "document_type__name__icontains", "document_type__name__iendswith", "document_type__name__iexact", "document_type__name__istartswith", "fields", "full_perms", "has_custom_fields", "id", "id__in", "is_in_inbox", "is_tagged", "mime_type", "modified__date__gt", "modified__date__gte", "modified__date__lt", "modified__date__lte", "modified__day", "modified__gt", "modified__gte", "modified__lt", "modified__lte", "modified__month", "modified__year", "ordering", "original_filename__icontains", "original_filename__iendswith", "original_filename__iexact", "original_filename__istartswith", "owner__id", "owner__id__in", "owner__id__none", "owner__isnull", "page", "page_size", "query", "search", "shared_by__id", "storage_path__id", "storage_path__id__in", "storage_path__id__none", "storage_path__isnull", "storage_path__name__icontains", "storage_path__name__iendswith", "storage_path__name__iexact", "storage_path__name__istartswith", "tags__id", "tags__id__all", "tags__id__in", "tags__id__none", "tags__name__icontains", "tags__name__iendswith", "tags__name__iexact", "tags__name__istartswith", "title__icontains", "title__iendswith", "title__iexact", "title__istartswith", "title_content", ]; const DOCUMENT_QUERY_PAPERLESS_FILTER_KEY_SET = new Set(exports.DOCUMENT_QUERY_PAPERLESS_FILTER_KEYS); function hasValue(value) { return value !== undefined && value !== null; } function setQueryParam(query, key, value, jsonEncode = false) { if (jsonEncode) { query.set(key, JSON.stringify(value)); return; } if (Array.isArray(value)) { query.set(key, value.map((item) => String(item)).join(",")); return; } query.set(key, String(value)); } function buildDocumentQueryString(args) { const query = new URLSearchParams(); const firstClassKeys = new Set(); for (const [argName, queryParamName] of Object.entries(FIRST_CLASS_QUERY_PARAM_MAP)) { const value = args[argName]; if (!hasValue(value)) { continue; } setQueryParam(query, queryParamName, value); firstClassKeys.add(queryParamName); } if (hasValue(args.custom_field_query)) { setQueryParam(query, "custom_field_query", args.custom_field_query, typeof args.custom_field_query !== "string"); firstClassKeys.add("custom_field_query"); } if (!args.paperless_filters) { return query.toString() ? `?${query.toString()}` : ""; } for (const [key, value] of Object.entries(args.paperless_filters)) { if (!DOCUMENT_QUERY_PAPERLESS_FILTER_KEY_SET.has(key)) { throw new Error(`Unsupported paperless_filters key '${key}'. Use documented /api/documents/ query parameter names only.`); } if (firstClassKeys.has(key)) { throw new Error(`Duplicate filter '${key}' provided both as a first-class argument and in paperless_filters.`); } setQueryParam(query, key, value); } return query.toString() ? `?${query.toString()}` : ""; }