Skip to main content
Glama
jina-ai

Jina AI Remote MCP Server

Official
by jina-ai

search_jina_blog

Search Jina AI's official blog for articles about AI, machine learning, neural search, embeddings, and Jina products to find documentation, tutorials, announcements, and technical deep-dives.

Instructions

Search Jina AI news and blog posts at jina.ai/news for articles about AI, machine learning, neural search, embeddings, and Jina products. Use this to find official Jina documentation, tutorials, product announcements, and technical deep-dives.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch terms to find relevant Jina blog posts (e.g., 'embeddings', 'reranker', 'ColBERT'). Can be a single query string or an array of queries for parallel search.
numNoMaximum number of blog posts to return, between 1-100
tbsNoTime-based search parameter, e.g., 'qdr:h' for past hour, can be qdr:h, qdr:d, qdr:w, qdr:m, qdr:y

Implementation Reference

  • Full MCP tool registration block containing the handler function, Zod schema validation, and execution logic for 'search_jina_blog'. Handles single and parallel queries by calling the executeJinaBlogSearch helper.
    if (isToolEnabled("search_jina_blog")) { server.tool( "search_jina_blog", "Search Jina AI news and blog posts at jina.ai/news for articles about AI, machine learning, neural search, embeddings, and Jina products. Use this to find official Jina documentation, tutorials, product announcements, and technical deep-dives.", { query: z.union([z.string(), z.array(z.string())]).describe("Search terms to find relevant Jina blog posts (e.g., 'embeddings', 'reranker', 'ColBERT'). Can be a single query string or an array of queries for parallel search."), num: z.number().default(30).describe("Maximum number of blog posts to return, between 1-100"), tbs: z.string().optional().describe("Time-based search parameter, e.g., 'qdr:h' for past hour, can be qdr:h, qdr:d, qdr:w, qdr:m, qdr:y") }, async ({ query, num, tbs }: { query: string | string[]; num: number; tbs?: string }) => { try { const props = getProps(); // Get Ghost API key from props (set in index.ts from env) const ghostApiKey = props.ghostApiKey; if (!ghostApiKey) { return createErrorResponse("Ghost API key not configured"); } // Handle single query or single-element array if (typeof query === 'string' || (Array.isArray(query) && query.length === 1)) { const singleQuery = typeof query === 'string' ? query : query[0]; const searchResult = await executeJinaBlogSearch({ query: singleQuery, num, tbs }, ghostApiKey); return { content: formatSingleSearchResultToContentItems(searchResult), }; } // Handle multiple queries with parallel search if (Array.isArray(query) && query.length > 1) { const searches = query.map(q => ({ query: q, num, tbs })); const uniqueSearches = searches.filter((search, index, self) => index === self.findIndex(s => s.query === search.query) ); const jinaBlogSearchFunction = async (searchArgs: SearchJinaBlogArgs) => { return executeJinaBlogSearch(searchArgs, ghostApiKey); }; const results = await executeParallelSearches(uniqueSearches, jinaBlogSearchFunction, { timeout: 30000 }); return { content: formatParallelSearchResultsToContentItems(results), }; } return createErrorResponse("Invalid query format"); } catch (error) { return createErrorResponse(`Error: ${error instanceof Error ? error.message : String(error)}`); } }, ); }
  • TypeScript interface defining the input arguments for the search_jina_blog tool, imported and used in the handler.
    export interface SearchJinaBlogArgs { query: string; num?: number; tbs?: string; }
  • Core helper function that implements the actual blog search logic using Ghost.io API, including query filtering, time-based constraints, and result transformation.
    export async function executeJinaBlogSearch( searchArgs: SearchJinaBlogArgs, ghostApiKey: string ): Promise<SearchResultOrError> { try { const limit = searchArgs.num || 30; // Build filter for Ghost NQL // Ghost Content API only supports filtering on specific fields (title, tag, author, etc.) // Full-text search on content/excerpt is not supported - only substring matching on title const filters: string[] = []; // Search in title using contains operator if (searchArgs.query) { // Escape single quotes in query const escapedQuery = searchArgs.query.replace(/'/g, "\\'"); filters.push(`title:~'${escapedQuery}'`); } // Map tbs (time-based search) to Ghost's published_at filter if (searchArgs.tbs) { const now = new Date(); let dateFilter: Date | null = null; switch (searchArgs.tbs) { case 'qdr:h': // past hour dateFilter = new Date(now.getTime() - 60 * 60 * 1000); break; case 'qdr:d': // past day dateFilter = new Date(now.getTime() - 24 * 60 * 60 * 1000); break; case 'qdr:w': // past week dateFilter = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); break; case 'qdr:m': // past month dateFilter = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); break; case 'qdr:y': // past year dateFilter = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); break; } if (dateFilter) { filters.push(`published_at:>'${dateFilter.toISOString()}'`); } } // Build URL with query parameters const params = new URLSearchParams({ key: ghostApiKey, limit: limit.toString(), fields: 'id,title,slug,excerpt,published_at,url,reading_time', order: 'published_at desc' }); if (filters.length > 0) { params.set('filter', filters.join('+')); } const response = await fetch(`https://jina-ai-gmbh.ghost.io/ghost/api/content/posts/?${params.toString()}`, { method: 'GET', headers: { 'Accept': 'application/json', }, }); if (!response.ok) { return { error: `Jina blog search failed for query "${searchArgs.query}": ${response.statusText}` }; } const data = await response.json() as any; // Transform Ghost posts to search result format const results = (data.posts || []).map((post: any) => { // Transform ghost.io URL to jina.ai/news URL let url = post.url || `https://jina.ai/news/${post.slug}`; if (url.includes('jina-ai-gmbh.ghost.io')) { url = url.replace('https://jina-ai-gmbh.ghost.io/podcast/', 'https://jina.ai/news/'); url = url.replace('https://jina-ai-gmbh.ghost.io/', 'https://jina.ai/news/'); } return { title: post.title, url, snippet: post.excerpt, date: post.published_at, reading_time: post.reading_time }; }); return { query: searchArgs.query, results }; } catch (error) { return { error: `Jina blog search failed for query "${searchArgs.query}": ${error instanceof Error ? error.message : String(error)}` }; } }
  • src/index.ts:100-102 (registration)
    Top-level call to registerJinaTools which includes the search_jina_blog tool registration based on enabledTools filter.
    registerJinaTools(server, () => currentProps, enabledTools); return server;
  • Zod schema for input validation in the MCP tool definition.
    { query: z.union([z.string(), z.array(z.string())]).describe("Search terms to find relevant Jina blog posts (e.g., 'embeddings', 'reranker', 'ColBERT'). Can be a single query string or an array of queries for parallel search."), num: z.number().default(30).describe("Maximum number of blog posts to return, between 1-100"), tbs: z.string().optional().describe("Time-based search parameter, e.g., 'qdr:h' for past hour, can be qdr:h, qdr:d, qdr:w, qdr:m, qdr:y") },

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jina-ai/MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server