pptx-viewer-mcp 1.1.26 → 1.2.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.
@@ -2,7 +2,7 @@
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { z } from 'zod';
5
- import { PptxHandler, hasTextProperties, PptxMarkdownConverter } from 'pptx-viewer-core';
5
+ import { validatePptx, repairPptx, PptxHandler, hasTextProperties, PptxMarkdownConverter, ThemePresets, getThemePreset, setChartType, setChartTitle, setChartGrouping, setChartLegend, setChartDataLabels, setChartAxis, setChartCategories, addChartSeries, removeChartSeries, updateChartSeriesValues, ChartBuilder, decomposeSmartArt, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, updateSmartArtNodeText, removeSmartArtNode, addSmartArtNode, findPlaceholders, applyTemplate, getSectionForSlide, moveSlidesToSection, reorderSections, removeSection, addSection, SvgExporter, replaceWithCustomGeometry, replaceShapeGeometry, setElementLocked, findLayoutByName, findLayoutByType } from 'pptx-viewer-core';
6
6
  import { resolve, isAbsolute, sep } from 'path';
7
7
  import { writeFile, readFile } from 'fs/promises';
8
8
 
@@ -260,6 +260,241 @@ var ManageCommentsSchema = z.object({
260
260
  resolved: z.boolean().optional(),
261
261
  includeResolved: z.boolean().optional()
262
262
  });
263
+ var GetThemeInfoSchema = z.object({
264
+ filePath: z.string().describe("Path to the PPTX file")
265
+ });
266
+ var ApplyThemePresetSchema = z.object({
267
+ filePath: z.string().describe("Path to the PPTX file"),
268
+ presetName: z.string().describe(
269
+ 'Theme preset name (e.g. "OFFICE", "MODERN_BLUE", "EARTH", "MONOCHROME", "VIBRANT", "CORPORATE", "MINIMAL", "DARK")'
270
+ )
271
+ });
272
+ var UpdateThemeColorsSchema = z.object({
273
+ filePath: z.string().describe("Path to the PPTX file"),
274
+ dk1: z.string().optional().describe("Dark 1 color (hex)"),
275
+ lt1: z.string().optional().describe("Light 1 color (hex)"),
276
+ dk2: z.string().optional().describe("Dark 2 color (hex)"),
277
+ lt2: z.string().optional().describe("Light 2 color (hex)"),
278
+ accent1: z.string().optional().describe("Accent 1 color (hex)"),
279
+ accent2: z.string().optional().describe("Accent 2 color (hex)"),
280
+ accent3: z.string().optional().describe("Accent 3 color (hex)"),
281
+ accent4: z.string().optional().describe("Accent 4 color (hex)"),
282
+ accent5: z.string().optional().describe("Accent 5 color (hex)"),
283
+ accent6: z.string().optional().describe("Accent 6 color (hex)"),
284
+ hlink: z.string().optional().describe("Hyperlink color (hex)"),
285
+ folHlink: z.string().optional().describe("Followed hyperlink color (hex)")
286
+ });
287
+ var UpdateThemeFontsSchema = z.object({
288
+ filePath: z.string().describe("Path to the PPTX file"),
289
+ majorFont: z.string().optional().describe("Heading (major) font family"),
290
+ minorFont: z.string().optional().describe("Body (minor) font family")
291
+ });
292
+ var UpdateChartSchema = z.object({
293
+ filePath: z.string().describe("Path to the PPTX file"),
294
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
295
+ elementId: z.string().describe("Chart element ID"),
296
+ chartType: z.string().optional().describe("New chart type (bar, line, pie, scatter, area, doughnut, radar)"),
297
+ title: z.string().optional().describe("Chart title"),
298
+ grouping: z.enum(["clustered", "stacked", "percentStacked"]).optional().describe("Bar/column grouping"),
299
+ legend: z.object({
300
+ show: z.boolean().optional(),
301
+ position: z.string().optional().describe("Legend position: b, t, l, r, tr")
302
+ }).optional().describe("Legend configuration"),
303
+ dataLabels: z.object({
304
+ show: z.boolean().optional(),
305
+ showValue: z.boolean().optional(),
306
+ showCategory: z.boolean().optional(),
307
+ showSeriesName: z.boolean().optional(),
308
+ showPercent: z.boolean().optional()
309
+ }).optional().describe("Data label configuration"),
310
+ axis: z.object({
311
+ type: z.string().describe("Axis type: valAx, catAx, dateAx, serAx"),
312
+ edit: z.object({
313
+ min: z.number().nullable().optional(),
314
+ max: z.number().nullable().optional(),
315
+ majorUnit: z.number().nullable().optional(),
316
+ title: z.string().nullable().optional(),
317
+ numberFormat: z.string().optional(),
318
+ majorGridlines: z.boolean().optional(),
319
+ minorGridlines: z.boolean().optional()
320
+ })
321
+ }).optional().describe("Axis configuration"),
322
+ categories: z.array(z.string()).optional().describe("Category axis labels")
323
+ });
324
+ var AddChartSeriesSchema = z.object({
325
+ filePath: z.string().describe("Path to the PPTX file"),
326
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
327
+ elementId: z.string().describe("Chart element ID"),
328
+ name: z.string().describe("Series name"),
329
+ values: z.array(z.number()).describe("Data values"),
330
+ color: z.string().optional().describe("Series color (hex)")
331
+ });
332
+ var RemoveChartSeriesSchema = z.object({
333
+ filePath: z.string().describe("Path to the PPTX file"),
334
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
335
+ elementId: z.string().describe("Chart element ID"),
336
+ seriesIndex: z.number().int().min(0).describe("Zero-based series index to remove")
337
+ });
338
+ var UpdateChartSeriesDataSchema = z.object({
339
+ filePath: z.string().describe("Path to the PPTX file"),
340
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
341
+ elementId: z.string().describe("Chart element ID"),
342
+ seriesIndex: z.number().int().min(0).describe("Zero-based series index"),
343
+ values: z.array(z.number()).describe("New data values")
344
+ });
345
+ var CreateChartSchema = z.object({
346
+ filePath: z.string().describe("Path to the PPTX file"),
347
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
348
+ chartType: z.string().describe("Chart type (bar, line, pie, scatter, area, doughnut, radar)"),
349
+ x: z.number().optional().describe("X position in points"),
350
+ y: z.number().optional().describe("Y position in points"),
351
+ width: z.number().optional().describe("Width in points"),
352
+ height: z.number().optional().describe("Height in points"),
353
+ title: z.string().optional().describe("Chart title"),
354
+ categories: z.array(z.string()).optional().describe("Category labels"),
355
+ series: z.array(
356
+ z.object({
357
+ name: z.string(),
358
+ values: z.array(z.number()),
359
+ color: z.string().optional()
360
+ })
361
+ ).optional().describe("Data series"),
362
+ legend: z.object({
363
+ show: z.boolean(),
364
+ position: z.string().optional()
365
+ }).optional().describe("Legend configuration")
366
+ });
367
+ var ManageSmartArtSchema = z.object({
368
+ filePath: z.string().describe("Path to the PPTX file"),
369
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
370
+ elementId: z.string().describe("SmartArt element ID"),
371
+ action: z.enum([
372
+ "getNodes",
373
+ "addNode",
374
+ "removeNode",
375
+ "updateNodeText",
376
+ "reorderNode",
377
+ "promoteNode",
378
+ "demoteNode",
379
+ "decompose"
380
+ ]).describe("SmartArt action"),
381
+ nodeId: z.string().optional().describe("Node ID (required for most actions)"),
382
+ text: z.string().optional().describe("Text for addNode or updateNodeText"),
383
+ afterNodeId: z.string().optional().describe("Insert after this node ID"),
384
+ direction: z.number().optional().describe("Reorder direction: 1 (forward) or -1 (backward)")
385
+ });
386
+ var FindPlaceholdersSchema = z.object({
387
+ filePath: z.string().describe("Path to the PPTX file")
388
+ });
389
+ var ApplyTemplateSchema = z.object({
390
+ filePath: z.string().describe("Path to the PPTX file"),
391
+ data: z.record(z.string(), z.unknown()).describe("Template data: keys are placeholder names, values are replacement values")
392
+ });
393
+ var GetMetadataSchema = z.object({
394
+ filePath: z.string().describe("Path to the PPTX file")
395
+ });
396
+ var UpdateMetadataSchema = z.object({
397
+ filePath: z.string().describe("Path to the PPTX file"),
398
+ title: z.string().optional().describe("Presentation title"),
399
+ subject: z.string().optional().describe("Subject"),
400
+ creator: z.string().optional().describe("Creator/author name"),
401
+ keywords: z.string().optional().describe("Keywords (comma-separated)"),
402
+ description: z.string().optional().describe("Description/comments"),
403
+ lastModifiedBy: z.string().optional().describe("Last modified by"),
404
+ category: z.string().optional().describe("Category"),
405
+ company: z.string().optional().describe("Company name"),
406
+ manager: z.string().optional().describe("Manager name"),
407
+ customProperties: z.array(
408
+ z.object({
409
+ name: z.string(),
410
+ value: z.union([z.string(), z.number(), z.boolean()])
411
+ })
412
+ ).optional().describe("Custom properties to set")
413
+ });
414
+ var ManageSectionsSchema = z.object({
415
+ filePath: z.string().describe("Path to the PPTX file"),
416
+ action: z.enum(["list", "add", "remove", "reorder", "moveSlides", "getForSlide"]).describe("Section action"),
417
+ name: z.string().optional().describe("Section name (for add)"),
418
+ slideIndices: z.array(z.number().int().min(0)).optional().describe("Slide indices (for add, moveSlides)"),
419
+ sectionId: z.string().optional().describe("Section ID (for remove, moveSlides)"),
420
+ sectionIds: z.array(z.string()).optional().describe("Ordered section IDs (for reorder)"),
421
+ slideIndex: z.number().int().min(0).optional().describe("Slide index (for getForSlide)")
422
+ });
423
+ var ExportToSvgSchema = z.object({
424
+ filePath: z.string().describe("Path to the PPTX file"),
425
+ slideIndices: z.array(z.number().int().min(0)).optional().describe("Specific slides to export (all if omitted)"),
426
+ includeHidden: z.boolean().optional().describe("Include hidden slides"),
427
+ defaultFontFamily: z.string().optional().describe("Default font family for SVG"),
428
+ defaultFontSize: z.number().optional().describe("Default font size")
429
+ });
430
+ var ExportSlideSvgSchema = z.object({
431
+ filePath: z.string().describe("Path to the PPTX file"),
432
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index to export"),
433
+ defaultFontFamily: z.string().optional().describe("Default font family for SVG"),
434
+ defaultFontSize: z.number().optional().describe("Default font size")
435
+ });
436
+ var ManageHyperlinksSchema = z.object({
437
+ filePath: z.string().describe("Path to the PPTX file"),
438
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
439
+ action: z.enum(["list", "set", "remove"]).describe("Hyperlink action"),
440
+ elementId: z.string().optional().describe("Element ID (for set/remove)"),
441
+ trigger: z.enum(["click", "hover"]).optional().describe("Action trigger (default: click)"),
442
+ url: z.string().optional().describe("URL for the hyperlink"),
443
+ tooltip: z.string().optional().describe("Tooltip text"),
444
+ targetSlideIndex: z.number().int().min(0).optional().describe("Target slide index for internal links"),
445
+ actionType: z.enum(["url", "slide", "nextSlide", "prevSlide", "firstSlide", "lastSlide", "endShow"]).optional().describe("Link action type")
446
+ });
447
+ var ReplaceGeometrySchema = z.object({
448
+ filePath: z.string().describe("Path to the PPTX file"),
449
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
450
+ elementId: z.string().describe("Shape element ID"),
451
+ shapeType: z.string().optional().describe('Preset shape type (e.g. "roundRect", "star5", "ellipse", "diamond")'),
452
+ svgPath: z.string().optional().describe("Custom SVG path data (mutually exclusive with shapeType)"),
453
+ pathWidth: z.number().optional().describe("Width of SVG path coordinate space"),
454
+ pathHeight: z.number().optional().describe("Height of SVG path coordinate space"),
455
+ adjustments: z.record(z.string(), z.number()).optional().describe("Shape adjustment values")
456
+ });
457
+ var SetElementLockSchema = z.object({
458
+ filePath: z.string().describe("Path to the PPTX file"),
459
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
460
+ elementId: z.string().describe("Element ID to lock/unlock"),
461
+ locked: z.boolean().describe("Whether the element should be locked"),
462
+ noMove: z.boolean().optional().describe("Prevent moving"),
463
+ noResize: z.boolean().optional().describe("Prevent resizing"),
464
+ noRotation: z.boolean().optional().describe("Prevent rotation"),
465
+ noSelect: z.boolean().optional().describe("Prevent selection"),
466
+ noTextEdit: z.boolean().optional().describe("Prevent text editing")
467
+ });
468
+ var ValidatePresentationSchema = z.object({
469
+ filePath: z.string().describe("Path to the PPTX file")
470
+ });
471
+ var RepairPresentationSchema = z.object({
472
+ filePath: z.string().describe("Path to the PPTX file")
473
+ });
474
+ var GetPresentationPropertiesSchema = z.object({
475
+ filePath: z.string().describe("Path to the PPTX file")
476
+ });
477
+ var UpdatePresentationPropertiesSchema = z.object({
478
+ filePath: z.string().describe("Path to the PPTX file"),
479
+ showType: z.enum(["presented", "browsed", "kiosk"]).optional().describe("Slideshow mode"),
480
+ loopContinuously: z.boolean().optional().describe("Loop the slideshow"),
481
+ showWithNarration: z.boolean().optional().describe("Play narration"),
482
+ showWithAnimation: z.boolean().optional().describe("Play animations"),
483
+ advanceMode: z.enum(["manual", "useTimings"]).optional().describe("Slide advance mode"),
484
+ showSlidesMode: z.enum(["all", "customShow", "range"]).optional().describe("Which slides to show"),
485
+ showSlidesFrom: z.number().int().min(1).optional().describe("Range start (1-based)"),
486
+ showSlidesTo: z.number().int().min(1).optional().describe("Range end (1-based)"),
487
+ penColor: z.string().optional().describe("Annotation pen color (hex)")
488
+ });
489
+ var GetLayoutsSchema = z.object({
490
+ filePath: z.string().describe("Path to the PPTX file")
491
+ });
492
+ var ApplyLayoutSchema = z.object({
493
+ filePath: z.string().describe("Path to the PPTX file"),
494
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
495
+ layoutName: z.string().optional().describe("Layout name to apply"),
496
+ layoutType: z.string().optional().describe("Layout type to apply (alternative to name)")
497
+ });
263
498
  function generateElementId() {
264
499
  return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
265
500
  }
@@ -367,7 +602,144 @@ function validateSlideIndex(slideIndex, slideCount) {
367
602
  return null;
368
603
  }
369
604
 
370
- // src/tools/content-tools.ts
605
+ // src/tools/chart-tools.ts
606
+ function updateChart(ctx, params) {
607
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
608
+ if (err) {
609
+ throw new Error(err);
610
+ }
611
+ const slide = ctx.pptxData.slides[params.slideIndex];
612
+ const el = slide.elements.find((e) => e.id === params.elementId);
613
+ if (!el) {
614
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
615
+ }
616
+ if (el.type !== "chart") {
617
+ throw new Error(`Element '${params.elementId}' is not a chart.`);
618
+ }
619
+ const chart = el;
620
+ if (params.chartType) {
621
+ setChartType(chart, params.chartType);
622
+ }
623
+ if (params.title !== void 0) {
624
+ setChartTitle(chart, params.title);
625
+ }
626
+ if (params.grouping) {
627
+ setChartGrouping(chart, params.grouping);
628
+ }
629
+ if (params.legend) {
630
+ setChartLegend(chart, {
631
+ show: params.legend.show,
632
+ position: params.legend.position
633
+ });
634
+ }
635
+ if (params.dataLabels) {
636
+ setChartDataLabels(chart, params.dataLabels);
637
+ }
638
+ if (params.axis) {
639
+ setChartAxis(chart, params.axis.type, params.axis.edit);
640
+ }
641
+ if (params.categories) {
642
+ setChartCategories(chart, params.categories);
643
+ }
644
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
645
+ }
646
+ function addChartSeriesT(ctx, params) {
647
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
648
+ if (err) {
649
+ throw new Error(err);
650
+ }
651
+ const slide = ctx.pptxData.slides[params.slideIndex];
652
+ const el = slide.elements.find((e) => e.id === params.elementId);
653
+ if (!el) {
654
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
655
+ }
656
+ if (el.type !== "chart") {
657
+ throw new Error(`Element '${params.elementId}' is not a chart.`);
658
+ }
659
+ const chart = el;
660
+ addChartSeries(chart, { name: params.name, values: params.values, color: params.color });
661
+ const seriesCount = chart.chartData?.series?.length ?? 0;
662
+ return {
663
+ pptxData: ctx.pptxData,
664
+ dirty: true,
665
+ result: { elementId: params.elementId, seriesCount }
666
+ };
667
+ }
668
+ function removeChartSeriesT(ctx, params) {
669
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
670
+ if (err) {
671
+ throw new Error(err);
672
+ }
673
+ const slide = ctx.pptxData.slides[params.slideIndex];
674
+ const el = slide.elements.find((e) => e.id === params.elementId);
675
+ if (!el) {
676
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
677
+ }
678
+ if (el.type !== "chart") {
679
+ throw new Error(`Element '${params.elementId}' is not a chart.`);
680
+ }
681
+ const chart = el;
682
+ removeChartSeries(chart, params.seriesIndex);
683
+ const seriesCount = chart.chartData?.series?.length ?? 0;
684
+ return {
685
+ pptxData: ctx.pptxData,
686
+ dirty: true,
687
+ result: { elementId: params.elementId, seriesCount }
688
+ };
689
+ }
690
+ function updateChartSeriesData(ctx, params) {
691
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
692
+ if (err) {
693
+ throw new Error(err);
694
+ }
695
+ const slide = ctx.pptxData.slides[params.slideIndex];
696
+ const el = slide.elements.find((e) => e.id === params.elementId);
697
+ if (!el) {
698
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
699
+ }
700
+ if (el.type !== "chart") {
701
+ throw new Error(`Element '${params.elementId}' is not a chart.`);
702
+ }
703
+ const chart = el;
704
+ updateChartSeriesValues(chart, params.seriesIndex, params.values);
705
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
706
+ }
707
+ function createChart(ctx, params) {
708
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
709
+ if (err) {
710
+ throw new Error(err);
711
+ }
712
+ const builder = ChartBuilder.create(
713
+ params.chartType
714
+ );
715
+ builder.position(params.x ?? 100, params.y ?? 100);
716
+ builder.size(params.width ?? 500, params.height ?? 350);
717
+ if (params.title) {
718
+ builder.title(params.title);
719
+ }
720
+ if (params.categories) {
721
+ builder.categories(params.categories);
722
+ }
723
+ if (params.series) {
724
+ for (const s of params.series) {
725
+ builder.addSeries(s.name, s.values, s.color);
726
+ }
727
+ }
728
+ if (params.legend) {
729
+ builder.legend(
730
+ params.legend.show,
731
+ params.legend.position
732
+ );
733
+ }
734
+ const chart = builder.build();
735
+ const slide = ctx.pptxData.slides[params.slideIndex];
736
+ slide.elements.push(chart);
737
+ return {
738
+ pptxData: ctx.pptxData,
739
+ dirty: true,
740
+ result: { elementId: chart.id, slideIndex: params.slideIndex }
741
+ };
742
+ }
371
743
  var MAX_REGEX_LEN = 200;
372
744
  function hasNestedUnboundedQuantifier(pattern) {
373
745
  return /\([^)]*[+*]\)[+*]/u.test(pattern);
@@ -1255,129 +1627,621 @@ function batchUpdateElements(ctx, params) {
1255
1627
  result: { updatedCount: count }
1256
1628
  };
1257
1629
  }
1258
-
1259
- // src/tools/slide-tools.ts
1260
- function getSlide(ctx, params) {
1261
- const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1262
- if (err) {
1263
- throw new Error(err);
1264
- }
1265
- const slide = ctx.pptxData.slides[params.slideIndex];
1266
- return {
1267
- pptxData: ctx.pptxData,
1268
- dirty: false,
1269
- result: {
1270
- slideIndex: params.slideIndex,
1271
- slideNumber: slide.slideNumber,
1272
- hidden: slide.hidden ?? false,
1273
- layoutName: slide.layoutName,
1274
- backgroundColor: slide.backgroundColor,
1275
- notes: slide.notes ?? "",
1276
- commentCount: slide.comments?.length ?? 0,
1277
- elements: slide.elements.map(describeElement)
1630
+ function exportToSvg(ctx, params) {
1631
+ if (params.slideIndices) {
1632
+ for (const si of params.slideIndices) {
1633
+ const err = validateSlideIndex(si, ctx.pptxData.slides.length);
1634
+ if (err) {
1635
+ throw new Error(err);
1636
+ }
1278
1637
  }
1279
- };
1280
- }
1281
- function addSlide(ctx, params) {
1282
- const { slides } = ctx.pptxData;
1283
- const insertAt = params.insertAfterIndex !== void 0 && params.insertAfterIndex >= 0 ? Math.min(params.insertAfterIndex + 1, slides.length) : slides.length;
1284
- const newSlide = {
1285
- id: generateSlideId(),
1286
- rId: "",
1287
- slideNumber: insertAt + 1,
1288
- elements: [],
1289
- backgroundColor: params.backgroundColor
1290
- };
1291
- slides.splice(insertAt, 0, newSlide);
1292
- slides.forEach((s, i) => {
1293
- s.slideNumber = i + 1;
1294
- });
1295
- return {
1296
- pptxData: ctx.pptxData,
1297
- dirty: true,
1298
- result: { newSlideIndex: insertAt, slideCount: slides.length }
1299
- };
1300
- }
1301
- function deleteSlides(ctx, params) {
1302
- const { slides } = ctx.pptxData;
1303
- if (!params.slideIndexes || params.slideIndexes.length === 0) {
1304
- throw new Error("slideIndexes is required for the delete action.");
1305
1638
  }
1306
- const indexSet = new Set(params.slideIndexes);
1307
- const invalid = params.slideIndexes.filter((i) => i < 0 || i >= slides.length);
1308
- if (invalid.length > 0) {
1309
- throw new Error(
1310
- `Invalid slide indexes: ${invalid.join(", ")}. Valid range: 0\u2013${slides.length - 1}.`
1311
- );
1639
+ const options = {};
1640
+ if (params.slideIndices) {
1641
+ options.slideIndices = params.slideIndices;
1312
1642
  }
1313
- if (indexSet.size >= slides.length) {
1314
- throw new Error("Cannot delete all slides. At least one slide must remain.");
1643
+ if (params.includeHidden !== void 0) {
1644
+ options.includeHidden = params.includeHidden;
1315
1645
  }
1316
- ctx.pptxData.slides = slides.filter((_, i) => !indexSet.has(i));
1317
- ctx.pptxData.slides.forEach((s, i) => {
1318
- s.slideNumber = i + 1;
1319
- });
1320
- return {
1321
- pptxData: ctx.pptxData,
1322
- dirty: true,
1323
- result: { deletedCount: indexSet.size, slideCount: ctx.pptxData.slides.length }
1324
- };
1325
- }
1326
- function reorderSlides(ctx, params) {
1327
- const { slides } = ctx.pptxData;
1328
- if (!params.newOrder || params.newOrder.length !== slides.length) {
1329
- throw new Error(
1330
- `newOrder must have exactly ${slides.length} indexes. Got ${params.newOrder?.length ?? 0}.`
1331
- );
1646
+ if (params.defaultFontFamily) {
1647
+ options.defaultFontFamily = params.defaultFontFamily;
1332
1648
  }
1333
- const sorted = [...params.newOrder].sort((a, b) => a - b);
1334
- const expected = slides.map((_, i) => i);
1335
- if (JSON.stringify(sorted) !== JSON.stringify(expected)) {
1336
- throw new Error(
1337
- `newOrder must contain each index from 0 to ${slides.length - 1} exactly once.`
1338
- );
1649
+ if (params.defaultFontSize) {
1650
+ options.defaultFontSize = params.defaultFontSize;
1339
1651
  }
1340
- ctx.pptxData.slides = params.newOrder.map((idx) => slides[idx]);
1341
- ctx.pptxData.slides.forEach((s, i) => {
1342
- s.slideNumber = i + 1;
1343
- });
1652
+ const svgStrings = SvgExporter.exportAll(ctx.pptxData, options);
1344
1653
  return {
1345
1654
  pptxData: ctx.pptxData,
1346
- dirty: true,
1347
- result: { slideCount: ctx.pptxData.slides.length }
1655
+ dirty: false,
1656
+ result: { svgStrings, slideCount: svgStrings.length }
1348
1657
  };
1349
1658
  }
1350
- function duplicateSlide(ctx, params) {
1351
- const { slides } = ctx.pptxData;
1352
- const err = validateSlideIndex(params.slideIndex, slides.length);
1659
+ function exportSlideSvg(ctx, params) {
1660
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1353
1661
  if (err) {
1354
1662
  throw new Error(err);
1355
1663
  }
1356
- const original = slides[params.slideIndex];
1357
- const clone = structuredClone(original);
1358
- clone.id = generateSlideId();
1359
- clone.rId = "";
1360
- for (const el of clone.elements) {
1361
- el.id = generateElementId();
1664
+ const slide = ctx.pptxData.slides[params.slideIndex];
1665
+ const width = ctx.pptxData.width ?? 960;
1666
+ const height = ctx.pptxData.height ?? 540;
1667
+ const options = {};
1668
+ if (params.defaultFontFamily) {
1669
+ options.defaultFontFamily = params.defaultFontFamily;
1362
1670
  }
1363
- const insertIndex = params.targetIndex ?? params.slideIndex + 1;
1364
- slides.splice(insertIndex, 0, clone);
1365
- slides.forEach((s, i) => {
1366
- s.slideNumber = i + 1;
1367
- });
1671
+ if (params.defaultFontSize) {
1672
+ options.defaultFontSize = params.defaultFontSize;
1673
+ }
1674
+ const svg = SvgExporter.exportSlide(slide, width, height, options);
1368
1675
  return {
1369
1676
  pptxData: ctx.pptxData,
1370
- dirty: true,
1371
- result: { newSlideIndex: insertIndex, slideCount: slides.length }
1677
+ dirty: false,
1678
+ result: { svg, slideIndex: params.slideIndex }
1372
1679
  };
1373
1680
  }
1374
- function updateSlideProperties(ctx, params) {
1681
+ function replaceGeometry(ctx, params) {
1375
1682
  const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1376
1683
  if (err) {
1377
1684
  throw new Error(err);
1378
1685
  }
1379
1686
  const slide = ctx.pptxData.slides[params.slideIndex];
1380
- if (params.backgroundColor !== void 0) {
1687
+ const el = slide.elements.find((e) => e.id === params.elementId);
1688
+ if (!el) {
1689
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1690
+ }
1691
+ if (el.type !== "shape") {
1692
+ throw new Error(`Element '${params.elementId}' is not a shape.`);
1693
+ }
1694
+ const shape = el;
1695
+ if (params.svgPath) {
1696
+ replaceWithCustomGeometry(shape, params.svgPath, params.pathWidth, params.pathHeight);
1697
+ return {
1698
+ pptxData: ctx.pptxData,
1699
+ dirty: true,
1700
+ result: { elementId: params.elementId, geometryType: "custom" }
1701
+ };
1702
+ }
1703
+ if (params.shapeType) {
1704
+ replaceShapeGeometry(shape, params.shapeType, params.adjustments);
1705
+ return {
1706
+ pptxData: ctx.pptxData,
1707
+ dirty: true,
1708
+ result: { elementId: params.elementId, geometryType: params.shapeType }
1709
+ };
1710
+ }
1711
+ throw new Error("Either shapeType or svgPath must be provided.");
1712
+ }
1713
+
1714
+ // src/tools/hyperlink-tools.ts
1715
+ function manageHyperlinks(ctx, params) {
1716
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1717
+ if (err) {
1718
+ throw new Error(err);
1719
+ }
1720
+ const slide = ctx.pptxData.slides[params.slideIndex];
1721
+ switch (params.action) {
1722
+ case "list": {
1723
+ const hyperlinks = [];
1724
+ for (const el of slide.elements) {
1725
+ if (el.actionClick) {
1726
+ hyperlinks.push({
1727
+ elementId: el.id,
1728
+ trigger: "click",
1729
+ url: el.actionClick.url,
1730
+ tooltip: el.actionClick.tooltip,
1731
+ targetSlideIndex: el.actionClick.targetSlideIndex,
1732
+ actionType: el.actionClick.action
1733
+ });
1734
+ }
1735
+ if (el.actionHover) {
1736
+ hyperlinks.push({
1737
+ elementId: el.id,
1738
+ trigger: "hover",
1739
+ url: el.actionHover.url,
1740
+ tooltip: el.actionHover.tooltip,
1741
+ targetSlideIndex: el.actionHover.targetSlideIndex,
1742
+ actionType: el.actionHover.action
1743
+ });
1744
+ }
1745
+ }
1746
+ return { pptxData: ctx.pptxData, dirty: false, result: { action: "list", hyperlinks } };
1747
+ }
1748
+ case "set": {
1749
+ if (!params.elementId) {
1750
+ throw new Error("elementId is required for set action.");
1751
+ }
1752
+ const el = slide.elements.find((e) => e.id === params.elementId);
1753
+ if (!el) {
1754
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1755
+ }
1756
+ const trigger = params.trigger ?? "click";
1757
+ const action = {};
1758
+ if (params.url) {
1759
+ action.url = params.url;
1760
+ }
1761
+ if (params.tooltip) {
1762
+ action.tooltip = params.tooltip;
1763
+ }
1764
+ if (params.targetSlideIndex !== void 0) {
1765
+ action.targetSlideIndex = params.targetSlideIndex;
1766
+ }
1767
+ if (params.actionType) {
1768
+ switch (params.actionType) {
1769
+ case "url":
1770
+ action.action = "ppaction://hlinkpres";
1771
+ break;
1772
+ case "slide":
1773
+ action.action = "ppaction://hlinksldjump";
1774
+ break;
1775
+ case "nextSlide":
1776
+ action.action = "ppaction://hlinkshowjump?jump=nextslide";
1777
+ break;
1778
+ case "prevSlide":
1779
+ action.action = "ppaction://hlinkshowjump?jump=previousslide";
1780
+ break;
1781
+ case "firstSlide":
1782
+ action.action = "ppaction://hlinkshowjump?jump=firstslide";
1783
+ break;
1784
+ case "lastSlide":
1785
+ action.action = "ppaction://hlinkshowjump?jump=lastslide";
1786
+ break;
1787
+ case "endShow":
1788
+ action.action = "ppaction://hlinkshowjump?jump=endshow";
1789
+ break;
1790
+ }
1791
+ }
1792
+ if (trigger === "click") {
1793
+ el.actionClick = action;
1794
+ } else {
1795
+ el.actionHover = action;
1796
+ }
1797
+ return {
1798
+ pptxData: ctx.pptxData,
1799
+ dirty: true,
1800
+ result: { action: "set", elementId: params.elementId }
1801
+ };
1802
+ }
1803
+ case "remove": {
1804
+ if (!params.elementId) {
1805
+ throw new Error("elementId is required for remove action.");
1806
+ }
1807
+ const el = slide.elements.find((e) => e.id === params.elementId);
1808
+ if (!el) {
1809
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1810
+ }
1811
+ const trigger = params.trigger ?? "click";
1812
+ if (trigger === "click") {
1813
+ el.actionClick = void 0;
1814
+ } else {
1815
+ el.actionHover = void 0;
1816
+ }
1817
+ return {
1818
+ pptxData: ctx.pptxData,
1819
+ dirty: true,
1820
+ result: { action: "remove", elementId: params.elementId }
1821
+ };
1822
+ }
1823
+ default:
1824
+ throw new Error(`Unknown hyperlink action: ${String(params.action)}`);
1825
+ }
1826
+ }
1827
+ function getLayouts(ctx) {
1828
+ const layouts = [];
1829
+ if (ctx.pptxData.slideMasters) {
1830
+ for (const master of ctx.pptxData.slideMasters) {
1831
+ if (master.layouts) {
1832
+ for (const layout of master.layouts) {
1833
+ layouts.push({
1834
+ name: layout.name ?? "Unknown",
1835
+ path: layout.path
1836
+ });
1837
+ }
1838
+ }
1839
+ }
1840
+ }
1841
+ if (ctx.pptxData.layoutOptions) {
1842
+ for (const opt of ctx.pptxData.layoutOptions) {
1843
+ if (!layouts.some((l) => l.path === opt.path)) {
1844
+ layouts.push({ name: opt.name, path: opt.path, type: opt.type });
1845
+ }
1846
+ }
1847
+ }
1848
+ return {
1849
+ pptxData: ctx.pptxData,
1850
+ dirty: false,
1851
+ result: { layouts, count: layouts.length }
1852
+ };
1853
+ }
1854
+ function applyLayout(ctx, params) {
1855
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1856
+ if (err) {
1857
+ throw new Error(err);
1858
+ }
1859
+ let layout;
1860
+ if (params.layoutName) {
1861
+ layout = findLayoutByName(ctx.pptxData, params.layoutName);
1862
+ } else if (params.layoutType) {
1863
+ layout = findLayoutByType(ctx.pptxData, params.layoutType);
1864
+ } else {
1865
+ throw new Error("Either layoutName or layoutType must be provided.");
1866
+ }
1867
+ if (!layout) {
1868
+ throw new Error(
1869
+ `Layout not found: ${params.layoutName ?? params.layoutType}. Use get_layouts to see available layouts.`
1870
+ );
1871
+ }
1872
+ const slide = ctx.pptxData.slides[params.slideIndex];
1873
+ slide.layoutName = layout.name;
1874
+ if (layout.path) {
1875
+ slide.layoutPath = layout.path;
1876
+ }
1877
+ return {
1878
+ pptxData: ctx.pptxData,
1879
+ dirty: true,
1880
+ result: { slideIndex: params.slideIndex, layoutName: layout.name ?? "Unknown" }
1881
+ };
1882
+ }
1883
+ function setElementLockT(ctx, params) {
1884
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1885
+ if (err) {
1886
+ throw new Error(err);
1887
+ }
1888
+ const slide = ctx.pptxData.slides[params.slideIndex];
1889
+ const idx = slide.elements.findIndex((e) => e.id === params.elementId);
1890
+ if (idx < 0) {
1891
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
1892
+ }
1893
+ const extraLocks = {};
1894
+ if (params.noMove !== void 0) {
1895
+ extraLocks["noMove"] = params.noMove;
1896
+ }
1897
+ if (params.noResize !== void 0) {
1898
+ extraLocks["noResize"] = params.noResize;
1899
+ }
1900
+ if (params.noRotation !== void 0) {
1901
+ extraLocks["noRotation"] = params.noRotation;
1902
+ }
1903
+ if (params.noSelect !== void 0) {
1904
+ extraLocks["noSelect"] = params.noSelect;
1905
+ }
1906
+ if (params.noTextEdit !== void 0) {
1907
+ extraLocks["noTextEdit"] = params.noTextEdit;
1908
+ }
1909
+ const updated = setElementLocked(slide.elements[idx], params.locked, extraLocks);
1910
+ slide.elements[idx] = updated;
1911
+ return {
1912
+ pptxData: ctx.pptxData,
1913
+ dirty: true,
1914
+ result: { elementId: params.elementId, locked: params.locked }
1915
+ };
1916
+ }
1917
+
1918
+ // src/tools/metadata-tools.ts
1919
+ function getMetadata(ctx) {
1920
+ return {
1921
+ pptxData: ctx.pptxData,
1922
+ dirty: false,
1923
+ result: {
1924
+ coreProperties: ctx.pptxData.coreProperties,
1925
+ appProperties: ctx.pptxData.appProperties,
1926
+ customProperties: ctx.pptxData.customProperties,
1927
+ slideCount: ctx.pptxData.slides.length
1928
+ }
1929
+ };
1930
+ }
1931
+ function updateMetadata(ctx, params) {
1932
+ const updated = [];
1933
+ if (params.title !== void 0 || params.subject !== void 0 || params.creator !== void 0 || params.keywords !== void 0 || params.description !== void 0 || params.lastModifiedBy !== void 0 || params.category !== void 0) {
1934
+ if (!ctx.pptxData.coreProperties) {
1935
+ ctx.pptxData.coreProperties = {};
1936
+ }
1937
+ const cp = ctx.pptxData.coreProperties;
1938
+ if (params.title !== void 0) {
1939
+ cp.title = params.title;
1940
+ updated.push("title");
1941
+ }
1942
+ if (params.subject !== void 0) {
1943
+ cp.subject = params.subject;
1944
+ updated.push("subject");
1945
+ }
1946
+ if (params.creator !== void 0) {
1947
+ cp.creator = params.creator;
1948
+ updated.push("creator");
1949
+ }
1950
+ if (params.keywords !== void 0) {
1951
+ cp.keywords = params.keywords;
1952
+ updated.push("keywords");
1953
+ }
1954
+ if (params.description !== void 0) {
1955
+ cp.description = params.description;
1956
+ updated.push("description");
1957
+ }
1958
+ if (params.lastModifiedBy !== void 0) {
1959
+ cp.lastModifiedBy = params.lastModifiedBy;
1960
+ updated.push("lastModifiedBy");
1961
+ }
1962
+ if (params.category !== void 0) {
1963
+ cp.category = params.category;
1964
+ updated.push("category");
1965
+ }
1966
+ }
1967
+ if (params.company !== void 0 || params.manager !== void 0) {
1968
+ if (!ctx.pptxData.appProperties) {
1969
+ ctx.pptxData.appProperties = {};
1970
+ }
1971
+ const ap = ctx.pptxData.appProperties;
1972
+ if (params.company !== void 0) {
1973
+ ap.company = params.company;
1974
+ updated.push("company");
1975
+ }
1976
+ if (params.manager !== void 0) {
1977
+ ap.manager = params.manager;
1978
+ updated.push("manager");
1979
+ }
1980
+ }
1981
+ if (params.customProperties) {
1982
+ if (!ctx.pptxData.customProperties) {
1983
+ ctx.pptxData.customProperties = [];
1984
+ }
1985
+ for (const cp of params.customProperties) {
1986
+ const valueStr = String(cp.value);
1987
+ const vtType = typeof cp.value === "number" ? "i4" : typeof cp.value === "boolean" ? "bool" : "lpwstr";
1988
+ const existing = ctx.pptxData.customProperties.find((p) => p.name === cp.name);
1989
+ if (existing) {
1990
+ existing.value = valueStr;
1991
+ existing.type = vtType;
1992
+ } else {
1993
+ ctx.pptxData.customProperties.push({ name: cp.name, value: valueStr, type: vtType });
1994
+ }
1995
+ updated.push(`custom:${cp.name}`);
1996
+ }
1997
+ }
1998
+ return {
1999
+ pptxData: ctx.pptxData,
2000
+ dirty: updated.length > 0,
2001
+ result: { updated }
2002
+ };
2003
+ }
2004
+
2005
+ // src/tools/presentation-tools.ts
2006
+ function getPresentationProperties(ctx) {
2007
+ return {
2008
+ pptxData: ctx.pptxData,
2009
+ dirty: false,
2010
+ result: {
2011
+ properties: ctx.pptxData.presentationProperties,
2012
+ slideCount: ctx.pptxData.slides.length,
2013
+ width: ctx.pptxData.width,
2014
+ height: ctx.pptxData.height
2015
+ }
2016
+ };
2017
+ }
2018
+ function updatePresentationProperties(ctx, params) {
2019
+ if (!ctx.pptxData.presentationProperties) {
2020
+ ctx.pptxData.presentationProperties = {};
2021
+ }
2022
+ const pp = ctx.pptxData.presentationProperties;
2023
+ if (params.showType !== void 0) {
2024
+ pp.showType = params.showType;
2025
+ }
2026
+ if (params.loopContinuously !== void 0) {
2027
+ pp.loopContinuously = params.loopContinuously;
2028
+ }
2029
+ if (params.showWithNarration !== void 0) {
2030
+ pp.showWithNarration = params.showWithNarration;
2031
+ }
2032
+ if (params.showWithAnimation !== void 0) {
2033
+ pp.showWithAnimation = params.showWithAnimation;
2034
+ }
2035
+ if (params.advanceMode !== void 0) {
2036
+ pp.advanceMode = params.advanceMode;
2037
+ }
2038
+ if (params.showSlidesMode !== void 0) {
2039
+ pp.showSlidesMode = params.showSlidesMode;
2040
+ }
2041
+ if (params.showSlidesFrom !== void 0) {
2042
+ pp.showSlidesFrom = params.showSlidesFrom;
2043
+ }
2044
+ if (params.showSlidesTo !== void 0) {
2045
+ pp.showSlidesTo = params.showSlidesTo;
2046
+ }
2047
+ if (params.penColor !== void 0) {
2048
+ pp.penColor = params.penColor;
2049
+ }
2050
+ return {
2051
+ pptxData: ctx.pptxData,
2052
+ dirty: true,
2053
+ result: { properties: pp }
2054
+ };
2055
+ }
2056
+ function manageSections(ctx, params) {
2057
+ switch (params.action) {
2058
+ case "list": {
2059
+ const sections = (ctx.pptxData.sections ?? []).map((s) => ({
2060
+ id: s.id,
2061
+ name: s.name,
2062
+ slideIds: s.slideIds,
2063
+ collapsed: s.collapsed,
2064
+ color: s.color
2065
+ }));
2066
+ return { pptxData: ctx.pptxData, dirty: false, result: { action: "list", sections } };
2067
+ }
2068
+ case "add": {
2069
+ if (!params.name) {
2070
+ throw new Error("name is required for add action.");
2071
+ }
2072
+ const section = addSection(ctx.pptxData, params.name, params.slideIndices ?? []);
2073
+ return {
2074
+ pptxData: ctx.pptxData,
2075
+ dirty: true,
2076
+ result: {
2077
+ action: "add",
2078
+ section: { id: section.id, name: section.name, slideIds: section.slideIds }
2079
+ }
2080
+ };
2081
+ }
2082
+ case "remove": {
2083
+ if (!params.sectionId) {
2084
+ throw new Error("sectionId is required for remove action.");
2085
+ }
2086
+ const success = removeSection(ctx.pptxData, params.sectionId);
2087
+ return { pptxData: ctx.pptxData, dirty: success, result: { action: "remove", success } };
2088
+ }
2089
+ case "reorder": {
2090
+ if (!params.sectionIds) {
2091
+ throw new Error("sectionIds is required for reorder action.");
2092
+ }
2093
+ reorderSections(ctx.pptxData, params.sectionIds);
2094
+ return { pptxData: ctx.pptxData, dirty: true, result: { action: "reorder", success: true } };
2095
+ }
2096
+ case "moveSlides": {
2097
+ if (!params.sectionId) {
2098
+ throw new Error("sectionId is required for moveSlides action.");
2099
+ }
2100
+ if (!params.slideIndices) {
2101
+ throw new Error("slideIndices is required for moveSlides action.");
2102
+ }
2103
+ const success = moveSlidesToSection(ctx.pptxData, params.slideIndices, params.sectionId);
2104
+ return { pptxData: ctx.pptxData, dirty: success, result: { action: "moveSlides", success } };
2105
+ }
2106
+ case "getForSlide": {
2107
+ if (params.slideIndex === void 0) {
2108
+ throw new Error("slideIndex is required for getForSlide action.");
2109
+ }
2110
+ const section = getSectionForSlide(ctx.pptxData, params.slideIndex);
2111
+ const info = section ? { id: section.id, name: section.name, slideIds: section.slideIds } : null;
2112
+ return {
2113
+ pptxData: ctx.pptxData,
2114
+ dirty: false,
2115
+ result: { action: "getForSlide", section: info }
2116
+ };
2117
+ }
2118
+ default:
2119
+ throw new Error(`Unknown section action: ${String(params.action)}`);
2120
+ }
2121
+ }
2122
+
2123
+ // src/tools/slide-tools.ts
2124
+ function getSlide(ctx, params) {
2125
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
2126
+ if (err) {
2127
+ throw new Error(err);
2128
+ }
2129
+ const slide = ctx.pptxData.slides[params.slideIndex];
2130
+ return {
2131
+ pptxData: ctx.pptxData,
2132
+ dirty: false,
2133
+ result: {
2134
+ slideIndex: params.slideIndex,
2135
+ slideNumber: slide.slideNumber,
2136
+ hidden: slide.hidden ?? false,
2137
+ layoutName: slide.layoutName,
2138
+ backgroundColor: slide.backgroundColor,
2139
+ notes: slide.notes ?? "",
2140
+ commentCount: slide.comments?.length ?? 0,
2141
+ elements: slide.elements.map(describeElement)
2142
+ }
2143
+ };
2144
+ }
2145
+ function addSlide(ctx, params) {
2146
+ const { slides } = ctx.pptxData;
2147
+ const insertAt = params.insertAfterIndex !== void 0 && params.insertAfterIndex >= 0 ? Math.min(params.insertAfterIndex + 1, slides.length) : slides.length;
2148
+ const newSlide = {
2149
+ id: generateSlideId(),
2150
+ rId: "",
2151
+ slideNumber: insertAt + 1,
2152
+ elements: [],
2153
+ backgroundColor: params.backgroundColor
2154
+ };
2155
+ slides.splice(insertAt, 0, newSlide);
2156
+ slides.forEach((s, i) => {
2157
+ s.slideNumber = i + 1;
2158
+ });
2159
+ return {
2160
+ pptxData: ctx.pptxData,
2161
+ dirty: true,
2162
+ result: { newSlideIndex: insertAt, slideCount: slides.length }
2163
+ };
2164
+ }
2165
+ function deleteSlides(ctx, params) {
2166
+ const { slides } = ctx.pptxData;
2167
+ if (!params.slideIndexes || params.slideIndexes.length === 0) {
2168
+ throw new Error("slideIndexes is required for the delete action.");
2169
+ }
2170
+ const indexSet = new Set(params.slideIndexes);
2171
+ const invalid = params.slideIndexes.filter((i) => i < 0 || i >= slides.length);
2172
+ if (invalid.length > 0) {
2173
+ throw new Error(
2174
+ `Invalid slide indexes: ${invalid.join(", ")}. Valid range: 0\u2013${slides.length - 1}.`
2175
+ );
2176
+ }
2177
+ if (indexSet.size >= slides.length) {
2178
+ throw new Error("Cannot delete all slides. At least one slide must remain.");
2179
+ }
2180
+ ctx.pptxData.slides = slides.filter((_, i) => !indexSet.has(i));
2181
+ ctx.pptxData.slides.forEach((s, i) => {
2182
+ s.slideNumber = i + 1;
2183
+ });
2184
+ return {
2185
+ pptxData: ctx.pptxData,
2186
+ dirty: true,
2187
+ result: { deletedCount: indexSet.size, slideCount: ctx.pptxData.slides.length }
2188
+ };
2189
+ }
2190
+ function reorderSlides(ctx, params) {
2191
+ const { slides } = ctx.pptxData;
2192
+ if (!params.newOrder || params.newOrder.length !== slides.length) {
2193
+ throw new Error(
2194
+ `newOrder must have exactly ${slides.length} indexes. Got ${params.newOrder?.length ?? 0}.`
2195
+ );
2196
+ }
2197
+ const sorted = [...params.newOrder].sort((a, b) => a - b);
2198
+ const expected = slides.map((_, i) => i);
2199
+ if (JSON.stringify(sorted) !== JSON.stringify(expected)) {
2200
+ throw new Error(
2201
+ `newOrder must contain each index from 0 to ${slides.length - 1} exactly once.`
2202
+ );
2203
+ }
2204
+ ctx.pptxData.slides = params.newOrder.map((idx) => slides[idx]);
2205
+ ctx.pptxData.slides.forEach((s, i) => {
2206
+ s.slideNumber = i + 1;
2207
+ });
2208
+ return {
2209
+ pptxData: ctx.pptxData,
2210
+ dirty: true,
2211
+ result: { slideCount: ctx.pptxData.slides.length }
2212
+ };
2213
+ }
2214
+ function duplicateSlide(ctx, params) {
2215
+ const { slides } = ctx.pptxData;
2216
+ const err = validateSlideIndex(params.slideIndex, slides.length);
2217
+ if (err) {
2218
+ throw new Error(err);
2219
+ }
2220
+ const original = slides[params.slideIndex];
2221
+ const clone = structuredClone(original);
2222
+ clone.id = generateSlideId();
2223
+ clone.rId = "";
2224
+ for (const el of clone.elements) {
2225
+ el.id = generateElementId();
2226
+ }
2227
+ const insertIndex = params.targetIndex ?? params.slideIndex + 1;
2228
+ slides.splice(insertIndex, 0, clone);
2229
+ slides.forEach((s, i) => {
2230
+ s.slideNumber = i + 1;
2231
+ });
2232
+ return {
2233
+ pptxData: ctx.pptxData,
2234
+ dirty: true,
2235
+ result: { newSlideIndex: insertIndex, slideCount: slides.length }
2236
+ };
2237
+ }
2238
+ function updateSlideProperties(ctx, params) {
2239
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
2240
+ if (err) {
2241
+ throw new Error(err);
2242
+ }
2243
+ const slide = ctx.pptxData.slides[params.slideIndex];
2244
+ if (params.backgroundColor !== void 0) {
1381
2245
  slide.backgroundColor = params.backgroundColor;
1382
2246
  }
1383
2247
  if (params.backgroundGradient !== void 0) {
@@ -1445,6 +2309,164 @@ function setCanvasSize(ctx, params) {
1445
2309
  result: { canvasWidth: params.width, canvasHeight: params.height }
1446
2310
  };
1447
2311
  }
2312
+ function extractNodes(data) {
2313
+ if (!data.nodes) {
2314
+ return [];
2315
+ }
2316
+ return data.nodes.map((n) => ({
2317
+ id: n.id,
2318
+ text: n.text ?? "",
2319
+ children: n.children?.map((c) => ({ id: c.id, text: c.text ?? "" }))
2320
+ }));
2321
+ }
2322
+ function manageSmartArt(ctx, params) {
2323
+ const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
2324
+ if (err) {
2325
+ throw new Error(err);
2326
+ }
2327
+ const slide = ctx.pptxData.slides[params.slideIndex];
2328
+ const el = slide.elements.find((e) => e.id === params.elementId);
2329
+ if (!el) {
2330
+ throw new Error(`Element '${params.elementId}' not found on slide ${params.slideIndex}.`);
2331
+ }
2332
+ if (el.type !== "smartArt") {
2333
+ throw new Error(`Element '${params.elementId}' is not a SmartArt.`);
2334
+ }
2335
+ const smartArt = el;
2336
+ if (!smartArt.smartArtData) {
2337
+ throw new Error(`SmartArt '${params.elementId}' has no data.`);
2338
+ }
2339
+ switch (params.action) {
2340
+ case "getNodes": {
2341
+ return {
2342
+ pptxData: ctx.pptxData,
2343
+ dirty: false,
2344
+ result: {
2345
+ action: "getNodes",
2346
+ elementId: params.elementId,
2347
+ nodes: extractNodes(smartArt.smartArtData)
2348
+ }
2349
+ };
2350
+ }
2351
+ case "addNode": {
2352
+ smartArt.smartArtData = addSmartArtNode(
2353
+ smartArt.smartArtData,
2354
+ params.text ?? "",
2355
+ params.afterNodeId
2356
+ );
2357
+ return {
2358
+ pptxData: ctx.pptxData,
2359
+ dirty: true,
2360
+ result: {
2361
+ action: "addNode",
2362
+ elementId: params.elementId,
2363
+ nodes: extractNodes(smartArt.smartArtData)
2364
+ }
2365
+ };
2366
+ }
2367
+ case "removeNode": {
2368
+ if (!params.nodeId) {
2369
+ throw new Error("nodeId is required for removeNode.");
2370
+ }
2371
+ smartArt.smartArtData = removeSmartArtNode(smartArt.smartArtData, params.nodeId);
2372
+ return {
2373
+ pptxData: ctx.pptxData,
2374
+ dirty: true,
2375
+ result: {
2376
+ action: "removeNode",
2377
+ elementId: params.elementId,
2378
+ nodes: extractNodes(smartArt.smartArtData)
2379
+ }
2380
+ };
2381
+ }
2382
+ case "updateNodeText": {
2383
+ if (!params.nodeId) {
2384
+ throw new Error("nodeId is required for updateNodeText.");
2385
+ }
2386
+ smartArt.smartArtData = updateSmartArtNodeText(
2387
+ smartArt.smartArtData,
2388
+ params.nodeId,
2389
+ params.text ?? ""
2390
+ );
2391
+ return {
2392
+ pptxData: ctx.pptxData,
2393
+ dirty: true,
2394
+ result: { action: "updateNodeText", elementId: params.elementId }
2395
+ };
2396
+ }
2397
+ case "reorderNode": {
2398
+ if (!params.nodeId) {
2399
+ throw new Error("nodeId is required for reorderNode.");
2400
+ }
2401
+ const dir = params.direction ?? 1;
2402
+ smartArt.smartArtData = reorderSmartArtNode(smartArt.smartArtData, params.nodeId, dir);
2403
+ return {
2404
+ pptxData: ctx.pptxData,
2405
+ dirty: true,
2406
+ result: {
2407
+ action: "reorderNode",
2408
+ elementId: params.elementId,
2409
+ nodes: extractNodes(smartArt.smartArtData)
2410
+ }
2411
+ };
2412
+ }
2413
+ case "promoteNode": {
2414
+ if (!params.nodeId) {
2415
+ throw new Error("nodeId is required for promoteNode.");
2416
+ }
2417
+ smartArt.smartArtData = promoteSmartArtNode(smartArt.smartArtData, params.nodeId);
2418
+ return {
2419
+ pptxData: ctx.pptxData,
2420
+ dirty: true,
2421
+ result: {
2422
+ action: "promoteNode",
2423
+ elementId: params.elementId,
2424
+ nodes: extractNodes(smartArt.smartArtData)
2425
+ }
2426
+ };
2427
+ }
2428
+ case "demoteNode": {
2429
+ if (!params.nodeId) {
2430
+ throw new Error("nodeId is required for demoteNode.");
2431
+ }
2432
+ smartArt.smartArtData = demoteSmartArtNode(smartArt.smartArtData, params.nodeId);
2433
+ return {
2434
+ pptxData: ctx.pptxData,
2435
+ dirty: true,
2436
+ result: {
2437
+ action: "demoteNode",
2438
+ elementId: params.elementId,
2439
+ nodes: extractNodes(smartArt.smartArtData)
2440
+ }
2441
+ };
2442
+ }
2443
+ case "decompose": {
2444
+ const bounds = {
2445
+ x: smartArt.x,
2446
+ y: smartArt.y,
2447
+ width: smartArt.width,
2448
+ height: smartArt.height
2449
+ };
2450
+ const decomposed = decomposeSmartArt(smartArt.smartArtData, bounds);
2451
+ if (!decomposed || decomposed.length === 0) {
2452
+ throw new Error("Failed to decompose SmartArt into shapes.");
2453
+ }
2454
+ const idx = slide.elements.indexOf(el);
2455
+ slide.elements.splice(idx, 1, ...decomposed);
2456
+ return {
2457
+ pptxData: ctx.pptxData,
2458
+ dirty: true,
2459
+ result: {
2460
+ action: "decompose",
2461
+ elementId: params.elementId,
2462
+ decomposedCount: decomposed.length
2463
+ }
2464
+ };
2465
+ }
2466
+ default:
2467
+ throw new Error(`Unknown SmartArt action: ${String(params.action)}`);
2468
+ }
2469
+ }
1448
2470
  function updateElementStyle(ctx, params) {
1449
2471
  const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
1450
2472
  if (err) {
@@ -1749,17 +2771,144 @@ function manageTableStructure(ctx, params) {
1749
2771
  }
1750
2772
  break;
1751
2773
  }
1752
- default: {
1753
- throw new Error(`Unknown action: ${String(params.action)}`);
2774
+ default: {
2775
+ throw new Error(`Unknown action: ${String(params.action)}`);
2776
+ }
2777
+ }
2778
+ return {
2779
+ pptxData: ctx.pptxData,
2780
+ dirty: true,
2781
+ result: {
2782
+ rowCount: tbl.tableData.rows.length,
2783
+ columnCount: tbl.tableData.columnWidths.length
2784
+ }
2785
+ };
2786
+ }
2787
+ function findPlaceholdersT(ctx) {
2788
+ const placeholders = findPlaceholders(ctx.pptxData);
2789
+ return {
2790
+ pptxData: ctx.pptxData,
2791
+ dirty: false,
2792
+ result: { placeholders, count: placeholders.length }
2793
+ };
2794
+ }
2795
+ function applyTemplateT(ctx, params) {
2796
+ applyTemplate(ctx.pptxData, params.data);
2797
+ return {
2798
+ pptxData: ctx.pptxData,
2799
+ dirty: true,
2800
+ result: { applied: true, slideCount: ctx.pptxData.slides.length }
2801
+ };
2802
+ }
2803
+ function getThemeInfo(ctx) {
2804
+ const theme = ctx.pptxData.theme;
2805
+ return {
2806
+ pptxData: ctx.pptxData,
2807
+ dirty: false,
2808
+ result: {
2809
+ themeName: theme?.name,
2810
+ colorScheme: theme?.colorScheme,
2811
+ fontScheme: theme?.fontScheme,
2812
+ availablePresets: Object.keys(ThemePresets)
2813
+ }
2814
+ };
2815
+ }
2816
+ function applyThemePreset(ctx, params) {
2817
+ const preset = getThemePreset(params.presetName);
2818
+ if (!preset) {
2819
+ throw new Error(
2820
+ `Unknown theme preset: "${params.presetName}". Available: ${Object.keys(ThemePresets).join(", ")}`
2821
+ );
2822
+ }
2823
+ if (!ctx.pptxData.theme) {
2824
+ ctx.pptxData.theme = {};
2825
+ }
2826
+ const theme = ctx.pptxData.theme;
2827
+ theme.name = preset.name;
2828
+ theme.colorScheme = preset.colors;
2829
+ if (preset.fonts) {
2830
+ theme.fontScheme = preset.fonts;
2831
+ }
2832
+ return {
2833
+ pptxData: ctx.pptxData,
2834
+ dirty: true,
2835
+ result: {
2836
+ appliedPreset: params.presetName,
2837
+ colorScheme: theme.colorScheme
2838
+ }
2839
+ };
2840
+ }
2841
+ function updateThemeColors(ctx, params) {
2842
+ if (!ctx.pptxData.theme) {
2843
+ ctx.pptxData.theme = {};
2844
+ }
2845
+ const theme = ctx.pptxData.theme;
2846
+ if (!theme.colorScheme) {
2847
+ theme.colorScheme = {
2848
+ dk1: "#000000",
2849
+ lt1: "#FFFFFF",
2850
+ dk2: "#44546A",
2851
+ lt2: "#E7E6E6",
2852
+ accent1: "#4472C4",
2853
+ accent2: "#ED7D31",
2854
+ accent3: "#A5A5A5",
2855
+ accent4: "#FFC000",
2856
+ accent5: "#5B9BD5",
2857
+ accent6: "#70AD47",
2858
+ hlink: "#0563C1",
2859
+ folHlink: "#954F72"
2860
+ };
2861
+ }
2862
+ const cs = theme.colorScheme;
2863
+ for (const key of Object.keys(params)) {
2864
+ if (params[key] !== void 0) {
2865
+ cs[key] = params[key];
1754
2866
  }
1755
2867
  }
1756
2868
  return {
1757
2869
  pptxData: ctx.pptxData,
1758
2870
  dirty: true,
2871
+ result: { colorScheme: cs }
2872
+ };
2873
+ }
2874
+ function updateThemeFonts(ctx, params) {
2875
+ if (!ctx.pptxData.theme) {
2876
+ ctx.pptxData.theme = {};
2877
+ }
2878
+ const theme = ctx.pptxData.theme;
2879
+ if (!theme.fontScheme) {
2880
+ theme.fontScheme = {};
2881
+ }
2882
+ if (params.majorFont !== void 0) {
2883
+ theme.fontScheme.majorFont = { latin: params.majorFont };
2884
+ }
2885
+ if (params.minorFont !== void 0) {
2886
+ theme.fontScheme.minorFont = { latin: params.minorFont };
2887
+ }
2888
+ return {
2889
+ pptxData: ctx.pptxData,
2890
+ dirty: true,
2891
+ result: { fontScheme: theme.fontScheme }
2892
+ };
2893
+ }
2894
+ async function validatePresentation(rawBytes) {
2895
+ const result = await validatePptx(rawBytes);
2896
+ return {
2897
+ valid: result.valid,
2898
+ issueCount: result.issues.length,
2899
+ errors: result.issues.filter((i) => i.severity === "error").length,
2900
+ warnings: result.issues.filter((i) => i.severity === "warning").length,
2901
+ issues: result.issues
2902
+ };
2903
+ }
2904
+ async function repairPresentation(rawBytes) {
2905
+ const result = await repairPptx(rawBytes);
2906
+ return {
1759
2907
  result: {
1760
- rowCount: tbl.tableData.rows.length,
1761
- columnCount: tbl.tableData.columnWidths.length
1762
- }
2908
+ repairCount: result.repairs.length,
2909
+ repairs: result.repairs
2910
+ },
2911
+ repairedBytes: result.repaired
1763
2912
  };
1764
2913
  }
1765
2914
  var PPTX_MIME_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
@@ -2398,6 +3547,372 @@ function createServer() {
2398
3547
  };
2399
3548
  }
2400
3549
  );
3550
+ server2.registerTool(
3551
+ "get_theme_info",
3552
+ {
3553
+ description: "Get current theme information (colors, fonts, available presets)",
3554
+ inputSchema: GetThemeInfoSchema.shape
3555
+ },
3556
+ async (params) => {
3557
+ const result = await runMcpTool(params.filePath, (ctx) => getThemeInfo(ctx));
3558
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3559
+ }
3560
+ );
3561
+ server2.registerTool(
3562
+ "apply_theme_preset",
3563
+ {
3564
+ description: "Apply a built-in theme preset to the presentation",
3565
+ inputSchema: ApplyThemePresetSchema.shape
3566
+ },
3567
+ async (params) => {
3568
+ const result = await runMcpTool(
3569
+ params.filePath,
3570
+ (ctx) => applyThemePreset(ctx, params)
3571
+ );
3572
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3573
+ }
3574
+ );
3575
+ server2.registerTool(
3576
+ "update_theme_colors",
3577
+ {
3578
+ description: "Update individual theme colors (accent1-6, dk1/2, lt1/2, hlink, folHlink)",
3579
+ inputSchema: UpdateThemeColorsSchema.shape
3580
+ },
3581
+ async (params) => {
3582
+ const result = await runMcpTool(
3583
+ params.filePath,
3584
+ (ctx) => updateThemeColors(ctx, params)
3585
+ );
3586
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3587
+ }
3588
+ );
3589
+ server2.registerTool(
3590
+ "update_theme_fonts",
3591
+ {
3592
+ description: "Update theme heading (major) and body (minor) fonts",
3593
+ inputSchema: UpdateThemeFontsSchema.shape
3594
+ },
3595
+ async (params) => {
3596
+ const result = await runMcpTool(
3597
+ params.filePath,
3598
+ (ctx) => updateThemeFonts(ctx, params)
3599
+ );
3600
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3601
+ }
3602
+ );
3603
+ server2.registerTool(
3604
+ "update_chart",
3605
+ {
3606
+ description: "Update chart type, title, legend, data labels, axis, or categories",
3607
+ inputSchema: UpdateChartSchema.shape
3608
+ },
3609
+ async (params) => {
3610
+ const result = await runMcpTool(
3611
+ params.filePath,
3612
+ (ctx) => updateChart(ctx, params)
3613
+ );
3614
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3615
+ }
3616
+ );
3617
+ server2.registerTool(
3618
+ "add_chart_series",
3619
+ {
3620
+ description: "Add a data series to an existing chart",
3621
+ inputSchema: AddChartSeriesSchema.shape
3622
+ },
3623
+ async (params) => {
3624
+ const result = await runMcpTool(
3625
+ params.filePath,
3626
+ (ctx) => addChartSeriesT(ctx, params)
3627
+ );
3628
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3629
+ }
3630
+ );
3631
+ server2.registerTool(
3632
+ "remove_chart_series",
3633
+ {
3634
+ description: "Remove a data series from a chart by index",
3635
+ inputSchema: RemoveChartSeriesSchema.shape
3636
+ },
3637
+ async (params) => {
3638
+ const result = await runMcpTool(
3639
+ params.filePath,
3640
+ (ctx) => removeChartSeriesT(ctx, params)
3641
+ );
3642
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3643
+ }
3644
+ );
3645
+ server2.registerTool(
3646
+ "update_chart_series_data",
3647
+ {
3648
+ description: "Update data values for a chart series",
3649
+ inputSchema: UpdateChartSeriesDataSchema.shape
3650
+ },
3651
+ async (params) => {
3652
+ const result = await runMcpTool(
3653
+ params.filePath,
3654
+ (ctx) => updateChartSeriesData(ctx, params)
3655
+ );
3656
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3657
+ }
3658
+ );
3659
+ server2.registerTool(
3660
+ "create_chart",
3661
+ {
3662
+ description: "Create a new chart element on a slide",
3663
+ inputSchema: CreateChartSchema.shape
3664
+ },
3665
+ async (params) => {
3666
+ const result = await runMcpTool(
3667
+ params.filePath,
3668
+ (ctx) => createChart(ctx, params)
3669
+ );
3670
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3671
+ }
3672
+ );
3673
+ server2.registerTool(
3674
+ "manage_smart_art",
3675
+ {
3676
+ description: "Manage SmartArt: get nodes, add/remove/reorder/promote/demote nodes, or decompose to shapes",
3677
+ inputSchema: ManageSmartArtSchema.shape
3678
+ },
3679
+ async (params) => {
3680
+ const result = await runMcpTool(
3681
+ params.filePath,
3682
+ (ctx) => manageSmartArt(ctx, params)
3683
+ );
3684
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3685
+ }
3686
+ );
3687
+ server2.registerTool(
3688
+ "find_placeholders",
3689
+ {
3690
+ description: "Discover all {{placeholder}} tokens in the presentation",
3691
+ inputSchema: FindPlaceholdersSchema.shape
3692
+ },
3693
+ async (params) => {
3694
+ const result = await runMcpTool(
3695
+ params.filePath,
3696
+ (ctx) => findPlaceholdersT(ctx)
3697
+ );
3698
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3699
+ }
3700
+ );
3701
+ server2.registerTool(
3702
+ "apply_template",
3703
+ {
3704
+ description: "Replace {{placeholder}} tokens with provided values (mail merge)",
3705
+ inputSchema: ApplyTemplateSchema.shape
3706
+ },
3707
+ async (params) => {
3708
+ const result = await runMcpTool(
3709
+ params.filePath,
3710
+ (ctx) => applyTemplateT(ctx, { data: params.data })
3711
+ );
3712
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3713
+ }
3714
+ );
3715
+ server2.registerTool(
3716
+ "get_metadata",
3717
+ {
3718
+ description: "Get presentation metadata (title, author, keywords, custom properties)",
3719
+ inputSchema: GetMetadataSchema.shape
3720
+ },
3721
+ async (params) => {
3722
+ const result = await runMcpTool(
3723
+ params.filePath,
3724
+ (ctx) => getMetadata(ctx)
3725
+ );
3726
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3727
+ }
3728
+ );
3729
+ server2.registerTool(
3730
+ "update_metadata",
3731
+ {
3732
+ description: "Update presentation metadata (title, author, company, custom properties)",
3733
+ inputSchema: UpdateMetadataSchema.shape
3734
+ },
3735
+ async (params) => {
3736
+ const result = await runMcpTool(
3737
+ params.filePath,
3738
+ (ctx) => updateMetadata(ctx, params)
3739
+ );
3740
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3741
+ }
3742
+ );
3743
+ server2.registerTool(
3744
+ "manage_sections",
3745
+ {
3746
+ description: "List, add, remove, reorder sections or move slides between sections",
3747
+ inputSchema: ManageSectionsSchema.shape
3748
+ },
3749
+ async (params) => {
3750
+ const result = await runMcpTool(
3751
+ params.filePath,
3752
+ (ctx) => manageSections(ctx, params)
3753
+ );
3754
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3755
+ }
3756
+ );
3757
+ server2.registerTool(
3758
+ "export_to_svg",
3759
+ {
3760
+ description: "Export all or specific slides to SVG strings",
3761
+ inputSchema: ExportToSvgSchema.shape
3762
+ },
3763
+ async (params) => {
3764
+ const result = await runMcpTool(
3765
+ params.filePath,
3766
+ (ctx) => exportToSvg(ctx, params)
3767
+ );
3768
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3769
+ }
3770
+ );
3771
+ server2.registerTool(
3772
+ "export_slide_svg",
3773
+ {
3774
+ description: "Export a single slide to SVG",
3775
+ inputSchema: ExportSlideSvgSchema.shape
3776
+ },
3777
+ async (params) => {
3778
+ const result = await runMcpTool(
3779
+ params.filePath,
3780
+ (ctx) => exportSlideSvg(ctx, params)
3781
+ );
3782
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3783
+ }
3784
+ );
3785
+ server2.registerTool(
3786
+ "manage_hyperlinks",
3787
+ {
3788
+ description: "List, set, or remove hyperlinks/actions on elements",
3789
+ inputSchema: ManageHyperlinksSchema.shape
3790
+ },
3791
+ async (params) => {
3792
+ const result = await runMcpTool(
3793
+ params.filePath,
3794
+ (ctx) => manageHyperlinks(ctx, params)
3795
+ );
3796
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3797
+ }
3798
+ );
3799
+ server2.registerTool(
3800
+ "replace_geometry",
3801
+ {
3802
+ description: "Replace a shape element geometry with a preset or custom SVG path",
3803
+ inputSchema: ReplaceGeometrySchema.shape
3804
+ },
3805
+ async (params) => {
3806
+ const result = await runMcpTool(
3807
+ params.filePath,
3808
+ (ctx) => replaceGeometry(ctx, {
3809
+ ...params,
3810
+ adjustments: params.adjustments
3811
+ })
3812
+ );
3813
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3814
+ }
3815
+ );
3816
+ server2.registerTool(
3817
+ "set_element_lock",
3818
+ {
3819
+ description: "Lock or unlock an element (prevent move, resize, rotation, selection, text edit)",
3820
+ inputSchema: SetElementLockSchema.shape
3821
+ },
3822
+ async (params) => {
3823
+ const result = await runMcpTool(
3824
+ params.filePath,
3825
+ (ctx) => setElementLockT(ctx, params)
3826
+ );
3827
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3828
+ }
3829
+ );
3830
+ server2.registerTool(
3831
+ "validate_presentation",
3832
+ {
3833
+ description: "Validate PPTX structural integrity (content types, relationships, XML)",
3834
+ inputSchema: ValidatePresentationSchema.shape
3835
+ },
3836
+ async (params) => {
3837
+ const { readFile: readFile2 } = await import('fs/promises');
3838
+ const safePath = resolveScopedFilePath(params.filePath);
3839
+ const bytes = await readFile2(safePath);
3840
+ const result = await validatePresentation(bytes.buffer);
3841
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3842
+ }
3843
+ );
3844
+ server2.registerTool(
3845
+ "repair_presentation",
3846
+ {
3847
+ description: "Auto-repair common PPTX structural issues",
3848
+ inputSchema: RepairPresentationSchema.shape
3849
+ },
3850
+ async (params) => {
3851
+ const { readFile: readFile2, writeFile: writeFile2 } = await import('fs/promises');
3852
+ const safePath = resolveScopedFilePath(params.filePath);
3853
+ const bytes = await readFile2(safePath);
3854
+ const { result, repairedBytes } = await repairPresentation(
3855
+ bytes.buffer
3856
+ );
3857
+ if (result.repairCount > 0) {
3858
+ await writeFile2(safePath, new Uint8Array(repairedBytes));
3859
+ }
3860
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3861
+ }
3862
+ );
3863
+ server2.registerTool(
3864
+ "get_presentation_properties",
3865
+ {
3866
+ description: "Get slideshow properties (show type, loop, advance mode, slide range)",
3867
+ inputSchema: GetPresentationPropertiesSchema.shape
3868
+ },
3869
+ async (params) => {
3870
+ const result = await runMcpTool(
3871
+ params.filePath,
3872
+ (ctx) => getPresentationProperties(ctx)
3873
+ );
3874
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3875
+ }
3876
+ );
3877
+ server2.registerTool(
3878
+ "update_presentation_properties",
3879
+ {
3880
+ description: "Update slideshow properties (show type, loop, advance mode, pen color)",
3881
+ inputSchema: UpdatePresentationPropertiesSchema.shape
3882
+ },
3883
+ async (params) => {
3884
+ const result = await runMcpTool(
3885
+ params.filePath,
3886
+ (ctx) => updatePresentationProperties(ctx, params)
3887
+ );
3888
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3889
+ }
3890
+ );
3891
+ server2.registerTool(
3892
+ "get_layouts",
3893
+ {
3894
+ description: "List available slide layouts from the presentation masters",
3895
+ inputSchema: GetLayoutsSchema.shape
3896
+ },
3897
+ async (params) => {
3898
+ const result = await runMcpTool(params.filePath, (ctx) => getLayouts(ctx));
3899
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3900
+ }
3901
+ );
3902
+ server2.registerTool(
3903
+ "apply_layout",
3904
+ {
3905
+ description: "Apply a slide layout to a specific slide",
3906
+ inputSchema: ApplyLayoutSchema.shape
3907
+ },
3908
+ async (params) => {
3909
+ const result = await runMcpTool(
3910
+ params.filePath,
3911
+ (ctx) => applyLayout(ctx, params)
3912
+ );
3913
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3914
+ }
3915
+ );
2401
3916
  return server2;
2402
3917
  }
2403
3918