pptx-viewer-mcp 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -68,6 +68,32 @@ var ConvertToMarkdownSchema = zod.z.object({
68
68
  var AccessibilityCheckSchema = zod.z.object({
69
69
  filePath: zod.z.string().describe("Path to the PPTX file")
70
70
  });
71
+ var ThemeColorRefSchema = zod.z.object({
72
+ scheme: zod.z.enum([
73
+ "dk1",
74
+ "lt1",
75
+ "dk2",
76
+ "lt2",
77
+ "accent1",
78
+ "accent2",
79
+ "accent3",
80
+ "accent4",
81
+ "accent5",
82
+ "accent6",
83
+ "hlink",
84
+ "folHlink",
85
+ "bg1",
86
+ "tx1",
87
+ "bg2",
88
+ "tx2",
89
+ "phClr"
90
+ ]),
91
+ lumMod: zod.z.number().optional().describe("Multiply HSL luminance (0..1)"),
92
+ lumOff: zod.z.number().optional().describe("Add to HSL luminance after lumMod (0..1)"),
93
+ tint: zod.z.number().optional().describe("Blend towards white (0..1)"),
94
+ shade: zod.z.number().optional().describe("Blend towards black (0..1)"),
95
+ alpha: zod.z.number().optional().describe("Opacity fraction, 1 = opaque")
96
+ });
71
97
  var AddElementSchema = zod.z.object({
72
98
  filePath: zod.z.string().describe("Path to the PPTX file"),
73
99
  slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
@@ -112,6 +138,12 @@ var UpdateElementSchema = zod.z.object({
112
138
  fontSize: zod.z.number().optional(),
113
139
  fontFamily: zod.z.string().optional(),
114
140
  fontColor: zod.z.string().optional(),
141
+ /**
142
+ * A theme colour for the run text. Wins on save; resolves `fontColor`
143
+ * immediately when `fontColor` was not also given. Passing `fontColor`
144
+ * alone clears a previously-set text theme colour.
145
+ */
146
+ fontThemeColor: ThemeColorRefSchema.optional(),
115
147
  bold: zod.z.boolean().optional(),
116
148
  italic: zod.z.boolean().optional(),
117
149
  underline: zod.z.boolean().optional(),
@@ -184,6 +216,32 @@ var BatchUpdateElementsSchema = zod.z.object({
184
216
  opacity: zod.z.number().min(0).max(1).optional(),
185
217
  hidden: zod.z.boolean().optional()
186
218
  });
219
+ var ThemeColorRefSchema2 = zod.z.object({
220
+ scheme: zod.z.enum([
221
+ "dk1",
222
+ "lt1",
223
+ "dk2",
224
+ "lt2",
225
+ "accent1",
226
+ "accent2",
227
+ "accent3",
228
+ "accent4",
229
+ "accent5",
230
+ "accent6",
231
+ "hlink",
232
+ "folHlink",
233
+ "bg1",
234
+ "tx1",
235
+ "bg2",
236
+ "tx2",
237
+ "phClr"
238
+ ]),
239
+ lumMod: zod.z.number().optional().describe("Multiply HSL luminance (0..1)"),
240
+ lumOff: zod.z.number().optional().describe("Add to HSL luminance after lumMod (0..1)"),
241
+ tint: zod.z.number().optional().describe("Blend towards white (0..1)"),
242
+ shade: zod.z.number().optional().describe("Blend towards black (0..1)"),
243
+ alpha: zod.z.number().optional().describe("Opacity fraction, 1 = opaque")
244
+ });
187
245
  var UpdateTableCellsSchema = zod.z.object({
188
246
  filePath: zod.z.string().describe("Path to the PPTX file"),
189
247
  slideIndex: zod.z.number().int().min(0),
@@ -210,6 +268,14 @@ var UpdateElementStyleSchema = zod.z.object({
210
268
  slideIndex: zod.z.number().int().min(0),
211
269
  elementId: zod.z.string(),
212
270
  fillColor: zod.z.string().optional(),
271
+ /**
272
+ * A theme colour for the fill (e.g. `{ scheme: 'accent1', lumMod: 0.8 }`
273
+ * for "Accent 1, Lighter 80%"). When set, the shape saves as
274
+ * `<a:schemeClr>` and keeps following the theme after a later theme
275
+ * change, instead of freezing today's resolved hex. Setting `fillColor`
276
+ * without this clears any previously-set fill theme colour.
277
+ */
278
+ fillThemeColor: ThemeColorRefSchema2.optional(),
213
279
  fillMode: zod.z.string().optional(),
214
280
  fillGradientStops: zod.z.array(
215
281
  zod.z.object({
@@ -222,6 +288,8 @@ var UpdateElementStyleSchema = zod.z.object({
222
288
  fillGradientType: zod.z.string().optional(),
223
289
  fillOpacity: zod.z.number().optional(),
224
290
  strokeColor: zod.z.string().optional(),
291
+ /** A theme colour for the outline; see `fillThemeColor` above. */
292
+ strokeThemeColor: ThemeColorRefSchema2.optional(),
225
293
  strokeWidth: zod.z.number().optional(),
226
294
  strokeDash: zod.z.string().optional(),
227
295
  strokeOpacity: zod.z.number().optional(),
@@ -268,6 +336,116 @@ var ManageCommentsSchema = zod.z.object({
268
336
  resolved: zod.z.boolean().optional(),
269
337
  includeResolved: zod.z.boolean().optional()
270
338
  });
339
+ var TABLE_STYLE_SECTIONS = [
340
+ "wholeTbl",
341
+ "band1H",
342
+ "band2H",
343
+ "band1V",
344
+ "band2V",
345
+ "lastCol",
346
+ "firstCol",
347
+ "lastRow",
348
+ "seCell",
349
+ "swCell",
350
+ "firstRow",
351
+ "neCell",
352
+ "nwCell"
353
+ ];
354
+ var TableStyleSimpleColorSchema = zod.z.object({
355
+ schemeColor: zod.z.string().describe('Theme colour key (e.g. "accent1"), or "" for a non-scheme fill'),
356
+ tint: zod.z.number().optional().describe("0-100000"),
357
+ shade: zod.z.number().optional().describe("0-100000"),
358
+ color: zod.z.string().optional().describe('Explicit hex colour, e.g. "#FF8800"')
359
+ });
360
+ var TableStyleFillSchema = zod.z.object({
361
+ schemeColor: zod.z.string(),
362
+ tint: zod.z.number().optional(),
363
+ shade: zod.z.number().optional(),
364
+ color: zod.z.string().optional(),
365
+ noFill: zod.z.boolean().optional(),
366
+ gradient: zod.z.object({
367
+ type: zod.z.enum(["linear", "radial"]),
368
+ angle: zod.z.number().optional(),
369
+ stops: zod.z.array(zod.z.object({ position: zod.z.number(), fill: TableStyleSimpleColorSchema }))
370
+ }).optional(),
371
+ pattern: zod.z.object({
372
+ preset: zod.z.string(),
373
+ foreground: TableStyleSimpleColorSchema.optional(),
374
+ background: TableStyleSimpleColorSchema.optional()
375
+ }).optional(),
376
+ image: zod.z.object({ path: zod.z.string().optional(), data: zod.z.string().optional() }).optional()
377
+ });
378
+ var TableStyleTextSchema = zod.z.object({
379
+ bold: zod.z.boolean().optional(),
380
+ italic: zod.z.boolean().optional(),
381
+ underline: zod.z.boolean().optional(),
382
+ fontSchemeColor: zod.z.string().optional(),
383
+ fontTint: zod.z.number().optional(),
384
+ fontShade: zod.z.number().optional(),
385
+ fontColor: zod.z.string().optional(),
386
+ fontFace: zod.z.string().optional(),
387
+ fontRefIdx: zod.z.string().optional()
388
+ });
389
+ var TableStyleBorderSideSchema = zod.z.object({
390
+ width: zod.z.number().optional(),
391
+ dash: zod.z.string().optional(),
392
+ fill: TableStyleSimpleColorSchema.optional(),
393
+ color: zod.z.string().optional(),
394
+ noFill: zod.z.boolean().optional()
395
+ });
396
+ var TableStyleBordersSchema = zod.z.object({
397
+ left: TableStyleBorderSideSchema.optional(),
398
+ right: TableStyleBorderSideSchema.optional(),
399
+ top: TableStyleBorderSideSchema.optional(),
400
+ bottom: TableStyleBorderSideSchema.optional(),
401
+ insideH: TableStyleBorderSideSchema.optional(),
402
+ insideV: TableStyleBorderSideSchema.optional(),
403
+ tl2br: TableStyleBorderSideSchema.optional(),
404
+ tr2bl: TableStyleBorderSideSchema.optional()
405
+ });
406
+ var TableStyleCell3DSchema = zod.z.object({
407
+ bevelWidth: zod.z.number().optional(),
408
+ bevelHeight: zod.z.number().optional(),
409
+ bevelPreset: zod.z.string().optional(),
410
+ material: zod.z.string().optional(),
411
+ lightRig: zod.z.string().optional(),
412
+ lightRigDirection: zod.z.string().optional()
413
+ });
414
+ var SetTableStyleSectionSchema = zod.z.object({
415
+ filePath: zod.z.string().describe("Path to the PPTX file"),
416
+ styleId: zod.z.string().describe('Table style GUID, e.g. "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"'),
417
+ section: zod.z.enum(TABLE_STYLE_SECTIONS).describe("Which of the 13 CT_TableStyle parts to patch"),
418
+ styleName: zod.z.string().optional().describe("Rename the style"),
419
+ fill: TableStyleFillSchema.optional(),
420
+ text: TableStyleTextSchema.optional(),
421
+ borders: TableStyleBordersSchema.optional(),
422
+ cell3D: TableStyleCell3DSchema.optional()
423
+ });
424
+ var CreateTableStyleSchema = zod.z.object({
425
+ filePath: zod.z.string().describe("Path to the PPTX file"),
426
+ styleName: zod.z.string().describe("Display name for the new style"),
427
+ basedOnStyleId: zod.z.string().optional().describe("Deep-clone every section from this existing style GUID as the starting point"),
428
+ setAsDefault: zod.z.boolean().optional().describe("Repoint ppt/tableStyles.xml's default style at the new one")
429
+ });
430
+ var DeleteTableStyleSchema = zod.z.object({
431
+ filePath: zod.z.string().describe("Path to the PPTX file"),
432
+ styleId: zod.z.string().describe("Table style GUID to remove"),
433
+ force: zod.z.boolean().optional().describe("Delete even though a table on this deck still references the style")
434
+ });
435
+ var AssignTableStyleSchema = zod.z.object({
436
+ filePath: zod.z.string().describe("Path to the PPTX file"),
437
+ slideIndex: zod.z.number().int().min(0),
438
+ elementId: zod.z.string().describe("Table element ID"),
439
+ styleId: zod.z.string().optional().describe(
440
+ "Table style GUID to assign. Defaults to the presentation's current default table style when omitted."
441
+ ),
442
+ bandedRows: zod.z.boolean().optional(),
443
+ bandedColumns: zod.z.boolean().optional(),
444
+ firstRowHeader: zod.z.boolean().optional(),
445
+ lastRow: zod.z.boolean().optional(),
446
+ firstCol: zod.z.boolean().optional(),
447
+ lastCol: zod.z.boolean().optional()
448
+ });
271
449
  var GetThemeInfoSchema = zod.z.object({
272
450
  filePath: zod.z.string().describe("Path to the PPTX file")
273
451
  });
@@ -297,6 +475,139 @@ var UpdateThemeFontsSchema = zod.z.object({
297
475
  majorFont: zod.z.string().optional().describe("Heading (major) font family"),
298
476
  minorFont: zod.z.string().optional().describe("Body (minor) font family")
299
477
  });
478
+ var MarkerSymbolSchema = zod.z.enum([
479
+ "circle",
480
+ "dash",
481
+ "diamond",
482
+ "dot",
483
+ "none",
484
+ "picture",
485
+ "plus",
486
+ "square",
487
+ "star",
488
+ "triangle",
489
+ "x",
490
+ "auto"
491
+ ]);
492
+ var DataLabelPositionSchema = zod.z.enum([
493
+ "bestFit",
494
+ "b",
495
+ "ctr",
496
+ "inBase",
497
+ "inEnd",
498
+ "l",
499
+ "outEnd",
500
+ "r",
501
+ "t"
502
+ ]);
503
+ var FormatChartDataPointSchema = zod.z.object({
504
+ filePath: zod.z.string().describe("Path to the PPTX file"),
505
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
506
+ elementId: zod.z.string().describe("Chart element ID"),
507
+ seriesIndex: zod.z.number().int().min(0).describe("Zero-based series index"),
508
+ pointIndex: zod.z.number().int().min(0).describe("Zero-based data point (category) index"),
509
+ fillColor: zod.z.string().optional().describe("Fill colour (hex) for this point only"),
510
+ strokeColor: zod.z.string().optional().describe("Outline colour (hex) for this point only"),
511
+ strokeWidth: zod.z.number().optional().describe("Outline width in points"),
512
+ strokeDashStyle: zod.z.string().optional().describe("Outline dash style, e.g. solid, dash, dashDot, sysDot"),
513
+ clearStyle: zod.z.boolean().optional().describe("Remove all per-point shape formatting"),
514
+ explosion: zod.z.number().nullable().optional().describe("Pie/doughnut slice pull-out distance 0-100; null clears it"),
515
+ marker: zod.z.object({
516
+ symbol: MarkerSymbolSchema.optional(),
517
+ size: zod.z.number().optional(),
518
+ fillColor: zod.z.string().optional()
519
+ }).nullable().optional().describe("Per-point marker override (line/scatter/bubble); null clears it")
520
+ });
521
+ var FormatChartDataLabelSchema = zod.z.object({
522
+ filePath: zod.z.string().describe("Path to the PPTX file"),
523
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
524
+ elementId: zod.z.string().describe("Chart element ID"),
525
+ seriesIndex: zod.z.number().int().min(0).describe("Zero-based series index"),
526
+ pointIndex: zod.z.number().int().min(0).describe("Zero-based data point (category) index"),
527
+ remove: zod.z.boolean().optional().describe("Remove the per-point label override entirely"),
528
+ showValue: zod.z.boolean().optional(),
529
+ showCategory: zod.z.boolean().optional(),
530
+ showSeriesName: zod.z.boolean().optional(),
531
+ showPercent: zod.z.boolean().optional(),
532
+ showLegendKey: zod.z.boolean().optional(),
533
+ position: DataLabelPositionSchema.optional(),
534
+ text: zod.z.string().optional().describe("Custom label text override; '' clears it"),
535
+ spPr: zod.z.object({
536
+ fillColor: zod.z.string().optional(),
537
+ strokeColor: zod.z.string().optional(),
538
+ strokeWidth: zod.z.number().optional(),
539
+ strokeDashStyle: zod.z.string().optional()
540
+ }).nullable().optional().describe("This label's own fill/line formatting; null removes it"),
541
+ txPr: zod.z.object({
542
+ fontFamily: zod.z.string().optional(),
543
+ fontSize: zod.z.number().optional(),
544
+ bold: zod.z.boolean().optional(),
545
+ italic: zod.z.boolean().optional(),
546
+ color: zod.z.string().optional()
547
+ }).nullable().optional().describe("This label's own font; null removes it")
548
+ });
549
+ var FormatChartSeriesSchema = zod.z.object({
550
+ filePath: zod.z.string().describe("Path to the PPTX file"),
551
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
552
+ elementId: zod.z.string().describe("Chart element ID"),
553
+ seriesIndex: zod.z.number().int().min(0).describe("Zero-based series index"),
554
+ marker: zod.z.object({
555
+ symbol: MarkerSymbolSchema.optional(),
556
+ size: zod.z.number().optional(),
557
+ fillColor: zod.z.string().optional(),
558
+ strokeColor: zod.z.string().optional(),
559
+ strokeWidth: zod.z.number().optional(),
560
+ strokeDashStyle: zod.z.string().optional()
561
+ }).nullable().optional().describe("Series marker (line/scatter/bubble/radar); null removes it"),
562
+ trendline: zod.z.object({
563
+ trendlineType: zod.z.enum([
564
+ "linear",
565
+ "exponential",
566
+ "logarithmic",
567
+ "polynomial",
568
+ "power",
569
+ "movingAvg"
570
+ ]),
571
+ color: zod.z.string().optional(),
572
+ lineWidth: zod.z.number().optional().describe("Trendline width in points"),
573
+ lineDashStyle: zod.z.string().optional(),
574
+ order: zod.z.number().optional().describe("Polynomial order, 2-6"),
575
+ period: zod.z.number().optional().describe("Moving-average period"),
576
+ displayEq: zod.z.boolean().optional(),
577
+ displayRSq: zod.z.boolean().optional()
578
+ }).nullable().optional().describe("Series trendline; null removes it"),
579
+ errorBars: zod.z.object({
580
+ direction: zod.z.enum(["x", "y"]),
581
+ barType: zod.z.enum(["both", "minus", "plus"]),
582
+ valType: zod.z.enum(["cust", "fixedVal", "percentage", "stdDev", "stdErr"]),
583
+ val: zod.z.number().optional(),
584
+ customPlus: zod.z.array(zod.z.number()).optional(),
585
+ customMinus: zod.z.array(zod.z.number()).optional(),
586
+ noEndCap: zod.z.boolean().optional(),
587
+ color: zod.z.string().optional(),
588
+ width: zod.z.number().optional().describe("Error-bar line width in points"),
589
+ dashStyle: zod.z.string().optional()
590
+ }).nullable().optional().describe("Series error bars; null removes them")
591
+ });
592
+ var SetChartHelperLineSchema = zod.z.object({
593
+ filePath: zod.z.string().describe("Path to the PPTX file"),
594
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
595
+ elementId: zod.z.string().describe("Chart element ID"),
596
+ line: zod.z.enum(["dropLines", "hiLowLines"]).describe("Which helper line to set"),
597
+ style: zod.z.object({
598
+ color: zod.z.string().optional(),
599
+ width: zod.z.number().optional(),
600
+ dashStyle: zod.z.string().optional()
601
+ }).nullable().describe("Line colour/width/dash; {} shows the line with defaults, null removes it")
602
+ });
603
+ var SetChartColorMapOverrideSchema = zod.z.object({
604
+ filePath: zod.z.string().describe("Path to the PPTX file"),
605
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
606
+ elementId: zod.z.string().describe("Chart element ID"),
607
+ overrides: zod.z.record(zod.z.string(), zod.z.string()).nullable().describe(
608
+ 'Theme colour role remap (bg1/tx1/bg2/tx2/accent1-6/hlink/folHlink -> role), e.g. { "accent1": "accent2" }; null removes the override'
609
+ )
610
+ });
300
611
  var UpdateChartSchema = zod.z.object({
301
612
  filePath: zod.z.string().describe("Path to the PPTX file"),
302
613
  slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
@@ -559,39 +870,6 @@ var ApplyLayoutSchema = zod.z.object({
559
870
  layoutName: zod.z.string().optional().describe("Layout name to apply"),
560
871
  layoutType: zod.z.string().optional().describe("Layout type to apply (alternative to name)")
561
872
  });
562
-
563
- // src/tools/chart-pareto.ts
564
- function cumulativePercent(values) {
565
- const total = values.reduce((sum, value) => sum + value, 0);
566
- if (total === 0) {
567
- return values.map(() => 0);
568
- }
569
- let running = 0;
570
- return values.map((value) => {
571
- running += value;
572
- return Math.round(running / total * 1e4) / 100;
573
- });
574
- }
575
- function applyParetoChartTypeAlias(chart, requestedType) {
576
- if (requestedType.trim().toLowerCase() !== "pareto" || !chart.chartData) {
577
- return;
578
- }
579
- const data = chart.chartData;
580
- data.chartType = "histogram";
581
- const [frequency] = data.series;
582
- if (!frequency) {
583
- return;
584
- }
585
- frequency.histogramOptions = { ...frequency.histogramOptions, layout: "histogram" };
586
- const hasParetoLine = data.series.some((s) => s.histogramOptions?.layout === "pareto");
587
- if (!hasParetoLine) {
588
- data.series.push({
589
- name: "Cumulative %",
590
- values: cumulativePercent(frequency.values),
591
- histogramOptions: { layout: "pareto" }
592
- });
593
- }
594
- }
595
873
  function generateElementId() {
596
874
  return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
597
875
  }
@@ -699,6 +977,133 @@ function validateSlideIndex(slideIndex, slideCount) {
699
977
  return null;
700
978
  }
701
979
 
980
+ // src/tools/chart-formatting-tools.ts
981
+ function findChartElement(ctx, slideIndex, elementId) {
982
+ const err = validateSlideIndex(slideIndex, ctx.pptxData.slides.length);
983
+ if (err) {
984
+ throw new Error(err);
985
+ }
986
+ const slide = ctx.pptxData.slides[slideIndex];
987
+ const el = slide.elements.find((e) => e.id === elementId);
988
+ if (!el) {
989
+ throw new Error(`Element '${elementId}' not found on slide ${slideIndex}.`);
990
+ }
991
+ if (el.type !== "chart") {
992
+ throw new Error(`Element '${elementId}' is not a chart.`);
993
+ }
994
+ return el;
995
+ }
996
+ function formatChartDataPoint(ctx, params) {
997
+ const chart = findChartElement(ctx, params.slideIndex, params.elementId);
998
+ if (params.clearStyle) {
999
+ pptxViewerCore.setChartDataPointStyle(chart, params.seriesIndex, params.pointIndex, null);
1000
+ } else if (params.fillColor !== void 0 || params.strokeColor !== void 0 || params.strokeWidth !== void 0 || params.strokeDashStyle !== void 0) {
1001
+ pptxViewerCore.setChartDataPointStyle(chart, params.seriesIndex, params.pointIndex, {
1002
+ ...params.fillColor !== void 0 ? { fillColor: params.fillColor } : {},
1003
+ ...params.strokeColor !== void 0 ? { strokeColor: params.strokeColor } : {},
1004
+ ...params.strokeWidth !== void 0 ? { strokeWidth: params.strokeWidth } : {},
1005
+ ...params.strokeDashStyle !== void 0 ? { strokeDashStyle: params.strokeDashStyle } : {}
1006
+ });
1007
+ }
1008
+ if (params.explosion !== void 0) {
1009
+ pptxViewerCore.setChartDataPointExplosion(chart, params.seriesIndex, params.pointIndex, params.explosion);
1010
+ }
1011
+ if (params.marker !== void 0) {
1012
+ pptxViewerCore.setChartDataPointMarker(chart, params.seriesIndex, params.pointIndex, params.marker);
1013
+ }
1014
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1015
+ }
1016
+ function formatChartDataLabel(ctx, params) {
1017
+ const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1018
+ if (params.remove) {
1019
+ pptxViewerCore.setChartDataPointLabel(chart, params.seriesIndex, params.pointIndex, null);
1020
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1021
+ }
1022
+ pptxViewerCore.setChartDataPointLabel(chart, params.seriesIndex, params.pointIndex, {
1023
+ ...params.showValue !== void 0 ? { showValue: params.showValue } : {},
1024
+ ...params.showCategory !== void 0 ? { showCategory: params.showCategory } : {},
1025
+ ...params.showSeriesName !== void 0 ? { showSeriesName: params.showSeriesName } : {},
1026
+ ...params.showPercent !== void 0 ? { showPercent: params.showPercent } : {},
1027
+ ...params.showLegendKey !== void 0 ? { showLegendKey: params.showLegendKey } : {},
1028
+ ...params.position !== void 0 ? { position: params.position } : {},
1029
+ ...params.text !== void 0 ? { text: params.text } : {},
1030
+ ...params.spPr !== void 0 ? { spPr: params.spPr } : {},
1031
+ ...params.txPr !== void 0 ? { txPr: params.txPr } : {}
1032
+ });
1033
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1034
+ }
1035
+ function formatChartSeries(ctx, params) {
1036
+ const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1037
+ if (params.marker !== void 0) {
1038
+ if (params.marker === null) {
1039
+ pptxViewerCore.setChartSeriesMarker(chart, params.seriesIndex, null);
1040
+ } else if (params.marker.strokeColor !== void 0 || params.marker.strokeWidth !== void 0 || params.marker.strokeDashStyle !== void 0) {
1041
+ pptxViewerCore.setChartSeriesMarker(chart, params.seriesIndex, {
1042
+ symbol: params.marker.symbol ?? "circle",
1043
+ ...params.marker.size !== void 0 ? { size: params.marker.size } : {},
1044
+ spPr: {
1045
+ ...params.marker.fillColor !== void 0 ? { fillColor: params.marker.fillColor } : {},
1046
+ ...params.marker.strokeColor !== void 0 ? { strokeColor: params.marker.strokeColor } : {},
1047
+ ...params.marker.strokeWidth !== void 0 ? { strokeWidth: params.marker.strokeWidth } : {},
1048
+ ...params.marker.strokeDashStyle !== void 0 ? { strokeDashStyle: params.marker.strokeDashStyle } : {}
1049
+ }
1050
+ });
1051
+ } else {
1052
+ pptxViewerCore.setChartSeriesMarker(chart, params.seriesIndex, params.marker);
1053
+ }
1054
+ }
1055
+ if (params.trendline !== void 0) {
1056
+ pptxViewerCore.setChartSeriesTrendline(chart, params.seriesIndex, params.trendline);
1057
+ }
1058
+ if (params.errorBars !== void 0) {
1059
+ pptxViewerCore.setChartSeriesErrorBars(chart, params.seriesIndex, params.errorBars);
1060
+ }
1061
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1062
+ }
1063
+ function setChartHelperLineT(ctx, params) {
1064
+ const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1065
+ pptxViewerCore.setChartHelperLine(chart, params.line, params.style);
1066
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1067
+ }
1068
+ function setChartColorMapOverrideT(ctx, params) {
1069
+ const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1070
+ pptxViewerCore.setChartColorMapOverride(chart, params.overrides);
1071
+ return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
1072
+ }
1073
+
1074
+ // src/tools/chart-pareto.ts
1075
+ function cumulativePercent(values) {
1076
+ const total = values.reduce((sum, value) => sum + value, 0);
1077
+ if (total === 0) {
1078
+ return values.map(() => 0);
1079
+ }
1080
+ let running = 0;
1081
+ return values.map((value) => {
1082
+ running += value;
1083
+ return Math.round(running / total * 1e4) / 100;
1084
+ });
1085
+ }
1086
+ function applyParetoChartTypeAlias(chart, requestedType) {
1087
+ if (requestedType.trim().toLowerCase() !== "pareto" || !chart.chartData) {
1088
+ return;
1089
+ }
1090
+ const data = chart.chartData;
1091
+ data.chartType = "histogram";
1092
+ const [frequency] = data.series;
1093
+ if (!frequency) {
1094
+ return;
1095
+ }
1096
+ frequency.histogramOptions = { ...frequency.histogramOptions, layout: "histogram" };
1097
+ const hasParetoLine = data.series.some((s) => s.histogramOptions?.layout === "pareto");
1098
+ if (!hasParetoLine) {
1099
+ data.series.push({
1100
+ name: "Cumulative %",
1101
+ values: cumulativePercent(frequency.values),
1102
+ histogramOptions: { layout: "pareto" }
1103
+ });
1104
+ }
1105
+ }
1106
+
702
1107
  // src/tools/chart-tools.ts
703
1108
  function updateChart(ctx, params) {
704
1109
  const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
@@ -839,7 +1244,7 @@ function createChart(ctx, params) {
839
1244
  result: { elementId: chart.id, slideIndex: params.slideIndex }
840
1245
  };
841
1246
  }
842
- function findChartElement(ctx, slideIndex, elementId) {
1247
+ function findChartElement2(ctx, slideIndex, elementId) {
843
1248
  const err = validateSlideIndex(slideIndex, ctx.pptxData.slides.length);
844
1249
  if (err) {
845
1250
  throw new Error(err);
@@ -869,17 +1274,17 @@ function buildShape(input) {
869
1274
  };
870
1275
  }
871
1276
  function listChartUserShapesT(ctx, params) {
872
- const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1277
+ const chart = findChartElement2(ctx, params.slideIndex, params.elementId);
873
1278
  return { pptxData: ctx.pptxData, dirty: false, result: { shapes: pptxViewerCore.listChartUserShapes(chart) } };
874
1279
  }
875
1280
  function addChartUserShapeT(ctx, params) {
876
- const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1281
+ const chart = findChartElement2(ctx, params.slideIndex, params.elementId);
877
1282
  pptxViewerCore.addChartUserShape(chart, buildShape(params.shape));
878
1283
  const index = (chart.chartData?.userShapes?.length ?? 1) - 1;
879
1284
  return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId, index } };
880
1285
  }
881
1286
  function updateChartUserShapeT(ctx, params) {
882
- const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1287
+ const chart = findChartElement2(ctx, params.slideIndex, params.elementId);
883
1288
  const patch = {
884
1289
  ...params.patch.kind ? { kind: params.patch.kind } : {},
885
1290
  ...params.patch.anchor ? { anchor: params.patch.anchor } : {},
@@ -896,7 +1301,7 @@ function updateChartUserShapeT(ctx, params) {
896
1301
  return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
897
1302
  }
898
1303
  function removeChartUserShapeT(ctx, params) {
899
- const chart = findChartElement(ctx, params.slideIndex, params.elementId);
1304
+ const chart = findChartElement2(ctx, params.slideIndex, params.elementId);
900
1305
  pptxViewerCore.removeChartUserShape(chart, params.index);
901
1306
  return { pptxData: ctx.pptxData, dirty: true, result: { elementId: params.elementId } };
902
1307
  }
@@ -1424,8 +1829,17 @@ function updateElement(ctx, params) {
1424
1829
  if (params.fontFamily !== void 0) {
1425
1830
  textEl.textStyle.fontFamily = params.fontFamily;
1426
1831
  }
1427
- if (params.fontColor !== void 0) {
1832
+ if (params.fontThemeColor !== void 0) {
1833
+ textEl.textStyle.colorRef = params.fontThemeColor;
1834
+ const resolved = pptxViewerCore.resolveThemeColorRef(params.fontThemeColor, ctx.pptxData.themeColorMap);
1835
+ if (params.fontColor !== void 0) {
1836
+ textEl.textStyle.color = params.fontColor;
1837
+ } else if (resolved) {
1838
+ textEl.textStyle.color = resolved;
1839
+ }
1840
+ } else if (params.fontColor !== void 0) {
1428
1841
  textEl.textStyle.color = params.fontColor;
1842
+ textEl.textStyle.colorRef = void 0;
1429
1843
  }
1430
1844
  if (params.bold !== void 0) {
1431
1845
  textEl.textStyle.bold = params.bold;
@@ -2665,8 +3079,17 @@ function updateElementStyle(ctx, params) {
2665
3079
  el.shapeStyle = {};
2666
3080
  }
2667
3081
  const ss = el.shapeStyle;
2668
- if (params.fillColor !== void 0) {
3082
+ if (params.fillThemeColor !== void 0) {
3083
+ ss.fillColorRef = params.fillThemeColor;
3084
+ const resolved = pptxViewerCore.resolveThemeColorRef(params.fillThemeColor, ctx.pptxData.themeColorMap);
3085
+ if (params.fillColor !== void 0) {
3086
+ ss.fillColor = params.fillColor;
3087
+ } else if (resolved) {
3088
+ ss.fillColor = resolved;
3089
+ }
3090
+ } else if (params.fillColor !== void 0) {
2669
3091
  ss.fillColor = params.fillColor;
3092
+ ss.fillColorRef = void 0;
2670
3093
  }
2671
3094
  if (params.fillMode !== void 0) {
2672
3095
  ss.fillMode = params.fillMode;
@@ -2683,8 +3106,17 @@ function updateElementStyle(ctx, params) {
2683
3106
  if (params.fillOpacity !== void 0) {
2684
3107
  ss.fillOpacity = params.fillOpacity;
2685
3108
  }
2686
- if (params.strokeColor !== void 0) {
3109
+ if (params.strokeThemeColor !== void 0) {
3110
+ ss.strokeColorRef = params.strokeThemeColor;
3111
+ const resolved = pptxViewerCore.resolveThemeColorRef(params.strokeThemeColor, ctx.pptxData.themeColorMap);
3112
+ if (params.strokeColor !== void 0) {
3113
+ ss.strokeColor = params.strokeColor;
3114
+ } else if (resolved) {
3115
+ ss.strokeColor = resolved;
3116
+ }
3117
+ } else if (params.strokeColor !== void 0) {
2687
3118
  ss.strokeColor = params.strokeColor;
3119
+ ss.strokeColorRef = void 0;
2688
3120
  }
2689
3121
  if (params.strokeWidth !== void 0) {
2690
3122
  ss.strokeWidth = params.strokeWidth;
@@ -2834,6 +3266,142 @@ function runAccessibilityCheck(ctx) {
2834
3266
  }
2835
3267
  };
2836
3268
  }
3269
+ function findTableElement(ctx, slideIndex, elementId) {
3270
+ const err = validateSlideIndex(slideIndex, ctx.pptxData.slides.length);
3271
+ if (err) {
3272
+ throw new Error(err);
3273
+ }
3274
+ const slide = ctx.pptxData.slides[slideIndex];
3275
+ const el = slide.elements.find((e) => e.id === elementId);
3276
+ if (!el) {
3277
+ throw new Error(`Element '${elementId}' not found on slide ${slideIndex}.`);
3278
+ }
3279
+ if (el.type !== "table") {
3280
+ throw new Error(`Element '${elementId}' is not a table.`);
3281
+ }
3282
+ return el;
3283
+ }
3284
+ function tableStyleIdsInUse(ctx) {
3285
+ const used = /* @__PURE__ */ new Set();
3286
+ for (const slide of ctx.pptxData.slides) {
3287
+ for (const el of slide.elements) {
3288
+ if (el.type === "table" && el.tableData?.tableStyleId) {
3289
+ used.add(pptxViewerCore.normalizeTableStyleGuid(el.tableData.tableStyleId));
3290
+ }
3291
+ }
3292
+ }
3293
+ return used;
3294
+ }
3295
+ function setTableStyleSection(ctx, params) {
3296
+ if (!pptxViewerCore.TABLE_STYLE_PART_SEQUENCE.includes(params.section)) {
3297
+ throw new Error(
3298
+ `Unknown table style section '${params.section}'. Valid sections: ${pptxViewerCore.TABLE_STYLE_PART_SEQUENCE.join(", ")}.`
3299
+ );
3300
+ }
3301
+ const map = ctx.pptxData.tableStyleMap ?? {};
3302
+ ctx.pptxData.tableStyleMap = map;
3303
+ const normalizedId = pptxViewerCore.normalizeTableStyleGuid(params.styleId);
3304
+ const entry = map[normalizedId];
3305
+ if (!entry) {
3306
+ throw new Error(
3307
+ `Table style '${params.styleId}' is not loaded in this presentation. Use create_table_style to add a new one.`
3308
+ );
3309
+ }
3310
+ if (params.styleName !== void 0) {
3311
+ entry.styleName = params.styleName;
3312
+ }
3313
+ const mutableEntry = entry;
3314
+ if (params.fill !== void 0) {
3315
+ mutableEntry[`${params.section}Fill`] = params.fill;
3316
+ }
3317
+ if (params.text !== void 0) {
3318
+ mutableEntry[`${params.section}Text`] = params.text;
3319
+ }
3320
+ if (params.borders !== void 0) {
3321
+ mutableEntry[`${params.section}Borders`] = params.borders;
3322
+ }
3323
+ if (params.cell3D !== void 0) {
3324
+ mutableEntry[`${params.section}Cell3D`] = params.cell3D;
3325
+ }
3326
+ return {
3327
+ pptxData: ctx.pptxData,
3328
+ dirty: true,
3329
+ result: { styleId: normalizedId, section: params.section }
3330
+ };
3331
+ }
3332
+ function createTableStyle(ctx, params) {
3333
+ const map = ctx.pptxData.tableStyleMap ?? {};
3334
+ ctx.pptxData.tableStyleMap = map;
3335
+ const basedOn = params.basedOnStyleId ? map[pptxViewerCore.normalizeTableStyleGuid(params.basedOnStyleId)] : void 0;
3336
+ if (params.basedOnStyleId && !basedOn) {
3337
+ throw new Error(`basedOnStyleId '${params.basedOnStyleId}' is not a loaded table style.`);
3338
+ }
3339
+ const entry = pptxViewerCore.createTableStyleEntry(map, { styleName: params.styleName, basedOn });
3340
+ pptxViewerCore.addTableStyleToMap(map, entry);
3341
+ return {
3342
+ pptxData: ctx.pptxData,
3343
+ dirty: true,
3344
+ result: { styleId: entry.styleId },
3345
+ ...params.setAsDefault ? { saveOptions: { tableStylesDefaultId: entry.styleId } } : {}
3346
+ };
3347
+ }
3348
+ function deleteTableStyle(ctx, params) {
3349
+ const normalizedId = pptxViewerCore.normalizeTableStyleGuid(params.styleId);
3350
+ if (!params.force) {
3351
+ const used = tableStyleIdsInUse(ctx);
3352
+ if (used.has(normalizedId)) {
3353
+ throw new Error(
3354
+ `Table style '${params.styleId}' is assigned to at least one table on this deck. Reassign those tables first, or pass force: true.`
3355
+ );
3356
+ }
3357
+ }
3358
+ const map = ctx.pptxData.tableStyleMap ?? {};
3359
+ ctx.pptxData.tableStyleMap = map;
3360
+ pptxViewerCore.deleteTableStyleFromMap(map, normalizedId);
3361
+ return {
3362
+ pptxData: ctx.pptxData,
3363
+ dirty: true,
3364
+ result: { deleted: true },
3365
+ saveOptions: { tableStylesToDelete: [normalizedId] }
3366
+ };
3367
+ }
3368
+ function assignTableStyle(ctx, params) {
3369
+ const el = findTableElement(ctx, params.slideIndex, params.elementId);
3370
+ if (!el.tableData) {
3371
+ throw new Error(`Table '${params.elementId}' has no tableData.`);
3372
+ }
3373
+ const styleId = params.styleId ?? ctx.pptxData.tableStylesDefaultId;
3374
+ if (!styleId) {
3375
+ throw new Error(
3376
+ "styleId was not provided and this presentation has no tableStylesDefaultId to fall back to."
3377
+ );
3378
+ }
3379
+ const normalizedId = pptxViewerCore.normalizeTableStyleGuid(styleId);
3380
+ el.tableData.tableStyleId = normalizedId;
3381
+ if (params.bandedRows !== void 0) {
3382
+ el.tableData.bandedRows = params.bandedRows;
3383
+ }
3384
+ if (params.bandedColumns !== void 0) {
3385
+ el.tableData.bandedColumns = params.bandedColumns;
3386
+ }
3387
+ if (params.firstRowHeader !== void 0) {
3388
+ el.tableData.firstRowHeader = params.firstRowHeader;
3389
+ }
3390
+ if (params.lastRow !== void 0) {
3391
+ el.tableData.lastRow = params.lastRow;
3392
+ }
3393
+ if (params.firstCol !== void 0) {
3394
+ el.tableData.firstCol = params.firstCol;
3395
+ }
3396
+ if (params.lastCol !== void 0) {
3397
+ el.tableData.lastCol = params.lastCol;
3398
+ }
3399
+ return {
3400
+ pptxData: ctx.pptxData,
3401
+ dirty: true,
3402
+ result: { elementId: params.elementId, styleId: normalizedId }
3403
+ };
3404
+ }
2837
3405
 
2838
3406
  // src/tools/table-tools.ts
2839
3407
  function updateTableCells(ctx, params) {
@@ -3113,11 +3681,17 @@ async function loadPresentation(filePath, execCtx) {
3113
3681
  const pptxData = await handler.load(rawBytes.buffer);
3114
3682
  return { pptxData, rawBytes };
3115
3683
  }
3116
- async function savePresentation(filePath, pptxData, rawBytes, execCtx) {
3684
+ async function savePresentation(filePath, pptxData, rawBytes, execCtx, tableStyleSaveOptions) {
3117
3685
  const handler = new pptxViewerCore.PptxHandler();
3118
3686
  await handler.load(rawBytes.buffer);
3119
3687
  const outputBytes = await handler.save(pptxData.slides, {
3120
- headerFooter: pptxData.headerFooter
3688
+ headerFooter: pptxData.headerFooter,
3689
+ // `pptxData.tableStyleMap` carries any in-memory table-style edits a
3690
+ // tool made (e.g. `set_table_style_section`, `create_table_style`);
3691
+ // without forwarding it here those edits were silently dropped on
3692
+ // save even though the core save pipeline already supports them.
3693
+ tableStyles: pptxData.tableStyleMap,
3694
+ ...tableStyleSaveOptions
3121
3695
  });
3122
3696
  if (execCtx.collaboration) {
3123
3697
  const room = execCtx.collaboration.getRoom(filePath);
@@ -3149,7 +3723,13 @@ async function executeToolWithContext(filePath, execCtx, toolFn) {
3149
3723
  const { pptxData, rawBytes } = await loadPresentation(filePath, execCtx);
3150
3724
  const toolResult = await toolFn({ pptxData });
3151
3725
  if (toolResult.dirty) {
3152
- const saveResult = await savePresentation(filePath, toolResult.pptxData, rawBytes, execCtx);
3726
+ const saveResult = await savePresentation(
3727
+ filePath,
3728
+ toolResult.pptxData,
3729
+ rawBytes,
3730
+ execCtx,
3731
+ toolResult.saveOptions
3732
+ );
3153
3733
  return { ...toolResult.result, ...saveResult };
3154
3734
  }
3155
3735
  return toolResult.result;
@@ -3629,6 +4209,70 @@ function createServer() {
3629
4209
  };
3630
4210
  }
3631
4211
  );
4212
+ server2.registerTool(
4213
+ "set_table_style_section",
4214
+ {
4215
+ description: "Patch the fill, text, borders, or cell3D of one section (wholeTbl, banding, first/last row/col, or a corner cell) of an existing loaded table style",
4216
+ inputSchema: SetTableStyleSectionSchema.shape
4217
+ },
4218
+ async (params) => {
4219
+ const result = await runMcpTool(
4220
+ params.filePath,
4221
+ (ctx) => setTableStyleSection(ctx, params)
4222
+ );
4223
+ return {
4224
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
4225
+ };
4226
+ }
4227
+ );
4228
+ server2.registerTool(
4229
+ "create_table_style",
4230
+ {
4231
+ description: "Create a new table style (optionally cloned from an existing one) and optionally set it as the default",
4232
+ inputSchema: CreateTableStyleSchema.shape
4233
+ },
4234
+ async (params) => {
4235
+ const result = await runMcpTool(
4236
+ params.filePath,
4237
+ (ctx) => createTableStyle(ctx, params)
4238
+ );
4239
+ return {
4240
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
4241
+ };
4242
+ }
4243
+ );
4244
+ server2.registerTool(
4245
+ "delete_table_style",
4246
+ {
4247
+ description: "Remove a table style from the presentation",
4248
+ inputSchema: DeleteTableStyleSchema.shape
4249
+ },
4250
+ async (params) => {
4251
+ const result = await runMcpTool(
4252
+ params.filePath,
4253
+ (ctx) => deleteTableStyle(ctx, params)
4254
+ );
4255
+ return {
4256
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
4257
+ };
4258
+ }
4259
+ );
4260
+ server2.registerTool(
4261
+ "assign_table_style",
4262
+ {
4263
+ description: "Assign a table style (and row/column emphasis flags) to an existing table",
4264
+ inputSchema: AssignTableStyleSchema.shape
4265
+ },
4266
+ async (params) => {
4267
+ const result = await runMcpTool(
4268
+ params.filePath,
4269
+ (ctx) => assignTableStyle(ctx, params)
4270
+ );
4271
+ return {
4272
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
4273
+ };
4274
+ }
4275
+ );
3632
4276
  server2.registerTool(
3633
4277
  "update_element_style",
3634
4278
  {
@@ -3875,6 +4519,76 @@ function createServer() {
3875
4519
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
3876
4520
  }
3877
4521
  );
4522
+ server2.registerTool(
4523
+ "format_chart_data_point",
4524
+ {
4525
+ description: "Set per-data-point chart formatting: fill/stroke colour, stroke width, dash style (c:dPt/c:spPr), explosion, and marker override",
4526
+ inputSchema: FormatChartDataPointSchema.shape
4527
+ },
4528
+ async (params) => {
4529
+ const result = await runMcpTool(
4530
+ params.filePath,
4531
+ (ctx) => formatChartDataPoint(ctx, params)
4532
+ );
4533
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4534
+ }
4535
+ );
4536
+ server2.registerTool(
4537
+ "format_chart_data_label",
4538
+ {
4539
+ description: "Set a per-data-point data-label override (c:dLbl): show flags, position, custom text, and the label's own fill/line formatting (c:spPr) and font (c:txPr)",
4540
+ inputSchema: FormatChartDataLabelSchema.shape
4541
+ },
4542
+ async (params) => {
4543
+ const result = await runMcpTool(
4544
+ params.filePath,
4545
+ (ctx) => formatChartDataLabel(ctx, params)
4546
+ );
4547
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4548
+ }
4549
+ );
4550
+ server2.registerTool(
4551
+ "format_chart_series",
4552
+ {
4553
+ description: "Set a chart series marker (c:marker), trendline (c:trendline, incl. line width/dash), or error bars (c:errBars, incl. line width/dash)",
4554
+ inputSchema: FormatChartSeriesSchema.shape
4555
+ },
4556
+ async (params) => {
4557
+ const result = await runMcpTool(
4558
+ params.filePath,
4559
+ (ctx) => formatChartSeries(ctx, params)
4560
+ );
4561
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4562
+ }
4563
+ );
4564
+ server2.registerTool(
4565
+ "set_chart_helper_line",
4566
+ {
4567
+ description: "Set or remove a chart-level helper line on a line/stock/combo chart: c:dropLines or c:hiLowLines",
4568
+ inputSchema: SetChartHelperLineSchema.shape
4569
+ },
4570
+ async (params) => {
4571
+ const result = await runMcpTool(
4572
+ params.filePath,
4573
+ (ctx) => setChartHelperLineT(ctx, params)
4574
+ );
4575
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4576
+ }
4577
+ );
4578
+ server2.registerTool(
4579
+ "set_chart_color_map_override",
4580
+ {
4581
+ description: "Set or remove a chart's theme colour-map override (c:clrMapOvr), remapping theme colour roles for this chart only",
4582
+ inputSchema: SetChartColorMapOverrideSchema.shape
4583
+ },
4584
+ async (params) => {
4585
+ const result = await runMcpTool(
4586
+ params.filePath,
4587
+ (ctx) => setChartColorMapOverrideT(ctx, params)
4588
+ );
4589
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4590
+ }
4591
+ );
3878
4592
  server2.registerTool(
3879
4593
  "chart_user_shape_list",
3880
4594
  {