Skip to main content
Glama

Google PSE MCP Server

by rendyfebry
index.ts5.54 kB
#!/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"; /** * Read API config from process.argv: * argv[2]: api_host (default: https://www.googleapis.com/customsearch) * argv[3]: api_key * argv[4]: cx */ const [ , , API_HOST = "https://www.googleapis.com/customsearch", API_KEY, CX, SITE_RESTRICTED_ARG ] = process.argv; // Parse optional siteRestricted CLI flag; default to true when omitted. const SITE_RESTRICTED_DEFAULT = SITE_RESTRICTED_ARG !== undefined ? SITE_RESTRICTED_ARG.toLowerCase() === "true" : true; const server = new Server( { name: "google-pse", version: "0.2.1" }, { capabilities: { tools: {}, resources: {}, } } ); // ListToolsRequestSchema handler: define both search tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search", description: "Search the Web using Google Custom Search API", inputSchema: { type: "object", properties: { q: { type: "string", description: "Search query" }, page: { type: "integer", description: "Page number" }, size: { type: "integer", description: "Number of search results to return per page. Valid values are integers between 1 and 10, inclusive." }, sort: { type: "string", description: "Sort expression (e.g., 'date'). Only 'date' is supported by the API." }, safe: { type: "boolean", description: "Enable safe search filtering. Default: false." }, lr: { type: "string", description: "Restricts the search to documents written in a particular language (e.g., lang_en, lang_ja)" }, siteRestricted: { type: "boolean", description: "If true, use the Site Restricted API endpoint (/v1/siterestrict). If false, use the standard API endpoint (/v1). Default: true." }, }, required: ["q"] } } ] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!request.params.arguments) { throw new Error("No arguments provided"); } // --- search tool implementation --- if (request.params.name === "search") { const args = request.params.arguments as any; const { q, page = 1, size = 10, lr, safe = false, sort } = args; if (!q) { throw new Error("Missing required argument: q"); } if (!API_KEY) { throw new Error("API_KEY is not configured"); } if (!CX) { throw new Error("CX is not configured"); } // Build query params const params = new URLSearchParams(); params.append("key", API_KEY); params.append("cx", CX); params.append("q", q); params.append("fields", "items(title,htmlTitle,link,snippet,htmlSnippet)"); // Language restriction if (lr !== undefined) { params.append("lr", String(lr)); } // SafeSearch mapping (boolean only) if (safe !== undefined) { if (typeof safe !== "boolean") { throw new Error("SafeSearch (safe) must be a boolean"); } params.append("safe", safe ? "active" : "off"); } // Sort validation if (sort !== undefined) { if (sort === "date") { params.append("sort", "date"); } else { throw new Error("Only 'date' is supported for sort"); } } // Pagination params.append("num", String(size)); if (page > 0 && size > 0) { const start = ((page - 1) * size) + 1; params.append("start", String(start)); } else { params.append("start", "1"); } const siteRestricted = args.siteRestricted !== undefined ? args.siteRestricted : SITE_RESTRICTED_DEFAULT; const endpoint = siteRestricted ? "/v1/siterestrict" : "/v1"; const url = `${API_HOST}${endpoint}?${params.toString()}`; const response = await fetch(url, { method: "GET" }); if (!response.ok) { throw new Error(`Search API request failed: ${response.status} ${response.statusText}`); } const result = await response.json(); // Return the items array (list of articles) const items = result?.items ?? []; return { content: [{ type: "text", text: JSON.stringify(items, null, 2) }] }; } throw new Error(`Unknown tool: ${request.params.name}`); }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); } runServer().catch(console.error);

Implementation Reference

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/rendyfebry/google-pse-mcp'

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