UNPKG

mcp-jinaai-grounding

Version:
155 lines (154 loc) 7.62 kB
#!/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; // Get your Jina AI API key for free: https://jina.ai/?sui=apikey const JINAAI_API_KEY = process.env.JINAAI_API_KEY; if (!JINAAI_API_KEY) { throw new Error('JINAAI_API_KEY environment variable is required'); } const is_valid_grounding_args = (args) => typeof args === 'object' && args !== null && typeof args.statement === 'string' && args.statement.trim() !== '' && (args.references === undefined || (Array.isArray(args.references) && args.references.every((ref) => typeof ref === 'string'))) && (args.no_cache === undefined || typeof args.no_cache === 'boolean'); class JinaGroundingServer { constructor() { this.base_url = 'https://g.jina.ai'; this.server = new Server({ name, version, }, { capabilities: { tools: {}, }, }); this.setup_handlers(); this.server.onerror = (error) => console.error('[MCP Error]', error); } setup_handlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'ground_statement', description: 'Ground a statement using real-time web search results to check factuality. ' + 'When providing URLs via the references parameter, ensure they are publicly accessible ' + 'and contain relevant information about the statement. If the URLs do not contain ' + 'the necessary information, try removing the URL restrictions to search the entire web.', inputSchema: { type: 'object', properties: { statement: { type: 'string', description: 'Statement to be grounded', }, references: { type: 'array', items: { type: 'string', }, description: 'Optional list of URLs to restrict search to. Only provide URLs that are ' + 'publicly accessible and contain information relevant to the statement. ' + 'If the URLs do not contain the necessary information, the grounding will fail. ' + 'For best results, either provide URLs you are certain contain the information, ' + 'or omit this parameter to search the entire web.', }, no_cache: { type: 'boolean', description: 'Whether to bypass cache for fresh results', default: false, }, }, required: ['statement'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== 'ground_statement') { throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); } const args = request.params.arguments; if (!is_valid_grounding_args(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid parameters. Required: statement (string). Optional: references (string[]), no_cache (boolean)'); } try { const headers = { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: `Bearer ${JINAAI_API_KEY}`, }; // Add optional headers if (args.references?.length) { headers['X-Site'] = args.references.join(','); } if (args.no_cache) { headers['X-No-Cache'] = 'true'; } const response = await fetch(this.base_url, { method: 'POST', headers, body: JSON.stringify({ statement: args.statement.trim(), }), }); if (!response.ok) { const error_text = await response.text(); let error_json; try { error_json = JSON.parse(error_text); } catch { throw new Error(`HTTP error! status: ${response.status}, message: ${error_text}`); } // Handle specific error cases if (error_json.status === 42206) { throw new McpError(ErrorCode.InvalidParams, 'The provided URLs did not contain relevant information for fact-checking. This can happen when:\n' + '1. The URLs are not publicly accessible\n' + '2. The URLs do not contain information about the specific statement\n' + '3. The information exists but is not in an easily searchable format\n\n' + 'Suggestions:\n' + '- Remove the URL restrictions to search the entire web\n' + '- Provide different URLs that you are certain contain the information\n' + '- Verify the URLs are publicly accessible and contain relevant content'); } // Handle other API errors throw new Error(`API error! status: ${response.status}, message: ${error_json.readableMessage || error_json.message || error_text}`); } const result = (await response.json()); if (result.code !== 200) { throw new Error(`API error! status: ${result.status}`); } return { content: [ { type: 'text', text: JSON.stringify(result.data, null, 2), }, ], }; } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new McpError(ErrorCode.InternalError, `Failed to ground statement: ${message}`); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Jina Grounding MCP server running on stdio'); } } const server = new JinaGroundingServer(); server.run().catch(console.error);