UNPKG

@rhofkens/mcp-quotes-server

Version:

A Model Context Protocol (MCP) server that provides quotes based on user requests

237 lines 9.13 kB
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