Skip to main content
Glama
satheeshds

Google Business Profile Review MCP Server

by satheeshds
mcpServer.ts16.9 kB
/** * Main MCP Server implementation for Google Business Profile Review management */ import { McpServer as BaseMcpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { getConfig } from '../utils/config.js'; import { logger } from '../utils/logger.js'; import { GoogleAuthService } from '../services/googleAuth.js'; import { ReviewService } from '../services/reviewService.js'; import { MockReviewService } from '../services/mockReviewService.js'; import { LLMService } from '../services/llmService.js'; import type { IReviewService } from '../types/index.js'; // Import tool implementations import { createListLocationsTool } from './tools/listLocations.js'; import { createGetUnRepliedReviewsTool } from './tools/getReviews.js'; import { createGenerateReplyTool } from './tools/generateReply.js'; import { createPostReplyTool } from './tools/postReply.js'; import { createGetReviewDayStatsTool } from './tools/getReviewDayStats.js'; // Import resource implementations import { createBusinessProfileResource } from './resources/businessProfile.js'; import { createReviewTemplatesResource } from './resources/reviewTemplates.js'; import { createLocationsResource } from './resources/locations.js'; import { createReviewsResource } from './resources/reviews.js'; // Import prompt implementations import { createReviewResponsePrompt } from './prompts/reviewResponse.js'; import { createSentimentAnalysisPrompt } from './prompts/sentimentAnalysis.js'; import { createManageReviewsPrompt } from './prompts/manageReviews.js'; import { createAnalyzeReviewStatsPrompt } from './prompts/analyzeReviewStats.js'; import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; import { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js'; export class McpServer { private config = getConfig(); private server: BaseMcpServer; // Services private googleAuthService?: GoogleAuthService; private reviewService: IReviewService; private llmService: LLMService; private isMockMode: boolean; constructor() { // Check if we're in mock mode this.isMockMode = process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development' || process.env.ENABLE_MOCK_MODE === 'true'; // Initialize MCP server this.server = new BaseMcpServer( { name: this.config.mcpServerName, version: this.config.mcpServerVersion }, { capabilities: { tools: {}, resources: {}, prompts: {}, logging: {} } } ); // Initialize services based on mode if (this.isMockMode) { logger.info('🧪 Starting in MOCK MODE - No Google API required'); this.reviewService = new MockReviewService(); } else { logger.info('🚀 Starting in PRODUCTION MODE - Google API required'); this.googleAuthService = new GoogleAuthService(); this.reviewService = new ReviewService(this.googleAuthService); } // Initialize LLM service // Note: Sampling capability will be added when MCP SDK supports it directly this.llmService = new LLMService(); this.setupServer(); } private setupServer(): void { logger.info('Setting up MCP server tools, resources, and prompts...'); // Register tools this.registerTools(); // Register resources this.registerResources(); // Register prompts this.registerPrompts(); logger.info('MCP server setup completed'); } private registerTools(): void { logger.debug('Registering MCP tools...'); // List locations tool const listLocationsTool = createListLocationsTool(this.reviewService); this.server.registerTool( 'list_locations', { title: listLocationsTool.schema.title, description: listLocationsTool.schema.description, inputSchema: {}, outputSchema: listLocationsTool.schema.outputSchema }, async (args: any) => { return await listLocationsTool.handler(args); } ); // Get reviews tool const getUnrepliedReviewsTool = createGetUnRepliedReviewsTool(this.reviewService); this.server.registerTool( 'get_unreplied_reviews', { title: getUnrepliedReviewsTool.schema.title, description: getUnrepliedReviewsTool.schema.description, inputSchema: getUnrepliedReviewsTool.schema.inputSchema, outputSchema: getUnrepliedReviewsTool.schema.outputSchema }, async (args: any) => { return await getUnrepliedReviewsTool.handler(args); } ); // Generate reply tool const generateReplyTool = createGenerateReplyTool(this.llmService); this.server.registerTool( 'generate_reply', { title: generateReplyTool.schema.title, description: generateReplyTool.schema.description, inputSchema: generateReplyTool.schema.inputSchema, outputSchema: generateReplyTool.schema.outputSchema }, async (args: any, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => { return await generateReplyTool.handler(args, extra); } ); // Post reply tool const postReplyTool = createPostReplyTool(this.reviewService); this.server.registerTool( 'post_reply', { title: postReplyTool.schema.title, description: postReplyTool.schema.description, inputSchema: postReplyTool.schema.inputSchema, outputSchema: postReplyTool.schema.outputSchema }, async (args: any) => { return await postReplyTool.handler(args); } ); // Get review day stats tool const getReviewDayStatsTool = createGetReviewDayStatsTool(this.reviewService); this.server.registerTool( 'get_review_day_stats', { title: getReviewDayStatsTool.schema.title, description: getReviewDayStatsTool.schema.description, inputSchema: getReviewDayStatsTool.schema.inputSchema, outputSchema: getReviewDayStatsTool.schema.outputSchema }, async (args: any) => { return await getReviewDayStatsTool.handler(args); } ); logger.debug('Tools registered successfully'); } private registerResources(): void { logger.debug('Registering MCP resources...'); // Business profile resource const businessProfileResource = createBusinessProfileResource(this.reviewService); this.server.registerResource( 'business_profile', businessProfileResource.uri, { description: businessProfileResource.description, mimeType: businessProfileResource.mimeType }, async () => { const result = await businessProfileResource.handler(); return { contents: result.contents }; } ); // Review templates resource const reviewTemplatesResource = createReviewTemplatesResource(); this.server.registerResource( 'review_templates', reviewTemplatesResource.uri, { description: reviewTemplatesResource.description, mimeType: reviewTemplatesResource.mimeType }, async () => { const result = await reviewTemplatesResource.handler(); return { contents: result.contents }; } ); // Locations resource const locationsResource = createLocationsResource(this.reviewService); this.server.registerResource( 'locations', locationsResource.uri, { description: locationsResource.description, mimeType: locationsResource.mimeType }, async () => { const result = await locationsResource.handler(); return { contents: result.contents }; } ); // Reviews resource with dynamic location support const reviewsResource = createReviewsResource(this.reviewService); // Register template for per-location reviews: reviews://{locationId} this.server.registerResource( 'reviews_by_location', 'reviews://{locationId}', { description: 'Reviews for a specific business location. Use the location ID (e.g., locations/123456789)', mimeType: reviewsResource.mimeType }, async (uri: URL) => { const result = await reviewsResource.handler(uri.href); return { contents: result.contents }; } ); // Also register reviews://all for convenience this.server.registerResource( 'reviews_all', 'reviews://all', { description: 'All unreplied reviews across all business locations', mimeType: reviewsResource.mimeType }, async () => { const result = await reviewsResource.handler('reviews://all'); return { contents: result.contents }; } ); logger.debug('Resources registered successfully'); } private registerPrompts(): void { logger.debug('Registering MCP prompts...'); // Review response prompt const reviewResponsePrompt = createReviewResponsePrompt(); this.server.registerPrompt( 'review_response', { title: 'Review Response Generator', description: reviewResponsePrompt.description, argsSchema: { reviewText: z.string().describe('The customer review text'), starRating: z.string().describe('Star rating (1-5)'), businessName: z.string().describe('Name of the business'), businessType: z.string().optional().describe('Type of business (restaurant, retail, etc.)'), customerName: z.string().optional().describe('Customer display name'), replyTone: z.string().describe('Desired tone for the reply'), previousReplies: z.string().optional().describe('JSON array of previous replies for consistency') } }, async (args: any) => { const context = { reviewText: args.reviewText, starRating: parseInt(args.starRating), businessName: args.businessName, businessType: args.businessType, customerName: args.customerName, replyTone: args.replyTone, previousReplies: args.previousReplies ? JSON.parse(args.previousReplies) : [] }; const prompt = await reviewResponsePrompt.handler(context); return { description: `Review response prompt for ${args.businessName}`, messages: [ { role: 'user', content: { type: 'text', text: prompt } } ] }; } ); // Sentiment analysis prompt const sentimentAnalysisPrompt = createSentimentAnalysisPrompt(); this.server.registerPrompt( 'sentiment_analysis', { title: 'Review Sentiment Analysis', description: sentimentAnalysisPrompt.description, argsSchema: { reviewText: z.string().describe('The review text to analyze'), includeEmotions: z.string().optional().describe('Include emotional analysis (true/false)'), includeKeywords: z.string().optional().describe('Include keyword extraction (true/false)') } }, async (args: any) => { const context = { reviewText: args.reviewText, includeEmotions: args.includeEmotions !== 'false', includeKeywords: args.includeKeywords !== 'false' }; const prompt = await sentimentAnalysisPrompt.handler(context); return { description: `Sentiment analysis for review: "${args.reviewText.substring(0, 50)}..."`, messages: [ { role: 'user', content: { type: 'text', text: prompt } } ] }; } ); // Manage pending reviews prompt const manageReviewsPrompt = createManageReviewsPrompt(); this.server.registerPrompt( 'manage_pending_reviews', { title: 'Manage Pending Reviews', description: manageReviewsPrompt.description, argsSchema: { // locationName: z.string().optional().describe('Specific location to check (optional)') } }, async (args: any) => { const prompt = await manageReviewsPrompt.handler(); return { description: 'Instructions to manage pending reviews using MCP resources', messages: [ { role: 'user', content: { type: 'text', text: prompt } } ] }; } ); // Analyze review statistics prompt const analyzeReviewStatsPrompt = createAnalyzeReviewStatsPrompt(); this.server.registerPrompt( 'analyze_review_stats', { title: 'Analyze Review Statistics', description: analyzeReviewStatsPrompt.description, argsSchema: {} }, async (args: any) => { const prompt = await analyzeReviewStatsPrompt.handler(); return { description: 'Instructions to analyze review statistics with comprehensive insights', messages: [ { role: 'user', content: { type: 'text', text: prompt } } ] }; } ); logger.debug('Prompts registered successfully'); } async start(): Promise<void> { logger.info('Starting MCP server with STDIO transport...'); const transport = new StdioServerTransport(); await this.server.connect(transport); logger.info(`MCP server started with STDIO transport`); // Keep the process alive for STDIO transport // The transport will handle stdin/stdout communication return new Promise<void>(() => { // This promise never resolves, keeping the process alive // The process will only exit via signal handlers or errors }); } async stop(): Promise<void> { logger.info('Stopping MCP server...'); logger.info('MCP server stopped'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/satheeshds/gbp-review-agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server