/**
* Search Posts Tool
*
* Provides the search-posts MCP tool for searching HackerNews content.
* Supports keyword search, tag filtering, numeric filters, and pagination.
*/
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { ZodError } from "zod";
import { hnApi } from "../services/hn-api.js";
import {
createSuccessResult,
handleAPIError,
handleValidationError,
} from "../utils/error-handlers.js";
import { SearchPostsInputSchema, validateInput } from "../utils/validators.js";
/**
* Execute the search-posts tool
*
* @param input - Tool input parameters
* @returns MCP tool result with search results or error
*
* @example
* ```typescript
* const result = await searchPostsTool({
* query: "AI",
* tags: ["story"],
* numericFilters: ["points>=100"],
* page: 0,
* hitsPerPage: 20
* });
* ```
*/
export async function searchPostsTool(input: unknown): Promise<CallToolResult> {
try {
// Validate input
const params = validateInput(SearchPostsInputSchema, input);
// Call HackerNews API
const results = await hnApi.search({
query: params.query,
tags: params.tags,
numericFilters: params.numericFilters,
page: params.page,
hitsPerPage: params.hitsPerPage,
});
// Return success result
return createSuccessResult(results);
} catch (error) {
// Handle validation errors
if (error instanceof ZodError) {
return handleValidationError(error);
}
// Handle API errors
return handleAPIError(error, "searching posts");
}
}
/**
* Tool metadata for MCP registration
*/
export const searchPostsToolMetadata = {
name: "search-posts",
description: `Search HackerNews for stories, comments, and other content by keyword.
Supports:
- Keyword search across titles, text, and authors
- Tag filtering (story, comment, poll, show_hn, ask_hn, front_page, author_USERNAME)
- Numeric filters for points, comments, and dates
- Pagination with customizable results per page
- Advanced filtering with OR logic and multiple conditions
Basic Examples:
- Search for AI stories: { "query": "AI", "tags": ["story"] }
- Find popular posts: { "query": "Python", "numericFilters": ["points>=100"] }
- Filter by author: { "query": "startup", "tags": ["author_pg"] }
- Date range: { "query": "startup", "numericFilters": ["created_at_i>1640000000"] }
Advanced Filtering Examples:
- High engagement posts: { "query": "programming", "numericFilters": ["points>=100", "num_comments>=50"] }
- OR logic for tags: { "query": "web", "tags": ["(story,poll)"] } - returns stories OR polls
- Author with filters: { "query": "", "tags": ["author_pg", "story"], "numericFilters": ["points>=50"] }
- Multiple conditions: { "query": "AI", "tags": ["story"], "numericFilters": ["points>=200", "num_comments>=100"] }
Numeric Filter Operators: < (less than), <= (less than or equal), = (equal), >= (greater than or equal), > (greater than)
Numeric Filter Fields: points, num_comments, created_at_i (Unix timestamp)
Tag Syntax:
- Single tag: ["story"] - only stories
- Multiple tags (AND): ["story", "show_hn"] - stories that are also show_hn
- OR logic: ["(story,poll)"] - stories OR polls
- Author filter: ["author_USERNAME"] - posts by specific author
Returns paginated results with hits, total count, and page information.`,
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query text (minimum 1 character)",
},
tags: {
type: "array",
items: { type: "string" },
description:
"Optional filter tags (e.g., ['story'], ['comment'], ['(story,poll)'] for OR logic, ['author_pg'] for author filter)",
},
numericFilters: {
type: "array",
items: { type: "string" },
description:
"Optional numeric filters (e.g., ['points>=100'], ['num_comments>=50'], ['created_at_i>1640000000']). Multiple filters use AND logic.",
},
page: {
type: "number",
description: "Page number (0-indexed, default: 0)",
default: 0,
},
hitsPerPage: {
type: "number",
description: "Results per page (1-1000, default: 20)",
default: 20,
},
},
required: ["query"],
},
};