Skip to main content
Glama

LLM Researcher

by Code-Hex
mcp-server.ts7.85 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { DuckDuckGoSearcher } from './search.js'; import { GitHubCodeSearcher } from './github-code-search.js'; import { ContentExtractor } from './extractor.js'; export class MCPResearchServer { private server: Server; private searcher: DuckDuckGoSearcher; private githubSearcher: GitHubCodeSearcher; private extractor: ContentExtractor; constructor() { this.server = new Server( { name: 'light-research-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.searcher = new DuckDuckGoSearcher(); this.githubSearcher = new GitHubCodeSearcher(); this.extractor = new ContentExtractor(); this.setupHandlers(); } private async performSearch( searcher: DuckDuckGoSearcher | GitHubCodeSearcher, query: string, next?: string, locale?: string ) { const options = locale ? { locale } : undefined; const searchResult = await searcher.search(query, next, options); const results = searchResult.results.map(result => ({ title: result.title, url: result.url, snippet: result.snippet, })); const response = { query, results, pagination: { currentPage: searchResult.currentPage, totalPages: searchResult.totalPages, totalResults: searchResult.totalResults, hasNextPage: searchResult.hasNextPage, hasPreviousPage: searchResult.hasPreviousPage, nextPageToken: this.getNextPageToken(searcher, searchResult), }, }; return { content: [ { type: 'text', text: JSON.stringify(response), }, ], }; } private getNextPageToken( searcher: DuckDuckGoSearcher | GitHubCodeSearcher, searchResult: any ): string | null { if (!searchResult.hasNextPage) return null; if (searcher instanceof GitHubCodeSearcher) { return (searchResult.currentPage + 1).toString(); } else { return searchResult.nextPageParams ? JSON.stringify(searchResult.nextPageParams) : null; } } private setupHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'github_code_search', description: 'Search GitHub source code files to research implementation approaches before writing code. Restrictions: Must include search terms (not just "language:js"), only source files <384KB, active repos only. Supports qualifiers: language:LANG, extension:EXT, filename:NAME, path:DIR, user:USER, org:ORG, repo:USER/REPO', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for GitHub source code. Must include search terms, not just qualifiers. Example: "useState language:javascript" or "error handling extension:go"', }, next: { type: 'string', description: 'Next page token from previous search results to get more results', }, }, required: ['query'], }, }, { name: 'duckduckgo_web_search', description: 'Search the web to research frameworks, libraries, and technologies. Useful for checking if approaches are current, finding documentation, and broad research on development topics. Supports operators: "exact phrase", -exclude, +include, site:domain.com, filetype:pdf, intitle:term, inurl:term', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for web content', }, next: { type: 'string', description: 'Next page token from previous search results to get more results', }, locale: { type: 'string', enum: ['wt-wt', 'us-en', 'uk-en', 'jp-jp', 'cn-zh'], description: 'DuckDuckGo locale for region-specific search results (default: wt-wt for No region)', default: 'wt-wt', }, }, required: ['query'], }, }, { name: 'extract_content', description: 'Extract detailed content from a URL. IMPORTANT: This tool must ONLY be used with URLs obtained from the search results of github_code_search or duckduckgo_web_search tools in this MCP server. Do not use with arbitrary external URLs.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to extract content from. Must be a URL from previous search results obtained through this MCP server\'s search tools (github_code_search or duckduckgo_web_search).', }, }, required: ['url'], }, }, ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'github_code_search': return await this.handleGitHubSearch(args); case 'duckduckgo_web_search': return await this.handleWebSearch(args); case 'extract_content': return await this.handleContentExtraction(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error executing ${name}: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } private async handleGitHubSearch(args: any) { const { query, next } = args; try { return await this.performSearch(this.githubSearcher, query, next); } catch (error) { throw new Error(`GitHub search failed: ${error instanceof Error ? error.message : String(error)}`); } } private async handleWebSearch(args: any) { const { query, next, locale = 'wt-wt' } = args; try { return await this.performSearch(this.searcher, query, next, locale); } catch (error) { throw new Error(`Web search failed: ${error instanceof Error ? error.message : String(error)}`); } } private async handleContentExtraction(args: any) { const { url } = args; try { const extractedContent = await this.extractor.extract(url); const response = { url: extractedContent.url, title: extractedContent.title, extractedAt: extractedContent.extractedAt, content: extractedContent.content, }; return { content: [ { type: 'text', text: JSON.stringify(response, null, 2), }, ], }; } catch (error) { throw new Error(`Content extraction failed: ${error instanceof Error ? error.message : String(error)}`); } } async start(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('MCP Research Server running on stdio'); } async close(): Promise<void> { await this.server.close(); await this.extractor.close(); } }

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/Code-Hex/light-research-mcp'

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