UNPKG

serper-search-scrape-mcp-server

Version:

Serper MCP Server supporting search and webpage scraping

195 lines (194 loc) 7.24 kB
#!/usr/bin/env node /** * MCP server implementation that provides web search capabilities via Serper API. */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { SerperClient } from "./services/serper-client.js"; import { SerperSearchTools } from "./tools/search-tool.js"; import { SerperPrompts } from "./prompts/index.js"; // Initialize Serper client with API key from environment const serperApiKey = process.env.SERPER_API_KEY; if (!serperApiKey) { throw new Error("SERPER_API_KEY environment variable is required"); } // Create Serper client, search tool, and prompts const serperClient = new SerperClient(serperApiKey); const searchTools = new SerperSearchTools(serperClient); const prompts = new SerperPrompts(searchTools); // Create MCP server const server = new Server({ name: "Serper MCP Server", version: "0.1.0", }, { capabilities: { tools: {}, prompts: {} }, }); /** * Handler that lists available tools. * Exposes a single "webSearch" tool for performing web searches. */ server.setRequestHandler(ListToolsRequestSchema, async () => { // Define input schema for search tool const searchInputSchema = { type: "object", properties: { q: { type: "string", description: "Search query string", }, gl: { type: "string", description: "Optional region code for search results in ISO 3166-1 alpha-2 format (e.g., 'us')", }, hl: { type: "string", description: "Optional language code for search results in ISO 639-1 format (e.g., 'en')", }, location: { type: "string", description: "Optional location for search results (e.g., 'SoHo, New York, United States', 'California, United States')", }, num: { type: "number", description: "Number of results to return (default: 10)", }, tbs: { type: "string", description: "Time-based search filter ('qdr:h' for past hour, 'qdr:d' for past day, 'qdr:w' for past week, 'qdr:m' for past month, 'qdr:y' for past year)", }, page: { type: "number", description: "Page number of results to return (default: 1)", }, autocorrect: { type: "boolean", description: "Whether to autocorrect spelling in query", }, }, required: ["q", "gl", "hl"], }; // Return list of tools with input schemas return { tools: [ { name: "google_search", description: "Tool to perform web searches via Serper API and retrieve rich results. It is able to retrieve organic search results, people also ask, related searches, and knowledge graph.", inputSchema: searchInputSchema, }, { name: "scrape", description: "Tool to scrape a webpage and retrieve the text and, optionally, the markdown content. It will retrieve also the JSON-LD metadata and the head metadata.", inputSchema: { type: "object", properties: { url: { type: "string", description: "The URL of the webpage to scrape.", }, includeMarkdown: { type: "boolean", description: "Whether to include markdown content.", default: false, }, }, required: ["url"], }, }, ], }; }); /** * Handler for the webSearch tool. * Performs a web search using Serper API and returns results. */ server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case "google_search": { const q = String(request.params.arguments?.q); const gl = request.params.arguments?.gl; const hl = request.params.arguments?.hl; const location = request.params.arguments?.location; const num = request.params.arguments?.num; const tbs = request.params.arguments?.tbs; const page = request.params.arguments?.page; const autocorrect = request.params.arguments?.autocorrect; if (!q || !gl || !hl) { throw new Error("Search query and region code and language are required"); } try { const result = await searchTools.search({ q, gl, hl, location, num, tbs, page, autocorrect, }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { throw new Error(`Search failed: ${error}`); } } case "batch_google_search": { const queries = request.params.arguments?.queries; const results = await searchTools.batchSearch(queries); return { content: [ { type: "text", text: JSON.stringify(results, null, 2), }, ], }; } case "scrape": { const url = request.params.arguments?.url; const includeMarkdown = request.params.arguments ?.includeMarkdown; const result = await searchTools.scrape({ url, includeMarkdown }); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } default: throw new Error("Unknown tool"); } }); // Handle prompts/list requests server.setRequestHandler(ListPromptsRequestSchema, async () => { return prompts.listPrompts(); }); // Handle prompts/get requests server.setRequestHandler(GetPromptRequestSchema, async (request) => { return prompts.getPrompt(request.params.name, request.params.arguments || {}); }); /** * Start the server using stdio transport. */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error("Server error:", error); process.exit(1); });