Skip to main content
Glama
index.ts6.12 kB
#!/usr/bin/env node import 'dotenv/config'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { ListToolsRequestSchema, ListToolsResultSchema, CallToolRequestSchema, CallToolResultSchema, } from '@modelcontextprotocol/sdk/types.js'; // Constants derived from MCP_NAME or default const CANONICAL_ID = (process.env.MCP_NAME ?? 'earch-mcp').trim(); // JSON Schemas for tool inputs const webSearchSchema = { type: 'object', properties: { q: { type: 'string', minLength: 1 }, count: { type: 'integer', minimum: 1, maximum: 50 }, country: { type: 'string' }, safesearch: { enum: ['off', 'moderate', 'strict'] }, freshness: { enum: ['pd', 'pw', 'pm', 'py'] }, }, required: ['q'], additionalProperties: false, } as const; const idsArraySchema = { type: 'object', properties: { ids: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 20, }, }, required: ['ids'], additionalProperties: false, } as const; const richCallbackSchema = { type: 'object', properties: { q: { type: 'string', minLength: 1 } }, required: ['q'], additionalProperties: false, } as const; // Utility fetch wrapper async function braveGet(url: string, params: Record<string, string | number | boolean | undefined>) { const apiKey = process.env.EARCH_MCP_API_KEY || process.env.BRAVE_API_KEY || process.env.BRAVE_SEARCH_API_KEY; if (!apiKey) { throw new Error('Missing API key. Set EARCH_MCP_API_KEY or BRAVE_API_KEY.'); } const headers: Record<string, string> = { Accept: 'application/json', 'Accept-Encoding': 'gzip', 'X-Subscription-Token': apiKey, }; const usp = new URLSearchParams(); for (const [k, v] of Object.entries(params)) { if (v === undefined) continue; usp.set(k, String(v)); } const endpoint = `${url}?${usp.toString()}`; const res = await fetch(endpoint, { headers }); if (!res.ok) { const body = await res.text().catch(() => ''); throw new Error(`Brave API error ${res.status}: ${body}`); } return res.json(); } // Tool registry type ToolDef = { name: string; description?: string; inputSchema: Record<string, unknown>; handler: (args: any) => Promise<{ content: Array<{ type: 'text'; text: string }> }>; }; const toolDefs: ToolDef[] = [ { name: 'web.search', description: 'Brave Web Search: returns results for query q', inputSchema: webSearchSchema, async handler(args) { const data = await braveGet('https://api.search.brave.com/res/v1/web/search', { q: args.q, count: args.count, country: args.country, safesearch: args.safesearch, freshness: args.freshness, }); return { content: [{ type: 'text', text: JSON.stringify(data) }] }; }, }, { name: 'local.pois', description: 'Brave Local Search POIs: fetch extra info for up to 20 location ids', inputSchema: idsArraySchema, async handler(args) { const usp = new URLSearchParams(); for (const id of args.ids as string[]) usp.append('ids', id); const data = await braveGet('https://api.search.brave.com/res/v1/local/pois', Object.fromEntries(usp.entries())); return { content: [{ type: 'text', text: JSON.stringify(data) }] }; }, }, { name: 'local.descriptions', description: 'Brave Local Search AI descriptions: fetch descriptions for up to 20 location ids', inputSchema: idsArraySchema, async handler(args) { const usp = new URLSearchParams(); for (const id of args.ids as string[]) usp.append('ids', id); const data = await braveGet('https://api.search.brave.com/res/v1/local/descriptions', Object.fromEntries(usp.entries())); return { content: [{ type: 'text', text: JSON.stringify(data) }] }; }, }, { name: 'web.rich', description: 'Brave Rich Search via callback flow. First enables rich callback, then fetches rich data', inputSchema: richCallbackSchema, async handler(args) { const first = await braveGet('https://api.search.brave.com/res/v1/web/search', { q: args.q, enable_rich_callback: 1, }); const callbackKey = (first as any)?.rich?.hint?.callback_key; if (!callbackKey) { return { content: [{ type: 'text', text: 'No rich results available for this query.' }] }; } const rich = await braveGet('https://api.search.brave.com/res/v1/web/rich', { callback_key: callbackKey, }); return { content: [{ type: 'text', text: JSON.stringify({ hint: (first as any)?.rich?.hint, rich }) }] }; }, }, ]; const toolList = toolDefs.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })); const toolMap = new Map(toolDefs.map((t) => [t.name, t])); async function main() { const server = new Server( { name: CANONICAL_ID, version: '0.1.0' }, { capabilities: { tools: { listChanged: false } } } ); // tools/list server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: toolList } as unknown as typeof ListToolsResultSchema._type; }); // tools/call server.setRequestHandler(CallToolRequestSchema, async (request) => { const name = request.params.name; const args = request.params.arguments ?? {}; const tool = toolMap.get(name); if (!tool) { return { content: [{ type: 'text', text: `Unknown tool: ${name}` }] } as unknown as typeof CallToolResultSchema._type; } try { const result = await tool.handler(args); return result as unknown as typeof CallToolResultSchema._type; } catch (err: any) { const message = err?.message ?? String(err); return { content: [{ type: 'text', text: `Error: ${message}` }] } as unknown as typeof CallToolResultSchema._type; } }); const transport = new StdioServerTransport(); await server.connect(transport); console.error(`[${CANONICAL_ID}] MCP server started on STDIO`); } main().catch((err) => { console.error(err); process.exit(1); });

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/nanameru/search-mcp'

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