Skip to content

MCP & Tools ​

pptx-viewer-mcp (source lives in packages/tools) provides the tooling for driving PPTX edits from AI agents and collaborative runtimes: 67 pure tool functions, Zod schemas for every tool input, an MCP server, and a Y.Doc collaboration codec. It is built entirely on top of pptx-viewer-core.

Where this fits

The tools take the PptxData model the core engine produces, mutate it as plain in-memory data, and hand it back. They add no file I/O or framework dependencies of their own - you (or the provided MCP server / execution pipeline) decide how to load and persist.

Install ​

bash
npm install pptx-viewer-mcp pptx-viewer-core
# optional - required only for the collaboration codec
npm install yjs

Entry points ​

The package exposes four import paths:

Entry pointContents
pptx-viewer-mcpThe 67 tool functions + provider types + execution pipeline (loadPresentation, savePresentation, executeToolWithContext).
pptx-viewer-mcp/schemasZod schemas for every tool input.
pptx-viewer-mcp/codecPptxCodec - the Y.Doc ↔ PPTX-bytes codec.
pptx-viewer-mcp/mcpcreateServer() - programmatic MCP server factory.

The 67 tool functions ​

Every tool is a pure function: it receives a ToolContext, returns a ToolResult, and performs no file I/O. They are grouped by concern:

GroupCountTools
Slide8getSlide, addSlide, deleteSlides, reorderSlides, duplicateSlide, updateSlideProperties, setSlideTransition, setCanvasSize
Element12addElement, updateElement, renameElement, deleteElements, arrangeElements, cloneElement, setElementAnimation, groupElements, ungroupElements, batchUpdateElements, setElementLockT, replaceGeometry
Table2updateTableCells, manageTableStructure
Table style4setTableStyleSection, createTableStyle, deleteTableStyle, assignTableStyle
Chart14createChart, updateChart, addChartSeriesT, removeChartSeriesT, updateChartSeriesData, listChartUserShapesT, addChartUserShapeT, updateChartUserShapeT, removeChartUserShapeT, formatChartDataPoint, formatChartDataLabel, formatChartSeries, setChartHelperLineT, setChartColorMapOverrideT
Style & theme5updateElementStyle, applyThemePreset, updateThemeColors, updateThemeFonts, getThemeInfo
Layout & template4applyLayout, getLayouts, applyTemplateT, findPlaceholdersT
Content5findText, replaceText, manageComments, manageHyperlinks, manageSmartArt
Sections1manageSections
Metadata & properties4getMetadata, updateMetadata, getPresentationProperties, updatePresentationProperties
Validation & repair3runAccessibilityCheck, validatePresentation, repairPresentation
Conversion & export5convertToMarkdown, exportToSvg, exportSlideSvg, exportToJson, importFromJson

The package additionally exports mergePresentationT and diffPresentationsT for merge/diff workflows; those two are plain functions and are not registered on the MCP server.

Tool contract ​

Tools share a uniform shape defined in the package's types:

ts
interface ToolContext {
	/** Current presentation data (parsed PptxData from pptx-viewer-core). */
	pptxData: PptxData;
	/** Optional: resolve external image paths to binary data. */
	resolveImage?: (path: string) => Promise<Uint8Array>;
	/** Optional: resolve external media paths to binary data. */
	resolveMedia?: (path: string) => Promise<Uint8Array>;
}

interface ToolResult<T = unknown> {
	/** The (potentially mutated) presentation data. */
	pptxData: PptxData;
	/** Tool-specific return value (slide info, element id, search results, …). */
	result: T;
	/** Whether pptxData was modified - signals the consumer to save. */
	dirty: boolean;
}

Usage modes ​

There are three ways to use the package, from lowest-level to fully managed.

1. Direct tool functions ​

Pair a tool with PptxHandler from the core engine for load/save. You own the lifecycle:

ts
import fs from 'node:fs/promises';

import { PptxHandler } from 'pptx-viewer-core';
import { addSlide, getSlide } from 'pptx-viewer-mcp';

// Load (slice: a Node Buffer's .buffer can be a larger pooled ArrayBuffer)
const handler = new PptxHandler();
const bytes = await fs.readFile('deck.pptx');
const pptxData = await handler.load(
	bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength),
);

// Run a tool
const ctx = { pptxData };
const { pptxData: updated, dirty } = addSlide(ctx, { insertAfterIndex: 0 });

// Save when dirty
if (dirty) {
	const out = await handler.save(updated.slides);
	await fs.writeFile('deck.pptx', out);
}

// Inspect a slide
const { result } = getSlide(ctx, { slideIndex: 0 });
console.log(result.elements);

2. The MCP server ​

Run the bundled MCP server so an MCP client (Claude Desktop, Cursor, etc.) can call the tools over stdio. Add it to your client configuration:

json
{
	"mcpServers": {
		"pptx": {
			"command": "npx",
			"args": ["pptx-viewer-mcp"]
		}
	}
}

The binary is also installed as pptx-tools after a global install (npm i -g pptx-viewer-mcp). All 67 tools are exposed over stdio under their snake_case names - e.g. get_slide, add_slide, batch_update_elements, convert_to_markdown. Each MCP tool accepts a filePath parameter; the server handles loading and saving internally.

To embed the server programmatically rather than via the CLI, use the factory from the /mcp entry point:

ts
import { createServer } from 'pptx-viewer-mcp/mcp';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = createServer(); // McpServer with all 67 tools registered
await server.connect(new StdioServerTransport());

3. executeToolWithContext with providers ​

executeToolWithContext wraps the load → tool → save cycle and, when a live collaboration room is available, routes the change through a Y.Doc instead of (or in addition to) disk:

ts
import {
	executeToolWithContext,
	replaceText,
	type ExecutionContext,
	type FileSystemProvider,
	type CollaborationProvider,
} from 'pptx-viewer-mcp';
import { readFile, writeFile } from 'node:fs/promises';

// Minimal file-system-only provider
const filesystem: FileSystemProvider = {
	readFile: (p) => readFile(p),
	writeFile: (p, data) => writeFile(p, data),
};

const execCtx: ExecutionContext = { filesystem };

const result = await executeToolWithContext('deck.pptx', execCtx, (ctx) =>
	replaceText(ctx, { find: 'Draft', replace: 'Final', caseSensitive: false }),
);

console.log(result.replacements, result.savedToDisk);

When execCtx.collaboration is set, the pipeline dehydrates the current Y.Doc state before running the tool (so unsaved edits are part of the tool's view) and re-hydrates the room after saving (so remote peers receive the change without a file reload).

Provider interfaces ​

CollaborationProvider, FileSystemProvider, and ViewerProvider are plain interfaces - implement them for any runtime (Node, Electron, browser, edge worker). The ExecutionContext bundles them together:

InterfaceResponsibility
FileSystemProviderreadFile(path) / writeFile(path, data) - where bytes come from and go.
CollaborationProviderResolve the active Y.Doc room and codec for a file path (getRoom, getCodec), plus agentOrigin(name) for undo isolation.
ViewerProviderPush live updates into an open viewer (replaceContent, openFile) - used by Electron integrations.
ExecutionContext{ filesystem, collaboration?, viewer?, agentName? } - passed to executeToolWithContext.

Architecture ​

ToolContext { pptxData }
        │
        ▼
tool function  →  ToolResult { pptxData, result, dirty }
                        │
                        ▼  (when dirty)
              savePresentation()
                ├─ collaboration room → hydrate Y.Doc → broadcast
                └─ no room → writeFile to disk

A tool only reports dirty: true when it actually mutates the data, so savePresentation() runs solely when there is something to persist. Saving then branches on whether a collaboration room exists for the file.

The PptxCodec collaboration codec ​

The /codec entry point exports PptxCodec, a bidirectional Y.Doc ↔ PPTX-bytes codec implementing the FormatCodec interface. It is what makes real-time collaborative editing of a deck possible: PPTX bytes hydrate into Yjs shared types, edits propagate through the CRDT, and the document dehydrates back to valid PPTX bytes.

ts
import { PptxCodec, ORIGIN_FILE_LOAD } from 'pptx-viewer-mcp/codec';
import { Doc as YDoc } from 'yjs';

const codec = new PptxCodec();
const ydoc = new YDoc();

// bytes → Y.Doc shared types
await codec.hydrate(ydoc, pptxBytes, ORIGIN_FILE_LOAD);

// …collaborative edits happen on ydoc…

// Y.Doc shared types → bytes
const out = await codec.dehydrate(ydoc); // Uint8Array (valid .pptx)

The FormatCodec contract it implements:

ts
interface FormatCodec {
	readonly formatId: string; // 'pptx'
	readonly extensions: string[]; // ['.pptx', '.ppt']
	hydrate(ydoc: YDoc, bytes: Uint8Array, origin?: string): Promise<void>;
	dehydrate(ydoc: YDoc, dirtyPaths?: string[]): Promise<Uint8Array>;
	observe(ydoc: YDoc, onChange: () => void): () => void;
}

The exported ORIGIN_FILE_LOAD constant tags the initial hydration so it can be distinguished from user edits in Yjs undo/origin handling.

See also ​

Released under the Apache-2.0 License.