mcp-jina-ai

#!/usr/bin/env node 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 fetch from "node-fetch"; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { ReadWebPageSchema, ReaderResponseSchema, SearchWebSchema, SearchResponseSchema, GroundingSchema, GroundingResponseSchema, } from './schemas.js' // Get your Jina AI API key for free: https://jina.ai/ const JINA_API_KEY = process.env.JINA_API_KEY; if (!JINA_API_KEY) { console.error("JINA_API_KEY environment variable is not set. You can get a key at https://jina.ai/"); process.exit(1); } const server = new Server({ name: "jina-mcp-server", version: "0.1.0", }, { capabilities: { tools: {} } }); async function readWebPage(params: z.infer<typeof ReadWebPageSchema>) { const headers: Record<string, string> = { 'Authorization': `Bearer ${JINA_API_KEY}`, 'Content-Type': 'application/json', 'Accept': 'application/json' }; if (params.with_links) headers['X-With-Links-Summary'] = 'true'; if (params.with_images) headers['X-With-Images-Summary'] = 'true'; if (params.with_generated_alt) headers['X-With-Generated-Alt'] = 'true'; if (params.no_cache) headers['X-No-Cache'] = 'true'; const response = await fetch('https://r.jina.ai/', { method: 'POST', headers, body: JSON.stringify({ url: params.url, options: params.format || 'Default' }) }); if (!response.ok) { throw new Error(`Jina AI API error: ${response.statusText}`); } return ReaderResponseSchema.parse(await response.json()); } async function searchWeb(params: z.infer<typeof SearchWebSchema>) { const headers: Record<string, string> = { 'Authorization': `Bearer ${JINA_API_KEY}`, 'Accept': 'application/json', 'X-Retain-Images': params.retain_images, 'X-With-Generated-Alt': params.with_generated_alt.toString(), 'X-Return-Format': params.return_format }; const queryString = encodeURIComponent(params.query); const url = `https://s.jina.ai/${queryString}?count=${params.count}`; const response = await fetch(url, { method: 'GET', headers, }); if (!response.ok) { throw new Error(`Jina AI Search API error: ${response.statusText}`); } return SearchResponseSchema.parse(await response.json()); } async function groundStatement(params: z.infer<typeof GroundingSchema>) { const headers: Record<string, string> = { 'Authorization': `Bearer ${JINA_API_KEY}`, 'Accept': 'application/json' }; const statementQuery = encodeURIComponent(params.statement); const url = `https://g.jina.ai/${statementQuery}${params.deepdive ? '?deepdive=true' : ''}`; const response = await fetch(url, { method: 'GET', headers, }); if (!response.ok) { throw new Error(`Jina AI Grounding API error: ${response.statusText}`); } return GroundingResponseSchema.parse(await response.json()); } server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "read_webpage", description: "Extract content from a webpage in a format optimized for LLMs", inputSchema: zodToJsonSchema(ReadWebPageSchema) }, { name: "search_web", description: "Search the web using Jina AI's search API", inputSchema: zodToJsonSchema(SearchWebSchema) }, { name: "fact_check", description: "Fact-check a statement using Jina AI's grounding engine", inputSchema: zodToJsonSchema(GroundingSchema) } ] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { if (!request.params.arguments) { throw new Error("Arguments are required"); } switch (request.params.name) { case "read_webpage": { const args = ReadWebPageSchema.parse(request.params.arguments); const result = await readWebPage(args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "search_web": { const args = SearchWebSchema.parse(request.params.arguments); const result = await searchWeb(args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case "fact_check": { const args = GroundingSchema.parse(request.params.arguments); const result = await groundStatement(args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { if (error instanceof z.ZodError) { throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); } throw error; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Jina AI MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });