@rhofkens/mcp-quotes-server
Version:
A Model Context Protocol (MCP) server that provides quotes based on user requests
237 lines • 9.13 kB
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { SerperService } from "./services/serper-service.js";
import { SerperApiError, SerperConfigurationError, } from "./types/serper-errors.js";
import { logger, logError } from "./utils/logger.js";
import { getPromptTemplateContent } from "./resources/prompt-template-content.js";
import { validateQuoteParameters } from "./utils/parameter-validator.js";
import { HttpTransportService } from "./services/http-transport-service.js";
import { parseEnvironmentConfig, getHttpServerConfig, } from "./config/environment-config.js";
export function createServer() {
return new McpServer({
name: "mcp-quotes-server",
version: "1.0.0",
});
}
export function registerTools(server) {
server.registerTool("get_quote", {
title: "Get Quote",
description: "Retrieves one or more quotes from a specified person or topic using Serper.dev API. Supports retrieving 1-10 quotes per request.",
inputSchema: {
person: z
.string()
.describe("The name of the person to get a quote from"),
topic: z
.string()
.optional()
.describe("Optional topic to filter quotes by"),
numberOfQuotes: z
.number()
.int()
.min(1)
.max(10)
.describe("Number of quotes to retrieve (1-10)")
.default(1),
},
}, async ({ person, topic, numberOfQuotes = 1 }) => {
try {
const validationResult = validateQuoteParameters({
person,
topic,
numberOfQuotes,
});
if (!validationResult.success) {
logger.warn("Parameter validation failed for get_quote tool", {
errors: validationResult.errors,
rawParams: { person, topic, numberOfQuotes },
});
return {
content: [
{
type: "text",
text: `Validation Error: ${validationResult.errors.join(", ")}`,
},
],
isError: true,
};
}
const apiKey = process.env.SERPER_API_KEY;
if (!apiKey) {
logger.error("SERPER_API_KEY environment variable not set");
return {
content: [
{
type: "text",
text: "Error: SERPER_API_KEY environment variable is not configured. Please set your Serper.dev API key.",
},
],
isError: true,
};
}
const { person: validatedPerson, topic: validatedTopic, numberOfQuotes: validatedNumberOfQuotes, } = validationResult.data;
const serperService = new SerperService(apiKey);
const quote = await serperService.getQuote({ person: validatedPerson, topic: validatedTopic }, validatedNumberOfQuotes);
logger.info("Quote(s) retrieved successfully", {
person: validatedPerson,
topic: validatedTopic,
numberOfQuotes: validatedNumberOfQuotes,
quoteLength: quote.length,
});
return {
content: [
{
type: "text",
text: quote,
},
],
};
}
catch (error) {
if (error instanceof SerperConfigurationError) {
logError("SerperService configuration error", error, {
person,
topic,
numberOfQuotes,
});
return {
content: [
{
type: "text",
text: `Configuration Error: ${error.message}`,
},
],
isError: true,
};
}
if (error instanceof SerperApiError) {
logError("SerperService API error", error, {
person,
topic,
numberOfQuotes,
statusCode: error.statusCode,
});
return {
content: [
{
type: "text",
text: `API Error: ${error.message}. Please try again later.`,
},
],
isError: true,
};
}
logError("Unexpected error in get_quote tool", error, {
person,
topic,
numberOfQuotes,
});
return {
content: [
{
type: "text",
text: "An unexpected error occurred while retrieving the quote(s). Please try again later.",
},
],
isError: true,
};
}
});
}
export function registerResources(server) {
logger.debug("Registering MCP resources...");
try {
server.registerResource("prompt-template", "prompt-template://quote-request", {
title: "Quote Request Prompt Template",
description: "Structured template for generating prompts related to quote requests, including parameter specifications and usage examples",
mimeType: "application/json",
}, async (uri) => {
logger.debug("Prompt template resource accessed", { uri: uri.href });
const content = getPromptTemplateContent();
return {
contents: [
{
uri: uri.href,
text: JSON.stringify(content, null, 2),
mimeType: "application/json",
},
],
};
});
logger.info("MCP resources registered successfully", {
resources: ["prompt-template://quote-request"],
});
}
catch (error) {
logError("Failed to register MCP resources", error);
throw new Error(`Resource registration failed: ${error.message}`);
}
}
export function createTransport() {
return new StdioServerTransport();
}
export async function startHttpServer() {
try {
logger.info("Starting MCP HTTP Server...");
const envConfig = parseEnvironmentConfig();
if (!envConfig.mcpHttpEnabled) {
logger.warn("HTTP transport is disabled via environment configuration");
return;
}
const createMcpServer = () => {
const server = createServer();
registerTools(server);
registerResources(server);
return server;
};
const httpConfig = getHttpServerConfig(envConfig);
const httpService = new HttpTransportService(httpConfig, createMcpServer);
await httpService.start();
logger.info("MCP HTTP Server started successfully", {
port: envConfig.mcpHttpPort,
https: envConfig.mcpHttpsEnabled,
tools: ["get_quote"],
resources: ["prompt-template://quote-request"],
});
}
catch (error) {
logError("Failed to start MCP HTTP Server", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
export async function startStdioServer() {
try {
logger.info("Starting MCP Stdio Server...");
const server = createServer();
registerTools(server);
registerResources(server);
const transport = createTransport();
await server.connect(transport);
logger.info("MCP Stdio Server started successfully and is listening for connections.", {
tools: ["get_quote"],
resources: ["prompt-template://quote-request"],
});
}
catch (error) {
logError("Failed to start MCP Stdio Server", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
export async function main() {
try {
logger.info("Starting MCP Quotes Server with dual transport support...");
const envConfig = parseEnvironmentConfig();
if (envConfig.mcpHttpEnabled) {
await startHttpServer();
}
else {
logger.info("HTTP transport disabled, starting stdio server");
await startStdioServer();
}
}
catch (error) {
logError("Failed to start MCP Quotes Server", error instanceof Error ? error : new Error(String(error)));
process.exit(1);
}
}
//# sourceMappingURL=index.js.map