@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
JavaScript
;
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()}` : "";
}