MCP2Brave
- src
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkg = JSON.parse(
readFileSync(join(__dirname, '..', 'package.json'), 'utf8'),
);
const { name, version } = pkg;
const TAVILY_API_KEY = process.env.TAVILY_API_KEY;
if (!TAVILY_API_KEY) {
throw new Error('TAVILY_API_KEY environment variable is required');
}
interface TavilySearchResponse {
results: Array<{
title: string;
url: string;
content: string;
score: number;
published_date?: string;
}>;
query: string;
answer?: string;
}
class TavilySearchServer {
private server: Server;
// Default trusted domains for high-quality results
private defaultIncludeDomains = [
'arxiv.org',
'scholar.google.com',
'science.gov',
'wikipedia.org',
'github.com',
'stackoverflow.com',
'developer.mozilla.org',
];
// Default domains to exclude for better result quality
private defaultExcludeDomains = [
'facebook.com',
'twitter.com',
'instagram.com',
'tiktok.com',
];
constructor() {
this.server = new Server(
{ name, version },
{
capabilities: {
tools: {},
},
},
);
this.setupToolHandlers();
}
private setupToolHandlers() {
this.server.setRequestHandler(
ListToolsRequestSchema,
async () => ({
tools: [
{
name: 'tavily_search',
description:
'Search the web using Tavily Search API, optimized for high-quality, factual results',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Search query',
},
search_depth: {
type: 'string',
description:
'The depth of the search ("basic" for faster results, "advanced" for more thorough search)',
enum: ['basic', 'advanced'],
default: 'basic',
},
include_answer: {
type: 'boolean',
description:
'Include an AI-generated answer based on search results',
default: true,
},
include_domains: {
type: 'array',
items: { type: 'string' },
description:
'List of trusted domains to include in search. Defaults to academic and technical sources if not specified.',
default: [],
},
exclude_domains: {
type: 'array',
items: { type: 'string' },
description:
'List of domains to exclude from search. Defaults to social media and potentially unreliable sources if not specified.',
default: [],
},
},
required: ['query'],
},
},
],
}),
);
this.server.setRequestHandler(
CallToolRequestSchema,
async (request) => {
if (request.params.name !== 'tavily_search') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`,
);
}
const {
query,
search_depth = 'basic',
include_answer = true,
include_domains = this.defaultIncludeDomains,
exclude_domains = this.defaultExcludeDomains,
} = request.params.arguments as {
query: string;
search_depth?: 'basic' | 'advanced';
include_answer?: boolean;
include_domains?: string[];
exclude_domains?: string[];
};
try {
const response = await fetch(
'https://api.tavily.com/search',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TAVILY_API_KEY}`,
},
body: JSON.stringify({
query,
search_depth,
include_answer,
include_domains,
exclude_domains,
}),
},
);
if (!response.ok) {
throw new Error(
`Tavily API error: ${response.statusText}`,
);
}
const data: TavilySearchResponse = await response.json();
// Format response for optimal LLM consumption
return {
content: [
{
type: 'text',
text: this.formatSearchResults(data),
},
],
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error performing search: ${
error instanceof Error
? error.message
: String(error)
}`,
},
],
isError: true,
};
}
},
);
}
private formatSearchResults(data: TavilySearchResponse): string {
let formattedText = `Search Results for "${data.query}":\n\n`;
// Include AI answer if available
if (data.answer) {
formattedText += `Summary: ${data.answer}\n\nDetailed Sources:\n`;
}
// Format individual results
formattedText += data.results
.map((result, i) => {
let source = `${i + 1}. ${result.title}\n URL: ${
result.url
}\n`;
if (result.published_date) {
source += ` Published: ${result.published_date}\n`;
}
source += ` Content: ${result.content}\n`;
return source;
})
.join('\n');
return formattedText;
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Tavily Search MCP server running on stdio');
}
}
const server = new TavilySearchServer();
server.run().catch(console.error);