Skip to main content
Glama
d-kimuson

ESA MCP Server

by d-kimuson

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

TableJSON Schema
NameRequiredDescriptionDefault
teamNameNomy-team
queryYes
orderNodesc
sortNobest_match
pageNo
perPageNo

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,
                }),
              },
            ],
          }
        })
    )
  • 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,
                }),
              },
            ],
          }
        })
    )
  • 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
    }
  • 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")
  • 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)}`,
            },
          ],
        }
      }
    }
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behaviors: pagination of responses, query syntax capabilities, and a strong recommendation to consult another tool for complete query usage. It doesn't cover authentication needs, rate limits, or error handling, but provides substantial operational context.

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

Conciseness3/5

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

The description is appropriately front-loaded with the core purpose and pagination behavior, but the extensive query examples (while valuable) make it quite lengthy. Every sentence earns its place by providing concrete usage guidance, but the structure could be improved by separating general guidance from exhaustive examples.

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

Completeness4/5

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

Given the complexity (6 parameters, no annotations, no output schema), the description provides substantial context about how to use the tool effectively. It covers query semantics thoroughly and mentions pagination. However, it doesn't explain what the response looks like (structure, fields) or address other parameters like 'order', 'sort', 'page', and 'perPage' beyond what the schema provides.

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

Parameters5/5

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

With 0% schema description coverage, the description must compensate for the lack of parameter documentation. It does this exceptionally well by providing extensive examples of how to use the 'query' parameter (the only required parameter), covering various query operators and syntax patterns. This adds significant meaning beyond the bare schema.

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 specific action ('Search posts in esa.io') and distinguishes it from siblings like 'read_esa_post' (single post) and 'read_esa_multiple_posts' (likely batch retrieval without search). It explicitly mentions the resource (posts) and platform (esa.io), making the purpose unambiguous.

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 (searching posts with queries) and implicitly distinguishes it from non-search siblings. However, it doesn't explicitly state when NOT to use it or name specific alternatives beyond the general reference to 'get_search_query_document' for query details.

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/d-kimuson/esa-mcp-server'

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