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")
    },
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions what content is searched (Jina blog/news), it doesn't describe important behavioral traits: whether results are paginated, what format they return, authentication requirements, rate limits, or error conditions. For a search tool with no annotation coverage, this leaves significant gaps in understanding how the tool behaves.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized with two sentences that efficiently convey purpose and usage. The first sentence establishes what the tool searches and where, while the second provides use cases. There's minimal redundancy, though the second sentence could be slightly more concise by combining some elements.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 3 parameters, 100% schema coverage, but no annotations and no output schema, the description provides adequate purpose and usage context but lacks behavioral transparency. The description covers what content is searched and use cases, but without annotations or output schema, it should ideally describe result format, pagination, or other behavioral aspects to be more complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents all three parameters. The description doesn't add any parameter-specific information beyond what's in the schema descriptions. It mentions general search terms but doesn't provide additional syntax, format, or usage details for parameters. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches Jina AI news and blog posts at a specific URL (jina.ai/news), listing specific content types (articles about AI, machine learning, neural search, embeddings, Jina products) and use cases (official documentation, tutorials, product announcements, technical deep-dives). It distinguishes from sibling tools like search_arxiv or search_web by specifying the Jina-specific source and content focus.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool: to find Jina-specific content including documentation, tutorials, announcements, and technical articles. It doesn't explicitly state when NOT to use it or name specific alternatives, but the Jina focus implicitly distinguishes it from general web search siblings like search_web.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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