#!/usr/bin/env node
import 'dotenv/config';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { webSearchTool } from './tools/web-search.js';
import { semanticRerankTool } from './tools/semantic-rerank.js';
/**
* LangSearch MCP Server
*
* Provides tools for web search and semantic reranking using the LangSearch API.
* Requires LANGSEARCH_API_KEY environment variable.
*/
// Validate required environment variables
const apiKey = process.env.LANGSEARCH_API_KEY;
if (!apiKey) {
console.error('Error: LANGSEARCH_API_KEY environment variable is required');
console.error('Please set it in your .env file or environment');
process.exit(1);
}
const baseUrl = process.env.LANGSEARCH_BASE_URL || 'https://api.langsearch.com';
// Create MCP server instance
const server = new McpServer({
name: 'langsearch-mcp-server',
version: '1.0.0'
});
// Configuration object to pass to tools
const config = {
apiKey,
baseUrl
};
/**
* Register Web Search Tool
*
* Enables searching the web with various filtering options including
* freshness filters, result counts, and full summaries.
*/
server.registerTool(
'langsearch_web_search',
{
title: 'LangSearch Web Search',
description: 'Search the web using LangSearch API with advanced filtering options. Returns web pages with titles, URLs, snippets, and optional summaries. Supports freshness filtering and customizable result counts.',
inputSchema: {
query: z.string().min(1).max(400).describe('The search query (max 400 characters, 50 words)'),
freshness: z.enum(['noLimit', 'onLimit', 'day', 'week', 'month']).optional().describe('Filter results by freshness: noLimit (default), onLimit, day, week, or month'),
summary: z.boolean().optional().describe('Include full summaries in results (default: true)'),
count: z.number().int().min(1).max(50).optional().describe('Number of results to return (1-50, default: 10)')
},
outputSchema: {
_type: z.string(),
queryContext: z.object({
originalQuery: z.string()
}),
webPages: z.object({
webSearchUrl: z.string(),
totalEstimatedMatches: z.number().nullable(),
value: z.array(z.object({
id: z.string(),
name: z.string(),
url: z.string(),
displayUrl: z.string(),
snippet: z.string(),
summary: z.string().optional(),
datePublished: z.string().nullable().optional(),
dateLastCrawled: z.string().nullable().optional()
}).passthrough()),
someResultsRemoved: z.boolean()
})
}
},
async (params) => webSearchTool(params, config)
);
/**
* Register Semantic Rerank Tool
*
* Reranks a list of documents based on semantic relevance to a query.
* Returns documents ordered by relevance score.
*/
server.registerTool(
'langsearch_semantic_rerank',
{
title: 'LangSearch Semantic Rerank',
description: 'Rerank documents based on semantic relevance to a query using LangSearch Reranker API. Evaluates semantic match between query and documents, returning them ordered by relevance score (0-1 scale). Useful for improving search result quality and finding most relevant content.',
inputSchema: {
query: z.string().min(1).describe('The search query to compare against documents'),
documents: z.array(z.string()).min(1).describe('Array of document texts to rerank'),
top_n: z.number().int().min(1).optional().describe('Number of top results to return (default: all documents)'),
return_documents: z.boolean().optional().describe('Whether to include document text in response (default: true)')
},
outputSchema: {
model: z.string(),
results: z.array(z.object({
index: z.number(),
document: z.object({
text: z.string()
}).optional(),
relevance_score: z.number()
}).passthrough())
}
},
async (params) => semanticRerankTool(params, config)
);
/**
* Start the server
*/
async function main() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('LangSearch MCP Server running on stdio');
console.error(`API Base URL: ${baseUrl}`);
console.error('Available tools: langsearch_web_search, langsearch_semantic_rerank');
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
// Handle process termination gracefully
process.on('SIGINT', () => {
console.error('Server shutting down...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.error('Server shutting down...');
process.exit(0);
});
main();