search_esa_posts
Search and filter posts in esa.io using customizable queries to find specific articles by keywords, categories, tags, authors, dates, or other criteria.
Instructions
Search posts in esa.io. Response is paginated. For efficient search, you can use customized queries like the following: keyword for partial match, "keyword" for exact match, keyword1 keyword2 for AND match, keyword1 OR keyword2 for OR match, -keyword for excluding keywords, title:keyword for title match, wip:true or wip:false for WIP posts, kind:stock or kind:flow for kind match, category:category_name for partial match with category name, in:category_name for prefix match with category name, on:category_name for exact match with category name, body:keyword for body match, tag:tag_name or tag:tag_name case_sensitive:true for tag match, user:screen_name for post author's screen name, updated_by:screen_name for post updater's screen name, comment:keyword for partial match with comments, starred:true or starred:false for starred posts, watched:true or watched:false for watched posts, watched_by:screen_name for screen name of members watching the post, sharing:true or sharing:false for shared posts, stars:>3 for posts with more than 3 stars, watches:>3 for posts with more than 3 watches, comments:>3 for posts with more than 3 comments, done:>=3 for posts with 3 or more done items, undone:>=3 for posts with 3 or more undone items, created:>YYYY-MM-DD for filtering by creation date, updated:>YYYY-MM-DD for filtering by update date.Strongly recommend see get_search_query_document for complete query usage.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| teamName | No | my-team | |
| query | Yes | ||
| order | No | desc | |
| sort | No | best_match | |
| page | No | ||
| perPage | No |
Implementation Reference
- src/server.ts:37-99 (registration)Registration of the search_esa_posts tool, including description, input schema (using imported order/sort schemas), and inline handler that calls ApiClient.searchPosts and formats output.server.tool( "search_esa_posts", "Search posts in esa.io. Response is paginated. " + "For efficient search, you can use customized queries like the following: " + 'keyword for partial match, "keyword" for exact match, ' + "keyword1 keyword2 for AND match, " + "keyword1 OR keyword2 for OR match, " + "-keyword for excluding keywords, " + "title:keyword for title match, " + "wip:true or wip:false for WIP posts, " + "kind:stock or kind:flow for kind match, " + "category:category_name for partial match with category name, " + "in:category_name for prefix match with category name, " + "on:category_name for exact match with category name, " + "body:keyword for body match, " + "tag:tag_name or tag:tag_name case_sensitive:true for tag match, " + "user:screen_name for post author's screen name, " + "updated_by:screen_name for post updater's screen name, " + "comment:keyword for partial match with comments, " + "starred:true or starred:false for starred posts, " + "watched:true or watched:false for watched posts, " + "watched_by:screen_name for screen name of members watching the post, " + "sharing:true or sharing:false for shared posts, " + "stars:>3 for posts with more than 3 stars, " + "watches:>3 for posts with more than 3 watches, " + "comments:>3 for posts with more than 3 comments, " + "done:>=3 for posts with 3 or more done items, " + "undone:>=3 for posts with 3 or more undone items, " + "created:>YYYY-MM-DD for filtering by creation date, " + "updated:>YYYY-MM-DD for filtering by update date." + "Strongly recommend see get_search_query_document for complete query usage.", { teamName: z.string().default(getRequiredEnv("DEFAULT_ESA_TEAM")), query: z.string(), order: orderSchema, sort: sortSchema, page: z.number().min(1).default(1), perPage: z.number().min(1).max(100).default(50), }, async (input) => await formatTool(async () => { const posts = await client.searchPosts( input.teamName, input.query, input.order, input.sort, input.page, input.perPage ) return { content: [ { type: "text", text: stringify({ posts: posts, nextPage: input.page + 1, }), }, ], } }) )
- src/server.ts:76-99 (handler)Inline handler function for search_esa_posts: invokes ApiClient.searchPosts with input params and wraps the paginated posts in YAML-formatted text response.async (input) => await formatTool(async () => { const posts = await client.searchPosts( input.teamName, input.query, input.order, input.sort, input.page, input.perPage ) return { content: [ { type: "text", text: stringify({ posts: posts, nextPage: input.page + 1, }), }, ], } }) )
- src/api.ts:56-88 (helper)ApiClient.searchPosts: core logic calls esa.io search API (getV1TeamsTeamNamePosts), strips body_html and body_md from posts to reduce size, returns post metadata.async searchPosts( teamName: string, query: string, order: z.infer<typeof orderSchema>, sort: z.infer<typeof sortSchema>, page: number, perPage: number ) { const response = await this.callApi(() => getV1TeamsTeamNamePosts( teamName, { q: query, order: order, sort: sort, page: page, per_page: perPage, }, { headers: { Authorization: `Bearer ${getRequiredEnv("ESA_API_KEY")}`, }, } ) ) // esa 的には取ってきちゃうが、LLM が呼むのに全文は大きすぎるので外す const posts = (response.data.posts ?? []).map( ({ body_html, body_md, ...others }) => others ) return posts }
- src/schema.ts:1-17 (schema)Zod schemas defining 'order' (asc/desc) and 'sort' (created/updated/number/stars/comments/best_match) options used in the search_esa_posts tool schema.import { z } from "zod" export const orderSchema = z .union([z.literal("asc"), z.literal("desc")]) .default("desc") export const sortSchema = z .union([ z.literal("created"), z.literal("updated"), z.literal("number"), z.literal("stars"), z.literal("comments"), z.literal("best_match"), ]) .default("best_match")
- src/formatTool.ts:34-61 (helper)formatTool utility: wraps tool execution result in MCP CallToolResult format, serializes to YAML text, handles errors.export const formatTool = async ( cb: () => unknown ): Promise<CallToolResult> => { try { const result = await cb() return { content: [ { type: "text", text: stringify(toResponse(result)), }, ], } } catch (error) { console.error("Error in formatTool:", error) return { isError: true, content: [ { type: "text", text: `Error: ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`, }, ], } } }