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/CHANGELOG.md +19 -0
- package/dist/codec/index.d.ts +7 -7
- package/dist/codec/index.d.ts.map +1 -1
- package/dist/index.d.ts +174 -79
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +139 -2
- package/dist/index.mjs +135 -4
- package/dist/mcp/index.js +260 -2
- package/dist/mcp/index.mjs +261 -3
- package/dist/schemas/index.d.ts +115 -73
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +49 -0
- package/dist/schemas/index.mjs +44 -1
- package/package.json +8 -7
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
|
|
package/dist/mcp/index.mjs
CHANGED
|
@@ -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
|
|