#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { GamExecutor } from './gam-executor.js';
import { allTools } from './tools/index.js';
/**
* GAM MCP Server
* Provides Google Workspace administration capabilities via GAM CLI
*/
async function main() {
// Initialize GAM executor with path from environment or default
const gamPath = process.env['GAM_PATH'] || 'gam';
const gamExecutor = new GamExecutor(gamPath);
// Validate GAM setup before starting
console.log('[MCP] Validating GAM installation...');
const isInstalled = await gamExecutor.checkGamInstallation();
if (!isInstalled) {
console.error('[MCP] GAM is not properly installed or configured');
console.error('[MCP] Please ensure GAM is installed and authenticated');
console.error('[MCP] Run "gam oauth create" to set up authentication');
process.exit(1);
}
console.log('[MCP] GAM installation validated successfully');
// Create MCP server
const server = new Server(
{
name: 'gam-mcp-server',
version: '1.0.0',
description: 'Google Workspace administration via GAM CLI - MCP Server for Claude'
},
{
capabilities: {
tools: {}
}
}
);
// Set up tools list handler
server.setRequestHandler(
z.object({ method: z.literal('tools/list') }),
async () => {
const tools = allTools.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}));
return { tools };
}
);
// Set up tool call handler
server.setRequestHandler(
z.object({ method: z.literal('tools/call'), params: z.object({ name: z.string(), arguments: z.any() }) }),
async (request) => {
const toolName = request.params.name;
const tool = allTools.find(t => t.name === toolName);
if (!tool) {
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: `Unknown tool: ${toolName}`,
data: null
}, null, 2)
}
],
isError: true
};
}
try {
// Validate input parameters
const args = request.params.arguments ?? {};
const validatedParams = tool.inputSchema.parse(args) as any;
// Execute tool handler
const result = await tool.handler(validatedParams);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
console.error(`[MCP] Tool ${toolName} error:`, error);
// Handle validation errors specifically
let errorMessage = 'Unknown error occurred';
if (error instanceof Error) {
errorMessage = error.message;
} else if (error && typeof error === 'object' && 'issues' in error) {
// Zod validation error
const zodError = error as { issues: Array<{ path: string[]; message: string }> };
errorMessage = `Validation error: ${zodError.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
).join(', ')}`;
}
return {
content: [
{
type: 'text',
text: JSON.stringify({
success: false,
error: errorMessage,
data: null
}, null, 2)
}
],
isError: true
};
}
}
);
// Set up error handling
server.onerror = (error: Error) => {
console.error('[MCP] Server error:', error);
};
process.on('SIGINT', () => {
console.log('[MCP] Received SIGINT, shutting down gracefully...');
server.close().then(() => {
console.log('[MCP] Server shutdown complete');
process.exit(0);
});
});
process.on('SIGTERM', () => {
console.log('[MCP] Received SIGTERM, shutting down gracefully...');
server.close().then(() => {
console.log('[MCP] Server shutdown complete');
process.exit(0);
});
});
try {
// Create transport and start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.log('[MCP] GAM MCP Server started successfully');
console.log('[MCP] Available tools:', allTools.map(t => t.name).join(', '));
} catch (error) {
console.error('[MCP] Failed to start server:', error);
process.exit(1);
}
}
// Start the server
main().catch((error) => {
console.error('[MCP] Failed to start server:', error);
process.exit(1);
});