UNPKG

@bohachu/google-slides-mcp

Version:

MCP server for Google Slides integration with service account authentication

362 lines (361 loc) 17.3 kB
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, }; } }); };