@modelcontextprotocol/server-pdf 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * PdfCommand — the wire protocol between server and viewer.
3
+ *
4
+ * The server enqueues these via the `interact` tool; the viewer polls
5
+ * `poll_pdf_commands` and receives them as `structuredContent.commands`.
6
+ *
7
+ * This file is the single source of truth for the command shape. Both
8
+ * `server.ts` (which enqueues) and `mcp-app.ts` (which consumes) import
9
+ * from here — a new command variant must be added exactly once.
10
+ *
11
+ * Uses `import type` so neither side pulls pdf-lib into its bundle.
12
+ */
13
+ import type { PdfAnnotationDef } from "./pdf-annotations.js";
14
+ /** Single form field assignment, as sent by `fill_form`. */
15
+ export interface FormFieldFill {
16
+ name: string;
17
+ value: string | boolean;
18
+ }
19
+ /**
20
+ * Partial annotation update: `id` + `type` pin the target, everything
21
+ * else is optional. Server validates shape; viewer merges into existing.
22
+ */
23
+ export type PdfAnnotationPatch = Partial<PdfAnnotationDef> & {
24
+ id: string;
25
+ type: PdfAnnotationDef["type"];
26
+ };
27
+ /** Page range for text/screenshot extraction. Omitted bound = open-ended. */
28
+ export interface PageInterval {
29
+ start?: number;
30
+ end?: number;
31
+ }
32
+ /**
33
+ * Commands the server can send to the viewer via the poll queue.
34
+ * Adding a variant here means adding a `case` in both the server's
35
+ * `interact` handler (to enqueue) and the viewer's `processCommands`
36
+ * (to execute).
37
+ */
38
+ export type PdfCommand = {
39
+ type: "navigate";
40
+ page: number;
41
+ } | {
42
+ type: "search";
43
+ query: string;
44
+ } | {
45
+ type: "find";
46
+ query: string;
47
+ } | {
48
+ type: "search_navigate";
49
+ matchIndex: number;
50
+ } | {
51
+ type: "zoom";
52
+ scale: number;
53
+ } | {
54
+ type: "add_annotations";
55
+ annotations: PdfAnnotationDef[];
56
+ } | {
57
+ type: "update_annotations";
58
+ annotations: PdfAnnotationPatch[];
59
+ } | {
60
+ type: "remove_annotations";
61
+ ids: string[];
62
+ } | {
63
+ type: "highlight_text";
64
+ id: string;
65
+ query: string;
66
+ page?: number;
67
+ color?: string;
68
+ content?: string;
69
+ } | {
70
+ type: "fill_form";
71
+ fields: FormFieldFill[];
72
+ } | {
73
+ type: "get_pages";
74
+ requestId: string;
75
+ intervals: PageInterval[];
76
+ getText: boolean;
77
+ getScreenshots: boolean;
78
+ } | {
79
+ type: "file_changed";
80
+ mtimeMs: number;
81
+ };
@@ -0,0 +1,186 @@
1
+ /**
2
+ * PDF Annotation Helpers
3
+ *
4
+ * Pure functions for annotation persistence (diff-based model),
5
+ * color conversion, and PDF annotation dict creation using pdf-lib.
6
+ *
7
+ * The diff-based model stores only changes relative to the PDF's
8
+ * native annotations: additions, removals, and modifications.
9
+ * This keeps localStorage small and preserves round-trip fidelity.
10
+ */
11
+ import { PDFDocument } from "pdf-lib";
12
+ export interface Rect {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+ export interface AnnotationBase {
19
+ id: string;
20
+ page: number;
21
+ }
22
+ export interface HighlightAnnotation extends AnnotationBase {
23
+ type: "highlight";
24
+ rects: Rect[];
25
+ color?: string;
26
+ content?: string;
27
+ }
28
+ export interface UnderlineAnnotation extends AnnotationBase {
29
+ type: "underline";
30
+ rects: Rect[];
31
+ color?: string;
32
+ }
33
+ export interface StrikethroughAnnotation extends AnnotationBase {
34
+ type: "strikethrough";
35
+ rects: Rect[];
36
+ color?: string;
37
+ }
38
+ export interface NoteAnnotation extends AnnotationBase {
39
+ type: "note";
40
+ x: number;
41
+ y: number;
42
+ content: string;
43
+ color?: string;
44
+ }
45
+ export interface RectangleAnnotation extends AnnotationBase {
46
+ type: "rectangle";
47
+ x: number;
48
+ y: number;
49
+ width: number;
50
+ height: number;
51
+ color?: string;
52
+ fillColor?: string;
53
+ rotation?: number;
54
+ }
55
+ export interface CircleAnnotation extends AnnotationBase {
56
+ type: "circle";
57
+ x: number;
58
+ y: number;
59
+ width: number;
60
+ height: number;
61
+ color?: string;
62
+ fillColor?: string;
63
+ }
64
+ export interface LineAnnotation extends AnnotationBase {
65
+ type: "line";
66
+ x1: number;
67
+ y1: number;
68
+ x2: number;
69
+ y2: number;
70
+ color?: string;
71
+ }
72
+ export interface FreetextAnnotation extends AnnotationBase {
73
+ type: "freetext";
74
+ x: number;
75
+ y: number;
76
+ content: string;
77
+ fontSize?: number;
78
+ color?: string;
79
+ }
80
+ export interface StampAnnotation extends AnnotationBase {
81
+ type: "stamp";
82
+ x: number;
83
+ y: number;
84
+ label: string;
85
+ color?: string;
86
+ rotation?: number;
87
+ }
88
+ export interface ImageAnnotation extends AnnotationBase {
89
+ type: "image";
90
+ x: number;
91
+ y: number;
92
+ width: number;
93
+ height: number;
94
+ imageData?: string;
95
+ imageUrl?: string;
96
+ mimeType?: string;
97
+ rotation?: number;
98
+ aspect?: "preserve" | "ignore";
99
+ }
100
+ export type PdfAnnotationDef = HighlightAnnotation | UnderlineAnnotation | StrikethroughAnnotation | NoteAnnotation | RectangleAnnotation | CircleAnnotation | LineAnnotation | FreetextAnnotation | StampAnnotation | ImageAnnotation;
101
+ /**
102
+ * Convert annotation coordinates from model space (top-left origin, Y↓)
103
+ * to internal PDF space (bottom-left origin, Y↑).
104
+ *
105
+ * Call this when receiving coordinates from the model via add/update_annotations.
106
+ */
107
+ export declare function convertFromModelCoords(def: PdfAnnotationDef, pageHeight: number): PdfAnnotationDef;
108
+ /**
109
+ * Convert annotation coordinates from internal PDF space (bottom-left origin, Y↑)
110
+ * to model space (top-left origin, Y↓).
111
+ *
112
+ * Call this when presenting coordinates to the model (e.g. in context strings).
113
+ */
114
+ export declare function convertToModelCoords(def: PdfAnnotationDef, pageHeight: number): PdfAnnotationDef;
115
+ /**
116
+ * Represents changes relative to the PDF's native annotations.
117
+ * Only this diff is stored in localStorage, keeping it small.
118
+ */
119
+ export interface AnnotationDiff {
120
+ /** Annotations created by the user (not in the original PDF) */
121
+ added: PdfAnnotationDef[];
122
+ /** PDF annotation ref strings that the user deleted */
123
+ removed: string[];
124
+ /** Form field values the user filled in */
125
+ formFields: Record<string, string | boolean>;
126
+ }
127
+ /** Create an empty diff */
128
+ export declare function emptyDiff(): AnnotationDiff;
129
+ /** Check if a diff has any changes */
130
+ export declare function isDiffEmpty(diff: AnnotationDiff): boolean;
131
+ /** Serialize diff to JSON string for localStorage */
132
+ export declare function serializeDiff(diff: AnnotationDiff): string;
133
+ /** Deserialize diff from JSON string. Returns empty diff on error. */
134
+ export declare function deserializeDiff(json: string): AnnotationDiff;
135
+ /**
136
+ * Merge PDF-native annotations with user diff to produce the final annotation set.
137
+ *
138
+ * @param pdfAnnotations - Annotations imported from the PDF file
139
+ * @param diff - User's local changes (additions, removals)
140
+ * @returns Merged annotation list
141
+ */
142
+ export declare function mergeAnnotations(pdfAnnotations: PdfAnnotationDef[], diff: AnnotationDiff): PdfAnnotationDef[];
143
+ /**
144
+ * Compute a diff given the PDF-native annotations and the current full set.
145
+ *
146
+ * @param pdfAnnotations - Original annotations from the PDF
147
+ * @param currentAnnotations - Current full annotation set (after user edits)
148
+ * @param formFields - Current form field values
149
+ * @returns The diff to persist
150
+ */
151
+ export declare function computeDiff(pdfAnnotations: PdfAnnotationDef[], currentAnnotations: PdfAnnotationDef[], formFields: Map<string, string | boolean>, baselineFormFields?: Map<string, string | boolean>): AnnotationDiff;
152
+ /**
153
+ * Parse a CSS color string to normalized RGB values (0-1 range).
154
+ * Supports hex (#rgb, #rrggbb, #rrggbbaa) and rgb()/rgba() notation.
155
+ */
156
+ export declare function cssColorToRgb(color: string): {
157
+ r: number;
158
+ g: number;
159
+ b: number;
160
+ } | null;
161
+ /** Default colors for each annotation type */
162
+ export declare function defaultColor(type: PdfAnnotationDef["type"]): string;
163
+ /**
164
+ * Add proper PDF annotation objects to a pdf-lib PDFDocument.
165
+ * Creates /Type /Annot dictionaries with correct /Subtype for each annotation type.
166
+ * These are real PDF annotations, editable in Acrobat/Preview.
167
+ */
168
+ export declare function addAnnotationDicts(pdfDoc: PDFDocument, annotations: PdfAnnotationDef[]): Promise<void>;
169
+ /**
170
+ * Build annotated PDF bytes from the original document.
171
+ * Applies user annotations and form fills, returns Uint8Array of the new PDF.
172
+ */
173
+ export declare function buildAnnotatedPdfBytes(pdfBytes: Uint8Array, annotations: PdfAnnotationDef[], formFields: Map<string, string | boolean>): Promise<Uint8Array>;
174
+ /**
175
+ * Convert a single PDF.js annotation object to our PdfAnnotationDef format.
176
+ * Returns null for unsupported annotation types.
177
+ */
178
+ export declare function importPdfjsAnnotation(ann: any, pageNum: number, index: number): PdfAnnotationDef | null;
179
+ /**
180
+ * Convert base64 string to Uint8Array.
181
+ */
182
+ export declare function base64ToUint8Array(base64: string): Uint8Array;
183
+ /**
184
+ * Convert Uint8Array to base64 string.
185
+ */
186
+ export declare function uint8ArrayToBase64(bytes: Uint8Array): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelcontextprotocol/server-pdf",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for loading and extracting text from PDF files with chunked pagination and interactive viewer",
6
6
  "repository": {
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build && tsc -p tsconfig.server.json && bun build server.ts --outdir dist --target node --external pdfjs-dist && bun build main.ts --outfile dist/index.js --target node --external \"./server.js\" --external pdfjs-dist --banner \"#!/usr/bin/env node\"",
18
18
  "watch": "cross-env INPUT=mcp-app.html vite build --watch",
19
- "serve": "bun --watch main.ts",
19
+ "serve": "bun --watch main.ts --enable-interact",
20
20
  "serve:stdio": "bun main.ts --stdio",
21
21
  "start": "cross-env NODE_ENV=development npm run build && npm run serve",
22
22
  "start:stdio": "cross-env NODE_ENV=development npm run build 1>&2 && npm run serve:stdio",
@@ -28,6 +28,7 @@
28
28
  "@modelcontextprotocol/sdk": "^1.24.0",
29
29
  "cors": "^2.8.5",
30
30
  "express": "^5.1.0",
31
+ "pdf-lib": "^1.17.1",
31
32
  "pdfjs-dist": "^5.0.0",
32
33
  "zod": "^4.1.13"
33
34
  },