pptx-viewer-mcp 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -881,6 +881,49 @@ var ApplyLayoutSchema = zod.z.object({
881
881
  layoutName: zod.z.string().optional().describe("Layout name to apply"),
882
882
  layoutType: zod.z.string().optional().describe("Layout type to apply (alternative to name)")
883
883
  });
884
+ var GetOleContentSchema = zod.z.object({
885
+ filePath: zod.z.string().describe("Path to the PPTX file"),
886
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
887
+ elementId: zod.z.string().describe("OLE element ID")
888
+ });
889
+ var SetOleSheetCellSchema = zod.z.object({
890
+ filePath: zod.z.string().describe("Path to the PPTX file"),
891
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
892
+ elementId: zod.z.string().describe("OLE element ID (Excel payload: .xlsx or .xls)"),
893
+ row: zod.z.number().int().min(0).describe("Zero-based row index on the first worksheet"),
894
+ col: zod.z.number().int().min(0).describe("Zero-based column index on the first worksheet"),
895
+ value: zod.z.string().describe("New cell value")
896
+ });
897
+ var SetOleDocumentParagraphSchema = zod.z.object({
898
+ filePath: zod.z.string().describe("Path to the PPTX file"),
899
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
900
+ elementId: zod.z.string().describe("OLE element ID (Word payload: .docx)"),
901
+ paragraphIndex: zod.z.number().int().min(0).describe("Zero-based paragraph index"),
902
+ text: zod.z.string().describe("New paragraph text")
903
+ });
904
+ var SetOleDeckSlideTitleSchema = zod.z.object({
905
+ filePath: zod.z.string().describe("Path to the PPTX file"),
906
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
907
+ elementId: zod.z.string().describe("OLE element ID (nested-deck payload: .pptx)"),
908
+ deckSlideIndex: zod.z.number().int().min(0).describe("Zero-based slide index within the nested deck"),
909
+ deckElementId: zod.z.string().describe(
910
+ "ID of the text-bearing shape to edit within the nested slide, from ole_get_content's deckSlides[n].elements[i].elementId"
911
+ ),
912
+ title: zod.z.string().describe("New text for the chosen shape")
913
+ });
914
+ var ReplaceOleFileSchema = zod.z.object({
915
+ filePath: zod.z.string().describe("Path to the PPTX file"),
916
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
917
+ elementId: zod.z.string().describe("OLE element ID"),
918
+ fileData: zod.z.string().describe('New payload as a base64 data URL (e.g. "data:application/pdf;base64,...")'),
919
+ fileName: zod.z.string().optional().describe("Optional new file name (also updates the resolved MIME type)")
920
+ });
921
+ var SetOleObjectNameSchema = zod.z.object({
922
+ filePath: zod.z.string().describe("Path to the PPTX file"),
923
+ slideIndex: zod.z.number().int().min(0).describe("Zero-based slide index"),
924
+ elementId: zod.z.string().describe("OLE element ID"),
925
+ name: zod.z.string().describe("New Object Name (p:oleObj/@name); blank/whitespace clears it")
926
+ });
884
927
  function generateElementId() {
885
928
  return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
886
929
  }
@@ -2849,6 +2892,129 @@ function updateMetadata(ctx, params) {
2849
2892
  result: { updated }
2850
2893
  };
2851
2894
  }
2895
+ function resolveOleElement(ctx, slideIndex, elementId) {
2896
+ const err = validateSlideIndex(slideIndex, ctx.pptxData.slides.length);
2897
+ if (err) {
2898
+ throw new Error(err);
2899
+ }
2900
+ const slide = ctx.pptxData.slides[slideIndex];
2901
+ const index = slide.elements.findIndex((e) => e.id === elementId);
2902
+ if (index === -1) {
2903
+ throw new Error(`Element '${elementId}' not found on slide ${slideIndex}.`);
2904
+ }
2905
+ const element = slide.elements[index];
2906
+ if (element.type !== "ole") {
2907
+ throw new Error(`Element '${elementId}' is not an OLE object.`);
2908
+ }
2909
+ return { slide, element, index };
2910
+ }
2911
+ function commitOleElement(slide, index, updated) {
2912
+ slide.elements.splice(index, 1, updated);
2913
+ slide.isDirty = true;
2914
+ }
2915
+ async function getOleContent(ctx, params) {
2916
+ const { element } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2917
+ const kind = await pptxViewerCore.resolveOleEditorKindFromPayload(element);
2918
+ const base = { pptxData: ctx.pptxData, dirty: false };
2919
+ switch (kind) {
2920
+ case "sheet-xlsx":
2921
+ case "sheet-xls": {
2922
+ const sheet = await pptxViewerCore.getOleSheetGrid(element);
2923
+ return { ...base, result: { elementId: element.id, kind, editable: true, sheet } };
2924
+ }
2925
+ case "document-docx":
2926
+ case "document-doc": {
2927
+ const paragraphs = await pptxViewerCore.getOleDocumentParagraphs(element);
2928
+ return { ...base, result: { elementId: element.id, kind, editable: true, paragraphs } };
2929
+ }
2930
+ case "deck-pptx": {
2931
+ const deckSlides = await pptxViewerCore.getOleNestedDeckDetail(element);
2932
+ return { ...base, result: { elementId: element.id, kind, editable: true, deckSlides } };
2933
+ }
2934
+ default:
2935
+ return {
2936
+ ...base,
2937
+ result: {
2938
+ elementId: element.id,
2939
+ kind,
2940
+ editable: false,
2941
+ message: `This OLE object's content (${kind}) is not directly editable; use ole_replace_file to replace it wholesale.`
2942
+ }
2943
+ };
2944
+ }
2945
+ }
2946
+ async function setOleSheetCell(ctx, params) {
2947
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2948
+ const updated = await pptxViewerCore.applyOleSheetCellEdit(element, {
2949
+ row: params.row,
2950
+ col: params.col,
2951
+ value: params.value
2952
+ });
2953
+ const changed = updated !== element;
2954
+ if (changed) {
2955
+ commitOleElement(slide, index, updated);
2956
+ }
2957
+ return {
2958
+ pptxData: ctx.pptxData,
2959
+ dirty: changed,
2960
+ result: { elementId: params.elementId, changed }
2961
+ };
2962
+ }
2963
+ async function setOleDocumentParagraph(ctx, params) {
2964
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2965
+ const updated = await pptxViewerCore.applyOleDocumentParagraphEdit(element, params.paragraphIndex, params.text);
2966
+ const changed = updated !== element;
2967
+ if (changed) {
2968
+ commitOleElement(slide, index, updated);
2969
+ }
2970
+ return {
2971
+ pptxData: ctx.pptxData,
2972
+ dirty: changed,
2973
+ result: { elementId: params.elementId, changed }
2974
+ };
2975
+ }
2976
+ async function setOleDeckSlideTitle(ctx, params) {
2977
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2978
+ const updated = await pptxViewerCore.applyOleNestedDeckElementTextEdit(
2979
+ element,
2980
+ params.deckSlideIndex,
2981
+ params.deckElementId,
2982
+ params.title
2983
+ );
2984
+ const changed = updated !== element;
2985
+ if (changed) {
2986
+ commitOleElement(slide, index, updated);
2987
+ }
2988
+ return {
2989
+ pptxData: ctx.pptxData,
2990
+ dirty: changed,
2991
+ result: { elementId: params.elementId, changed }
2992
+ };
2993
+ }
2994
+ async function setOleObjectNameT(ctx, params) {
2995
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2996
+ const updated = await pptxViewerCore.setOleObjectName(element, params.name);
2997
+ commitOleElement(slide, index, updated);
2998
+ return {
2999
+ pptxData: ctx.pptxData,
3000
+ dirty: true,
3001
+ result: { elementId: params.elementId, changed: true }
3002
+ };
3003
+ }
3004
+ async function replaceOleFileT(ctx, params) {
3005
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
3006
+ const parsed = pptxViewerCore.parseDataUrlToBytes(params.fileData);
3007
+ if (!parsed) {
3008
+ throw new Error('fileData must be a base64 data URL, e.g. "data:<mime-type>;base64,<...>".');
3009
+ }
3010
+ const updated = await pptxViewerCore.replaceOleFile(element, parsed.bytes, params.fileName);
3011
+ commitOleElement(slide, index, updated);
3012
+ return {
3013
+ pptxData: ctx.pptxData,
3014
+ dirty: true,
3015
+ result: { elementId: params.elementId, changed: true }
3016
+ };
3017
+ }
2852
3018
 
2853
3019
  // src/tools/presentation-tools.ts
2854
3020
  function getPresentationProperties(ctx) {
@@ -3451,8 +3617,6 @@ function assignTableStyle(ctx, params) {
3451
3617
  result: { elementId: params.elementId, styleId: normalizedId }
3452
3618
  };
3453
3619
  }
3454
-
3455
- // src/tools/table-tools.ts
3456
3620
  function updateTableCells(ctx, params) {
3457
3621
  const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
3458
3622
  if (err) {
@@ -3509,6 +3673,8 @@ function manageTableStructure(ctx, params) {
3509
3673
  throw new Error(`Table '${params.elementId}' has no tableData.`);
3510
3674
  }
3511
3675
  const { rows, columnWidths } = tbl.tableData;
3676
+ const source = { ...tbl, tableData: structuredClone(tbl.tableData) };
3677
+ let edit;
3512
3678
  const colCount = columnWidths.length;
3513
3679
  const rowCount = rows.length;
3514
3680
  switch (params.action) {
@@ -3521,6 +3687,7 @@ function manageTableStructure(ctx, params) {
3521
3687
  }))
3522
3688
  };
3523
3689
  rows.splice(pos, 0, newRow);
3690
+ edit = { axis: "row", action: "insert", index: pos };
3524
3691
  break;
3525
3692
  }
3526
3693
  case "deleteRow": {
@@ -3532,6 +3699,7 @@ function manageTableStructure(ctx, params) {
3532
3699
  throw new Error(`Row index ${ref} out of range (0\u2013${rowCount - 1}).`);
3533
3700
  }
3534
3701
  rows.splice(ref, 1);
3702
+ edit = { axis: "row", action: "delete", index: ref };
3535
3703
  break;
3536
3704
  }
3537
3705
  case "insertColumn": {
@@ -3542,6 +3710,7 @@ function manageTableStructure(ctx, params) {
3542
3710
  columnWidths[i] *= scaleFactor;
3543
3711
  }
3544
3712
  columnWidths.splice(pos, 0, newWidth);
3713
+ edit = { axis: "column", action: "insert", index: pos };
3545
3714
  for (let r = 0; r < rows.length; r++) {
3546
3715
  rows[r].cells.splice(pos, 0, {
3547
3716
  text: params.cellTexts?.[r] ?? ""
@@ -3559,6 +3728,7 @@ function manageTableStructure(ctx, params) {
3559
3728
  }
3560
3729
  const removedWidth = columnWidths[ref];
3561
3730
  columnWidths.splice(ref, 1);
3731
+ edit = { axis: "column", action: "delete", index: ref };
3562
3732
  const remaining = columnWidths.length;
3563
3733
  if (remaining > 0) {
3564
3734
  const extra = removedWidth / remaining;
@@ -3575,6 +3745,10 @@ function manageTableStructure(ctx, params) {
3575
3745
  throw new Error(`Unknown action: ${String(params.action)}`);
3576
3746
  }
3577
3747
  }
3748
+ const rawXml = pptxViewerCore.rebuildTableStructureInRawXml(source, tbl.tableData, edit);
3749
+ if (rawXml) {
3750
+ tbl.rawXml = rawXml;
3751
+ }
3578
3752
  return {
3579
3753
  pptxData: ctx.pptxData,
3580
3754
  dirty: true,
@@ -4962,6 +5136,90 @@ function createServer() {
4962
5136
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4963
5137
  }
4964
5138
  );
5139
+ server2.registerTool(
5140
+ "ole_get_content",
5141
+ {
5142
+ description: "Inspect an embedded OLE object's editable content: resolved kind, plus the sheet grid, paragraph list, or nested-deck slide summaries, whichever applies",
5143
+ inputSchema: GetOleContentSchema.shape
5144
+ },
5145
+ async (params) => {
5146
+ const result = await runMcpTool(
5147
+ params.filePath,
5148
+ (ctx) => getOleContent(ctx, params)
5149
+ );
5150
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5151
+ }
5152
+ );
5153
+ server2.registerTool(
5154
+ "ole_set_sheet_cell",
5155
+ {
5156
+ description: "Edit one cell of an embedded Excel OLE object's first worksheet",
5157
+ inputSchema: SetOleSheetCellSchema.shape
5158
+ },
5159
+ async (params) => {
5160
+ const result = await runMcpTool(
5161
+ params.filePath,
5162
+ (ctx) => setOleSheetCell(ctx, params)
5163
+ );
5164
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5165
+ }
5166
+ );
5167
+ server2.registerTool(
5168
+ "ole_set_document_paragraph",
5169
+ {
5170
+ description: "Replace one paragraph's text in an embedded Word (.docx or legacy .doc) OLE object",
5171
+ inputSchema: SetOleDocumentParagraphSchema.shape
5172
+ },
5173
+ async (params) => {
5174
+ const result = await runMcpTool(
5175
+ params.filePath,
5176
+ (ctx) => setOleDocumentParagraph(ctx, params)
5177
+ );
5178
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5179
+ }
5180
+ );
5181
+ server2.registerTool(
5182
+ "ole_set_deck_slide_title",
5183
+ {
5184
+ description: "Edit one text-bearing shape on one slide of an embedded nested-deck (PowerPoint) OLE object, via a full load/save round-trip through the nested deck (call ole_get_content first to discover each slide's elementIds)",
5185
+ inputSchema: SetOleDeckSlideTitleSchema.shape
5186
+ },
5187
+ async (params) => {
5188
+ const result = await runMcpTool(
5189
+ params.filePath,
5190
+ (ctx) => setOleDeckSlideTitle(ctx, params)
5191
+ );
5192
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5193
+ }
5194
+ );
5195
+ server2.registerTool(
5196
+ "ole_replace_file",
5197
+ {
5198
+ description: `Replace an embedded OLE object's payload wholesale with an arbitrary file; works for any OLE kind, including a generic "Package" object or an unsupported format`,
5199
+ inputSchema: ReplaceOleFileSchema.shape
5200
+ },
5201
+ async (params) => {
5202
+ const result = await runMcpTool(
5203
+ params.filePath,
5204
+ (ctx) => replaceOleFileT(ctx, params)
5205
+ );
5206
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5207
+ }
5208
+ );
5209
+ server2.registerTool(
5210
+ "ole_set_object_name",
5211
+ {
5212
+ description: "Rename an OLE object's Object Name (p:oleObj/@name); regenerates the baked-in icon caption too when the object is displayed as an icon",
5213
+ inputSchema: SetOleObjectNameSchema.shape
5214
+ },
5215
+ async (params) => {
5216
+ const result = await runMcpTool(
5217
+ params.filePath,
5218
+ (ctx) => setOleObjectNameT(ctx, params)
5219
+ );
5220
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5221
+ }
5222
+ );
4965
5223
  return server2;
4966
5224
  }
4967
5225
 
@@ -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 { validatePptx, repairPptx, PptxHandler, hasTextProperties, resolveThemeColorRef, cloneElement as cloneElement$1, isTemplateElementId, makeStoreAwareId, reassignDescendantIds, groupElements as groupElements$1, ungroupElements as ungroupElements$1, TABLE_STYLE_PART_SEQUENCE, normalizeTableStyleGuid, createTableStyleEntry, addTableStyleToMap, deleteTableStyleFromMap, PptxMarkdownConverter, ThemePresets, getThemePreset, setChartType, setChartTitle, setChartGrouping, setChartLegend, setChartDataLabels, setChartAxis, setChartCategories, addChartSeries, removeChartSeries, updateChartSeriesValues, ChartBuilder, setChartDataPointStyle, setChartDataPointExplosion, setChartDataPointMarker, setChartDataPointLabel, setChartSeriesMarker, setChartSeriesTrendline, setChartSeriesErrorBars, setChartHelperLine, setChartColorMapOverride, listChartUserShapes, addChartUserShape, updateChartUserShape, removeChartUserShape, decomposeSmartArt, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, updateSmartArtNodeText, removeSmartArtNode, addSmartArtNode, findPlaceholders, applyTemplate, getSectionForSlide, moveSlidesToSection, reorderSections, removeSection, addSection, SvgExporter, buildPptxJsonDocument, deserializePptxFromJson, applyImportedPptxData, replaceWithCustomGeometry, replaceShapeGeometry, setElementLocked, findLayoutByName, findLayoutByType, resolveDisplayedChartTypeName } from 'pptx-viewer-core';
5
+ import { validatePptx, repairPptx, PptxHandler, hasTextProperties, resolveThemeColorRef, cloneElement as cloneElement$1, isTemplateElementId, makeStoreAwareId, reassignDescendantIds, groupElements as groupElements$1, ungroupElements as ungroupElements$1, rebuildTableStructureInRawXml, TABLE_STYLE_PART_SEQUENCE, normalizeTableStyleGuid, createTableStyleEntry, addTableStyleToMap, deleteTableStyleFromMap, PptxMarkdownConverter, ThemePresets, getThemePreset, setChartType, setChartTitle, setChartGrouping, setChartLegend, setChartDataLabels, setChartAxis, setChartCategories, addChartSeries, removeChartSeries, updateChartSeriesValues, ChartBuilder, setChartDataPointStyle, setChartDataPointExplosion, setChartDataPointMarker, setChartDataPointLabel, setChartSeriesMarker, setChartSeriesTrendline, setChartSeriesErrorBars, setChartHelperLine, setChartColorMapOverride, listChartUserShapes, addChartUserShape, updateChartUserShape, removeChartUserShape, decomposeSmartArt, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, updateSmartArtNodeText, removeSmartArtNode, addSmartArtNode, findPlaceholders, applyTemplate, getSectionForSlide, moveSlidesToSection, reorderSections, removeSection, addSection, SvgExporter, buildPptxJsonDocument, deserializePptxFromJson, applyImportedPptxData, replaceWithCustomGeometry, replaceShapeGeometry, setElementLocked, findLayoutByName, findLayoutByType, resolveOleEditorKindFromPayload, getOleNestedDeckDetail, getOleDocumentParagraphs, getOleSheetGrid, applyOleSheetCellEdit, applyOleDocumentParagraphEdit, applyOleNestedDeckElementTextEdit, parseDataUrlToBytes, replaceOleFile, setOleObjectName, resolveDisplayedChartTypeName } from 'pptx-viewer-core';
6
6
  import { writeFile, readFile } from 'fs/promises';
7
7
  import { resolve, isAbsolute, sep } from 'path';
8
8
 
@@ -879,6 +879,49 @@ var ApplyLayoutSchema = z.object({
879
879
  layoutName: z.string().optional().describe("Layout name to apply"),
880
880
  layoutType: z.string().optional().describe("Layout type to apply (alternative to name)")
881
881
  });
882
+ var GetOleContentSchema = z.object({
883
+ filePath: z.string().describe("Path to the PPTX file"),
884
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
885
+ elementId: z.string().describe("OLE element ID")
886
+ });
887
+ var SetOleSheetCellSchema = z.object({
888
+ filePath: z.string().describe("Path to the PPTX file"),
889
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
890
+ elementId: z.string().describe("OLE element ID (Excel payload: .xlsx or .xls)"),
891
+ row: z.number().int().min(0).describe("Zero-based row index on the first worksheet"),
892
+ col: z.number().int().min(0).describe("Zero-based column index on the first worksheet"),
893
+ value: z.string().describe("New cell value")
894
+ });
895
+ var SetOleDocumentParagraphSchema = z.object({
896
+ filePath: z.string().describe("Path to the PPTX file"),
897
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
898
+ elementId: z.string().describe("OLE element ID (Word payload: .docx)"),
899
+ paragraphIndex: z.number().int().min(0).describe("Zero-based paragraph index"),
900
+ text: z.string().describe("New paragraph text")
901
+ });
902
+ var SetOleDeckSlideTitleSchema = z.object({
903
+ filePath: z.string().describe("Path to the PPTX file"),
904
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
905
+ elementId: z.string().describe("OLE element ID (nested-deck payload: .pptx)"),
906
+ deckSlideIndex: z.number().int().min(0).describe("Zero-based slide index within the nested deck"),
907
+ deckElementId: z.string().describe(
908
+ "ID of the text-bearing shape to edit within the nested slide, from ole_get_content's deckSlides[n].elements[i].elementId"
909
+ ),
910
+ title: z.string().describe("New text for the chosen shape")
911
+ });
912
+ var ReplaceOleFileSchema = z.object({
913
+ filePath: z.string().describe("Path to the PPTX file"),
914
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
915
+ elementId: z.string().describe("OLE element ID"),
916
+ fileData: z.string().describe('New payload as a base64 data URL (e.g. "data:application/pdf;base64,...")'),
917
+ fileName: z.string().optional().describe("Optional new file name (also updates the resolved MIME type)")
918
+ });
919
+ var SetOleObjectNameSchema = z.object({
920
+ filePath: z.string().describe("Path to the PPTX file"),
921
+ slideIndex: z.number().int().min(0).describe("Zero-based slide index"),
922
+ elementId: z.string().describe("OLE element ID"),
923
+ name: z.string().describe("New Object Name (p:oleObj/@name); blank/whitespace clears it")
924
+ });
882
925
  function generateElementId() {
883
926
  return `el-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
884
927
  }
@@ -2847,6 +2890,129 @@ function updateMetadata(ctx, params) {
2847
2890
  result: { updated }
2848
2891
  };
2849
2892
  }
2893
+ function resolveOleElement(ctx, slideIndex, elementId) {
2894
+ const err = validateSlideIndex(slideIndex, ctx.pptxData.slides.length);
2895
+ if (err) {
2896
+ throw new Error(err);
2897
+ }
2898
+ const slide = ctx.pptxData.slides[slideIndex];
2899
+ const index = slide.elements.findIndex((e) => e.id === elementId);
2900
+ if (index === -1) {
2901
+ throw new Error(`Element '${elementId}' not found on slide ${slideIndex}.`);
2902
+ }
2903
+ const element = slide.elements[index];
2904
+ if (element.type !== "ole") {
2905
+ throw new Error(`Element '${elementId}' is not an OLE object.`);
2906
+ }
2907
+ return { slide, element, index };
2908
+ }
2909
+ function commitOleElement(slide, index, updated) {
2910
+ slide.elements.splice(index, 1, updated);
2911
+ slide.isDirty = true;
2912
+ }
2913
+ async function getOleContent(ctx, params) {
2914
+ const { element } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2915
+ const kind = await resolveOleEditorKindFromPayload(element);
2916
+ const base = { pptxData: ctx.pptxData, dirty: false };
2917
+ switch (kind) {
2918
+ case "sheet-xlsx":
2919
+ case "sheet-xls": {
2920
+ const sheet = await getOleSheetGrid(element);
2921
+ return { ...base, result: { elementId: element.id, kind, editable: true, sheet } };
2922
+ }
2923
+ case "document-docx":
2924
+ case "document-doc": {
2925
+ const paragraphs = await getOleDocumentParagraphs(element);
2926
+ return { ...base, result: { elementId: element.id, kind, editable: true, paragraphs } };
2927
+ }
2928
+ case "deck-pptx": {
2929
+ const deckSlides = await getOleNestedDeckDetail(element);
2930
+ return { ...base, result: { elementId: element.id, kind, editable: true, deckSlides } };
2931
+ }
2932
+ default:
2933
+ return {
2934
+ ...base,
2935
+ result: {
2936
+ elementId: element.id,
2937
+ kind,
2938
+ editable: false,
2939
+ message: `This OLE object's content (${kind}) is not directly editable; use ole_replace_file to replace it wholesale.`
2940
+ }
2941
+ };
2942
+ }
2943
+ }
2944
+ async function setOleSheetCell(ctx, params) {
2945
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2946
+ const updated = await applyOleSheetCellEdit(element, {
2947
+ row: params.row,
2948
+ col: params.col,
2949
+ value: params.value
2950
+ });
2951
+ const changed = updated !== element;
2952
+ if (changed) {
2953
+ commitOleElement(slide, index, updated);
2954
+ }
2955
+ return {
2956
+ pptxData: ctx.pptxData,
2957
+ dirty: changed,
2958
+ result: { elementId: params.elementId, changed }
2959
+ };
2960
+ }
2961
+ async function setOleDocumentParagraph(ctx, params) {
2962
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2963
+ const updated = await applyOleDocumentParagraphEdit(element, params.paragraphIndex, params.text);
2964
+ const changed = updated !== element;
2965
+ if (changed) {
2966
+ commitOleElement(slide, index, updated);
2967
+ }
2968
+ return {
2969
+ pptxData: ctx.pptxData,
2970
+ dirty: changed,
2971
+ result: { elementId: params.elementId, changed }
2972
+ };
2973
+ }
2974
+ async function setOleDeckSlideTitle(ctx, params) {
2975
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2976
+ const updated = await applyOleNestedDeckElementTextEdit(
2977
+ element,
2978
+ params.deckSlideIndex,
2979
+ params.deckElementId,
2980
+ params.title
2981
+ );
2982
+ const changed = updated !== element;
2983
+ if (changed) {
2984
+ commitOleElement(slide, index, updated);
2985
+ }
2986
+ return {
2987
+ pptxData: ctx.pptxData,
2988
+ dirty: changed,
2989
+ result: { elementId: params.elementId, changed }
2990
+ };
2991
+ }
2992
+ async function setOleObjectNameT(ctx, params) {
2993
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
2994
+ const updated = await setOleObjectName(element, params.name);
2995
+ commitOleElement(slide, index, updated);
2996
+ return {
2997
+ pptxData: ctx.pptxData,
2998
+ dirty: true,
2999
+ result: { elementId: params.elementId, changed: true }
3000
+ };
3001
+ }
3002
+ async function replaceOleFileT(ctx, params) {
3003
+ const { slide, element, index } = resolveOleElement(ctx, params.slideIndex, params.elementId);
3004
+ const parsed = parseDataUrlToBytes(params.fileData);
3005
+ if (!parsed) {
3006
+ throw new Error('fileData must be a base64 data URL, e.g. "data:<mime-type>;base64,<...>".');
3007
+ }
3008
+ const updated = await replaceOleFile(element, parsed.bytes, params.fileName);
3009
+ commitOleElement(slide, index, updated);
3010
+ return {
3011
+ pptxData: ctx.pptxData,
3012
+ dirty: true,
3013
+ result: { elementId: params.elementId, changed: true }
3014
+ };
3015
+ }
2850
3016
 
2851
3017
  // src/tools/presentation-tools.ts
2852
3018
  function getPresentationProperties(ctx) {
@@ -3449,8 +3615,6 @@ function assignTableStyle(ctx, params) {
3449
3615
  result: { elementId: params.elementId, styleId: normalizedId }
3450
3616
  };
3451
3617
  }
3452
-
3453
- // src/tools/table-tools.ts
3454
3618
  function updateTableCells(ctx, params) {
3455
3619
  const err = validateSlideIndex(params.slideIndex, ctx.pptxData.slides.length);
3456
3620
  if (err) {
@@ -3507,6 +3671,8 @@ function manageTableStructure(ctx, params) {
3507
3671
  throw new Error(`Table '${params.elementId}' has no tableData.`);
3508
3672
  }
3509
3673
  const { rows, columnWidths } = tbl.tableData;
3674
+ const source = { ...tbl, tableData: structuredClone(tbl.tableData) };
3675
+ let edit;
3510
3676
  const colCount = columnWidths.length;
3511
3677
  const rowCount = rows.length;
3512
3678
  switch (params.action) {
@@ -3519,6 +3685,7 @@ function manageTableStructure(ctx, params) {
3519
3685
  }))
3520
3686
  };
3521
3687
  rows.splice(pos, 0, newRow);
3688
+ edit = { axis: "row", action: "insert", index: pos };
3522
3689
  break;
3523
3690
  }
3524
3691
  case "deleteRow": {
@@ -3530,6 +3697,7 @@ function manageTableStructure(ctx, params) {
3530
3697
  throw new Error(`Row index ${ref} out of range (0\u2013${rowCount - 1}).`);
3531
3698
  }
3532
3699
  rows.splice(ref, 1);
3700
+ edit = { axis: "row", action: "delete", index: ref };
3533
3701
  break;
3534
3702
  }
3535
3703
  case "insertColumn": {
@@ -3540,6 +3708,7 @@ function manageTableStructure(ctx, params) {
3540
3708
  columnWidths[i] *= scaleFactor;
3541
3709
  }
3542
3710
  columnWidths.splice(pos, 0, newWidth);
3711
+ edit = { axis: "column", action: "insert", index: pos };
3543
3712
  for (let r = 0; r < rows.length; r++) {
3544
3713
  rows[r].cells.splice(pos, 0, {
3545
3714
  text: params.cellTexts?.[r] ?? ""
@@ -3557,6 +3726,7 @@ function manageTableStructure(ctx, params) {
3557
3726
  }
3558
3727
  const removedWidth = columnWidths[ref];
3559
3728
  columnWidths.splice(ref, 1);
3729
+ edit = { axis: "column", action: "delete", index: ref };
3560
3730
  const remaining = columnWidths.length;
3561
3731
  if (remaining > 0) {
3562
3732
  const extra = removedWidth / remaining;
@@ -3573,6 +3743,10 @@ function manageTableStructure(ctx, params) {
3573
3743
  throw new Error(`Unknown action: ${String(params.action)}`);
3574
3744
  }
3575
3745
  }
3746
+ const rawXml = rebuildTableStructureInRawXml(source, tbl.tableData, edit);
3747
+ if (rawXml) {
3748
+ tbl.rawXml = rawXml;
3749
+ }
3576
3750
  return {
3577
3751
  pptxData: ctx.pptxData,
3578
3752
  dirty: true,
@@ -4960,6 +5134,90 @@ function createServer() {
4960
5134
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
4961
5135
  }
4962
5136
  );
5137
+ server2.registerTool(
5138
+ "ole_get_content",
5139
+ {
5140
+ description: "Inspect an embedded OLE object's editable content: resolved kind, plus the sheet grid, paragraph list, or nested-deck slide summaries, whichever applies",
5141
+ inputSchema: GetOleContentSchema.shape
5142
+ },
5143
+ async (params) => {
5144
+ const result = await runMcpTool(
5145
+ params.filePath,
5146
+ (ctx) => getOleContent(ctx, params)
5147
+ );
5148
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5149
+ }
5150
+ );
5151
+ server2.registerTool(
5152
+ "ole_set_sheet_cell",
5153
+ {
5154
+ description: "Edit one cell of an embedded Excel OLE object's first worksheet",
5155
+ inputSchema: SetOleSheetCellSchema.shape
5156
+ },
5157
+ async (params) => {
5158
+ const result = await runMcpTool(
5159
+ params.filePath,
5160
+ (ctx) => setOleSheetCell(ctx, params)
5161
+ );
5162
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5163
+ }
5164
+ );
5165
+ server2.registerTool(
5166
+ "ole_set_document_paragraph",
5167
+ {
5168
+ description: "Replace one paragraph's text in an embedded Word (.docx or legacy .doc) OLE object",
5169
+ inputSchema: SetOleDocumentParagraphSchema.shape
5170
+ },
5171
+ async (params) => {
5172
+ const result = await runMcpTool(
5173
+ params.filePath,
5174
+ (ctx) => setOleDocumentParagraph(ctx, params)
5175
+ );
5176
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5177
+ }
5178
+ );
5179
+ server2.registerTool(
5180
+ "ole_set_deck_slide_title",
5181
+ {
5182
+ description: "Edit one text-bearing shape on one slide of an embedded nested-deck (PowerPoint) OLE object, via a full load/save round-trip through the nested deck (call ole_get_content first to discover each slide's elementIds)",
5183
+ inputSchema: SetOleDeckSlideTitleSchema.shape
5184
+ },
5185
+ async (params) => {
5186
+ const result = await runMcpTool(
5187
+ params.filePath,
5188
+ (ctx) => setOleDeckSlideTitle(ctx, params)
5189
+ );
5190
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5191
+ }
5192
+ );
5193
+ server2.registerTool(
5194
+ "ole_replace_file",
5195
+ {
5196
+ description: `Replace an embedded OLE object's payload wholesale with an arbitrary file; works for any OLE kind, including a generic "Package" object or an unsupported format`,
5197
+ inputSchema: ReplaceOleFileSchema.shape
5198
+ },
5199
+ async (params) => {
5200
+ const result = await runMcpTool(
5201
+ params.filePath,
5202
+ (ctx) => replaceOleFileT(ctx, params)
5203
+ );
5204
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5205
+ }
5206
+ );
5207
+ server2.registerTool(
5208
+ "ole_set_object_name",
5209
+ {
5210
+ description: "Rename an OLE object's Object Name (p:oleObj/@name); regenerates the baked-in icon caption too when the object is displayed as an icon",
5211
+ inputSchema: SetOleObjectNameSchema.shape
5212
+ },
5213
+ async (params) => {
5214
+ const result = await runMcpTool(
5215
+ params.filePath,
5216
+ (ctx) => setOleObjectNameT(ctx, params)
5217
+ );
5218
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5219
+ }
5220
+ );
4963
5221
  return server2;
4964
5222
  }
4965
5223