pptx-viewer-mcp 1.1.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,2337 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { z } from 'zod';
5
+ import { PptxHandler, hasTextProperties, PptxMarkdownConverter } from 'pptx-viewer-core';
6
+ import { writeFile, readFile } from 'fs/promises';
7
+
8
+ var GetSlideSchema = z.object({
9
+ filePath: z.string().describe("Path to the PPTX file"),
10
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index")
11
+ });
12
+ var AddSlideSchema = z.object({
13
+ filePath: z.string().describe("Path to the PPTX file"),
14
+ insertAfterIndex: z.number().int().min(0).optional().describe("Insert after this slide index"),
15
+ backgroundColor: z.string().optional().describe("Background color (hex)")
16
+ });
17
+ var DeleteSlidesSchema = z.object({
18
+ filePath: z.string().describe("Path to the PPTX file"),
19
+ slideIndexes: z.array(z.number().int().min(0)).min(1).describe("Slide indexes to delete")
20
+ });
21
+ var ReorderSlidesSchema = z.object({
22
+ filePath: z.string().describe("Path to the PPTX file"),
23
+ newOrder: z.array(z.number().int().min(0)).min(1).describe("New slide order as array of indexes")
24
+ });
25
+ var DuplicateSlideSchema = z.object({
26
+ filePath: z.string().describe("Path to the PPTX file"),
27
+ slideIndex: z.number().int().min(0).describe("Slide to duplicate"),
28
+ targetIndex: z.number().int().min(0).optional().describe("Where to insert the duplicate")
29
+ });
30
+ var UpdateSlidePropertiesSchema = z.object({
31
+ filePath: z.string().describe("Path to the PPTX file"),
32
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
33
+ backgroundColor: z.string().optional().describe("Background color (hex)"),
34
+ backgroundGradient: z.string().optional().describe("Background gradient CSS"),
35
+ backgroundImage: z.string().optional().describe("Background image data URL"),
36
+ notes: z.string().optional().describe("Speaker notes text"),
37
+ hidden: z.boolean().optional().describe("Whether slide is hidden")
38
+ });
39
+ var SetSlideTransitionSchema = z.object({
40
+ filePath: z.string().describe("Path to the PPTX file"),
41
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
42
+ type: z.string().describe('Transition type (e.g. "fade", "wipe", "none")'),
43
+ durationMs: z.number().int().min(0).optional().describe("Duration in ms"),
44
+ direction: z.string().optional().describe('Direction (e.g. "left", "right")'),
45
+ advanceOnClick: z.boolean().optional().describe("Advance on mouse click"),
46
+ advanceAfterMs: z.number().int().min(0).optional().describe("Auto-advance after ms")
47
+ });
48
+ var SetCanvasSizeSchema = z.object({
49
+ filePath: z.string().describe("Path to the PPTX file"),
50
+ width: z.number().positive().describe("Canvas width in points"),
51
+ height: z.number().positive().describe("Canvas height in points")
52
+ });
53
+ var ConvertToMarkdownSchema = z.object({
54
+ filePath: z.string().describe("Path to the PPTX file"),
55
+ outputDir: z.string().optional().describe("Output directory for markdown and media"),
56
+ mediaFolderName: z.string().optional().describe("Name for extracted media folder"),
57
+ includeMetadata: z.boolean().optional().describe("Include presentation metadata"),
58
+ slideRange: z.object({
59
+ start: z.number().int().min(0).optional(),
60
+ end: z.number().int().min(0).optional()
61
+ }).optional().describe("Slide range to convert"),
62
+ includeSpeakerNotes: z.boolean().optional().describe("Include speaker notes"),
63
+ semanticMode: z.boolean().optional().describe("Use semantic (clean) markdown mode")
64
+ });
65
+ var AccessibilityCheckSchema = z.object({
66
+ filePath: z.string().describe("Path to the PPTX file")
67
+ });
68
+ var AddElementSchema = z.object({
69
+ filePath: z.string().describe("Path to the PPTX file"),
70
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
71
+ type: z.enum(["text", "shape", "image", "table", "connector"]).describe("Element type to add"),
72
+ x: z.number().optional().describe("X position in points"),
73
+ y: z.number().optional().describe("Y position in points"),
74
+ width: z.number().optional().describe("Width in points"),
75
+ height: z.number().optional().describe("Height in points"),
76
+ text: z.string().optional().describe("Text content"),
77
+ fontSize: z.number().optional().describe("Font size in points"),
78
+ fontFamily: z.string().optional().describe("Font family name"),
79
+ fontColor: z.string().optional().describe("Font color (hex)"),
80
+ bold: z.boolean().optional().describe("Bold text"),
81
+ italic: z.boolean().optional().describe("Italic text"),
82
+ underline: z.boolean().optional().describe("Underline text"),
83
+ alignment: z.enum(["left", "center", "right", "justify"]).optional().describe("Text alignment"),
84
+ shapeType: z.string().optional().describe('Shape type (e.g. "rect", "ellipse")'),
85
+ fillColor: z.string().optional().describe("Fill color (hex)"),
86
+ strokeColor: z.string().optional().describe("Stroke color (hex)"),
87
+ strokeWidth: z.number().optional().describe("Stroke width in points"),
88
+ imageData: z.string().optional().describe("Image as base64 data URL"),
89
+ altText: z.string().optional().describe("Alt text for accessibility"),
90
+ rows: z.number().int().min(1).optional().describe("Number of table rows"),
91
+ columns: z.number().int().min(1).optional().describe("Number of table columns"),
92
+ cellData: z.array(z.array(z.string())).optional().describe("Table cell data as 2D array"),
93
+ headerRow: z.boolean().optional().describe("Style first row as header"),
94
+ startArrow: z.string().optional().describe("Start arrow type"),
95
+ endArrow: z.string().optional().describe("End arrow type"),
96
+ startShapeId: z.string().optional().describe("Connect start to shape ID"),
97
+ endShapeId: z.string().optional().describe("Connect end to shape ID")
98
+ });
99
+ var UpdateElementSchema = z.object({
100
+ filePath: z.string().describe("Path to the PPTX file"),
101
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
102
+ elementId: z.string().describe("Element ID to update"),
103
+ x: z.number().optional(),
104
+ y: z.number().optional(),
105
+ width: z.number().optional(),
106
+ height: z.number().optional(),
107
+ rotation: z.number().optional(),
108
+ text: z.string().optional(),
109
+ fontSize: z.number().optional(),
110
+ fontFamily: z.string().optional(),
111
+ fontColor: z.string().optional(),
112
+ bold: z.boolean().optional(),
113
+ italic: z.boolean().optional(),
114
+ underline: z.boolean().optional(),
115
+ alignment: z.enum(["left", "center", "right", "justify"]).optional(),
116
+ fillColor: z.string().optional(),
117
+ strokeColor: z.string().optional(),
118
+ strokeWidth: z.number().optional(),
119
+ opacity: z.number().min(0).max(1).optional(),
120
+ hidden: z.boolean().optional(),
121
+ flipH: z.boolean().optional(),
122
+ flipV: z.boolean().optional()
123
+ });
124
+ var DeleteElementsSchema = z.object({
125
+ filePath: z.string().describe("Path to the PPTX file"),
126
+ slideIndex: z.number().int().min(0),
127
+ elementIds: z.array(z.string()).min(1).describe("Element IDs to delete")
128
+ });
129
+ var ArrangeElementsSchema = z.object({
130
+ filePath: z.string().describe("Path to the PPTX file"),
131
+ slideIndex: z.number().int().min(0),
132
+ action: z.enum(["align", "reorderLayer"]),
133
+ elementIds: z.array(z.string()).optional().describe("Element IDs (for align)"),
134
+ alignment: z.enum(["left", "right", "top", "bottom", "centerH", "centerV"]).optional(),
135
+ elementId: z.string().optional().describe("Element ID (for reorderLayer)"),
136
+ layerAction: z.enum(["bringForward", "sendBackward", "bringToFront", "sendToBack"]).optional()
137
+ });
138
+ var CloneElementSchema = z.object({
139
+ filePath: z.string().describe("Path to the PPTX file"),
140
+ slideIndex: z.number().int().min(0),
141
+ elementId: z.string(),
142
+ targetSlideIndexes: z.array(z.number().int().min(0)).optional(),
143
+ offsetX: z.number().optional(),
144
+ offsetY: z.number().optional()
145
+ });
146
+ var SetElementAnimationSchema = z.object({
147
+ filePath: z.string().describe("Path to the PPTX file"),
148
+ slideIndex: z.number().int().min(0),
149
+ elementId: z.string(),
150
+ entrance: z.string().optional(),
151
+ exit: z.string().optional(),
152
+ durationMs: z.number().optional(),
153
+ delayMs: z.number().optional(),
154
+ order: z.number().optional()
155
+ });
156
+ var GroupElementsSchema = z.object({
157
+ filePath: z.string().describe("Path to the PPTX file"),
158
+ slideIndex: z.number().int().min(0),
159
+ elementIds: z.array(z.string()).min(2).describe("At least 2 element IDs to group")
160
+ });
161
+ var UngroupElementsSchema = z.object({
162
+ filePath: z.string().describe("Path to the PPTX file"),
163
+ slideIndex: z.number().int().min(0),
164
+ groupElementId: z.string().describe("Group element ID to ungroup")
165
+ });
166
+ var BatchUpdateElementsSchema = z.object({
167
+ filePath: z.string().describe("Path to the PPTX file"),
168
+ slideIndex: z.number().int().min(0),
169
+ elementIds: z.array(z.string()).min(1),
170
+ x: z.number().optional(),
171
+ y: z.number().optional(),
172
+ width: z.number().optional(),
173
+ height: z.number().optional(),
174
+ rotation: z.number().optional(),
175
+ opacity: z.number().min(0).max(1).optional(),
176
+ hidden: z.boolean().optional()
177
+ });
178
+ var UpdateTableCellsSchema = z.object({
179
+ filePath: z.string().describe("Path to the PPTX file"),
180
+ slideIndex: z.number().int().min(0),
181
+ elementId: z.string().describe("Table element ID"),
182
+ cells: z.array(
183
+ z.object({
184
+ row: z.number().int().min(0),
185
+ col: z.number().int().min(0),
186
+ text: z.string()
187
+ })
188
+ ).min(1).describe("Cells to update")
189
+ });
190
+ var ManageTableStructureSchema = z.object({
191
+ filePath: z.string().describe("Path to the PPTX file"),
192
+ slideIndex: z.number().int().min(0),
193
+ elementId: z.string().describe("Table element ID"),
194
+ action: z.enum(["insertRow", "deleteRow", "insertColumn", "deleteColumn"]),
195
+ position: z.number().int().min(0).optional().describe("Insertion position index"),
196
+ referenceIndex: z.number().int().min(0).optional(),
197
+ cellTexts: z.array(z.string()).optional()
198
+ });
199
+ var UpdateElementStyleSchema = z.object({
200
+ filePath: z.string().describe("Path to the PPTX file"),
201
+ slideIndex: z.number().int().min(0),
202
+ elementId: z.string(),
203
+ fillColor: z.string().optional(),
204
+ fillMode: z.string().optional(),
205
+ fillGradientStops: z.array(
206
+ z.object({
207
+ color: z.string(),
208
+ position: z.number(),
209
+ opacity: z.number().optional()
210
+ })
211
+ ).optional(),
212
+ fillGradientAngle: z.number().optional(),
213
+ fillGradientType: z.string().optional(),
214
+ fillOpacity: z.number().optional(),
215
+ strokeColor: z.string().optional(),
216
+ strokeWidth: z.number().optional(),
217
+ strokeDash: z.string().optional(),
218
+ strokeOpacity: z.number().optional(),
219
+ shadowColor: z.string().optional(),
220
+ shadowBlur: z.number().optional(),
221
+ shadowOffsetX: z.number().optional(),
222
+ shadowOffsetY: z.number().optional(),
223
+ shadowOpacity: z.number().optional(),
224
+ glowColor: z.string().optional(),
225
+ glowRadius: z.number().optional(),
226
+ glowOpacity: z.number().optional(),
227
+ softEdgeRadius: z.number().optional(),
228
+ cropLeft: z.number().optional(),
229
+ cropTop: z.number().optional(),
230
+ cropRight: z.number().optional(),
231
+ cropBottom: z.number().optional(),
232
+ brightness: z.number().optional(),
233
+ contrast: z.number().optional(),
234
+ grayscale: z.boolean().optional(),
235
+ altText: z.string().optional()
236
+ });
237
+ var FindTextSchema = z.object({
238
+ filePath: z.string().describe("Path to the PPTX file"),
239
+ query: z.string().describe("Text to search for"),
240
+ useRegex: z.boolean().optional().describe("Treat query as regex"),
241
+ caseSensitive: z.boolean().optional().describe("Case-sensitive search"),
242
+ slideIndexes: z.array(z.number().int().min(0)).optional().describe("Limit search to specific slides")
243
+ });
244
+ var ReplaceTextSchema = z.object({
245
+ filePath: z.string().describe("Path to the PPTX file"),
246
+ query: z.string().describe("Text to find"),
247
+ replacement: z.string().describe("Replacement text"),
248
+ useRegex: z.boolean().optional(),
249
+ caseSensitive: z.boolean().optional(),
250
+ slideIndexes: z.array(z.number().int().min(0)).optional()
251
+ });
252
+ var ManageCommentsSchema = z.object({
253
+ filePath: z.string().describe("Path to the PPTX file"),
254
+ action: z.enum(["list", "add", "delete", "resolve"]),
255
+ slideIndex: z.number().int().min(0).optional(),
256
+ text: z.string().optional(),
257
+ author: z.string().optional(),
258
+ commentId: z.string().optional(),
259
+ resolved: z.boolean().optional(),
260
+ includeResolved: z.boolean().optional()
261
+ });
262
+ function generateElementId() {
263
+ return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
264
+ }
265
+ function generateSlideId() {
266
+ return `slide-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
267
+ }
268
+ function describeElement(el) {
269
+ const base = {
270
+ id: el.id,
271
+ type: el.type,
272
+ x: Math.round(el.x),
273
+ y: Math.round(el.y),
274
+ width: Math.round(el.width),
275
+ height: Math.round(el.height)
276
+ };
277
+ if (el.rotation) {
278
+ base.rotation = el.rotation;
279
+ }
280
+ if (el.hidden) {
281
+ base.hidden = true;
282
+ }
283
+ if (el.opacity !== void 0 && el.opacity !== 1) {
284
+ base.opacity = el.opacity;
285
+ }
286
+ if (hasTextProperties(el)) {
287
+ base.text = el.text ?? "";
288
+ if (el.textStyle) {
289
+ const ts = {};
290
+ if (el.textStyle.fontSize) {
291
+ ts.fontSize = el.textStyle.fontSize;
292
+ }
293
+ if (el.textStyle.fontFamily) {
294
+ ts.fontFamily = el.textStyle.fontFamily;
295
+ }
296
+ if (el.textStyle.bold) {
297
+ ts.bold = true;
298
+ }
299
+ if (el.textStyle.italic) {
300
+ ts.italic = true;
301
+ }
302
+ if (el.textStyle.underline) {
303
+ ts.underline = el.textStyle.underline;
304
+ }
305
+ if (el.textStyle.align) {
306
+ ts.align = el.textStyle.align;
307
+ }
308
+ if (el.textStyle.color) {
309
+ ts.color = el.textStyle.color;
310
+ }
311
+ if (Object.keys(ts).length > 0) {
312
+ base.textStyle = ts;
313
+ }
314
+ }
315
+ }
316
+ if ("shapeType" in el && el.shapeType) {
317
+ base.shapeType = el.shapeType;
318
+ }
319
+ if ("shapeStyle" in el && el.shapeStyle) {
320
+ const ss = {};
321
+ const style = el.shapeStyle;
322
+ if (style.fillColor) {
323
+ ss.fillColor = style.fillColor;
324
+ }
325
+ if (style.strokeColor) {
326
+ ss.strokeColor = style.strokeColor;
327
+ }
328
+ if (style.strokeWidth) {
329
+ ss.strokeWidth = style.strokeWidth;
330
+ }
331
+ if (Object.keys(ss).length > 0) {
332
+ base.shapeStyle = ss;
333
+ }
334
+ }
335
+ if (el.type === "image" || el.type === "picture") {
336
+ const img = el;
337
+ if (img.altText) {
338
+ base.altText = img.altText;
339
+ }
340
+ if (img.imagePath) {
341
+ base.imagePath = img.imagePath;
342
+ }
343
+ base.hasImageData = Boolean(img.imageData);
344
+ }
345
+ if (el.type === "table") {
346
+ const tbl = el;
347
+ if (tbl.tableData) {
348
+ base.rowCount = tbl.tableData.rows.length;
349
+ base.columnCount = tbl.tableData.columnWidths.length;
350
+ base.cells = tbl.tableData.rows.map((row) => row.cells.map((cell) => cell.text ?? ""));
351
+ }
352
+ }
353
+ if (el.type === "chart") {
354
+ base.chartType = el.chartData?.type;
355
+ }
356
+ if (el.type === "group") {
357
+ const grp = el;
358
+ base.childCount = grp.children?.length ?? 0;
359
+ }
360
+ return base;
361
+ }
362
+ function validateSlideIndex(slideIndex, slideCount) {
363
+ if (slideIndex < 0 || slideIndex >= slideCount) {
364
+ return `Slide index ${slideIndex} out of range (0\u2013${slideCount - 1}).`;
365
+ }
366
+ return null;
367
+ }
368
+
369
+ // src/tools/content-tools.ts
370
+ function buildSearchRegex(query, useRegex, caseSensitive) {
371
+ const pattern = useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
372
+ const flags = caseSensitive ? "g" : "gi";
373
+ return new RegExp(pattern, flags);
374
+ }
375
+ function findText(ctx, params) {
376
+ const regex = buildSearchRegex(
377
+ params.query,
378
+ params.useRegex === true,
379
+ params.caseSensitive === true
380
+ );
381
+ const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
382
+ const matches = [];
383
+ for (const si of targetIndexes) {
384
+ const err = validateSlideIndex(si, ctx.pptxData.slides.length);
385
+ if (err) {
386
+ throw new Error(err);
387
+ }
388
+ const slide = ctx.pptxData.slides[si];
389
+ for (const el of slide.elements) {
390
+ if (hasTextProperties(el)) {
391
+ const textEl = el;
392
+ if (textEl.text) {
393
+ const localMatches = Array.from(textEl.text.matchAll(regex));
394
+ for (const m of localMatches) {
395
+ matches.push({
396
+ slideIndex: si,
397
+ elementId: el.id,
398
+ elementType: el.type,
399
+ matchedText: m[0],
400
+ context: textEl.text.slice(
401
+ Math.max(0, (m.index ?? 0) - 20),
402
+ (m.index ?? 0) + m[0].length + 20
403
+ )
404
+ });
405
+ }
406
+ }
407
+ }
408
+ if (el.type === "table") {
409
+ const tbl = el;
410
+ if (tbl.tableData) {
411
+ for (const row of tbl.tableData.rows) {
412
+ for (const cell of row.cells) {
413
+ if (cell.text) {
414
+ const localMatches = Array.from(cell.text.matchAll(regex));
415
+ for (const m of localMatches) {
416
+ matches.push({
417
+ slideIndex: si,
418
+ elementId: el.id,
419
+ elementType: "table-cell",
420
+ matchedText: m[0],
421
+ context: cell.text.slice(
422
+ Math.max(0, (m.index ?? 0) - 20),
423
+ (m.index ?? 0) + m[0].length + 20
424
+ )
425
+ });
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ if (slide.notes) {
434
+ const noteMatches = Array.from(slide.notes.matchAll(regex));
435
+ for (const m of noteMatches) {
436
+ matches.push({
437
+ slideIndex: si,
438
+ elementId: "notes",
439
+ elementType: "notes",
440
+ matchedText: m[0],
441
+ context: slide.notes.slice(
442
+ Math.max(0, (m.index ?? 0) - 20),
443
+ (m.index ?? 0) + m[0].length + 20
444
+ )
445
+ });
446
+ }
447
+ }
448
+ }
449
+ return {
450
+ pptxData: ctx.pptxData,
451
+ dirty: false,
452
+ result: {
453
+ query: params.query,
454
+ matchCount: matches.length,
455
+ matches
456
+ }
457
+ };
458
+ }
459
+ function replaceText(ctx, params) {
460
+ const regex = buildSearchRegex(
461
+ params.query,
462
+ params.useRegex === true,
463
+ params.caseSensitive === true
464
+ );
465
+ const targetIndexes = params.slideIndexes ? params.slideIndexes : ctx.pptxData.slides.map((_, i) => i);
466
+ let count = 0;
467
+ for (const si of targetIndexes) {
468
+ const err = validateSlideIndex(si, ctx.pptxData.slides.length);
469
+ if (err) {
470
+ throw new Error(err);
471
+ }
472
+ const slide = ctx.pptxData.slides[si];
473
+ for (const el of slide.elements) {
474
+ if (hasTextProperties(el)) {
475
+ const textEl = el;
476
+ if (textEl.text) {
477
+ const before = textEl.text;
478
+ const after = before.replace(regex, params.replacement);
479
+ if (after !== before) {
480
+ const matchCount = Array.from(before.matchAll(regex)).length;
481
+ count += matchCount;
482
+ textEl.text = after;
483
+ if (textEl.textSegments && textEl.textSegments.length > 0) {
484
+ const firstSeg = textEl.textSegments[0];
485
+ const updatedFirst = firstSeg.text.replace(regex, params.replacement);
486
+ if (updatedFirst !== firstSeg.text) {
487
+ firstSeg.text = updatedFirst;
488
+ } else {
489
+ textEl.textSegments = [{ text: after, style: firstSeg.style }];
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
495
+ if (el.type === "table") {
496
+ const tbl = el;
497
+ if (tbl.tableData) {
498
+ for (const row of tbl.tableData.rows) {
499
+ for (const cell of row.cells) {
500
+ if (cell.text) {
501
+ const before = cell.text;
502
+ const after = before.replace(regex, params.replacement);
503
+ if (after !== before) {
504
+ count += Array.from(before.matchAll(regex)).length;
505
+ cell.text = after;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+ if (slide.notes) {
514
+ const before = slide.notes;
515
+ const after = before.replace(regex, params.replacement);
516
+ if (after !== before) {
517
+ count += Array.from(before.matchAll(regex)).length;
518
+ slide.notes = after;
519
+ }
520
+ }
521
+ }
522
+ return {
523
+ pptxData: ctx.pptxData,
524
+ dirty: count > 0,
525
+ result: {
526
+ query: params.query,
527
+ replacement: params.replacement,
528
+ replacementCount: count
529
+ }
530
+ };
531
+ }
532
+ function manageComments(ctx, params) {
533
+ switch (params.action) {
534
+ case "list": {
535
+ const all = [];
536
+ for (let si = 0; si < ctx.pptxData.slides.length; si++) {
537
+ if (params.slideIndex !== void 0 && si !== params.slideIndex) {
538
+ continue;
539
+ }
540
+ const slide = ctx.pptxData.slides[si];
541
+ for (const c of slide.comments ?? []) {
542
+ if (!params.includeResolved && c.resolved) {
543
+ continue;
544
+ }
545
+ all.push({
546
+ id: c.id,
547
+ slideIndex: si,
548
+ text: c.text,
549
+ author: c.author,
550
+ createdAt: c.createdAt,
551
+ resolved: c.resolved
552
+ });
553
+ }
554
+ }
555
+ return {
556
+ pptxData: ctx.pptxData,
557
+ dirty: false,
558
+ result: { action: "list", comments: all, count: all.length }
559
+ };
560
+ }
561
+ case "add": {
562
+ if (params.slideIndex === void 0) {
563
+ throw new Error("slideIndex is required for add action.");
564
+ }
565
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
566
+ if (err) {
567
+ throw new Error(err);
568
+ }
569
+ if (!params.text) {
570
+ throw new Error("text is required for add action.");
571
+ }
572
+ const slide = ctx.pptxData.slides[params.slideIndex];
573
+ if (!slide.comments) {
574
+ slide.comments = [];
575
+ }
576
+ const newId = `comment-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
577
+ slide.comments.push({
578
+ id: newId,
579
+ text: params.text,
580
+ author: params.author,
581
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
582
+ resolved: false
583
+ });
584
+ return {
585
+ pptxData: ctx.pptxData,
586
+ dirty: true,
587
+ result: { action: "add", commentId: newId }
588
+ };
589
+ }
590
+ case "delete": {
591
+ if (!params.commentId) {
592
+ throw new Error("commentId is required for delete action.");
593
+ }
594
+ let deleted = false;
595
+ for (const slide of ctx.pptxData.slides) {
596
+ if (!slide.comments) {
597
+ continue;
598
+ }
599
+ const before = slide.comments.length;
600
+ slide.comments = slide.comments.filter((c) => c.id !== params.commentId);
601
+ if (slide.comments.length < before) {
602
+ deleted = true;
603
+ break;
604
+ }
605
+ }
606
+ if (!deleted) {
607
+ throw new Error(`Comment '${params.commentId}' not found.`);
608
+ }
609
+ return {
610
+ pptxData: ctx.pptxData,
611
+ dirty: true,
612
+ result: { action: "delete", commentId: params.commentId }
613
+ };
614
+ }
615
+ case "resolve": {
616
+ if (!params.commentId) {
617
+ throw new Error("commentId is required for resolve action.");
618
+ }
619
+ let found = false;
620
+ for (const slide of ctx.pptxData.slides) {
621
+ const comment = slide.comments?.find((c) => c.id === params.commentId);
622
+ if (comment) {
623
+ comment.resolved = params.resolved !== false;
624
+ found = true;
625
+ break;
626
+ }
627
+ }
628
+ if (!found) {
629
+ throw new Error(`Comment '${params.commentId}' not found.`);
630
+ }
631
+ return {
632
+ pptxData: ctx.pptxData,
633
+ dirty: true,
634
+ result: { action: "resolve", commentId: params.commentId }
635
+ };
636
+ }
637
+ default: {
638
+ throw new Error(`Unknown action: ${String(params.action)}`);
639
+ }
640
+ }
641
+ }
642
+ async function convertToMarkdown(ctx, params) {
643
+ const options = {
644
+ mediaFolderName: params.mediaFolderName ?? "media",
645
+ includeMetadata: params.includeMetadata !== false,
646
+ slideRange: params.slideRange,
647
+ includeSpeakerNotes: params.includeSpeakerNotes !== false,
648
+ semanticMode: params.semanticMode === true,
649
+ sourceName: params.sourceName ?? "unknown"
650
+ };
651
+ const converter = new PptxMarkdownConverter(params.outputDir ?? ".", options);
652
+ const markdown = await converter.convert(ctx.pptxData);
653
+ return {
654
+ pptxData: ctx.pptxData,
655
+ dirty: false,
656
+ result: {
657
+ markdown,
658
+ slidesConverted: converter.slidesConverted,
659
+ totalSlides: converter.presentationSlides,
660
+ imagesExtracted: converter.imagesExtracted
661
+ }
662
+ };
663
+ }
664
+ function addElement(ctx, params) {
665
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
666
+ if (err) {
667
+ throw new Error(err);
668
+ }
669
+ const slide = ctx.pptxData.slides[params.slideIndex];
670
+ const id = generateElementId();
671
+ const x = params.x ?? 100;
672
+ const y = params.y ?? 100;
673
+ const width = params.width ?? 300;
674
+ const height = params.height ?? 60;
675
+ switch (params.type) {
676
+ case "text": {
677
+ const el = {
678
+ id,
679
+ type: "text",
680
+ x,
681
+ y,
682
+ width,
683
+ height,
684
+ text: params.text ?? "",
685
+ textStyle: {
686
+ fontSize: params.fontSize,
687
+ fontFamily: params.fontFamily,
688
+ color: params.fontColor,
689
+ bold: params.bold,
690
+ italic: params.italic,
691
+ underline: params.underline,
692
+ align: params.alignment
693
+ },
694
+ textSegments: [
695
+ {
696
+ text: params.text ?? "",
697
+ style: {
698
+ fontSize: params.fontSize,
699
+ fontFamily: params.fontFamily,
700
+ color: params.fontColor,
701
+ bold: params.bold,
702
+ italic: params.italic,
703
+ underline: params.underline
704
+ }
705
+ }
706
+ ]
707
+ };
708
+ slide.elements.push(el);
709
+ break;
710
+ }
711
+ case "shape": {
712
+ const el = {
713
+ id,
714
+ type: "shape",
715
+ x,
716
+ y,
717
+ width,
718
+ height,
719
+ shapeType: params.shapeType ?? "rect",
720
+ text: params.text,
721
+ textStyle: params.text ? {
722
+ fontSize: params.fontSize,
723
+ fontFamily: params.fontFamily,
724
+ color: params.fontColor,
725
+ bold: params.bold,
726
+ italic: params.italic,
727
+ align: params.alignment
728
+ } : void 0,
729
+ shapeStyle: {
730
+ fillColor: params.fillColor,
731
+ strokeColor: params.strokeColor,
732
+ strokeWidth: params.strokeWidth
733
+ }
734
+ };
735
+ slide.elements.push(el);
736
+ break;
737
+ }
738
+ case "image": {
739
+ const el = {
740
+ id,
741
+ type: "image",
742
+ x,
743
+ y,
744
+ width,
745
+ height,
746
+ imageData: params.imageData,
747
+ altText: params.altText
748
+ };
749
+ slide.elements.push(el);
750
+ break;
751
+ }
752
+ case "table": {
753
+ const rowCount = params.rows ?? 2;
754
+ const colCount = params.columns ?? 2;
755
+ const colWidth = 1 / colCount;
756
+ const tableRows = Array.from({ length: rowCount }, (_, r) => ({
757
+ height: 40,
758
+ cells: Array.from({ length: colCount }, (__, c) => ({
759
+ text: params.cellData?.[r]?.[c] ?? ""
760
+ }))
761
+ }));
762
+ const tableEl = {
763
+ id,
764
+ type: "table",
765
+ x,
766
+ y,
767
+ width,
768
+ height: params.height ?? rowCount * 40,
769
+ tableData: {
770
+ rows: tableRows,
771
+ columnWidths: Array.from({ length: colCount }, () => colWidth),
772
+ firstRowHeader: params.headerRow ?? false
773
+ }
774
+ };
775
+ slide.elements.push(tableEl);
776
+ break;
777
+ }
778
+ case "connector": {
779
+ const el = {
780
+ id,
781
+ type: "connector",
782
+ x,
783
+ y,
784
+ width,
785
+ height,
786
+ shapeStyle: {
787
+ strokeColor: params.strokeColor ?? "#000000",
788
+ strokeWidth: params.strokeWidth ?? 1,
789
+ connectorStartArrow: params.startArrow,
790
+ connectorEndArrow: params.endArrow
791
+ }
792
+ };
793
+ slide.elements.push(el);
794
+ break;
795
+ }
796
+ default: {
797
+ throw new Error(`Unknown element type: ${String(params.type)}`);
798
+ }
799
+ }
800
+ return {
801
+ pptxData: ctx.pptxData,
802
+ dirty: true,
803
+ result: { elementId: id, slideIndex: params.slideIndex }
804
+ };
805
+ }
806
+ function updateElement(ctx, params) {
807
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
808
+ if (err) {
809
+ throw new Error(err);
810
+ }
811
+ const slide = ctx.pptxData.slides[params.slideIndex];
812
+ const el = slide.elements.find((e) => e.id === params.elementId);
813
+ if (!el) {
814
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
815
+ }
816
+ if (params.x !== void 0) {
817
+ el.x = params.x;
818
+ }
819
+ if (params.y !== void 0) {
820
+ el.y = params.y;
821
+ }
822
+ if (params.width !== void 0) {
823
+ el.width = params.width;
824
+ }
825
+ if (params.height !== void 0) {
826
+ el.height = params.height;
827
+ }
828
+ if (params.rotation !== void 0) {
829
+ el.rotation = params.rotation;
830
+ }
831
+ if (params.opacity !== void 0) {
832
+ el.opacity = params.opacity;
833
+ }
834
+ if (params.hidden !== void 0) {
835
+ el.hidden = params.hidden;
836
+ }
837
+ if (params.flipH !== void 0) {
838
+ el.flipHorizontal = params.flipH;
839
+ }
840
+ if (params.flipV !== void 0) {
841
+ el.flipVertical = params.flipV;
842
+ }
843
+ if (hasTextProperties(el)) {
844
+ const textEl = el;
845
+ if (params.text !== void 0) {
846
+ textEl.text = params.text;
847
+ if (textEl.textSegments && textEl.textSegments.length > 0) {
848
+ textEl.textSegments[0].text = params.text;
849
+ } else {
850
+ textEl.textSegments = [{ text: params.text, style: {} }];
851
+ }
852
+ }
853
+ if (!textEl.textStyle) {
854
+ textEl.textStyle = {};
855
+ }
856
+ if (params.fontSize !== void 0) {
857
+ textEl.textStyle.fontSize = params.fontSize;
858
+ }
859
+ if (params.fontFamily !== void 0) {
860
+ textEl.textStyle.fontFamily = params.fontFamily;
861
+ }
862
+ if (params.fontColor !== void 0) {
863
+ textEl.textStyle.color = params.fontColor;
864
+ }
865
+ if (params.bold !== void 0) {
866
+ textEl.textStyle.bold = params.bold;
867
+ }
868
+ if (params.italic !== void 0) {
869
+ textEl.textStyle.italic = params.italic;
870
+ }
871
+ if (params.underline !== void 0) {
872
+ textEl.textStyle.underline = params.underline;
873
+ }
874
+ if (params.alignment !== void 0) {
875
+ textEl.textStyle.align = params.alignment;
876
+ }
877
+ }
878
+ if ("shapeStyle" in el && el.shapeStyle) {
879
+ const ss = el.shapeStyle;
880
+ if (params.fillColor !== void 0) {
881
+ ss.fillColor = params.fillColor;
882
+ }
883
+ if (params.strokeColor !== void 0) {
884
+ ss.strokeColor = params.strokeColor;
885
+ }
886
+ if (params.strokeWidth !== void 0) {
887
+ ss.strokeWidth = params.strokeWidth;
888
+ }
889
+ } else if (params.fillColor !== void 0 || params.strokeColor !== void 0 || params.strokeWidth !== void 0) {
890
+ if ("shapeStyle" in el) {
891
+ el.shapeStyle = {
892
+ fillColor: params.fillColor,
893
+ strokeColor: params.strokeColor,
894
+ strokeWidth: params.strokeWidth
895
+ };
896
+ }
897
+ }
898
+ return {
899
+ pptxData: ctx.pptxData,
900
+ dirty: true,
901
+ result: { elementId: params.elementId }
902
+ };
903
+ }
904
+ function deleteElements(ctx, params) {
905
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
906
+ if (err) {
907
+ throw new Error(err);
908
+ }
909
+ const slide = ctx.pptxData.slides[params.slideIndex];
910
+ const idSet = new Set(params.elementIds);
911
+ const notFound = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
912
+ if (notFound.length > 0) {
913
+ throw new Error(`Elements not found: ${notFound.join(", ")}`);
914
+ }
915
+ const before = slide.elements.length;
916
+ slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
917
+ return {
918
+ pptxData: ctx.pptxData,
919
+ dirty: true,
920
+ result: { deletedCount: before - slide.elements.length }
921
+ };
922
+ }
923
+ function arrangeElements(ctx, params) {
924
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
925
+ if (err) {
926
+ throw new Error(err);
927
+ }
928
+ const slide = ctx.pptxData.slides[params.slideIndex];
929
+ if (params.action === "align") {
930
+ if (!params.elementIds || params.elementIds.length === 0) {
931
+ throw new Error("elementIds is required for align action.");
932
+ }
933
+ const targets = slide.elements.filter((e) => params.elementIds.includes(e.id));
934
+ if (targets.length === 0) {
935
+ throw new Error("No matching elements found for align.");
936
+ }
937
+ switch (params.alignment) {
938
+ case "left": {
939
+ const minX = Math.min(...targets.map((e) => e.x));
940
+ for (const t of targets) {
941
+ t.x = minX;
942
+ }
943
+ break;
944
+ }
945
+ case "right": {
946
+ const maxRight = Math.max(...targets.map((e) => e.x + e.width));
947
+ for (const t of targets) {
948
+ t.x = maxRight - t.width;
949
+ }
950
+ break;
951
+ }
952
+ case "top": {
953
+ const minY = Math.min(...targets.map((e) => e.y));
954
+ for (const t of targets) {
955
+ t.y = minY;
956
+ }
957
+ break;
958
+ }
959
+ case "bottom": {
960
+ const maxBottom = Math.max(...targets.map((e) => e.y + e.height));
961
+ for (const t of targets) {
962
+ t.y = maxBottom - t.height;
963
+ }
964
+ break;
965
+ }
966
+ case "centerH": {
967
+ const avgCX = targets.reduce((s, e) => s + e.x + e.width / 2, 0) / targets.length;
968
+ for (const t of targets) {
969
+ t.x = avgCX - t.width / 2;
970
+ }
971
+ break;
972
+ }
973
+ case "centerV": {
974
+ const avgCY = targets.reduce((s, e) => s + e.y + e.height / 2, 0) / targets.length;
975
+ for (const t of targets) {
976
+ t.y = avgCY - t.height / 2;
977
+ }
978
+ break;
979
+ }
980
+ default: {
981
+ throw new Error(`Unknown alignment: ${String(params.alignment)}`);
982
+ }
983
+ }
984
+ } else if (params.action === "reorderLayer") {
985
+ if (!params.elementId) {
986
+ throw new Error("elementId is required for reorderLayer action.");
987
+ }
988
+ const idx = slide.elements.findIndex((e) => e.id === params.elementId);
989
+ if (idx < 0) {
990
+ throw new Error(`Element '${params.elementId}' not found.`);
991
+ }
992
+ const [el] = slide.elements.splice(idx, 1);
993
+ switch (params.layerAction) {
994
+ case "bringToFront":
995
+ slide.elements.push(el);
996
+ break;
997
+ case "sendToBack":
998
+ slide.elements.unshift(el);
999
+ break;
1000
+ case "bringForward": {
1001
+ const newIdx = Math.min(idx + 1, slide.elements.length);
1002
+ slide.elements.splice(newIdx, 0, el);
1003
+ break;
1004
+ }
1005
+ case "sendBackward": {
1006
+ const newIdx = Math.max(idx - 1, 0);
1007
+ slide.elements.splice(newIdx, 0, el);
1008
+ break;
1009
+ }
1010
+ default: {
1011
+ slide.elements.splice(idx, 0, el);
1012
+ throw new Error(`Unknown layerAction: ${String(params.layerAction)}`);
1013
+ }
1014
+ }
1015
+ } else {
1016
+ throw new Error(`Unknown action: ${String(params.action)}`);
1017
+ }
1018
+ return {
1019
+ pptxData: ctx.pptxData,
1020
+ dirty: true,
1021
+ result: { slideIndex: params.slideIndex }
1022
+ };
1023
+ }
1024
+ function cloneElement(ctx, params) {
1025
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1026
+ if (err) {
1027
+ throw new Error(err);
1028
+ }
1029
+ const srcSlide = ctx.pptxData.slides[params.slideIndex];
1030
+ const original = srcSlide.elements.find((e) => e.id === params.elementId);
1031
+ if (!original) {
1032
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1033
+ }
1034
+ const targets = params.targetSlideIndexes ?? [params.slideIndex];
1035
+ const clonedIds = [];
1036
+ const offsetX = params.offsetX ?? 20;
1037
+ const offsetY = params.offsetY ?? 20;
1038
+ for (const targetIdx of targets) {
1039
+ const terr = validateSlideIndex(targetIdx, ctx.pptxData.slides.length);
1040
+ if (terr) {
1041
+ throw new Error(terr);
1042
+ }
1043
+ const clone = structuredClone(original);
1044
+ clone.id = generateElementId();
1045
+ clone.x += offsetX;
1046
+ clone.y += offsetY;
1047
+ ctx.pptxData.slides[targetIdx].elements.push(clone);
1048
+ clonedIds.push(clone.id);
1049
+ }
1050
+ return {
1051
+ pptxData: ctx.pptxData,
1052
+ dirty: true,
1053
+ result: { clonedIds }
1054
+ };
1055
+ }
1056
+ function setElementAnimation(ctx, params) {
1057
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1058
+ if (err) {
1059
+ throw new Error(err);
1060
+ }
1061
+ const slide = ctx.pptxData.slides[params.slideIndex];
1062
+ const elExists = slide.elements.some((e) => e.id === params.elementId);
1063
+ if (!elExists) {
1064
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1065
+ }
1066
+ if (!slide.animations) {
1067
+ slide.animations = [];
1068
+ }
1069
+ const existing = slide.animations.find((a) => a.elementId === params.elementId);
1070
+ if (existing) {
1071
+ if (params.entrance !== void 0) {
1072
+ existing.entrance = params.entrance;
1073
+ }
1074
+ if (params.exit !== void 0) {
1075
+ existing.exit = params.exit;
1076
+ }
1077
+ if (params.durationMs !== void 0) {
1078
+ existing.durationMs = params.durationMs;
1079
+ }
1080
+ if (params.delayMs !== void 0) {
1081
+ existing.delayMs = params.delayMs;
1082
+ }
1083
+ if (params.order !== void 0) {
1084
+ existing.order = params.order;
1085
+ }
1086
+ } else {
1087
+ const anim = {
1088
+ elementId: params.elementId,
1089
+ entrance: params.entrance,
1090
+ exit: params.exit,
1091
+ durationMs: params.durationMs,
1092
+ delayMs: params.delayMs,
1093
+ order: params.order
1094
+ };
1095
+ slide.animations.push(anim);
1096
+ }
1097
+ return {
1098
+ pptxData: ctx.pptxData,
1099
+ dirty: true,
1100
+ result: { elementId: params.elementId }
1101
+ };
1102
+ }
1103
+ function groupElements(ctx, params) {
1104
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1105
+ if (err) {
1106
+ throw new Error(err);
1107
+ }
1108
+ if (params.elementIds.length < 2) {
1109
+ throw new Error("At least 2 elements are required to form a group.");
1110
+ }
1111
+ const slide = ctx.pptxData.slides[params.slideIndex];
1112
+ const idSet = new Set(params.elementIds);
1113
+ const children = slide.elements.filter((e) => idSet.has(e.id));
1114
+ if (children.length !== params.elementIds.length) {
1115
+ const missing = params.elementIds.filter((id) => !slide.elements.some((e) => e.id === id));
1116
+ throw new Error(`Elements not found: ${missing.join(", ")}`);
1117
+ }
1118
+ const minX = Math.min(...children.map((e) => e.x));
1119
+ const minY = Math.min(...children.map((e) => e.y));
1120
+ const maxX = Math.max(...children.map((e) => e.x + e.width));
1121
+ const maxY = Math.max(...children.map((e) => e.y + e.height));
1122
+ const localChildren = children.map((e) => {
1123
+ const c = structuredClone(e);
1124
+ c.x -= minX;
1125
+ c.y -= minY;
1126
+ return c;
1127
+ });
1128
+ slide.elements = slide.elements.filter((e) => !idSet.has(e.id));
1129
+ const groupId = generateElementId();
1130
+ const group = {
1131
+ id: groupId,
1132
+ type: "group",
1133
+ x: minX,
1134
+ y: minY,
1135
+ width: maxX - minX,
1136
+ height: maxY - minY,
1137
+ children: localChildren
1138
+ };
1139
+ slide.elements.push(group);
1140
+ return {
1141
+ pptxData: ctx.pptxData,
1142
+ dirty: true,
1143
+ result: { groupId }
1144
+ };
1145
+ }
1146
+ function ungroupElements(ctx, params) {
1147
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1148
+ if (err) {
1149
+ throw new Error(err);
1150
+ }
1151
+ const slide = ctx.pptxData.slides[params.slideIndex];
1152
+ const groupIdx = slide.elements.findIndex((e) => e.id === params.groupElementId);
1153
+ if (groupIdx < 0) {
1154
+ throw new Error(`Group element '${params.groupElementId}' not found.`);
1155
+ }
1156
+ const group = slide.elements[groupIdx];
1157
+ if (group.type !== "group") {
1158
+ throw new Error(`Element '${params.groupElementId}' is not a group.`);
1159
+ }
1160
+ const grp = group;
1161
+ const restored = grp.children.map((child) => {
1162
+ const c = structuredClone(child);
1163
+ c.x += grp.x;
1164
+ c.y += grp.y;
1165
+ return c;
1166
+ });
1167
+ slide.elements.splice(groupIdx, 1, ...restored);
1168
+ return {
1169
+ pptxData: ctx.pptxData,
1170
+ dirty: true,
1171
+ result: { restoredIds: restored.map((c) => c.id) }
1172
+ };
1173
+ }
1174
+ function batchUpdateElements(ctx, params) {
1175
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1176
+ if (err) {
1177
+ throw new Error(err);
1178
+ }
1179
+ const slide = ctx.pptxData.slides[params.slideIndex];
1180
+ const idSet = new Set(params.elementIds);
1181
+ let count = 0;
1182
+ for (const el of slide.elements) {
1183
+ if (!idSet.has(el.id)) {
1184
+ continue;
1185
+ }
1186
+ if (params.x !== void 0) {
1187
+ el.x = params.x;
1188
+ }
1189
+ if (params.y !== void 0) {
1190
+ el.y = params.y;
1191
+ }
1192
+ if (params.width !== void 0) {
1193
+ el.width = params.width;
1194
+ }
1195
+ if (params.height !== void 0) {
1196
+ el.height = params.height;
1197
+ }
1198
+ if (params.rotation !== void 0) {
1199
+ el.rotation = params.rotation;
1200
+ }
1201
+ if (params.opacity !== void 0) {
1202
+ el.opacity = params.opacity;
1203
+ }
1204
+ if (params.hidden !== void 0) {
1205
+ el.hidden = params.hidden;
1206
+ }
1207
+ count++;
1208
+ }
1209
+ return {
1210
+ pptxData: ctx.pptxData,
1211
+ dirty: true,
1212
+ result: { updatedCount: count }
1213
+ };
1214
+ }
1215
+
1216
+ // src/tools/slide-tools.ts
1217
+ function getSlide(ctx, params) {
1218
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1219
+ if (err) {
1220
+ throw new Error(err);
1221
+ }
1222
+ const slide = ctx.pptxData.slides[params.slideIndex];
1223
+ return {
1224
+ pptxData: ctx.pptxData,
1225
+ dirty: false,
1226
+ result: {
1227
+ slideIndex: params.slideIndex,
1228
+ slideNumber: slide.slideNumber,
1229
+ hidden: slide.hidden ?? false,
1230
+ layoutName: slide.layoutName,
1231
+ backgroundColor: slide.backgroundColor,
1232
+ notes: slide.notes ?? "",
1233
+ commentCount: slide.comments?.length ?? 0,
1234
+ elements: slide.elements.map(describeElement)
1235
+ }
1236
+ };
1237
+ }
1238
+ function addSlide(ctx, params) {
1239
+ const { slides } = ctx.pptxData;
1240
+ const insertAt = params.insertAfterIndex !== void 0 && params.insertAfterIndex >= 0 ? Math.min(params.insertAfterIndex + 1, slides.length) : slides.length;
1241
+ const newSlide = {
1242
+ id: generateSlideId(),
1243
+ rId: "",
1244
+ slideNumber: insertAt + 1,
1245
+ elements: [],
1246
+ backgroundColor: params.backgroundColor
1247
+ };
1248
+ slides.splice(insertAt, 0, newSlide);
1249
+ slides.forEach((s, i) => {
1250
+ s.slideNumber = i + 1;
1251
+ });
1252
+ return {
1253
+ pptxData: ctx.pptxData,
1254
+ dirty: true,
1255
+ result: { newSlideIndex: insertAt, slideCount: slides.length }
1256
+ };
1257
+ }
1258
+ function deleteSlides(ctx, params) {
1259
+ const { slides } = ctx.pptxData;
1260
+ if (!params.slideIndexes || params.slideIndexes.length === 0) {
1261
+ throw new Error("slideIndexes is required for the delete action.");
1262
+ }
1263
+ const indexSet = new Set(params.slideIndexes);
1264
+ const invalid = params.slideIndexes.filter((i) => i < 0 || i >= slides.length);
1265
+ if (invalid.length > 0) {
1266
+ throw new Error(
1267
+ `Invalid slide indexes: ${invalid.join(", ")}. Valid range: 0\u2013${slides.length - 1}.`
1268
+ );
1269
+ }
1270
+ if (indexSet.size >= slides.length) {
1271
+ throw new Error("Cannot delete all slides. At least one slide must remain.");
1272
+ }
1273
+ ctx.pptxData.slides = slides.filter((_, i) => !indexSet.has(i));
1274
+ ctx.pptxData.slides.forEach((s, i) => {
1275
+ s.slideNumber = i + 1;
1276
+ });
1277
+ return {
1278
+ pptxData: ctx.pptxData,
1279
+ dirty: true,
1280
+ result: { deletedCount: indexSet.size, slideCount: ctx.pptxData.slides.length }
1281
+ };
1282
+ }
1283
+ function reorderSlides(ctx, params) {
1284
+ const { slides } = ctx.pptxData;
1285
+ if (!params.newOrder || params.newOrder.length !== slides.length) {
1286
+ throw new Error(
1287
+ `newOrder must have exactly ${slides.length} indexes. Got ${params.newOrder?.length ?? 0}.`
1288
+ );
1289
+ }
1290
+ const sorted = [...params.newOrder].sort((a, b) => a - b);
1291
+ const expected = slides.map((_, i) => i);
1292
+ if (JSON.stringify(sorted) !== JSON.stringify(expected)) {
1293
+ throw new Error(
1294
+ `newOrder must contain each index from 0 to ${slides.length - 1} exactly once.`
1295
+ );
1296
+ }
1297
+ ctx.pptxData.slides = params.newOrder.map((idx) => slides[idx]);
1298
+ ctx.pptxData.slides.forEach((s, i) => {
1299
+ s.slideNumber = i + 1;
1300
+ });
1301
+ return {
1302
+ pptxData: ctx.pptxData,
1303
+ dirty: true,
1304
+ result: { slideCount: ctx.pptxData.slides.length }
1305
+ };
1306
+ }
1307
+ function duplicateSlide(ctx, params) {
1308
+ const { slides } = ctx.pptxData;
1309
+ const err = validateSlideIndex(params.slideIndex, slides.length);
1310
+ if (err) {
1311
+ throw new Error(err);
1312
+ }
1313
+ const original = slides[params.slideIndex];
1314
+ const clone = structuredClone(original);
1315
+ clone.id = generateSlideId();
1316
+ clone.rId = "";
1317
+ for (const el of clone.elements) {
1318
+ el.id = generateElementId();
1319
+ }
1320
+ const insertIndex = params.targetIndex ?? params.slideIndex + 1;
1321
+ slides.splice(insertIndex, 0, clone);
1322
+ slides.forEach((s, i) => {
1323
+ s.slideNumber = i + 1;
1324
+ });
1325
+ return {
1326
+ pptxData: ctx.pptxData,
1327
+ dirty: true,
1328
+ result: { newSlideIndex: insertIndex, slideCount: slides.length }
1329
+ };
1330
+ }
1331
+ function updateSlideProperties(ctx, params) {
1332
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1333
+ if (err) {
1334
+ throw new Error(err);
1335
+ }
1336
+ const slide = ctx.pptxData.slides[params.slideIndex];
1337
+ if (params.backgroundColor !== void 0) {
1338
+ slide.backgroundColor = params.backgroundColor;
1339
+ }
1340
+ if (params.backgroundGradient !== void 0) {
1341
+ slide.backgroundGradient = params.backgroundGradient;
1342
+ }
1343
+ if (params.backgroundImage !== void 0) {
1344
+ slide.backgroundImage = params.backgroundImage;
1345
+ }
1346
+ if (params.notes !== void 0) {
1347
+ slide.notes = params.notes;
1348
+ }
1349
+ if (params.hidden !== void 0) {
1350
+ slide.hidden = params.hidden;
1351
+ }
1352
+ return {
1353
+ pptxData: ctx.pptxData,
1354
+ dirty: true,
1355
+ result: { slideIndex: params.slideIndex }
1356
+ };
1357
+ }
1358
+ function setSlideTransition(ctx, params) {
1359
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1360
+ if (err) {
1361
+ throw new Error(err);
1362
+ }
1363
+ const slide = ctx.pptxData.slides[params.slideIndex];
1364
+ if (params.type === "none") {
1365
+ slide.transition = void 0;
1366
+ } else {
1367
+ const transition = {
1368
+ type: params.type
1369
+ };
1370
+ if (params.durationMs !== void 0) {
1371
+ transition.durationMs = params.durationMs;
1372
+ }
1373
+ if (params.direction !== void 0) {
1374
+ transition.direction = params.direction;
1375
+ }
1376
+ if (params.advanceOnClick !== void 0) {
1377
+ transition.advanceOnClick = params.advanceOnClick;
1378
+ }
1379
+ if (params.advanceAfterMs !== void 0) {
1380
+ transition.advanceAfterMs = params.advanceAfterMs;
1381
+ }
1382
+ slide.transition = transition;
1383
+ }
1384
+ return {
1385
+ pptxData: ctx.pptxData,
1386
+ dirty: true,
1387
+ result: { slideIndex: params.slideIndex, transitionType: params.type }
1388
+ };
1389
+ }
1390
+ function setCanvasSize(ctx, params) {
1391
+ ctx.pptxData.width = params.width;
1392
+ ctx.pptxData.height = params.height;
1393
+ if (ctx.pptxData.widthEmu !== void 0) {
1394
+ ctx.pptxData.widthEmu = Math.round(params.width * 12700);
1395
+ }
1396
+ if (ctx.pptxData.heightEmu !== void 0) {
1397
+ ctx.pptxData.heightEmu = Math.round(params.height * 12700);
1398
+ }
1399
+ return {
1400
+ pptxData: ctx.pptxData,
1401
+ dirty: true,
1402
+ result: { canvasWidth: params.width, canvasHeight: params.height }
1403
+ };
1404
+ }
1405
+ function updateElementStyle(ctx, params) {
1406
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1407
+ if (err) {
1408
+ throw new Error(err);
1409
+ }
1410
+ const slide = ctx.pptxData.slides[params.slideIndex];
1411
+ const el = slide.elements.find((e) => e.id === params.elementId);
1412
+ if (!el) {
1413
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1414
+ }
1415
+ if ("shapeStyle" in el) {
1416
+ if (!el.shapeStyle) {
1417
+ el.shapeStyle = {};
1418
+ }
1419
+ const ss = el.shapeStyle;
1420
+ if (params.fillColor !== void 0) {
1421
+ ss.fillColor = params.fillColor;
1422
+ }
1423
+ if (params.fillMode !== void 0) {
1424
+ ss.fillMode = params.fillMode;
1425
+ }
1426
+ if (params.fillGradientStops !== void 0) {
1427
+ ss.fillGradientStops = params.fillGradientStops;
1428
+ }
1429
+ if (params.fillGradientAngle !== void 0) {
1430
+ ss.fillGradientAngle = params.fillGradientAngle;
1431
+ }
1432
+ if (params.fillGradientType !== void 0) {
1433
+ ss.fillGradientType = params.fillGradientType;
1434
+ }
1435
+ if (params.fillOpacity !== void 0) {
1436
+ ss.fillOpacity = params.fillOpacity;
1437
+ }
1438
+ if (params.strokeColor !== void 0) {
1439
+ ss.strokeColor = params.strokeColor;
1440
+ }
1441
+ if (params.strokeWidth !== void 0) {
1442
+ ss.strokeWidth = params.strokeWidth;
1443
+ }
1444
+ if (params.strokeDash !== void 0) {
1445
+ ss.strokeDash = params.strokeDash;
1446
+ }
1447
+ if (params.strokeOpacity !== void 0) {
1448
+ ss.strokeOpacity = params.strokeOpacity;
1449
+ }
1450
+ if (params.shadowColor !== void 0) {
1451
+ ss.shadowColor = params.shadowColor;
1452
+ }
1453
+ if (params.shadowBlur !== void 0) {
1454
+ ss.shadowBlur = params.shadowBlur;
1455
+ }
1456
+ if (params.shadowOffsetX !== void 0) {
1457
+ ss.shadowOffsetX = params.shadowOffsetX;
1458
+ }
1459
+ if (params.shadowOffsetY !== void 0) {
1460
+ ss.shadowOffsetY = params.shadowOffsetY;
1461
+ }
1462
+ if (params.shadowOpacity !== void 0) {
1463
+ ss.shadowOpacity = params.shadowOpacity;
1464
+ }
1465
+ if (params.glowColor !== void 0) {
1466
+ ss.glowColor = params.glowColor;
1467
+ }
1468
+ if (params.glowRadius !== void 0) {
1469
+ ss.glowRadius = params.glowRadius;
1470
+ }
1471
+ if (params.glowOpacity !== void 0) {
1472
+ ss.glowOpacity = params.glowOpacity;
1473
+ }
1474
+ if (params.softEdgeRadius !== void 0) {
1475
+ ss.softEdgeRadius = params.softEdgeRadius;
1476
+ }
1477
+ }
1478
+ if (el.type === "image" || el.type === "picture") {
1479
+ const img = el;
1480
+ if (params.altText !== void 0) {
1481
+ img.altText = params.altText;
1482
+ }
1483
+ if (params.cropLeft !== void 0) {
1484
+ img.cropLeft = params.cropLeft;
1485
+ }
1486
+ if (params.cropTop !== void 0) {
1487
+ img.cropTop = params.cropTop;
1488
+ }
1489
+ if (params.cropRight !== void 0) {
1490
+ img.cropRight = params.cropRight;
1491
+ }
1492
+ if (params.cropBottom !== void 0) {
1493
+ img.cropBottom = params.cropBottom;
1494
+ }
1495
+ if (params.brightness !== void 0 || params.contrast !== void 0 || params.grayscale !== void 0) {
1496
+ if (!img.imageEffects) {
1497
+ img.imageEffects = {};
1498
+ }
1499
+ if (params.brightness !== void 0) {
1500
+ img.imageEffects.brightness = params.brightness;
1501
+ }
1502
+ if (params.contrast !== void 0) {
1503
+ img.imageEffects.contrast = params.contrast;
1504
+ }
1505
+ if (params.grayscale !== void 0) {
1506
+ img.imageEffects.grayscale = params.grayscale;
1507
+ }
1508
+ }
1509
+ }
1510
+ return {
1511
+ pptxData: ctx.pptxData,
1512
+ dirty: true,
1513
+ result: { elementId: params.elementId }
1514
+ };
1515
+ }
1516
+ function runAccessibilityCheck(ctx) {
1517
+ const issues = [];
1518
+ for (let si = 0; si < ctx.pptxData.slides.length; si++) {
1519
+ const slide = ctx.pptxData.slides[si];
1520
+ let slideHasText = false;
1521
+ for (const el of slide.elements) {
1522
+ if (el.type === "image" || el.type === "picture") {
1523
+ const img = el;
1524
+ if (!img.altText) {
1525
+ issues.push({
1526
+ slideIndex: si,
1527
+ elementId: el.id,
1528
+ severity: "error",
1529
+ message: `Image element '${el.id}' has no alt text.`
1530
+ });
1531
+ }
1532
+ }
1533
+ if (hasTextProperties(el)) {
1534
+ const textEl = el;
1535
+ slideHasText = true;
1536
+ if (!textEl.text || textEl.text.trim() === "") {
1537
+ issues.push({
1538
+ slideIndex: si,
1539
+ elementId: el.id,
1540
+ severity: "info",
1541
+ message: `Text element '${el.id}' is empty.`
1542
+ });
1543
+ }
1544
+ const fs = textEl.textStyle?.fontSize;
1545
+ if (fs !== void 0 && fs < 10) {
1546
+ issues.push({
1547
+ slideIndex: si,
1548
+ elementId: el.id,
1549
+ severity: "warning",
1550
+ message: `Text element '${el.id}' has very small font size (${fs}pt < 10pt).`
1551
+ });
1552
+ }
1553
+ const textColor = textEl.textStyle?.color?.toLowerCase();
1554
+ const bgColor = slide.backgroundColor?.toLowerCase();
1555
+ if (textColor && bgColor && textColor === bgColor) {
1556
+ issues.push({
1557
+ slideIndex: si,
1558
+ elementId: el.id,
1559
+ severity: "error",
1560
+ message: `Text element '${el.id}' has same color as slide background (${textColor}).`
1561
+ });
1562
+ }
1563
+ }
1564
+ }
1565
+ if (!slideHasText) {
1566
+ issues.push({
1567
+ slideIndex: si,
1568
+ severity: "warning",
1569
+ message: `Slide ${si + 1} contains no text elements.`
1570
+ });
1571
+ }
1572
+ }
1573
+ const errorCount = issues.filter((i) => i.severity === "error").length;
1574
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
1575
+ const infoCount = issues.filter((i) => i.severity === "info").length;
1576
+ return {
1577
+ pptxData: ctx.pptxData,
1578
+ dirty: false,
1579
+ result: {
1580
+ slideCount: ctx.pptxData.slides.length,
1581
+ totalIssues: issues.length,
1582
+ errorCount,
1583
+ warningCount,
1584
+ infoCount,
1585
+ issues
1586
+ }
1587
+ };
1588
+ }
1589
+
1590
+ // src/tools/table-tools.ts
1591
+ function updateTableCells(ctx, params) {
1592
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1593
+ if (err) {
1594
+ throw new Error(err);
1595
+ }
1596
+ const slide = ctx.pptxData.slides[params.slideIndex];
1597
+ const el = slide.elements.find((e) => e.id === params.elementId);
1598
+ if (!el) {
1599
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1600
+ }
1601
+ if (el.type !== "table") {
1602
+ throw new Error(`Element '${params.elementId}' is not a table.`);
1603
+ }
1604
+ const tbl = el;
1605
+ if (!tbl.tableData) {
1606
+ throw new Error(`Table '${params.elementId}' has no tableData.`);
1607
+ }
1608
+ let count = 0;
1609
+ for (const cellUpdate of params.cells) {
1610
+ const row = tbl.tableData.rows[cellUpdate.row];
1611
+ if (!row) {
1612
+ throw new Error(
1613
+ `Row ${cellUpdate.row} out of range (table has ${tbl.tableData.rows.length} rows).`
1614
+ );
1615
+ }
1616
+ const cell = row.cells[cellUpdate.col];
1617
+ if (!cell) {
1618
+ throw new Error(`Column ${cellUpdate.col} out of range (row has ${row.cells.length} cells).`);
1619
+ }
1620
+ cell.text = cellUpdate.text;
1621
+ count++;
1622
+ }
1623
+ return {
1624
+ pptxData: ctx.pptxData,
1625
+ dirty: true,
1626
+ result: { updatedCount: count }
1627
+ };
1628
+ }
1629
+ function manageTableStructure(ctx, params) {
1630
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1631
+ if (err) {
1632
+ throw new Error(err);
1633
+ }
1634
+ const slide = ctx.pptxData.slides[params.slideIndex];
1635
+ const el = slide.elements.find((e) => e.id === params.elementId);
1636
+ if (!el) {
1637
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1638
+ }
1639
+ if (el.type !== "table") {
1640
+ throw new Error(`Element '${params.elementId}' is not a table.`);
1641
+ }
1642
+ const tbl = el;
1643
+ if (!tbl.tableData) {
1644
+ throw new Error(`Table '${params.elementId}' has no tableData.`);
1645
+ }
1646
+ const { rows, columnWidths } = tbl.tableData;
1647
+ const colCount = columnWidths.length;
1648
+ const rowCount = rows.length;
1649
+ switch (params.action) {
1650
+ case "insertRow": {
1651
+ const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), rowCount) : rowCount;
1652
+ const newRow = {
1653
+ height: 40,
1654
+ cells: Array.from({ length: colCount }, (_, c) => ({
1655
+ text: params.cellTexts?.[c] ?? ""
1656
+ }))
1657
+ };
1658
+ rows.splice(pos, 0, newRow);
1659
+ break;
1660
+ }
1661
+ case "deleteRow": {
1662
+ if (rowCount <= 1) {
1663
+ throw new Error("Cannot delete the last row of a table.");
1664
+ }
1665
+ const ref = params.referenceIndex ?? rowCount - 1;
1666
+ if (ref < 0 || ref >= rowCount) {
1667
+ throw new Error(`Row index ${ref} out of range (0\u2013${rowCount - 1}).`);
1668
+ }
1669
+ rows.splice(ref, 1);
1670
+ break;
1671
+ }
1672
+ case "insertColumn": {
1673
+ const pos = params.position !== void 0 ? Math.min(Math.max(params.position, 0), colCount) : colCount;
1674
+ const newWidth = 1 / (colCount + 1);
1675
+ const scaleFactor = colCount / (colCount + 1);
1676
+ for (let i = 0; i < columnWidths.length; i++) {
1677
+ columnWidths[i] *= scaleFactor;
1678
+ }
1679
+ columnWidths.splice(pos, 0, newWidth);
1680
+ for (let r = 0; r < rows.length; r++) {
1681
+ rows[r].cells.splice(pos, 0, {
1682
+ text: params.cellTexts?.[r] ?? ""
1683
+ });
1684
+ }
1685
+ break;
1686
+ }
1687
+ case "deleteColumn": {
1688
+ if (colCount <= 1) {
1689
+ throw new Error("Cannot delete the last column of a table.");
1690
+ }
1691
+ const ref = params.referenceIndex ?? colCount - 1;
1692
+ if (ref < 0 || ref >= colCount) {
1693
+ throw new Error(`Column index ${ref} out of range (0\u2013${colCount - 1}).`);
1694
+ }
1695
+ const removedWidth = columnWidths[ref];
1696
+ columnWidths.splice(ref, 1);
1697
+ const remaining = columnWidths.length;
1698
+ if (remaining > 0) {
1699
+ const extra = removedWidth / remaining;
1700
+ for (let i = 0; i < columnWidths.length; i++) {
1701
+ columnWidths[i] += extra;
1702
+ }
1703
+ }
1704
+ for (const row of rows) {
1705
+ row.cells.splice(ref, 1);
1706
+ }
1707
+ break;
1708
+ }
1709
+ default: {
1710
+ throw new Error(`Unknown action: ${String(params.action)}`);
1711
+ }
1712
+ }
1713
+ return {
1714
+ pptxData: ctx.pptxData,
1715
+ dirty: true,
1716
+ result: {
1717
+ rowCount: tbl.tableData.rows.length,
1718
+ columnCount: tbl.tableData.columnWidths.length
1719
+ }
1720
+ };
1721
+ }
1722
+ var PPTX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
1723
+ async function loadPresentation(filePath, execCtx) {
1724
+ const rawBytes = await execCtx.filesystem.readFile(filePath);
1725
+ if (execCtx.collaboration) {
1726
+ const room = execCtx.collaboration.getRoom(filePath);
1727
+ if (room) {
1728
+ const codec = execCtx.collaboration.getCodec(filePath);
1729
+ if (codec) {
1730
+ const currentBytes = await codec.dehydrate(room.ydoc);
1731
+ const handler2 = new PptxHandler();
1732
+ const pptxData2 = await handler2.load(currentBytes.buffer);
1733
+ return { pptxData: pptxData2, rawBytes };
1734
+ }
1735
+ }
1736
+ }
1737
+ const handler = new PptxHandler();
1738
+ const pptxData = await handler.load(rawBytes.buffer);
1739
+ return { pptxData, rawBytes };
1740
+ }
1741
+ async function savePresentation(filePath, pptxData, rawBytes, execCtx) {
1742
+ const handler = new PptxHandler();
1743
+ await handler.load(rawBytes.buffer);
1744
+ const outputBytes = await handler.save(pptxData.slides, {
1745
+ headerFooter: pptxData.headerFooter
1746
+ });
1747
+ if (execCtx.collaboration) {
1748
+ const room = execCtx.collaboration.getRoom(filePath);
1749
+ if (room) {
1750
+ const codec = execCtx.collaboration.getCodec(filePath);
1751
+ if (codec) {
1752
+ const origin = execCtx.collaboration.agentOrigin(execCtx.agentName ?? "pptx-tool");
1753
+ await codec.hydrate(room.ydoc, outputBytes, origin);
1754
+ if (execCtx.viewer) {
1755
+ execCtx.viewer.replaceContent(filePath, outputBytes, {
1756
+ markDirty: false,
1757
+ mimeType: PPTX_MIME_TYPE
1758
+ });
1759
+ }
1760
+ return { savedToDisk: false, routedThroughCollaboration: true };
1761
+ }
1762
+ }
1763
+ }
1764
+ await execCtx.filesystem.writeFile(filePath, outputBytes);
1765
+ if (execCtx.viewer) {
1766
+ execCtx.viewer.replaceContent(filePath, outputBytes, {
1767
+ markDirty: false,
1768
+ mimeType: PPTX_MIME_TYPE
1769
+ });
1770
+ }
1771
+ return { savedToDisk: true, routedThroughCollaboration: false };
1772
+ }
1773
+ async function executeToolWithContext(filePath, execCtx, toolFn) {
1774
+ const { pptxData, rawBytes } = await loadPresentation(filePath, execCtx);
1775
+ const toolResult = await toolFn({ pptxData });
1776
+ if (toolResult.dirty) {
1777
+ const saveResult = await savePresentation(filePath, toolResult.pptxData, rawBytes, execCtx);
1778
+ return { ...toolResult.result, ...saveResult };
1779
+ }
1780
+ return toolResult.result;
1781
+ }
1782
+
1783
+ // src/mcp/handlers.ts
1784
+ var nodeFs = {
1785
+ readFile: async (path) => new Uint8Array(await readFile(path)),
1786
+ writeFile: async (path, data) => {
1787
+ await writeFile(path, data);
1788
+ }
1789
+ };
1790
+ function createMcpContext() {
1791
+ return { filesystem: nodeFs };
1792
+ }
1793
+ async function runMcpTool(filePath, fn) {
1794
+ const ctx = createMcpContext();
1795
+ return executeToolWithContext(filePath, ctx, fn);
1796
+ }
1797
+
1798
+ // src/mcp/server.ts
1799
+ function createServer() {
1800
+ const server2 = new McpServer({
1801
+ name: "pptx-viewer-tools",
1802
+ version: "1.0.0"
1803
+ });
1804
+ server2.registerTool(
1805
+ "get_slide",
1806
+ {
1807
+ description: "Inspect a single slide \u2014 returns layout, notes, elements",
1808
+ inputSchema: GetSlideSchema.shape
1809
+ },
1810
+ async (params) => {
1811
+ const result = await runMcpTool(
1812
+ params.filePath,
1813
+ (ctx) => getSlide(ctx, { slideIndex: params.slideIndex })
1814
+ );
1815
+ return {
1816
+ content: [
1817
+ {
1818
+ type: "text",
1819
+ text: JSON.stringify(result, null, 2)
1820
+ }
1821
+ ]
1822
+ };
1823
+ }
1824
+ );
1825
+ server2.registerTool(
1826
+ "add_slide",
1827
+ {
1828
+ description: "Add a new blank slide to the presentation",
1829
+ inputSchema: AddSlideSchema.shape
1830
+ },
1831
+ async (params) => {
1832
+ const result = await runMcpTool(
1833
+ params.filePath,
1834
+ (ctx) => addSlide(ctx, params)
1835
+ );
1836
+ return {
1837
+ content: [
1838
+ {
1839
+ type: "text",
1840
+ text: JSON.stringify(result, null, 2)
1841
+ }
1842
+ ]
1843
+ };
1844
+ }
1845
+ );
1846
+ server2.registerTool(
1847
+ "delete_slides",
1848
+ {
1849
+ description: "Delete one or more slides by index",
1850
+ inputSchema: DeleteSlidesSchema.shape
1851
+ },
1852
+ async (params) => {
1853
+ const result = await runMcpTool(
1854
+ params.filePath,
1855
+ (ctx) => deleteSlides(ctx, {
1856
+ slideIndexes: params.slideIndexes
1857
+ })
1858
+ );
1859
+ return {
1860
+ content: [
1861
+ {
1862
+ type: "text",
1863
+ text: JSON.stringify(result, null, 2)
1864
+ }
1865
+ ]
1866
+ };
1867
+ }
1868
+ );
1869
+ server2.registerTool(
1870
+ "reorder_slides",
1871
+ {
1872
+ description: "Reorder slides by providing a new index array",
1873
+ inputSchema: ReorderSlidesSchema.shape
1874
+ },
1875
+ async (params) => {
1876
+ const result = await runMcpTool(
1877
+ params.filePath,
1878
+ (ctx) => reorderSlides(ctx, { newOrder: params.newOrder })
1879
+ );
1880
+ return {
1881
+ content: [
1882
+ {
1883
+ type: "text",
1884
+ text: JSON.stringify(result, null, 2)
1885
+ }
1886
+ ]
1887
+ };
1888
+ }
1889
+ );
1890
+ server2.registerTool(
1891
+ "duplicate_slide",
1892
+ {
1893
+ description: "Duplicate a slide with new element IDs",
1894
+ inputSchema: DuplicateSlideSchema.shape
1895
+ },
1896
+ async (params) => {
1897
+ const result = await runMcpTool(
1898
+ params.filePath,
1899
+ (ctx) => duplicateSlide(ctx, params)
1900
+ );
1901
+ return {
1902
+ content: [
1903
+ {
1904
+ type: "text",
1905
+ text: JSON.stringify(result, null, 2)
1906
+ }
1907
+ ]
1908
+ };
1909
+ }
1910
+ );
1911
+ server2.registerTool(
1912
+ "update_slide_properties",
1913
+ {
1914
+ description: "Update slide background, notes, or visibility",
1915
+ inputSchema: UpdateSlidePropertiesSchema.shape
1916
+ },
1917
+ async (params) => {
1918
+ const result = await runMcpTool(
1919
+ params.filePath,
1920
+ (ctx) => updateSlideProperties(ctx, params)
1921
+ );
1922
+ return {
1923
+ content: [
1924
+ {
1925
+ type: "text",
1926
+ text: JSON.stringify(result, null, 2)
1927
+ }
1928
+ ]
1929
+ };
1930
+ }
1931
+ );
1932
+ server2.registerTool(
1933
+ "set_slide_transition",
1934
+ {
1935
+ description: "Set or remove a slide transition effect",
1936
+ inputSchema: SetSlideTransitionSchema.shape
1937
+ },
1938
+ async (params) => {
1939
+ const result = await runMcpTool(
1940
+ params.filePath,
1941
+ (ctx) => setSlideTransition(ctx, params)
1942
+ );
1943
+ return {
1944
+ content: [
1945
+ {
1946
+ type: "text",
1947
+ text: JSON.stringify(result, null, 2)
1948
+ }
1949
+ ]
1950
+ };
1951
+ }
1952
+ );
1953
+ server2.registerTool(
1954
+ "set_canvas_size",
1955
+ {
1956
+ description: "Change the presentation canvas dimensions",
1957
+ inputSchema: SetCanvasSizeSchema.shape
1958
+ },
1959
+ async (params) => {
1960
+ const result = await runMcpTool(
1961
+ params.filePath,
1962
+ (ctx) => setCanvasSize(ctx, params)
1963
+ );
1964
+ return {
1965
+ content: [
1966
+ {
1967
+ type: "text",
1968
+ text: JSON.stringify(result, null, 2)
1969
+ }
1970
+ ]
1971
+ };
1972
+ }
1973
+ );
1974
+ server2.registerTool(
1975
+ "add_element",
1976
+ {
1977
+ description: "Add a text, shape, image, table, or connector element",
1978
+ inputSchema: AddElementSchema.shape
1979
+ },
1980
+ async (params) => {
1981
+ const result = await runMcpTool(
1982
+ params.filePath,
1983
+ (ctx) => addElement(ctx, params)
1984
+ );
1985
+ return {
1986
+ content: [
1987
+ {
1988
+ type: "text",
1989
+ text: JSON.stringify(result, null, 2)
1990
+ }
1991
+ ]
1992
+ };
1993
+ }
1994
+ );
1995
+ server2.registerTool(
1996
+ "update_element",
1997
+ {
1998
+ description: "Update element position, size, text, or style",
1999
+ inputSchema: UpdateElementSchema.shape
2000
+ },
2001
+ async (params) => {
2002
+ const result = await runMcpTool(
2003
+ params.filePath,
2004
+ (ctx) => updateElement(ctx, params)
2005
+ );
2006
+ return {
2007
+ content: [
2008
+ {
2009
+ type: "text",
2010
+ text: JSON.stringify(result, null, 2)
2011
+ }
2012
+ ]
2013
+ };
2014
+ }
2015
+ );
2016
+ server2.registerTool(
2017
+ "delete_elements",
2018
+ {
2019
+ description: "Delete one or more elements by ID",
2020
+ inputSchema: DeleteElementsSchema.shape
2021
+ },
2022
+ async (params) => {
2023
+ const result = await runMcpTool(
2024
+ params.filePath,
2025
+ (ctx) => deleteElements(ctx, params)
2026
+ );
2027
+ return {
2028
+ content: [
2029
+ {
2030
+ type: "text",
2031
+ text: JSON.stringify(result, null, 2)
2032
+ }
2033
+ ]
2034
+ };
2035
+ }
2036
+ );
2037
+ server2.registerTool(
2038
+ "arrange_elements",
2039
+ {
2040
+ description: "Align elements or change z-order (layer)",
2041
+ inputSchema: ArrangeElementsSchema.shape
2042
+ },
2043
+ async (params) => {
2044
+ const result = await runMcpTool(
2045
+ params.filePath,
2046
+ (ctx) => arrangeElements(ctx, params)
2047
+ );
2048
+ return {
2049
+ content: [
2050
+ {
2051
+ type: "text",
2052
+ text: JSON.stringify(result, null, 2)
2053
+ }
2054
+ ]
2055
+ };
2056
+ }
2057
+ );
2058
+ server2.registerTool(
2059
+ "clone_element",
2060
+ {
2061
+ description: "Clone an element within or across slides",
2062
+ inputSchema: CloneElementSchema.shape
2063
+ },
2064
+ async (params) => {
2065
+ const result = await runMcpTool(
2066
+ params.filePath,
2067
+ (ctx) => cloneElement(ctx, params)
2068
+ );
2069
+ return {
2070
+ content: [
2071
+ {
2072
+ type: "text",
2073
+ text: JSON.stringify(result, null, 2)
2074
+ }
2075
+ ]
2076
+ };
2077
+ }
2078
+ );
2079
+ server2.registerTool(
2080
+ "set_element_animation",
2081
+ {
2082
+ description: "Set entrance/exit animation on an element",
2083
+ inputSchema: SetElementAnimationSchema.shape
2084
+ },
2085
+ async (params) => {
2086
+ const result = await runMcpTool(
2087
+ params.filePath,
2088
+ (ctx) => setElementAnimation(ctx, params)
2089
+ );
2090
+ return {
2091
+ content: [
2092
+ {
2093
+ type: "text",
2094
+ text: JSON.stringify(result, null, 2)
2095
+ }
2096
+ ]
2097
+ };
2098
+ }
2099
+ );
2100
+ server2.registerTool(
2101
+ "group_elements",
2102
+ {
2103
+ description: "Group multiple elements into a group",
2104
+ inputSchema: GroupElementsSchema.shape
2105
+ },
2106
+ async (params) => {
2107
+ const result = await runMcpTool(
2108
+ params.filePath,
2109
+ (ctx) => groupElements(ctx, params)
2110
+ );
2111
+ return {
2112
+ content: [
2113
+ {
2114
+ type: "text",
2115
+ text: JSON.stringify(result, null, 2)
2116
+ }
2117
+ ]
2118
+ };
2119
+ }
2120
+ );
2121
+ server2.registerTool(
2122
+ "ungroup_elements",
2123
+ {
2124
+ description: "Ungroup a group element back to individual elements",
2125
+ inputSchema: UngroupElementsSchema.shape
2126
+ },
2127
+ async (params) => {
2128
+ const result = await runMcpTool(
2129
+ params.filePath,
2130
+ (ctx) => ungroupElements(ctx, params)
2131
+ );
2132
+ return {
2133
+ content: [
2134
+ {
2135
+ type: "text",
2136
+ text: JSON.stringify(result, null, 2)
2137
+ }
2138
+ ]
2139
+ };
2140
+ }
2141
+ );
2142
+ server2.registerTool(
2143
+ "batch_update_elements",
2144
+ {
2145
+ description: "Apply the same properties to multiple elements",
2146
+ inputSchema: BatchUpdateElementsSchema.shape
2147
+ },
2148
+ async (params) => {
2149
+ const result = await runMcpTool(
2150
+ params.filePath,
2151
+ (ctx) => batchUpdateElements(ctx, params)
2152
+ );
2153
+ return {
2154
+ content: [
2155
+ {
2156
+ type: "text",
2157
+ text: JSON.stringify(result, null, 2)
2158
+ }
2159
+ ]
2160
+ };
2161
+ }
2162
+ );
2163
+ server2.registerTool(
2164
+ "update_table_cells",
2165
+ {
2166
+ description: "Update text content of table cells",
2167
+ inputSchema: UpdateTableCellsSchema.shape
2168
+ },
2169
+ async (params) => {
2170
+ const result = await runMcpTool(
2171
+ params.filePath,
2172
+ (ctx) => updateTableCells(ctx, params)
2173
+ );
2174
+ return {
2175
+ content: [
2176
+ {
2177
+ type: "text",
2178
+ text: JSON.stringify(result, null, 2)
2179
+ }
2180
+ ]
2181
+ };
2182
+ }
2183
+ );
2184
+ server2.registerTool(
2185
+ "manage_table_structure",
2186
+ {
2187
+ description: "Insert or delete table rows and columns",
2188
+ inputSchema: ManageTableStructureSchema.shape
2189
+ },
2190
+ async (params) => {
2191
+ const result = await runMcpTool(
2192
+ params.filePath,
2193
+ (ctx) => manageTableStructure(ctx, params)
2194
+ );
2195
+ return {
2196
+ content: [
2197
+ {
2198
+ type: "text",
2199
+ text: JSON.stringify(result, null, 2)
2200
+ }
2201
+ ]
2202
+ };
2203
+ }
2204
+ );
2205
+ server2.registerTool(
2206
+ "update_element_style",
2207
+ {
2208
+ description: "Update fill, stroke, shadow, glow, or image effects",
2209
+ inputSchema: UpdateElementStyleSchema.shape
2210
+ },
2211
+ async (params) => {
2212
+ const result = await runMcpTool(
2213
+ params.filePath,
2214
+ (ctx) => updateElementStyle(ctx, params)
2215
+ );
2216
+ return {
2217
+ content: [
2218
+ {
2219
+ type: "text",
2220
+ text: JSON.stringify(result, null, 2)
2221
+ }
2222
+ ]
2223
+ };
2224
+ }
2225
+ );
2226
+ server2.registerTool(
2227
+ "run_accessibility_check",
2228
+ {
2229
+ description: "Audit the presentation for accessibility issues",
2230
+ inputSchema: AccessibilityCheckSchema.shape
2231
+ },
2232
+ async (params) => {
2233
+ const result = await runMcpTool(
2234
+ params.filePath,
2235
+ (ctx) => runAccessibilityCheck(ctx)
2236
+ );
2237
+ return {
2238
+ content: [
2239
+ {
2240
+ type: "text",
2241
+ text: JSON.stringify(result, null, 2)
2242
+ }
2243
+ ]
2244
+ };
2245
+ }
2246
+ );
2247
+ server2.registerTool(
2248
+ "find_text",
2249
+ {
2250
+ description: "Search for text across all slides",
2251
+ inputSchema: FindTextSchema.shape
2252
+ },
2253
+ async (params) => {
2254
+ const result = await runMcpTool(
2255
+ params.filePath,
2256
+ (ctx) => findText(ctx, params)
2257
+ );
2258
+ return {
2259
+ content: [
2260
+ {
2261
+ type: "text",
2262
+ text: JSON.stringify(result, null, 2)
2263
+ }
2264
+ ]
2265
+ };
2266
+ }
2267
+ );
2268
+ server2.registerTool(
2269
+ "replace_text",
2270
+ {
2271
+ description: "Find and replace text across slides",
2272
+ inputSchema: ReplaceTextSchema.shape
2273
+ },
2274
+ async (params) => {
2275
+ const result = await runMcpTool(
2276
+ params.filePath,
2277
+ (ctx) => replaceText(ctx, params)
2278
+ );
2279
+ return {
2280
+ content: [
2281
+ {
2282
+ type: "text",
2283
+ text: JSON.stringify(result, null, 2)
2284
+ }
2285
+ ]
2286
+ };
2287
+ }
2288
+ );
2289
+ server2.registerTool(
2290
+ "manage_comments",
2291
+ {
2292
+ description: "List, add, delete, or resolve slide comments",
2293
+ inputSchema: ManageCommentsSchema.shape
2294
+ },
2295
+ async (params) => {
2296
+ const result = await runMcpTool(
2297
+ params.filePath,
2298
+ (ctx) => manageComments(ctx, params)
2299
+ );
2300
+ return {
2301
+ content: [
2302
+ {
2303
+ type: "text",
2304
+ text: JSON.stringify(result, null, 2)
2305
+ }
2306
+ ]
2307
+ };
2308
+ }
2309
+ );
2310
+ server2.registerTool(
2311
+ "convert_to_markdown",
2312
+ {
2313
+ description: "Convert the presentation to Markdown format",
2314
+ inputSchema: ConvertToMarkdownSchema.shape
2315
+ },
2316
+ async (params) => {
2317
+ const result = await runMcpTool(
2318
+ params.filePath,
2319
+ (ctx) => convertToMarkdown(ctx, params)
2320
+ );
2321
+ return {
2322
+ content: [
2323
+ {
2324
+ type: "text",
2325
+ text: JSON.stringify(result, null, 2)
2326
+ }
2327
+ ]
2328
+ };
2329
+ }
2330
+ );
2331
+ return server2;
2332
+ }
2333
+
2334
+ // src/mcp/index.ts
2335
+ var server = createServer();
2336
+ var transport = new StdioServerTransport();
2337
+ void server.connect(transport);