search_posts
Search HackerNews posts using keywords with filters for content type, author, date ranges, points, and comments. Retrieve paginated results sorted by relevance or date.
Instructions
Search HackerNews posts by keywords with advanced filtering options. Supports filtering by content type (story, comment, poll, etc.), author, date ranges, points thresholds, and comment counts. Returns paginated results with metadata. Default sort is by relevance, but can sort chronologically with sortByDate=true.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | ||
| tags | No | ||
| author | No | ||
| storyId | No | ||
| minPoints | No | ||
| maxPoints | No | ||
| minComments | No | ||
| maxComments | No | ||
| dateAfter | No | ||
| dateBefore | No | ||
| sortByDate | No | ||
| page | No | ||
| hitsPerPage | No |
Implementation Reference
- src/tools/search-posts.ts:15-68 (handler)The core handler function that implements the logic for the 'search_posts' tool. It parses and validates the input arguments, performs additional business logic validations, constructs search parameters, invokes the HackerNews API (with optional date sorting), and formats the paginated response.export async function handleSearchPosts(args: unknown) { // Validate input const parseResult = SearchPostsInputSchema.safeParse(args); if (!parseResult.success) { throw new ValidationError("Invalid search parameters", parseResult.error.errors); } const input: SearchPostsInput = parseResult.data; // Additional validation if (input.minPoints !== undefined && input.maxPoints !== undefined) { if (input.minPoints > input.maxPoints) { throw new ValidationError("minPoints cannot be greater than maxPoints"); } } if (input.minComments !== undefined && input.maxComments !== undefined) { if (input.minComments > input.maxComments) { throw new ValidationError("minComments cannot be greater than maxComments"); } } if (input.dateAfter && input.dateBefore) { const after = new Date(input.dateAfter); const before = new Date(input.dateBefore); if (after > before) { throw new ValidationError("dateAfter cannot be later than dateBefore"); } } // Build API parameters const params = buildSearchParams(input); // Call appropriate API endpoint const result = input.sortByDate ? await apiClient.searchByDate(params) : await apiClient.search(params); // Format response with pagination metadata const response = { results: result.hits, pagination: { totalResults: result.nbHits, currentPage: result.page, totalPages: result.nbPages, resultsPerPage: result.hitsPerPage, }, processingTimeMS: result.processingTimeMS, remainingQuota: apiClient.getRemainingQuota(), }; return formatToolResponse(response); }
- src/schemas/index.ts:73-93 (schema)Zod schema defining the input structure and validation rules for the search_posts tool, including optional filters for query, tags, author, points, comments, dates, sorting, and pagination.* Schema for search_posts tool input */ export const SearchPostsInputSchema = z.object({ query: z.string().max(1000).optional(), tags: z .array(z.enum(["story", "comment", "poll", "pollopt", "show_hn", "ask_hn", "front_page"])) .optional(), author: z.string().min(1).max(15).optional(), storyId: z.number().int().positive().optional(), minPoints: z.number().int().nonnegative().optional(), maxPoints: z.number().int().nonnegative().optional(), minComments: z.number().int().nonnegative().optional(), maxComments: z.number().int().nonnegative().optional(), dateAfter: z.string().datetime().optional(), dateBefore: z.string().datetime().optional(), sortByDate: z.boolean().optional().default(false), page: z.number().int().nonnegative().max(999).optional().default(0), hitsPerPage: z.number().int().min(1).max(100).optional().default(20), }); export type SearchPostsInput = z.infer<typeof SearchPostsInputSchema>;
- src/tools/index.ts:26-31 (registration)Registration of the search_posts tool in the MCP tools list, providing name, description, and JSON schema for input validation in tool discovery.{ name: "search_posts", description: "Search HackerNews posts by keywords with advanced filtering options. Supports filtering by content type (story, comment, poll, etc.), author, date ranges, points thresholds, and comment counts. Returns paginated results with metadata. Default sort is by relevance, but can sort chronologically with sortByDate=true.", inputSchema: zodToJsonSchema(SearchPostsInputSchema), },
- src/index.ts:52-54 (registration)Dispatch/registration in the main MCP server request handler switch statement that routes 'search_posts' tool calls to the specific handler function.case "search_posts": return await handleSearchPosts(args);
- src/services/search-service.ts:11-95 (helper)Helper function that transforms SearchPostsInput into HN Algolia API search parameters, handling tags, numeric filters for points/comments/dates, and pagination.export function buildSearchParams(input: SearchPostsInput): { query?: string; tags?: string; numericFilters?: string; page?: number; hitsPerPage?: number; } { const params: { query?: string; tags?: string; numericFilters?: string; page?: number; hitsPerPage?: number; } = {}; // Query string if (input.query) { params.query = input.query; } // Build tags array const tags: string[] = []; // Add content type tags if (input.tags && input.tags.length > 0) { tags.push(...input.tags); } // Add author tag if (input.author) { tags.push(`author_${input.author}`); } // Add story ID tag if (input.storyId) { tags.push(`story_${input.storyId}`); } if (tags.length > 0) { params.tags = formatTags(tags); } // Build numeric filters array const numericFilters: string[] = []; // Points filters if (input.minPoints !== undefined) { numericFilters.push(`points>=${input.minPoints}`); } if (input.maxPoints !== undefined) { numericFilters.push(`points<=${input.maxPoints}`); } // Comments filters if (input.minComments !== undefined) { numericFilters.push(`num_comments>=${input.minComments}`); } if (input.maxComments !== undefined) { numericFilters.push(`num_comments<=${input.maxComments}`); } // Date filters if (input.dateAfter) { const timestamp = dateToUnixTimestamp(input.dateAfter); numericFilters.push(`created_at_i>=${timestamp}`); } if (input.dateBefore) { const timestamp = dateToUnixTimestamp(input.dateBefore); numericFilters.push(`created_at_i<=${timestamp}`); } if (numericFilters.length > 0) { params.numericFilters = formatNumericFilters(numericFilters); } // Pagination if (input.page !== undefined) { params.page = input.page; } if (input.hitsPerPage !== undefined) { params.hitsPerPage = input.hitsPerPage; } return params; }