Skip to main content
Glama
sergeyklay

poe2-mcp-server

by sergeyklay

PoE2 Meta Build Overview

poe2_meta_builds
Read-onlyIdempotent

Retrieve class distribution statistics and meta-build trends for Path of Exile 2 from poe.ninja, showing popular classes with percentage shares and trend directions among ladder characters.

Instructions

Get class distribution statistics for Path of Exile 2 from poe.ninja.

Shows the most popular classes with their percentage share and trend direction among indexed ladder characters.

Args:

  • league (string): League name (default: "Fate of the Vaal")

  • class_name (string): Optional — filter by class, e.g. "Witch", "Lich", "Sorceress"

Returns: Class distribution with percentages and trend indicators.

Examples:

  • "What's the current meta?" → call with defaults

  • "Most popular Witch builds?" → class_name="Witch"

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
leagueNoPoE2 league nameFate of the Vaal
class_nameNoFilter by class name, e.g. Witch, Lich, Warrior, Sorceress

Implementation Reference

  • Main handler function for poe2_meta_builds tool. Fetches build data from poe.ninja API, filters by league and optional class name, formats class distribution statistics with percentages and trend indicators, and returns formatted markdown output.
    async ({ league, class_name }) => {
      try {
        const data = await getNinjaBuildIndex();
        const queryLower = league.toLowerCase();
    
        // Find matching league by name (case-insensitive contains) or URL slug
        const entry: BuildLeagueEntry | undefined = data.leagueBuilds.find(
          (e) =>
            e.leagueName.toLowerCase().includes(queryLower) ||
            e.leagueUrl.toLowerCase() === queryLower,
        );
    
        if (!entry) {
          const available = data.leagueBuilds.map((e) => `"${e.leagueName}"`).join(', ');
          return {
            content: [
              {
                type: 'text',
                text: `League "${league}" not found. Available leagues: ${available}.`,
              },
            ],
          };
        }
    
        const { statistics, total } = entry;
    
        // Build parallel index arrays, optionally filtered by class_name
        let indices = statistics.class.map((_, i) => i);
        if (class_name) {
          const classQuery = class_name.toLowerCase();
          indices = indices.filter((i) => statistics.class[i]!.toLowerCase().includes(classQuery));
        }
    
        const lines: string[] = [
          `## Meta Builds Overview — ${entry.leagueName}`,
          '',
          `Total indexed characters: ${total.toLocaleString()}`,
          '',
          '### Class Distribution',
        ];
    
        if (indices.length === 0 && class_name) {
          lines.push(
            `No classes matching "${class_name}". Available classes: ${statistics.class.join(', ')}`,
          );
        } else {
          for (const i of indices) {
            const className = statistics.class[i]!;
            const pct = statistics.percentage[i] ?? 0;
            const trend = statistics.trend[i] ?? 0;
            lines.push(`- **${className}**: ${pct}%${trendLabel(trend)}`);
          }
        }
    
        return {
          content: [{ type: 'text', text: lines.join('\n') }],
        };
      } catch (error) {
        const msg = error instanceof Error ? error.message : String(error);
        return {
          isError: true,
          content: [
            {
              type: 'text',
              text: `Error fetching meta builds: ${msg}\n\nNote: poe.ninja build API may not be available for all leagues.`,
            },
          ],
        };
      }
    },
  • Tool registration schema defining input parameters: 'league' (string, defaults to 'Fate of the Vaal') and optional 'class_name' filter. Includes tool metadata, descriptions, and annotations (readOnly, idempotent, openWorld).
      server.registerTool(
        'poe2_meta_builds',
        {
          title: 'PoE2 Meta Build Overview',
          description: `Get class distribution statistics for Path of Exile 2 from poe.ninja.
    
    Shows the most popular classes with their percentage share and trend direction among indexed ladder characters.
    
    Args:
      - league (string): League name (default: "Fate of the Vaal")
      - class_name (string): Optional — filter by class, e.g. "Witch", "Lich", "Sorceress"
    
    Returns: Class distribution with percentages and trend indicators.
    
    Examples:
      - "What's the current meta?" → call with defaults
      - "Most popular Witch builds?" → class_name="Witch"`,
          inputSchema: {
            league: z.string().default(DEFAULT_LEAGUE).describe('PoE2 league name'),
            class_name: z
              .string()
              .optional()
              .describe('Filter by class name, e.g. Witch, Lich, Warrior, Sorceress'),
          },
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
  • Complete tool registration function registerBuildTools that registers the poe2_meta_builds tool with the MCP server, including schema definition and handler function.
    export function registerBuildTools(server: McpServer): void {
      // ── poe2_meta_builds ──────────────────────────────────────────────
      server.registerTool(
        'poe2_meta_builds',
        {
          title: 'PoE2 Meta Build Overview',
          description: `Get class distribution statistics for Path of Exile 2 from poe.ninja.
    
    Shows the most popular classes with their percentage share and trend direction among indexed ladder characters.
    
    Args:
      - league (string): League name (default: "Fate of the Vaal")
      - class_name (string): Optional — filter by class, e.g. "Witch", "Lich", "Sorceress"
    
    Returns: Class distribution with percentages and trend indicators.
    
    Examples:
      - "What's the current meta?" → call with defaults
      - "Most popular Witch builds?" → class_name="Witch"`,
          inputSchema: {
            league: z.string().default(DEFAULT_LEAGUE).describe('PoE2 league name'),
            class_name: z
              .string()
              .optional()
              .describe('Filter by class name, e.g. Witch, Lich, Warrior, Sorceress'),
          },
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
        async ({ league, class_name }) => {
          try {
            const data = await getNinjaBuildIndex();
            const queryLower = league.toLowerCase();
    
            // Find matching league by name (case-insensitive contains) or URL slug
            const entry: BuildLeagueEntry | undefined = data.leagueBuilds.find(
              (e) =>
                e.leagueName.toLowerCase().includes(queryLower) ||
                e.leagueUrl.toLowerCase() === queryLower,
            );
    
            if (!entry) {
              const available = data.leagueBuilds.map((e) => `"${e.leagueName}"`).join(', ');
              return {
                content: [
                  {
                    type: 'text',
                    text: `League "${league}" not found. Available leagues: ${available}.`,
                  },
                ],
              };
            }
    
            const { statistics, total } = entry;
    
            // Build parallel index arrays, optionally filtered by class_name
            let indices = statistics.class.map((_, i) => i);
            if (class_name) {
              const classQuery = class_name.toLowerCase();
              indices = indices.filter((i) => statistics.class[i]!.toLowerCase().includes(classQuery));
            }
    
            const lines: string[] = [
              `## Meta Builds Overview — ${entry.leagueName}`,
              '',
              `Total indexed characters: ${total.toLocaleString()}`,
              '',
              '### Class Distribution',
            ];
    
            if (indices.length === 0 && class_name) {
              lines.push(
                `No classes matching "${class_name}". Available classes: ${statistics.class.join(', ')}`,
              );
            } else {
              for (const i of indices) {
                const className = statistics.class[i]!;
                const pct = statistics.percentage[i] ?? 0;
                const trend = statistics.trend[i] ?? 0;
                lines.push(`- **${className}**: ${pct}%${trendLabel(trend)}`);
              }
            }
    
            return {
              content: [{ type: 'text', text: lines.join('\n') }],
            };
          } catch (error) {
            const msg = error instanceof Error ? error.message : String(error);
            return {
              isError: true,
              content: [
                {
                  type: 'text',
                  text: `Error fetching meta builds: ${msg}\n\nNote: poe.ninja build API may not be available for all leagues.`,
                },
              ],
            };
          }
        },
      );
    }
  • getNinjaBuildIndex function that fetches PoE2 build index state from poe.ninja API, returning class distribution statistics for all leagues.
    export async function getNinjaBuildIndex(): Promise<BuildIndexStateResponse> {
      const url = 'https://poe.ninja/poe2/api/data/build-index-state';
      return fetchJson<BuildIndexStateResponse>(url, ninjaLimiter);
    }
  • Type definitions for BuildLeagueStatistics, BuildLeagueEntry, and BuildIndexStateResponse interfaces that define the structure of poe.ninja build API responses.
    interface BuildLeagueStatistics {
      class: string[];
      percentage: number[];
      trend: number[];
    }
    
    export interface BuildLeagueEntry {
      leagueName: string;
      leagueUrl: string;
      total: number;
      status: number;
      statistics: BuildLeagueStatistics;
    }
    
    export interface BuildIndexStateResponse {
      leagueBuilds: BuildLeagueEntry[];
    }
Behavior3/5

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

Annotations already cover read-only, open-world, idempotent, and non-destructive traits, so the description adds value by specifying the data source (poe.ninja) and the type of statistics (percentage share, trend direction), but doesn't detail behavioral aspects like rate limits, authentication needs, or data freshness.

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

Conciseness5/5

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

The description is well-structured and front-loaded with the core purpose, followed by concise sections for arguments, returns, and examples. Every sentence adds value without redundancy, making it efficient and easy to parse.

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 tool's low complexity, rich annotations, and full schema coverage, the description is mostly complete. However, the lack of an output schema means it should ideally detail return values more explicitly, though it mentions 'percentages and trend indicators' which provides some context.

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?

With 100% schema description coverage, the schema fully documents the two parameters. The description adds minimal semantics by mentioning filtering examples and default usage, but doesn't provide additional meaning beyond what the schema already specifies, such as valid league names or class options.

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 ('Get class distribution statistics') and resource ('Path of Exile 2 from poe.ninja'), distinguishing it from sibling tools like currency or item tools by focusing on class meta data rather than economic or wiki information.

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

Usage Guidelines5/5

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

It provides explicit guidance on when to use the tool with examples ('What's the current meta?' and 'Most popular Witch builds?'), including when to apply the optional filter parameter, though it doesn't explicitly state when not to use it versus alternatives like sibling tools.

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/sergeyklay/poe2-mcp-server'

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