index.ts•9.59 kB
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ToolSchema,
CallToolResult,
} from '@modelcontextprotocol/sdk/types.js';
import * as dotenv from 'dotenv';
import { GoogleSlidesAuth, GoogleSlidesConfig } from './auth.js';
import { GoogleSlidesService } from './slides.js';
// Load environment variables
dotenv.config();
interface McpError extends Error {
code?: string;
}
class GoogleSlidesMCPServer {
private server: Server;
private auth: GoogleSlidesAuth;
private slidesService: GoogleSlidesService;
constructor() {
this.server = new Server(
{
name: 'google-slides-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Initialize Google Slides authentication
const config: GoogleSlidesConfig = {
clientId: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:3000/oauth2callback',
};
this.auth = new GoogleSlidesAuth(config);
this.slidesService = new GoogleSlidesService(this.auth);
this.setupHandlers();
}
private setupHandlers(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'create_slide',
description: 'Create a new slide in a Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the Google Slides presentation',
},
insertionIndex: {
type: 'number',
description: 'Position where to insert the slide (optional, defaults to 0)',
},
},
required: ['presentationId'],
},
},
{
name: 'add_rectangle',
description: 'Add a rectangle to a slide with 20% of slide dimensions',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the Google Slides presentation',
},
slideId: {
type: 'string',
description: 'The ID of the slide to add the rectangle to',
},
x: {
type: 'number',
description: 'X position of the rectangle (optional, defaults to center)',
},
y: {
type: 'number',
description: 'Y position of the rectangle (optional, defaults to center)',
},
width: {
type: 'number',
description: 'Width of the rectangle (optional, defaults to 20% of slide width)',
},
height: {
type: 'number',
description: 'Height of the rectangle (optional, defaults to 20% of slide height)',
},
},
required: ['presentationId', 'slideId'],
},
},
{
name: 'get_presentation_info',
description: 'Get information about a Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the Google Slides presentation',
},
},
required: ['presentationId'],
},
},
{
name: 'list_slides',
description: 'List all slides in a Google Slides presentation',
inputSchema: {
type: 'object',
properties: {
presentationId: {
type: 'string',
description: 'The ID of the Google Slides presentation',
},
},
required: ['presentationId'],
},
},
{
name: 'get_auth_url',
description: 'Get the OAuth2 authorization URL for Google Slides access',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'authenticate',
description: 'Complete OAuth2 authentication with authorization code',
inputSchema: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'Authorization code from OAuth2 flow',
},
},
required: ['code'],
},
},
],
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'get_auth_url':
return await this.handleGetAuthUrl();
case 'authenticate':
return await this.handleAuthenticate(args as { code: string });
case 'create_slide':
return await this.handleCreateSlide(args as {
presentationId: string;
insertionIndex?: number;
});
case 'add_rectangle':
return await this.handleAddRectangle(args as {
presentationId: string;
slideId: string;
x?: number;
y?: number;
width?: number;
height?: number;
});
case 'get_presentation_info':
return await this.handleGetPresentationInfo(args as {
presentationId: string;
});
case 'list_slides':
return await this.handleListSlides(args as {
presentationId: string;
});
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const mcpError = error as McpError;
return {
content: [
{
type: 'text',
text: `Error: ${mcpError.message}`,
},
],
isError: true,
};
}
});
}
private async handleGetAuthUrl(): Promise<CallToolResult> {
const authUrl = this.auth.getAuthUrl();
return {
content: [
{
type: 'text',
text: `Please visit this URL to authorize the application:\n${authUrl}`,
},
],
};
}
private async handleAuthenticate(args: { code: string }): Promise<CallToolResult> {
await this.auth.getTokens(args.code);
return {
content: [
{
type: 'text',
text: 'Authentication successful! You can now use Google Slides tools.',
},
],
};
}
private async handleCreateSlide(args: {
presentationId: string;
insertionIndex?: number;
}): Promise<CallToolResult> {
const slideInfo = await this.slidesService.createSlide(
args.presentationId,
args.insertionIndex
);
return {
content: [
{
type: 'text',
text: `Successfully created new slide with ID: ${slideInfo.slideId}`,
},
],
};
}
private async handleAddRectangle(args: {
presentationId: string;
slideId: string;
x?: number;
y?: number;
width?: number;
height?: number;
}): Promise<CallToolResult> {
const rectangleId = await this.slidesService.addRectangle(args.presentationId, {
slideId: args.slideId,
x: args.x,
y: args.y,
width: args.width,
height: args.height,
});
return {
content: [
{
type: 'text',
text: `Successfully added rectangle with ID: ${rectangleId}. Rectangle dimensions are 20% of slide size.`,
},
],
};
}
private async handleGetPresentationInfo(args: {
presentationId: string;
}): Promise<CallToolResult> {
const info = await this.slidesService.getPresentationInfo(args.presentationId);
return {
content: [
{
type: 'text',
text: `Presentation: ${info.title}\nSlide count: ${info.slideCount}\nPage size: ${info.pageSize.width.magnitude} x ${info.pageSize.height.magnitude} ${info.pageSize.width.unit}`,
},
],
};
}
private async handleListSlides(args: {
presentationId: string;
}): Promise<CallToolResult> {
const slides = await this.slidesService.listSlides(args.presentationId);
const slidesList = slides
.map((slide) => `- ${slide.title} (ID: ${slide.slideId})`)
.join('\n');
return {
content: [
{
type: 'text',
text: `Slides in presentation:\n${slidesList}`,
},
],
};
}
async run(): Promise<void> {
// Try to load existing tokens
await this.auth.loadTokens();
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
// Start the server
const server = new GoogleSlidesMCPServer();
server.run().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});