@bohachu/google-slides-mcp
Version:
MCP server for Google Slides integration with service account authentication
362 lines (361 loc) • 17.3 kB
JavaScript
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { CreatePresentationArgsSchema, GetPresentationArgsSchema, BatchUpdatePresentationArgsSchema, GetPageArgsSchema, SummarizePresentationArgsSchema, movePresentationSchema, GetSlidesArgsSchema, GetSlidePreviewArgsSchema, DuplicatePresentationArgsSchema, GetSlideTextElementsArgsSchema, ReplaceTextByElementArgsSchema, AnalyzeTemplateStructureArgsSchema, } from './schemas.js';
import { createPresentationTool } from './tools/createPresentation.js';
import { getPresentationTool } from './tools/getPresentation.js';
import { batchUpdatePresentationTool } from './tools/batchUpdatePresentation.js';
import { getPageTool } from './tools/getPage.js';
import { summarizePresentationTool } from './tools/summarizePresentation.js';
import { MovePresentationTool } from './tools/movePresentation.js';
import { getSlidesTool } from './tools/getSlides.js';
import { getSlidePreviewTool } from './tools/getSlidePreview.js';
import { DuplicatePresentationTool } from './tools/duplicatePresentation.js';
import { getSlideTextElementsTool } from './tools/getSlideTextElements.js';
import { replaceTextByElementTool } from './tools/replaceTextByElement.js';
import { analyzeTemplateStructureTool } from './tools/analyzeTemplateStructure.js';
import { executeTool } from './utils/toolExecutor.js';
export const setupToolHandlers = (server, slides, auth) => {
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_presentation',
description: 'Create a new Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'The title of the presentation.',
},
},
required: ['title'],
},
},
{
name: 'get_presentation',
description: 'Get details about a Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to retrieve.',
},
fields: {
type: 'string',
description: 'Optional. A mask specifying which fields to include in the response (e.g., "slides,pageSize").',
},
},
required: ['presentationId'],
},
},
{
name: 'batch_update_presentation',
description: 'Apply a batch of updates to a Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to update.',
},
requests: {
type: 'array',
description: 'A list of update requests to apply. See Google Slides API documentation for request structures.',
items: { type: 'object' },
},
writeControl: {
type: 'object',
description: 'Optional. Provides control over how write requests are executed.',
properties: {
requiredRevisionId: { type: 'string' },
targetRevisionId: { type: 'string' },
},
},
},
required: ['presentationId', 'requests'],
},
},
{
name: 'get_page',
description: 'Get details about a specific page (slide) in a presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation.',
},
pageObjectId: {
type: 'string',
description: 'The object ID of the page (slide) to retrieve.',
},
},
required: ['presentationId', 'pageObjectId'],
},
},
{
name: 'summarize_presentation',
description: 'Extract text content from all slides in a presentation for summarization purposes',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to summarize.',
},
include_notes: {
type: 'boolean',
description: 'Optional. Whether to include speaker notes in the summary (default: false).',
},
},
required: ['presentationId'],
},
},
{
name: 'move_presentation',
description: 'Move or copy a presentation to a specific Google Drive folder',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to move.',
},
folderId: {
type: 'string',
description: 'The ID of the target Google Drive folder.',
},
copyInstead: {
type: 'boolean',
description: 'If true, creates a copy in the target folder instead of moving (default: false).',
},
newName: {
type: 'string',
description: 'New name for the presentation (only used when copyInstead is true).',
},
},
required: ['presentationId', 'folderId'],
},
},
{
name: 'get_slides',
description: 'Get a range of slides from a presentation with pagination support',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation.',
},
startIndex: {
type: 'number',
description: 'The starting slide index (0-based, inclusive). Default: 0',
},
endIndex: {
type: 'number',
description: 'The ending slide index (0-based, exclusive). Default: all slides',
},
},
required: ['presentationId'],
},
},
{
name: 'get_slide_preview',
description: 'Get a visual preview (thumbnail) of a specific slide',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation.',
},
slideId: {
type: 'string',
description: 'The object ID of the slide to preview.',
},
mimeType: {
type: 'string',
enum: ['PNG', 'JPEG'],
description: 'The MIME type of the thumbnail image. Default: PNG',
},
},
required: ['presentationId', 'slideId'],
},
},
{
name: 'duplicate_presentation',
description: 'Create a complete copy of an existing presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to duplicate.',
},
newTitle: {
type: 'string',
description: 'The title for the new presentation copy.',
},
},
required: ['presentationId', 'newTitle'],
},
},
{
name: 'get_slide_text_elements',
description: 'Get all text elements from a slide with their IDs, positions, and styles',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation.',
},
slideId: {
type: 'string',
description: 'The object ID of the slide.',
},
includeStyles: {
type: 'boolean',
description: 'Include text formatting information. Default: true',
},
includePosition: {
type: 'boolean',
description: 'Include element position/layout info. Default: true',
},
},
required: ['presentationId', 'slideId'],
},
},
{
name: 'replace_text_by_element',
description: 'Replace text in specific elements while preserving formatting',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation.',
},
replacements: {
type: 'array',
description: 'Array of replacement operations',
items: {
type: 'object',
properties: {
elementId: {
type: 'string',
description: 'The object ID of the element containing text',
},
newText: {
type: 'string',
description: 'The new text to insert',
},
preserveStyle: {
type: 'boolean',
description: 'Preserve original text style. Default: true',
},
},
required: ['elementId', 'newText'],
},
},
},
required: ['presentationId', 'replacements'],
},
},
{
name: 'analyze_template_structure',
description: 'Analyze template structure to understand text elements and suggest content mapping',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the presentation to analyze.',
},
slideIndices: {
type: 'array',
items: { type: 'number' },
description: 'Specific slide indices to analyze. If not provided, analyzes all slides.',
},
},
required: ['presentationId'],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'create_presentation':
return executeTool(slides, name, args, CreatePresentationArgsSchema, createPresentationTool);
case 'get_presentation':
return executeTool(slides, name, args, GetPresentationArgsSchema, getPresentationTool);
case 'batch_update_presentation':
return executeTool(slides, name, args, BatchUpdatePresentationArgsSchema, batchUpdatePresentationTool);
case 'get_page':
return executeTool(slides, name, args, GetPageArgsSchema, getPageTool);
case 'summarize_presentation':
return executeTool(slides, name, args, SummarizePresentationArgsSchema, summarizePresentationTool);
case 'move_presentation': {
if (!auth) {
return {
content: [{ type: 'text', text: 'Authentication object not available for move operation' }],
isError: true,
};
}
const moveTool = new MovePresentationTool(slides, auth);
try {
const validatedArgs = movePresentationSchema.parse(args);
const result = await moveTool.execute(validatedArgs);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
case 'get_slides':
return executeTool(slides, name, args, GetSlidesArgsSchema, getSlidesTool);
case 'get_slide_preview':
return executeTool(slides, name, args, GetSlidePreviewArgsSchema, getSlidePreviewTool);
case 'duplicate_presentation': {
if (!auth) {
return {
content: [{ type: 'text', text: 'Authentication object not available for duplicate operation' }],
isError: true,
};
}
const duplicateTool = new DuplicatePresentationTool(slides, auth);
try {
const validatedArgs = DuplicatePresentationArgsSchema.parse(args);
const result = await duplicateTool.execute(validatedArgs);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
}
case 'get_slide_text_elements':
return executeTool(slides, name, args, GetSlideTextElementsArgsSchema, getSlideTextElementsTool);
case 'replace_text_by_element':
return executeTool(slides, name, args, ReplaceTextByElementArgsSchema, replaceTextByElementTool);
case 'analyze_template_structure':
return executeTool(slides, name, args, AnalyzeTemplateStructureArgsSchema, analyzeTemplateStructureTool);
default:
return {
content: [{ type: 'text', text: `Unknown tool requested: ${name}` }],
isError: true,
errorCode: ErrorCode.MethodNotFound,
};
}
});
};