index.ts•12.4 kB
import { z } from 'zod';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { ExaClient } from '../client.js';
import {
formatSearchResults,
formatCompanyResults,
formatLinkedInResults,
formatDeepResearchResults,
formatCrawlResults,
formatError
} from '../formatters.js';
/**
* Tool definitions for Exa MCP server
*/
// Web Search Tool Schema
const webSearchSchema = z.object({
query: z.string().describe("Search query for finding information on the web"),
num_results: z.number().optional().default(10).describe("Number of results to return (default: 10, max: 100)"),
include_domains: z.array(z.string()).optional().describe("Only include results from these domains"),
exclude_domains: z.array(z.string()).optional().describe("Exclude results from these domains"),
start_crawl_date: z.string().optional().describe("Start date for crawled content (YYYY-MM-DD)"),
end_crawl_date: z.string().optional().describe("End date for crawled content (YYYY-MM-DD)"),
start_published_date: z.string().optional().describe("Start date for published content (YYYY-MM-DD)"),
end_published_date: z.string().optional().describe("End date for published content (YYYY-MM-DD)"),
use_autoprompt: z.boolean().optional().default(true).describe("Let Exa AI optimize the search query"),
type: z.enum(['keyword', 'neural', 'magic']).optional().default('neural').describe("Search type: 'keyword' for exact match, 'neural' for semantic search, 'magic' for best results"),
category: z.string().optional().describe("Filter by content category (news, blog, research, etc.)"),
include_text: z.boolean().optional().default(false).describe("Include extracted text content from pages"),
include_highlights: z.boolean().optional().default(false).describe("Include highlighted snippets from pages"),
include_summary: z.boolean().optional().default(false).describe("Include AI-generated summaries of pages")
});
// Company Research Tool Schema
const companyResearchSchema = z.object({
company_name: z.string().describe("Name of the company to research"),
include_news: z.boolean().optional().default(true).describe("Include recent news and announcements"),
include_overview: z.boolean().optional().default(true).describe("Include company overview and description"),
include_competitors: z.boolean().optional().default(false).describe("Include information about competitors"),
include_reviews: z.boolean().optional().default(false).describe("Include customer reviews and feedback"),
max_results_per_category: z.number().optional().default(5).describe("Maximum results per category (default: 5)")
});
// Crawling Tool Schema
const crawlingSchema = z.object({
urls: z.array(z.string()).describe("List of URLs to crawl and extract content from"),
include_text: z.boolean().optional().default(true).describe("Include extracted text content"),
include_highlights: z.boolean().optional().default(false).describe("Include key highlights from the content"),
include_summary: z.boolean().optional().default(false).describe("Include AI-generated summary"),
summary_query: z.string().optional().describe("Custom query for generating summaries")
});
// LinkedIn Search Tool Schema
const linkedInSearchSchema = z.object({
query: z.string().describe("Search query for LinkedIn profiles or companies"),
search_type: z.enum(['people', 'companies', 'both']).optional().default('both').describe("Type of LinkedIn search"),
num_results: z.number().optional().default(10).describe("Number of results to return"),
include_bios: z.boolean().optional().default(true).describe("Include bio/description text"),
include_summaries: z.boolean().optional().default(false).describe("Include AI-generated summaries")
});
// Deep Research Start Tool Schema
const deepResearchStartSchema = z.object({
topic: z.string().describe("Research topic or question"),
research_type: z.enum(['comprehensive', 'news', 'academic', 'market']).optional().default('comprehensive').describe("Type of research to perform"),
max_results: z.number().optional().default(50).describe("Maximum total results to collect"),
time_range: z.enum(['week', 'month', 'quarter', 'year', 'all']).optional().describe("Time range for research")
});
// Deep Research Check Tool Schema
const deepResearchCheckSchema = z.object({
task_id: z.string().describe("Task ID returned from deep_researcher_start")
});
/**
* Get tool definitions based on enabled tools
*/
export function getToolDefinitions(enabledTools: string[]): Tool[] {
const allTools: Tool[] = [
{
name: 'web_search_exa',
description: 'Search the web using Exa AI for up-to-date information',
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query for finding information on the web"
},
num_results: {
type: "number",
description: "Number of results to return (default: 10, max: 100)",
default: 10
},
include_domains: {
type: "array",
items: { type: "string" },
description: "Only include results from these domains"
},
exclude_domains: {
type: "array",
items: { type: "string" },
description: "Exclude results from these domains"
},
start_crawl_date: {
type: "string",
description: "Start date for crawled content (YYYY-MM-DD)"
},
end_crawl_date: {
type: "string",
description: "End date for crawled content (YYYY-MM-DD)"
},
start_published_date: {
type: "string",
description: "Start date for published content (YYYY-MM-DD)"
},
end_published_date: {
type: "string",
description: "End date for published content (YYYY-MM-DD)"
},
use_autoprompt: {
type: "boolean",
description: "Let Exa AI optimize the search query",
default: true
},
type: {
type: "string",
enum: ["keyword", "neural", "magic"],
description: "Search type: 'keyword' for exact match, 'neural' for semantic search, 'magic' for best results",
default: "neural"
},
category: {
type: "string",
description: "Filter by content category (news, blog, research, etc.)"
},
include_text: {
type: "boolean",
description: "Include extracted text content from pages",
default: false
},
include_highlights: {
type: "boolean",
description: "Include highlighted snippets from pages",
default: false
},
include_summary: {
type: "boolean",
description: "Include AI-generated summaries of pages",
default: false
}
},
required: ["query"]
}
},
{
name: 'company_research_exa',
description: 'Research companies and organizations comprehensively',
inputSchema: {
type: "object",
properties: {
company_name: {
type: "string",
description: "Name of the company to research"
},
include_news: {
type: "boolean",
description: "Include recent news and announcements",
default: true
},
include_overview: {
type: "boolean",
description: "Include company overview and description",
default: true
},
include_competitors: {
type: "boolean",
description: "Include information about competitors",
default: false
},
include_reviews: {
type: "boolean",
description: "Include customer reviews and feedback",
default: false
},
max_results_per_category: {
type: "number",
description: "Maximum results per category (default: 5)",
default: 5
}
},
required: ["company_name"]
}
}
];
return allTools.filter(tool => enabledTools.includes(tool.name));
}
/**
* Handle tool calls
*/
export async function handleToolCall(client: ExaClient, toolName: string, args: any) {
try {
switch (toolName) {
case 'web_search_exa': {
const params = webSearchSchema.parse(args);
const results = await client.search(params);
return {
content: [{
type: "text",
text: formatSearchResults(results)
}]
};
}
case 'company_research_exa': {
const params = companyResearchSchema.parse(args);
const results = await client.researchCompany(params);
return {
content: [{
type: "text",
text: formatCompanyResults(results)
}]
};
}
case 'crawling_exa': {
const params = crawlingSchema.parse(args);
const results = await client.crawl(params);
return {
content: [{
type: "text",
text: formatCrawlResults(results)
}]
};
}
case 'linkedin_search_exa': {
const params = linkedInSearchSchema.parse(args);
const results = await client.searchLinkedIn(params);
return {
content: [{
type: "text",
text: formatLinkedInResults(results)
}]
};
}
case 'deep_researcher_start': {
const params = deepResearchStartSchema.parse(args);
const result = await client.startDeepResearch(params);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
case 'deep_researcher_check': {
const params = deepResearchCheckSchema.parse(args);
const result = await client.checkDeepResearch(params.task_id);
return {
content: [{
type: "text",
text: result.results ? formatDeepResearchResults(result.results) : JSON.stringify(result, null, 2)
}]
};
}
default:
throw new Error(`Unknown tool: ${toolName}`);
}
} catch (error) {
return {
content: [{
type: "text",
text: formatError(error)
}],
isError: true
};
}
}