pdfx-cli 0.0.1 → 0.4.1

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,1261 @@
1
+ // src/mcp/index.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { z as z8 } from "zod";
5
+ import { zodToJsonSchema } from "zod-to-json-schema";
6
+
7
+ // src/mcp/tools/add-command.ts
8
+ import dedent from "dedent";
9
+ import { z as z2 } from "zod";
10
+
11
+ // ../shared/src/schemas.ts
12
+ import { z } from "zod";
13
+ var colorTokensSchema = z.object({
14
+ foreground: z.string().min(1),
15
+ background: z.string().min(1),
16
+ muted: z.string().min(1),
17
+ mutedForeground: z.string().min(1),
18
+ primary: z.string().min(1),
19
+ primaryForeground: z.string().min(1),
20
+ border: z.string().min(1),
21
+ accent: z.string().min(1),
22
+ destructive: z.string().min(1),
23
+ success: z.string().min(1),
24
+ warning: z.string().min(1),
25
+ info: z.string().min(1)
26
+ });
27
+ var headingFontSizeSchema = z.object({
28
+ h1: z.number().positive(),
29
+ h2: z.number().positive(),
30
+ h3: z.number().positive(),
31
+ h4: z.number().positive(),
32
+ h5: z.number().positive(),
33
+ h6: z.number().positive()
34
+ });
35
+ var typographyTokensSchema = z.object({
36
+ body: z.object({
37
+ fontFamily: z.string().min(1),
38
+ fontSize: z.number().positive(),
39
+ lineHeight: z.number().positive()
40
+ }),
41
+ heading: z.object({
42
+ fontFamily: z.string().min(1),
43
+ fontWeight: z.number().int().min(100).max(900),
44
+ lineHeight: z.number().positive(),
45
+ fontSize: headingFontSizeSchema
46
+ })
47
+ });
48
+ var spacingTokensSchema = z.object({
49
+ page: z.object({
50
+ marginTop: z.number().min(0),
51
+ marginRight: z.number().min(0),
52
+ marginBottom: z.number().min(0),
53
+ marginLeft: z.number().min(0)
54
+ }),
55
+ sectionGap: z.number().min(0),
56
+ paragraphGap: z.number().min(0),
57
+ componentGap: z.number().min(0)
58
+ });
59
+ var pageTokensSchema = z.object({
60
+ size: z.enum(["A4", "LETTER", "LEGAL"]),
61
+ orientation: z.enum(["portrait", "landscape"])
62
+ });
63
+ var primitiveTokensSchema = z.object({
64
+ typography: z.record(z.number()),
65
+ spacing: z.record(z.number()),
66
+ fontWeights: z.object({
67
+ regular: z.number(),
68
+ medium: z.number(),
69
+ semibold: z.number(),
70
+ bold: z.number()
71
+ }),
72
+ lineHeights: z.object({
73
+ tight: z.number(),
74
+ normal: z.number(),
75
+ relaxed: z.number()
76
+ }),
77
+ borderRadius: z.object({
78
+ none: z.number(),
79
+ sm: z.number(),
80
+ md: z.number(),
81
+ lg: z.number(),
82
+ full: z.number()
83
+ }),
84
+ letterSpacing: z.object({
85
+ tight: z.number(),
86
+ normal: z.number(),
87
+ wide: z.number(),
88
+ wider: z.number()
89
+ })
90
+ });
91
+ var themeSchema = z.object({
92
+ name: z.string().min(1),
93
+ primitives: primitiveTokensSchema,
94
+ colors: colorTokensSchema,
95
+ typography: typographyTokensSchema,
96
+ spacing: spacingTokensSchema,
97
+ page: pageTokensSchema
98
+ });
99
+ var configSchema = z.object({
100
+ $schema: z.string().optional(),
101
+ componentDir: z.string().min(1, "componentDir must not be empty"),
102
+ registry: z.string().url("registry must be a valid URL"),
103
+ theme: z.string().min(1).optional(),
104
+ blockDir: z.string().min(1).optional()
105
+ });
106
+ var registryFileTypes = [
107
+ "registry:component",
108
+ "registry:lib",
109
+ "registry:style",
110
+ "registry:template",
111
+ "registry:block",
112
+ "registry:file"
113
+ ];
114
+ var registryFileSchema = z.object({
115
+ path: z.string().min(1),
116
+ content: z.string(),
117
+ type: z.enum(registryFileTypes)
118
+ });
119
+ var registryItemSchema = z.object({
120
+ name: z.string().min(1),
121
+ type: z.string().optional(),
122
+ title: z.string().optional(),
123
+ description: z.string().optional(),
124
+ files: z.array(registryFileSchema).min(1, "Component must have at least one file"),
125
+ dependencies: z.array(z.string()).optional(),
126
+ devDependencies: z.array(z.string()).optional(),
127
+ registryDependencies: z.array(z.string()).optional(),
128
+ peerComponents: z.array(z.string()).optional()
129
+ });
130
+ var registryIndexItemSchema = z.object({
131
+ name: z.string().min(1),
132
+ type: z.string(),
133
+ title: z.string(),
134
+ description: z.string(),
135
+ files: z.array(
136
+ z.object({
137
+ path: z.string().min(1),
138
+ type: z.string()
139
+ })
140
+ ),
141
+ dependencies: z.array(z.string()).optional(),
142
+ devDependencies: z.array(z.string()).optional(),
143
+ registryDependencies: z.array(z.string()).optional(),
144
+ peerComponents: z.array(z.string()).optional()
145
+ });
146
+ var registrySchema = z.object({
147
+ $schema: z.string(),
148
+ name: z.string(),
149
+ homepage: z.string(),
150
+ items: z.array(registryIndexItemSchema)
151
+ });
152
+ var componentNameSchema = z.string().regex(
153
+ /^[a-z][a-z0-9-]*$/,
154
+ "Component name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens"
155
+ );
156
+
157
+ // ../shared/src/themes/primitives.ts
158
+ var defaultPrimitives = {
159
+ typography: {
160
+ xs: 10,
161
+ sm: 12,
162
+ base: 15,
163
+ lg: 18,
164
+ xl: 22,
165
+ "2xl": 28,
166
+ "3xl": 36
167
+ },
168
+ spacing: {
169
+ 0: 0,
170
+ 0.5: 2,
171
+ 1: 4,
172
+ 2: 8,
173
+ 3: 12,
174
+ 4: 16,
175
+ 5: 20,
176
+ 6: 24,
177
+ 8: 32,
178
+ 10: 40,
179
+ 12: 48,
180
+ 16: 64
181
+ },
182
+ fontWeights: {
183
+ regular: 400,
184
+ medium: 500,
185
+ semibold: 600,
186
+ bold: 700
187
+ },
188
+ lineHeights: {
189
+ tight: 1.2,
190
+ normal: 1.4,
191
+ relaxed: 1.6
192
+ },
193
+ borderRadius: {
194
+ none: 0,
195
+ sm: 2,
196
+ md: 4,
197
+ lg: 8,
198
+ full: 9999
199
+ },
200
+ letterSpacing: {
201
+ tight: -0.025,
202
+ normal: 0,
203
+ wide: 0.025,
204
+ wider: 0.05
205
+ }
206
+ };
207
+
208
+ // ../shared/src/themes/professional.ts
209
+ var professionalTheme = {
210
+ name: "professional",
211
+ primitives: defaultPrimitives,
212
+ colors: {
213
+ foreground: "#18181b",
214
+ background: "#ffffff",
215
+ muted: "#f4f4f5",
216
+ mutedForeground: "#71717a",
217
+ primary: "#18181b",
218
+ primaryForeground: "#ffffff",
219
+ border: "#e4e4e7",
220
+ accent: "#3b82f6",
221
+ destructive: "#dc2626",
222
+ success: "#16a34a",
223
+ warning: "#d97706",
224
+ info: "#0ea5e9"
225
+ },
226
+ typography: {
227
+ body: {
228
+ fontFamily: "Helvetica",
229
+ fontSize: 11,
230
+ lineHeight: 1.6
231
+ },
232
+ heading: {
233
+ fontFamily: "Times-Roman",
234
+ fontWeight: 700,
235
+ lineHeight: 1.25,
236
+ fontSize: {
237
+ h1: 32,
238
+ h2: 24,
239
+ h3: 20,
240
+ h4: 16,
241
+ h5: 14,
242
+ h6: 12
243
+ }
244
+ }
245
+ },
246
+ spacing: {
247
+ page: {
248
+ marginTop: 56,
249
+ marginRight: 48,
250
+ marginBottom: 56,
251
+ marginLeft: 48
252
+ },
253
+ sectionGap: 28,
254
+ paragraphGap: 10,
255
+ componentGap: 14
256
+ },
257
+ page: {
258
+ size: "A4",
259
+ orientation: "portrait"
260
+ }
261
+ };
262
+
263
+ // ../shared/src/themes/modern.ts
264
+ var modernTheme = {
265
+ name: "modern",
266
+ primitives: defaultPrimitives,
267
+ colors: {
268
+ foreground: "#0f172a",
269
+ background: "#ffffff",
270
+ muted: "#f1f5f9",
271
+ mutedForeground: "#64748b",
272
+ primary: "#334155",
273
+ primaryForeground: "#ffffff",
274
+ border: "#e2e8f0",
275
+ accent: "#6366f1",
276
+ destructive: "#ef4444",
277
+ success: "#22c55e",
278
+ warning: "#f59e0b",
279
+ info: "#3b82f6"
280
+ },
281
+ typography: {
282
+ body: {
283
+ fontFamily: "Helvetica",
284
+ fontSize: 11,
285
+ lineHeight: 1.6
286
+ },
287
+ heading: {
288
+ fontFamily: "Helvetica",
289
+ fontWeight: 600,
290
+ lineHeight: 1.25,
291
+ fontSize: {
292
+ h1: 28,
293
+ h2: 22,
294
+ h3: 18,
295
+ h4: 16,
296
+ h5: 14,
297
+ h6: 12
298
+ }
299
+ }
300
+ },
301
+ spacing: {
302
+ page: {
303
+ marginTop: 40,
304
+ marginRight: 40,
305
+ marginBottom: 40,
306
+ marginLeft: 40
307
+ },
308
+ sectionGap: 24,
309
+ paragraphGap: 10,
310
+ componentGap: 12
311
+ },
312
+ page: {
313
+ size: "A4",
314
+ orientation: "portrait"
315
+ }
316
+ };
317
+
318
+ // ../shared/src/themes/minimal.ts
319
+ var minimalTheme = {
320
+ name: "minimal",
321
+ primitives: defaultPrimitives,
322
+ colors: {
323
+ foreground: "#18181b",
324
+ background: "#ffffff",
325
+ muted: "#fafafa",
326
+ mutedForeground: "#a1a1aa",
327
+ primary: "#18181b",
328
+ primaryForeground: "#ffffff",
329
+ border: "#e4e4e7",
330
+ accent: "#71717a",
331
+ destructive: "#b91c1c",
332
+ success: "#15803d",
333
+ warning: "#a16207",
334
+ info: "#0369a1"
335
+ },
336
+ typography: {
337
+ body: {
338
+ fontFamily: "Helvetica",
339
+ fontSize: 11,
340
+ lineHeight: 1.65
341
+ },
342
+ heading: {
343
+ fontFamily: "Courier",
344
+ fontWeight: 600,
345
+ lineHeight: 1.25,
346
+ fontSize: {
347
+ h1: 24,
348
+ h2: 20,
349
+ h3: 16,
350
+ h4: 14,
351
+ h5: 12,
352
+ h6: 10
353
+ }
354
+ }
355
+ },
356
+ spacing: {
357
+ page: {
358
+ marginTop: 72,
359
+ marginRight: 56,
360
+ marginBottom: 72,
361
+ marginLeft: 56
362
+ },
363
+ sectionGap: 36,
364
+ paragraphGap: 14,
365
+ componentGap: 18
366
+ },
367
+ page: {
368
+ size: "A4",
369
+ orientation: "portrait"
370
+ }
371
+ };
372
+
373
+ // ../shared/src/themes/index.ts
374
+ var themePresets = {
375
+ professional: professionalTheme,
376
+ modern: modernTheme,
377
+ minimal: minimalTheme
378
+ };
379
+
380
+ // ../shared/src/errors.ts
381
+ var PdfxError = class extends Error {
382
+ constructor(message, code, suggestion) {
383
+ super(message);
384
+ this.code = code;
385
+ this.suggestion = suggestion;
386
+ this.name = "PdfxError";
387
+ }
388
+ };
389
+ var RegistryError = class extends PdfxError {
390
+ constructor(message, suggestion) {
391
+ super(message, "REGISTRY_ERROR", suggestion);
392
+ this.name = "RegistryError";
393
+ }
394
+ };
395
+ var NetworkError = class extends PdfxError {
396
+ constructor(message, suggestion) {
397
+ super(
398
+ message,
399
+ "NETWORK_ERROR",
400
+ suggestion ?? "Check your internet connection and registry URL"
401
+ );
402
+ this.name = "NetworkError";
403
+ }
404
+ };
405
+
406
+ // src/constants.ts
407
+ var DEFAULTS = {
408
+ REGISTRY_URL: "https://pdfx.akashpise.dev/r",
409
+ SCHEMA_URL: "https://pdfx.akashpise.dev/schema.json",
410
+ COMPONENT_DIR: "./src/components/pdfx",
411
+ THEME_FILE: "./src/lib/pdfx-theme.ts",
412
+ BLOCK_DIR: "./src/blocks/pdfx"
413
+ };
414
+ var REGISTRY_SUBPATHS = {
415
+ BLOCKS: "blocks"
416
+ };
417
+ var FETCH_TIMEOUT_MS = 1e4;
418
+
419
+ // src/mcp/utils.ts
420
+ var REGISTRY_BASE = DEFAULTS.REGISTRY_URL;
421
+ var BLOCKS_BASE = `${DEFAULTS.REGISTRY_URL}/${REGISTRY_SUBPATHS.BLOCKS}`;
422
+ async function fetchRegistryIndex() {
423
+ let response;
424
+ try {
425
+ response = await fetch(`${REGISTRY_BASE}/index.json`, {
426
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
427
+ });
428
+ } catch (err) {
429
+ const isTimeout = err instanceof Error && err.name === "TimeoutError";
430
+ throw new NetworkError(
431
+ isTimeout ? "Registry request timed out. Check your internet connection." : "Could not reach the PDFx registry. Check your internet connection."
432
+ );
433
+ }
434
+ if (!response.ok) {
435
+ throw new RegistryError(`Registry returned HTTP ${response.status}`);
436
+ }
437
+ let data;
438
+ try {
439
+ data = await response.json();
440
+ } catch {
441
+ throw new RegistryError("Registry returned invalid JSON");
442
+ }
443
+ const result = registrySchema.safeParse(data);
444
+ if (!result.success) {
445
+ throw new RegistryError("Registry index has an unexpected format");
446
+ }
447
+ return result.data.items;
448
+ }
449
+ async function fetchRegistryItem(name, base = REGISTRY_BASE) {
450
+ const url = `${base}/${name}.json`;
451
+ let response;
452
+ try {
453
+ response = await fetch(url, {
454
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
455
+ });
456
+ } catch (err) {
457
+ const isTimeout = err instanceof Error && err.name === "TimeoutError";
458
+ throw new NetworkError(
459
+ isTimeout ? `Registry request timed out for "${name}".` : "Could not reach the PDFx registry. Check your internet connection."
460
+ );
461
+ }
462
+ if (!response.ok) {
463
+ if (response.status === 404) {
464
+ throw new RegistryError(
465
+ `"${name}" not found in the registry. Use list_components or list_blocks to see available items.`
466
+ );
467
+ }
468
+ throw new RegistryError(`Registry returned HTTP ${response.status} for "${name}"`);
469
+ }
470
+ let data;
471
+ try {
472
+ data = await response.json();
473
+ } catch {
474
+ throw new RegistryError(`Registry returned invalid JSON for "${name}"`);
475
+ }
476
+ const result = registryItemSchema.safeParse(data);
477
+ if (!result.success) {
478
+ throw new RegistryError(`Unexpected registry format for "${name}"`);
479
+ }
480
+ return result.data;
481
+ }
482
+ function textResponse(text) {
483
+ return { content: [{ type: "text", text }] };
484
+ }
485
+ function errorResponse(error) {
486
+ const message = error instanceof Error ? error.message : String(error);
487
+ return {
488
+ content: [{ type: "text", text: `Error: ${message}` }],
489
+ isError: true
490
+ };
491
+ }
492
+
493
+ // src/mcp/tools/add-command.ts
494
+ var getAddCommandSchema = z2.object({
495
+ items: z2.array(z2.string().min(1)).min(1).describe(
496
+ "Item names to add, e.g. ['table', 'heading'] for components or ['invoice-modern'] for blocks"
497
+ ),
498
+ type: z2.enum(["component", "block"]).describe("Whether the items are components or blocks")
499
+ });
500
+ async function getAddCommand(args) {
501
+ const isBlock = args.type === "block";
502
+ const cmd = isBlock ? `npx pdfx-cli block add ${args.items.join(" ")}` : `npx pdfx-cli add ${args.items.join(" ")}`;
503
+ const installDir = isBlock ? "src/blocks/pdfx/" : "src/components/pdfx/";
504
+ const inspectTool = isBlock ? "get_block" : "get_component";
505
+ const itemList = args.items.map((i) => `- \`${i}\``).join("\n");
506
+ return textResponse(dedent`
507
+ # Add Command
508
+
509
+ \`\`\`bash
510
+ ${cmd}
511
+ \`\`\`
512
+
513
+ **Items:**
514
+ ${itemList}
515
+
516
+ **What this does:**
517
+ - Copies source files into \`${installDir}\`
518
+ - You own the code — no runtime package is added
519
+ ${isBlock ? "- The block includes a complete document layout ready to customize" : "- Each component gets its own subdirectory inside componentDir"}
520
+
521
+ **Before running:** make sure \`pdfx.json\` exists. Run \`npx pdfx-cli init\` if not.
522
+
523
+ **See source first:** call \`${inspectTool}\` with the item name to review the code before adding.
524
+
525
+ **After adding:** call \`get_audit_checklist\` to verify your setup is correct.
526
+ `);
527
+ }
528
+
529
+ // src/mcp/tools/audit.ts
530
+ import dedent2 from "dedent";
531
+ async function getAuditChecklist() {
532
+ return textResponse(dedent2`
533
+ # PDFx Setup Audit Checklist
534
+
535
+ Work through this after adding components or generating PDF document code.
536
+
537
+ ## Configuration
538
+ - [ ] \`pdfx.json\` exists in the project root
539
+ - [ ] \`componentDir\` path in \`pdfx.json\` is correct (default: \`./src/components/pdfx\`)
540
+ - [ ] Theme file exists at the path set in \`pdfx.json\` (default: \`./src/lib/pdfx-theme.ts\`)
541
+
542
+ ## Dependencies
543
+ - [ ] \`@react-pdf/renderer\` is installed — run \`npm ls @react-pdf/renderer\` to confirm
544
+ - [ ] Version is ≥ 3.0.0 (PDFx requires react-pdf v3+)
545
+
546
+ ## Imports
547
+ - [ ] PDFx components use **named exports**: \`import { Table, Heading } from '@/components/pdfx/...'\`
548
+ - [ ] \`Document\` and \`Page\` are imported from \`@react-pdf/renderer\`, not from PDFx
549
+ - [ ] No Tailwind classes, CSS variables, or DOM APIs are used inside PDF components
550
+ - [ ] All styles use \`StyleSheet.create({})\` from \`@react-pdf/renderer\`
551
+
552
+ ## Rendering
553
+ - [ ] The root PDF component is **not** inside a React Server Component
554
+ - [ ] Using \`renderToBuffer\`, \`PDFViewer\`, or \`PDFDownloadLink\` to render the document
555
+ - [ ] Root component returns \`<Document><Page>...</Page></Document>\`
556
+ - [ ] No console errors about missing fonts
557
+
558
+ ## TypeScript
559
+ - [ ] No TypeScript errors in component files
560
+ - [ ] Theme is typed as \`PdfxTheme\` (imported as a type from \`@pdfx/shared\`)
561
+
562
+ ---
563
+
564
+ ## Common Issues & Fixes
565
+
566
+ ### "Cannot find module @/components/pdfx/..."
567
+ The component hasn't been added yet. Run:
568
+ \`\`\`bash
569
+ npx pdfx-cli add <component-name>
570
+ \`\`\`
571
+
572
+ ### "Invalid hook call"
573
+ PDFx components render to PDF, not to the DOM — React hooks are not supported inside them.
574
+ Move hook calls to the parent component and pass data down as props.
575
+
576
+ ### "Text strings must be rendered inside \`<Text>\` component"
577
+ Wrap all string literals in \`<Text>\` from \`@react-pdf/renderer\`:
578
+ \`\`\`tsx
579
+ import { Text } from '@react-pdf/renderer';
580
+ // ✗ Wrong: <View>Hello</View>
581
+ // ✓ Correct: <View><Text>Hello</Text></View>
582
+ \`\`\`
583
+
584
+ ### Fonts not loading / rendering as a fallback
585
+ Register custom fonts in your theme file:
586
+ \`\`\`tsx
587
+ import { Font } from '@react-pdf/renderer';
588
+
589
+ Font.register({
590
+ family: 'Inter',
591
+ src: 'https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2',
592
+ });
593
+ \`\`\`
594
+
595
+ ### PDF renders blank or empty
596
+ Ensure your root component returns a \`<Document>\` with at least one \`<Page>\` inside:
597
+ \`\`\`tsx
598
+ import { Document, Page } from '@react-pdf/renderer';
599
+
600
+ export function MyDocument() {
601
+ return (
602
+ <Document>
603
+ <Page size="A4">
604
+ {/* content here */}
605
+ </Page>
606
+ </Document>
607
+ );
608
+ }
609
+ \`\`\`
610
+
611
+ ### @react-pdf/renderer TypeScript errors
612
+ Install the types package:
613
+ \`\`\`bash
614
+ npm install --save-dev @react-pdf/types
615
+ \`\`\`
616
+
617
+ ---
618
+
619
+ ## @react-pdf/renderer Layout Constraints
620
+
621
+ These are **fundamental PDF rendering limitations** — they cannot be fixed with CSS-like
622
+ style tweaks. Understanding them will save hours of debugging.
623
+
624
+ ### ⚠️ CRITICAL: Do NOT mix \`<View>\` and \`<Text>\` in the same flex row
625
+
626
+ In HTML, inline elements (spans, badges) can sit next to block text freely.
627
+ In \`@react-pdf/renderer\`, \`View\` and \`Text\` are fundamentally different node types.
628
+ Placing a \`View\`-based component (e.g. \`<Badge>\`, \`<PdfAlert>\`) **inline** alongside
629
+ a \`<Text>\` node in the same flex row causes irrecoverable misalignment, overlap, and
630
+ overflow that no amount of padding, margin, or \`alignItems\` can fix.
631
+
632
+ **Wrong — will cause layout corruption:**
633
+ \`\`\`tsx
634
+ {/* Badge is a View; Text is a Text node — they CANNOT share a flex row */}
635
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
636
+ <Text>INV-2026-001</Text>
637
+ <Badge label="PAID" variant="success" />
638
+ </View>
639
+ \`\`\`
640
+
641
+ **Correct — place View-based components on their own line:**
642
+ \`\`\`tsx
643
+ <View style={{ flexDirection: 'column', gap: 2 }}>
644
+ <Text>INV-2026-001</Text>
645
+ <Badge label="PAID" variant="success" />
646
+ </View>
647
+ \`\`\`
648
+
649
+ PDFx components that are \`View\`-based (cannot be mixed inline with \`<Text>\`):
650
+ \`Badge\`, \`PdfAlert\`, \`Card\`, \`Divider\`, \`KeyValue\`, \`Section\`, \`Table\`, \`DataTable\`,
651
+ \`PdfGraph\`, \`PdfImage\`, \`PdfSignatureBlock\`, \`PdfList\`
652
+
653
+ ### No \`position: absolute\` stacking inside \`<Text>\` nodes
654
+ Absolute positioning works on \`View\` elements but not inside \`Text\` runs.
655
+
656
+ ### \`gap\` only works between \`View\` siblings
657
+ Use \`gap\` on a \`View\` container whose children are all \`View\` elements.
658
+ If any child is a raw \`Text\` node, use \`marginBottom\` on siblings instead.
659
+
660
+ ### No percentage-based font sizes
661
+ \`@react-pdf/renderer\` requires numeric pt/px values for \`fontSize\`. Do not use strings like \`"1rem"\` or \`"120%"\`.
662
+ `);
663
+ }
664
+
665
+ // src/mcp/tools/blocks.ts
666
+ import dedent3 from "dedent";
667
+ import { z as z3 } from "zod";
668
+ var listBlocksSchema = z3.object({});
669
+ async function listBlocks() {
670
+ const items = await fetchRegistryIndex();
671
+ const blocks = items.filter((i) => i.type === "registry:block");
672
+ const invoices = blocks.filter((b) => b.name.startsWith("invoice-"));
673
+ const reports = blocks.filter((b) => b.name.startsWith("report-"));
674
+ const others = blocks.filter(
675
+ (b) => !b.name.startsWith("invoice-") && !b.name.startsWith("report-")
676
+ );
677
+ const formatBlock = (b) => {
678
+ const peers = b.peerComponents?.length ? ` _(requires: ${b.peerComponents.join(", ")})_` : "";
679
+ return `- **${b.name}** \u2014 ${b.description ?? "No description"}${peers}`;
680
+ };
681
+ const sections = [];
682
+ if (invoices.length > 0) {
683
+ sections.push(
684
+ `### Invoice Blocks (${invoices.length})
685
+ ${invoices.map(formatBlock).join("\n")}`
686
+ );
687
+ }
688
+ if (reports.length > 0) {
689
+ sections.push(`### Report Blocks (${reports.length})
690
+ ${reports.map(formatBlock).join("\n")}`);
691
+ }
692
+ if (others.length > 0) {
693
+ sections.push(`### Other Blocks (${others.length})
694
+ ${others.map(formatBlock).join("\n")}`);
695
+ }
696
+ return textResponse(dedent3`
697
+ # PDFx Blocks (${blocks.length})
698
+
699
+ Blocks are complete, copy-paste ready document layouts. Unlike components, they are full documents ready to customize.
700
+
701
+ ${sections.join("\n\n")}
702
+
703
+ ---
704
+ Add a block: \`npx pdfx-cli block add <name>\`
705
+ See full source: call \`get_block\` with the block name
706
+ `);
707
+ }
708
+ var getBlockSchema = z3.object({
709
+ block: z3.string().min(1).describe("Block name, e.g. 'invoice-modern', 'report-financial'")
710
+ });
711
+ async function getBlock(args) {
712
+ const item = await fetchRegistryItem(args.block, BLOCKS_BASE);
713
+ const fileList = item.files.map((f) => `- \`${f.path}\``).join("\n");
714
+ const peers = item.peerComponents?.length ? item.peerComponents.join(", ") : "none";
715
+ const fileSources = item.files.map(
716
+ (f) => dedent3`
717
+ ### \`${f.path}\`
718
+ \`\`\`tsx
719
+ ${f.content}
720
+ \`\`\`
721
+ `
722
+ ).join("\n\n");
723
+ return textResponse(dedent3`
724
+ # ${item.title ?? item.name}
725
+
726
+ ${item.description ?? ""}
727
+
728
+ ## Files
729
+ ${fileList}
730
+
731
+ ## Required PDFx Components
732
+ ${peers}
733
+
734
+ ## Add Command
735
+ \`\`\`bash
736
+ npx pdfx-cli block add ${args.block}
737
+ \`\`\`
738
+
739
+ ## Source Code
740
+ ${fileSources}
741
+ `);
742
+ }
743
+
744
+ // src/mcp/tools/components.ts
745
+ import dedent4 from "dedent";
746
+ import { z as z4 } from "zod";
747
+ var listComponentsSchema = z4.object({});
748
+ async function listComponents() {
749
+ const items = await fetchRegistryIndex();
750
+ const components = items.filter((i) => i.type === "registry:ui");
751
+ const rows = components.map((c) => `- **${c.name}** \u2014 ${c.description ?? "No description"}`).join("\n");
752
+ return textResponse(dedent4`
753
+ # PDFx Components (${components.length})
754
+
755
+ ${rows}
756
+
757
+ ---
758
+ Add a component: \`npx pdfx-cli add <name>\`
759
+ See full source, props, and exact export name: call \`get_component\` with the component name
760
+ `);
761
+ }
762
+ var getComponentSchema = z4.object({
763
+ component: z4.string().min(1).describe("Component name, e.g. 'table', 'heading', 'data-table'")
764
+ });
765
+ async function getComponent(args) {
766
+ const item = await fetchRegistryItem(args.component);
767
+ const fileList = item.files.map((f) => `- \`${f.path}\``).join("\n");
768
+ const deps = item.dependencies?.length ? item.dependencies.join(", ") : "none";
769
+ const devDeps = item.devDependencies?.length ? item.devDependencies.join(", ") : "none";
770
+ const registryDeps = item.registryDependencies?.length ? item.registryDependencies.join(", ") : "none";
771
+ const primaryContent = item.files[0]?.content ?? "";
772
+ const primaryPath = item.files[0]?.path ?? "";
773
+ const exportNames = extractAllExportNames(primaryContent);
774
+ const mainExport = extractExportName(primaryContent);
775
+ const exportSection = exportNames.length > 0 ? dedent4`
776
+ ## Exports
777
+ **Main component export:** \`${mainExport ?? exportNames[0]}\`
778
+
779
+ All named exports from \`${primaryPath}\`:
780
+ ${exportNames.map((n) => `- \`${n}\``).join("\n")}
781
+
782
+ **Import after \`npx pdfx-cli@latest add ${args.component}\`:**
783
+ \`\`\`tsx
784
+ import { ${mainExport ?? exportNames[0]} } from './components/pdfx/${args.component}/pdfx-${args.component}';
785
+ \`\`\`
786
+ ` : "";
787
+ const fileSources = item.files.map(
788
+ (f) => dedent4`
789
+ ### \`${f.path}\`
790
+ \`\`\`tsx
791
+ ${f.content}
792
+ \`\`\`
793
+ `
794
+ ).join("\n\n");
795
+ return textResponse(dedent4`
796
+ # ${item.title ?? item.name}
797
+
798
+ ${item.description ?? ""}
799
+
800
+ ## Files
801
+ ${fileList}
802
+
803
+ ## Dependencies
804
+ - Runtime: ${deps}
805
+ - Dev: ${devDeps}
806
+ - Other PDFx components required: ${registryDeps}
807
+
808
+ ${exportSection}
809
+
810
+ ## Add Command
811
+ \`\`\`bash
812
+ npx pdfx-cli add ${args.component}
813
+ \`\`\`
814
+
815
+ ## Source Code
816
+ ${fileSources}
817
+ `);
818
+ }
819
+ function extractExportName(source) {
820
+ if (!source) return null;
821
+ const matches = [...source.matchAll(/export\s+(?:function|const)\s+([A-Z][A-Za-z0-9]*)/g)];
822
+ if (matches.length === 0) return null;
823
+ return matches[0][1] ?? null;
824
+ }
825
+ function extractAllExportNames(source) {
826
+ const seen = /* @__PURE__ */ new Set();
827
+ const results = [];
828
+ for (const m of source.matchAll(/export\s+(?:function|const|class)\s+([A-Za-z][A-Za-z0-9]*)/g)) {
829
+ const name = m[1];
830
+ if (name && !seen.has(name)) {
831
+ seen.add(name);
832
+ results.push(name);
833
+ }
834
+ }
835
+ for (const m of source.matchAll(/export\s+\{([^}]+)\}/g)) {
836
+ for (const part of m[1].split(",")) {
837
+ const name = part.trim().split(/\s+as\s+/).pop()?.trim();
838
+ if (name && /^[A-Za-z]/.test(name) && !seen.has(name)) {
839
+ seen.add(name);
840
+ results.push(name);
841
+ }
842
+ }
843
+ }
844
+ return results;
845
+ }
846
+
847
+ // src/mcp/tools/installation.ts
848
+ import dedent5 from "dedent";
849
+ import { z as z5 } from "zod";
850
+ var getInstallationSchema = z5.object({
851
+ framework: z5.enum(["nextjs", "react", "vite", "remix", "other"]).describe("Target framework"),
852
+ package_manager: z5.enum(["npm", "pnpm", "yarn", "bun"]).describe("Package manager to use")
853
+ });
854
+ function installCmd(pm, pkg, dev = false) {
855
+ const base = { npm: "npm install", pnpm: "pnpm add", yarn: "yarn add", bun: "bun add" }[pm];
856
+ const devFlag = { npm: "--save-dev", pnpm: "-D", yarn: "--dev", bun: "-d" }[pm];
857
+ return dev ? `${base} ${devFlag} ${pkg}` : `${base} ${pkg}`;
858
+ }
859
+ var FRAMEWORK_NOTES = {
860
+ nextjs: dedent5`
861
+ ## Next.js Notes
862
+
863
+ **App Router** — Use a Route Handler to serve PDFs:
864
+ \`\`\`tsx
865
+ // app/api/pdf/route.ts
866
+ import { renderToBuffer } from '@react-pdf/renderer';
867
+ import { MyDocument } from '@/components/pdfx/my-document';
868
+
869
+ export async function GET() {
870
+ const buffer = await renderToBuffer(<MyDocument />);
871
+ return new Response(buffer, {
872
+ headers: { 'Content-Type': 'application/pdf' },
873
+ });
874
+ }
875
+ \`\`\`
876
+
877
+ **Important:** Do NOT render PDFx components inside React Server Components.
878
+ Always use a \`'use client'\` boundary or a Route Handler.
879
+
880
+ **Pages Router** — Use an API route:
881
+ \`\`\`tsx
882
+ // pages/api/pdf.ts
883
+ import type { NextApiRequest, NextApiResponse } from 'next';
884
+ import { renderToBuffer } from '@react-pdf/renderer';
885
+ import { MyDocument } from '@/components/pdfx/my-document';
886
+
887
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
888
+ const buffer = await renderToBuffer(<MyDocument />);
889
+ res.setHeader('Content-Type', 'application/pdf');
890
+ res.send(buffer);
891
+ }
892
+ \`\`\`
893
+ `,
894
+ react: dedent5`
895
+ ## React Notes
896
+
897
+ Display a PDF inline with \`PDFViewer\`:
898
+ \`\`\`tsx
899
+ import { PDFViewer } from '@react-pdf/renderer';
900
+ import { MyDocument } from './components/pdfx/my-document';
901
+
902
+ export function App() {
903
+ return (
904
+ <PDFViewer width="100%" height="600px">
905
+ <MyDocument />
906
+ </PDFViewer>
907
+ );
908
+ }
909
+ \`\`\`
910
+
911
+ Trigger a download with \`PDFDownloadLink\`:
912
+ \`\`\`tsx
913
+ import { PDFDownloadLink } from '@react-pdf/renderer';
914
+ import { MyDocument } from './components/pdfx/my-document';
915
+
916
+ export function DownloadButton() {
917
+ return (
918
+ <PDFDownloadLink document={<MyDocument />} fileName="document.pdf">
919
+ {({ loading }) => (loading ? 'Generating PDF...' : 'Download PDF')}
920
+ </PDFDownloadLink>
921
+ );
922
+ }
923
+ \`\`\`
924
+ `,
925
+ vite: dedent5`
926
+ ## Vite Notes
927
+
928
+ Works with both \`vite + react\` and \`vite + react-swc\` templates.
929
+
930
+ For client-side rendering, use \`PDFViewer\` or \`PDFDownloadLink\` from \`@react-pdf/renderer\`.
931
+
932
+ For server-side generation, use a separate Node.js server or Vite's server-side features.
933
+ `,
934
+ remix: dedent5`
935
+ ## Remix Notes
936
+
937
+ Use a resource route to serve PDFs:
938
+ \`\`\`tsx
939
+ // app/routes/pdf.tsx
940
+ import { renderToStream } from '@react-pdf/renderer';
941
+ import { MyDocument } from '~/components/pdfx/my-document';
942
+
943
+ export async function loader() {
944
+ const stream = await renderToStream(<MyDocument />);
945
+ return new Response(stream as unknown as ReadableStream, {
946
+ headers: { 'Content-Type': 'application/pdf' },
947
+ });
948
+ }
949
+ \`\`\`
950
+ `,
951
+ other: dedent5`
952
+ ## General Notes
953
+
954
+ \`@react-pdf/renderer\` works in any Node.js ≥ 18 environment.
955
+
956
+ - **Buffer output**: \`await renderToBuffer(<MyDocument />)\`
957
+ - **Stream output**: \`await renderToStream(<MyDocument />)\`
958
+ - **Client-side**: Use \`PDFViewer\` or \`PDFDownloadLink\` from \`@react-pdf/renderer\`
959
+ `
960
+ };
961
+ async function getInstallation(args) {
962
+ const pm = args.package_manager;
963
+ const fw = args.framework;
964
+ return textResponse(dedent5`
965
+ # PDFx Setup Guide: ${fw} + ${pm}
966
+
967
+ ## Step 1 — Install the peer dependency
968
+
969
+ \`\`\`bash
970
+ ${installCmd(pm, "@react-pdf/renderer")}
971
+ \`\`\`
972
+
973
+ ## Step 2 — Initialize PDFx in your project
974
+
975
+ \`\`\`bash
976
+ npx pdfx-cli init
977
+ \`\`\`
978
+
979
+ This creates \`pdfx.json\` in your project root and generates a theme file at \`src/lib/pdfx-theme.ts\`.
980
+
981
+ ## Step 3 — Add your first component
982
+
983
+ \`\`\`bash
984
+ npx pdfx-cli add heading text table
985
+ \`\`\`
986
+
987
+ Components are copied into \`src/components/pdfx/\`. You own the source — there is no runtime package dependency.
988
+
989
+ ## Step 4 — Or start with a complete document block
990
+
991
+ \`\`\`bash
992
+ npx pdfx-cli block add invoice-modern
993
+ \`\`\`
994
+
995
+ ${FRAMEWORK_NOTES[fw]}
996
+
997
+ ## Generated pdfx.json
998
+
999
+ \`\`\`json
1000
+ {
1001
+ "$schema": "https://pdfx.akashpise.dev/schema.json",
1002
+ "componentDir": "./src/components/pdfx",
1003
+ "blockDir": "./src/blocks/pdfx",
1004
+ "registry": "https://pdfx.akashpise.dev/r",
1005
+ "theme": "./src/lib/pdfx-theme.ts"
1006
+ }
1007
+ \`\`\`
1008
+
1009
+ ## pdfx.json Field Reference
1010
+
1011
+ All four fields are **required**. Relative paths must start with \`./\` or \`../\`.
1012
+
1013
+ | Field | Type | Description | Default |
1014
+ |-------|------|-------------|---------|
1015
+ | \`componentDir\` | string | Where individual components are installed | \`./src/components/pdfx\` |
1016
+ | \`blockDir\` | string | Where full document blocks are installed | \`./src/blocks/pdfx\` |
1017
+ | \`registry\` | string (URL) | Registry base URL (must start with http) | \`https://pdfx.akashpise.dev/r\` |
1018
+ | \`theme\` | string | Path to your generated theme file | \`./src/lib/pdfx-theme.ts\` |
1019
+
1020
+ > **Non-interactive init (CI / AI agents):** pass \`--yes\` to accept all defaults:
1021
+ > \`\`\`bash
1022
+ > npx pdfx-cli init --yes
1023
+ > \`\`\`
1024
+
1025
+ ## Troubleshooting
1026
+
1027
+ | Problem | Fix |
1028
+ |---------|-----|
1029
+ | TypeScript errors on \`@react-pdf/renderer\` | \`${installCmd(pm, "@react-pdf/types", true)}\` |
1030
+ | "Cannot find module @/components/pdfx/..." | Run \`npx pdfx-cli@latest add <component>\` to install it |
1031
+ | PDF renders blank | Ensure root returns \`<Document><Page>...</Page></Document>\` |
1032
+ | "Invalid hook call" | PDFx components cannot use React hooks — pass data as props |
1033
+
1034
+ ---
1035
+ Next: call \`get_audit_checklist\` to verify your setup is correct.
1036
+ `);
1037
+ }
1038
+
1039
+ // src/mcp/tools/search.ts
1040
+ import dedent6 from "dedent";
1041
+ import { z as z6 } from "zod";
1042
+ var searchRegistrySchema = z6.object({
1043
+ query: z6.string().min(1).describe("Search query \u2014 matched against component name, title, and description"),
1044
+ type: z6.enum(["all", "component", "block"]).optional().default("all").describe("Filter by item type (default: all)"),
1045
+ limit: z6.number().int().positive().max(50).optional().default(20).describe("Maximum number of results to return (default: 20, max: 50)")
1046
+ });
1047
+ async function searchRegistry(args) {
1048
+ const items = await fetchRegistryIndex();
1049
+ const q = args.query.toLowerCase();
1050
+ let pool = items;
1051
+ if (args.type === "component") {
1052
+ pool = items.filter((i) => i.type === "registry:ui");
1053
+ } else if (args.type === "block") {
1054
+ pool = items.filter((i) => i.type === "registry:block");
1055
+ }
1056
+ const scored = pool.map((item) => {
1057
+ const name = item.name.toLowerCase();
1058
+ const title = (item.title ?? "").toLowerCase();
1059
+ const desc = (item.description ?? "").toLowerCase();
1060
+ let score = 0;
1061
+ if (name === q) score = 100;
1062
+ else if (name.startsWith(q)) score = 80;
1063
+ else if (name.includes(q)) score = 60;
1064
+ else if (title.includes(q)) score = 40;
1065
+ else if (desc.includes(q)) score = 20;
1066
+ return { item, score };
1067
+ }).filter((r) => r.score > 0).sort((a, b) => b.score - a.score).slice(0, args.limit);
1068
+ if (scored.length === 0) {
1069
+ return textResponse(dedent6`
1070
+ # Search: "${args.query}"
1071
+
1072
+ No results found. Try a broader query or browse all items:
1073
+ - \`list_components\` — see all 24 components
1074
+ - \`list_blocks\` — see all 10 blocks
1075
+ `);
1076
+ }
1077
+ const rows = scored.map(({ item }) => {
1078
+ const typeLabel2 = item.type === "registry:ui" ? "component" : "block";
1079
+ const addCmd = item.type === "registry:ui" ? `npx pdfx-cli add ${item.name}` : `npx pdfx-cli block add ${item.name}`;
1080
+ return dedent6`
1081
+ - **${item.name}** _(${typeLabel2})_ — ${item.description ?? "No description"}
1082
+ \`${addCmd}\`
1083
+ `;
1084
+ });
1085
+ const typeLabel = args.type === "all" ? "components + blocks" : args.type === "component" ? "components" : "blocks";
1086
+ return textResponse(dedent6`
1087
+ # Search: "${args.query}" — ${scored.length} result${scored.length === 1 ? "" : "s"} (${typeLabel})
1088
+
1089
+ ${rows.join("\n")}
1090
+ `);
1091
+ }
1092
+
1093
+ // src/mcp/tools/theme.ts
1094
+ import dedent7 from "dedent";
1095
+ import { z as z7 } from "zod";
1096
+ var getThemeSchema = z7.object({
1097
+ theme: z7.enum(["professional", "modern", "minimal"]).describe("Theme preset name")
1098
+ });
1099
+ async function getTheme(args) {
1100
+ const preset = themePresets[args.theme];
1101
+ const { colors, typography, spacing, page } = preset;
1102
+ return textResponse(dedent7`
1103
+ # PDFx Theme: ${args.theme}
1104
+
1105
+ ## Colors
1106
+ | Token | Value |
1107
+ |-------|-------|
1108
+ | foreground | \`${colors.foreground}\` |
1109
+ | background | \`${colors.background}\` |
1110
+ | primary | \`${colors.primary}\` |
1111
+ | primaryForeground | \`${colors.primaryForeground}\` |
1112
+ | accent | \`${colors.accent}\` |
1113
+ | muted | \`${colors.muted}\` |
1114
+ | mutedForeground | \`${colors.mutedForeground}\` |
1115
+ | border | \`${colors.border}\` |
1116
+ | destructive | \`${colors.destructive}\` |
1117
+ | success | \`${colors.success}\` |
1118
+ | warning | \`${colors.warning}\` |
1119
+ | info | \`${colors.info}\` |
1120
+
1121
+ ## Typography
1122
+
1123
+ ### Body
1124
+ - Font family: \`${typography.body.fontFamily}\`
1125
+ - Font size: ${typography.body.fontSize}pt
1126
+ - Line height: ${typography.body.lineHeight}
1127
+
1128
+ ### Headings
1129
+ - Font family: \`${typography.heading.fontFamily}\`
1130
+ - Font weight: ${typography.heading.fontWeight}
1131
+ - Line height: ${typography.heading.lineHeight}
1132
+ - h1: ${typography.heading.fontSize.h1}pt
1133
+ - h2: ${typography.heading.fontSize.h2}pt
1134
+ - h3: ${typography.heading.fontSize.h3}pt
1135
+ - h4: ${typography.heading.fontSize.h4}pt
1136
+ - h5: ${typography.heading.fontSize.h5}pt
1137
+ - h6: ${typography.heading.fontSize.h6}pt
1138
+
1139
+ ## Spacing
1140
+ - Page margins: top=${spacing.page.marginTop}pt · right=${spacing.page.marginRight}pt · bottom=${spacing.page.marginBottom}pt · left=${spacing.page.marginLeft}pt
1141
+ - Section gap: ${spacing.sectionGap}pt
1142
+ - Paragraph gap: ${spacing.paragraphGap}pt
1143
+ - Component gap: ${spacing.componentGap}pt
1144
+
1145
+ ## Page
1146
+ - Size: ${page.size}
1147
+ - Orientation: ${page.orientation}
1148
+
1149
+ ## Apply This Theme
1150
+ \`\`\`bash
1151
+ npx pdfx-cli theme switch ${args.theme}
1152
+ \`\`\`
1153
+
1154
+ ## Usage in Components
1155
+ \`\`\`tsx
1156
+ // Access theme values in a PDFx component
1157
+ import type { PdfxTheme } from '@pdfx/shared';
1158
+
1159
+ interface Props {
1160
+ theme: PdfxTheme;
1161
+ }
1162
+
1163
+ export function MyComponent({ theme }: Props) {
1164
+ return (
1165
+ <View style={{ backgroundColor: theme.colors.background }}>
1166
+ <Text style={{ color: theme.colors.foreground, fontSize: theme.typography.body.fontSize }}>
1167
+ Content
1168
+ </Text>
1169
+ </View>
1170
+ );
1171
+ }
1172
+ \`\`\`
1173
+ `);
1174
+ }
1175
+
1176
+ // src/mcp/index.ts
1177
+ var server = new Server(
1178
+ { name: "pdfx", version: "1.0.0" },
1179
+ { capabilities: { tools: {} } }
1180
+ );
1181
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1182
+ tools: [
1183
+ {
1184
+ name: "list_components",
1185
+ description: "List all available PDFx PDF components with names and descriptions. Call this first to discover what components exist before adding any.",
1186
+ inputSchema: zodToJsonSchema(listComponentsSchema)
1187
+ },
1188
+ {
1189
+ name: "get_component",
1190
+ description: "Get the full source code, files, and dependencies for a specific PDFx component. Use this to understand the API and props before using it in generated code.",
1191
+ inputSchema: zodToJsonSchema(getComponentSchema)
1192
+ },
1193
+ {
1194
+ name: "list_blocks",
1195
+ description: "List all PDFx pre-built document blocks (complete invoice and report layouts ready to customize).",
1196
+ inputSchema: zodToJsonSchema(listBlocksSchema)
1197
+ },
1198
+ {
1199
+ name: "get_block",
1200
+ description: "Get the full source code for a PDFx document block. Returns the complete layout code ready to customize for your use case.",
1201
+ inputSchema: zodToJsonSchema(getBlockSchema)
1202
+ },
1203
+ {
1204
+ name: "search_registry",
1205
+ description: "Search PDFx components and blocks by name or description. Use this when you know what you need but not the exact item name.",
1206
+ inputSchema: zodToJsonSchema(searchRegistrySchema)
1207
+ },
1208
+ {
1209
+ name: "get_theme",
1210
+ description: "Get the full design token values for a PDFx theme preset (professional, modern, or minimal). Use this to understand colors, typography, and spacing before customizing documents.",
1211
+ inputSchema: zodToJsonSchema(getThemeSchema)
1212
+ },
1213
+ {
1214
+ name: "get_installation",
1215
+ description: "Get step-by-step PDFx setup instructions for a specific framework and package manager. Use this when setting up PDFx in a new project.",
1216
+ inputSchema: zodToJsonSchema(getInstallationSchema)
1217
+ },
1218
+ {
1219
+ name: "get_add_command",
1220
+ description: "Get the exact CLI command string to add specific PDFx components or blocks to a project.",
1221
+ inputSchema: zodToJsonSchema(getAddCommandSchema)
1222
+ },
1223
+ {
1224
+ name: "get_audit_checklist",
1225
+ description: "Get a post-generation checklist to verify PDFx is set up correctly. Call this after adding components or generating PDF document code.",
1226
+ inputSchema: zodToJsonSchema(z8.object({}))
1227
+ }
1228
+ ]
1229
+ }));
1230
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1231
+ const args = request.params.arguments ?? {};
1232
+ try {
1233
+ switch (request.params.name) {
1234
+ case "list_components":
1235
+ return await listComponents();
1236
+ case "get_component":
1237
+ return await getComponent(getComponentSchema.parse(args));
1238
+ case "list_blocks":
1239
+ return await listBlocks();
1240
+ case "get_block":
1241
+ return await getBlock(getBlockSchema.parse(args));
1242
+ case "search_registry":
1243
+ return await searchRegistry(searchRegistrySchema.parse(args));
1244
+ case "get_theme":
1245
+ return await getTheme(getThemeSchema.parse(args));
1246
+ case "get_installation":
1247
+ return await getInstallation(getInstallationSchema.parse(args));
1248
+ case "get_add_command":
1249
+ return await getAddCommand(getAddCommandSchema.parse(args));
1250
+ case "get_audit_checklist":
1251
+ return await getAuditChecklist();
1252
+ default:
1253
+ return errorResponse(new Error(`Unknown tool: ${request.params.name}`));
1254
+ }
1255
+ } catch (error) {
1256
+ return errorResponse(error);
1257
+ }
1258
+ });
1259
+ export {
1260
+ server
1261
+ };