#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Create server
const server = new Server(
{
name: "reviewreply-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "generate_review_reply",
description:
"Generate professional responses to customer reviews. Returns 3 variations of professionally crafted replies suitable for Google, Yelp, or similar review platforms.",
inputSchema: {
type: "object",
properties: {
review_text: {
type: "string",
description: "The customer review text to respond to",
},
tone: {
type: "string",
description:
"Tone of the response: professional, friendly, empathetic, or grateful",
enum: ["professional", "friendly", "empathetic", "grateful"],
},
business_name: {
type: "string",
description:
"Name or type of business (e.g., 'Italian Restaurant', 'Auto Repair Shop')",
},
stars: {
type: "number",
description: "Star rating of the review (1-5). Defaults to 4 if not provided.",
minimum: 1,
maximum: 5,
},
},
required: ["review_text", "tone", "business_name"],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name !== "generate_review_reply") {
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
const { review_text, tone, business_name, stars = 4 } = args;
if (!review_text || !tone || !business_name) {
return {
content: [
{
type: "text",
text: "Missing required fields: review_text, tone, and business_name are required",
},
],
isError: true,
};
}
try {
const systemPrompt = `You are an expert at writing business review responses. You help local businesses respond to customer reviews on Google, Yelp, and similar platforms.
Your responses should:
- Be genuine, warm, and human-sounding (never robotic or templated)
- Address specific points mentioned in the review
- Be appropriately concise (2-4 sentences for positive reviews, 3-5 for negative)
- Never be defensive or dismissive
- Show gratitude for feedback
- For negative reviews: acknowledge the issue, apologize sincerely, offer to make it right
- For positive reviews: express genuine thanks, reinforce what they enjoyed
- Match the business type context (${business_name})
- NEVER include placeholder text like [Name] or [Business Name] — write as if the business owner is responding directly`;
const userPrompt = `Write 3 different response options for this ${stars}-star review. Each response should have a slightly different approach while maintaining a ${tone.toLowerCase()} tone.
Review (${stars}/5 stars):
"${review_text}"
Business type: ${business_name}
Desired tone: ${tone}
Return exactly 3 responses in this JSON format:
[
{"tone": "${tone}", "response": "..."},
{"tone": "${tone} (variation)", "response": "..."},
{"tone": "${tone} (alternative)", "response": "..."}
]
Return ONLY the JSON array, no other text.`;
const message = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: userPrompt }],
system: systemPrompt,
});
const textContent = message.content.find((c) => c.type === "text");
if (!textContent || textContent.type !== "text") {
throw new Error("No text response from AI");
}
// Parse the JSON response
let responses;
const responseText = textContent.text.trim();
try {
responses = JSON.parse(responseText);
} catch {
const jsonMatch = responseText.match(/\[[\s\S]*\]/);
if (jsonMatch) {
responses = JSON.parse(jsonMatch[0]);
} else {
throw new Error("Could not parse AI response");
}
}
if (!Array.isArray(responses) || responses.length === 0) {
throw new Error("Invalid response format");
}
// Format output nicely
const output = responses
.map(
(r, i) =>
`**Option ${i + 1}** (${r.tone}):\n${r.response}`
)
.join("\n\n---\n\n");
return {
content: [
{
type: "text",
text: output,
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
return {
content: [
{
type: "text",
text: `Failed to generate review response: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Run server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("ReviewReply MCP server running on stdio");
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});