Skip to main content
Glama
sergeyklay

poe2-mcp-server

by sergeyklay

PoE2 Item Price Lookup

poe2_item_price
Read-onlyIdempotent

Look up current market prices for Path of Exile 2 items from poe.ninja. Search by item name across exchange categories like Currency, Essences, Runes, and Fragments to get chaos-equivalent values and trade volumes.

Instructions

Look up the current market price of an item in Path of Exile 2 from poe.ninja.

Searches by partial name match across exchange categories (Currency, Fragments, Essences, Soul Cores, Idols, Runes, etc.).

Args:

  • name (string): Item name or partial name, e.g. "divine", "essence", "rune"

  • type (string): Exchange category to search. If omitted, searches all categories.

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

Returns: Matching items with chaos-equivalent values and trade volumes.

Examples:

  • "How much is a Divine Orb?" → name="divine", type="Currency"

  • "Price of essences" → name="essence", type="Essences"

  • "Find rune prices" → name="rune", type="Runes"

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYesItem name or partial match
typeNoExchange category to search. If omitted, searches all categories.
leagueNoPoE2 league nameFate of the Vaal

Implementation Reference

  • Main handler function for poe2_item_price tool. Accepts item name, type, and league parameters. Searches across exchange categories on poe.ninja API, matches items by partial name, calculates chaos-equivalent values, and returns formatted results with prices and volumes.
    async ({ name, type, league }) => {
      try {
        const typesToSearch = type ? [type] : [...EXCHANGE_TYPES];
        const query = name.toLowerCase();
        const results: Array<{
          name: string;
          type: string;
          chaos: number;
          volume: number;
        }> = [];
    
        for (const t of typesToSearch) {
          try {
            const data = await getNinjaExchangeOverview(league, t);
    
            const coreNames = new Map<string, string>();
            for (const item of data.core.items) {
              coreNames.set(item.id, item.name);
            }
    
            const chaosRate = data.core.rates[data.core.secondary] ?? 1;
    
            for (const line of data.lines) {
              const itemName = coreNames.get(line.id);
              const matchesQuery =
                line.id.toLowerCase().includes(query) ||
                (itemName?.toLowerCase().includes(query) ?? false);
    
              if (matchesQuery) {
                results.push({
                  name: displayName(line.id, coreNames),
                  type: t,
                  chaos: line.primaryValue * chaosRate,
                  volume: line.volumePrimaryValue ?? 0,
                });
              }
            }
          } catch {
            // Some categories may not be available, skip silently
          }
        }
    
        if (results.length === 0) {
          return {
            content: [
              {
                type: 'text',
                text: `No items found matching "${name}" in ${league}.\n\nTip: Try a shorter name. Available categories: ${EXCHANGE_TYPES.join(', ')}`,
              },
            ],
          };
        }
    
        results.sort((a, b) => b.chaos - a.chaos);
        const top = results.slice(0, 15);
    
        const lines: string[] = [
          `## Item Prices: "${name}" — ${league}`,
          `Found ${results.length} match(es)`,
          '',
        ];
        for (const r of top) {
          lines.push(`**${r.name}** [${r.type}]`);
          lines.push(`- Chaos: ${r.chaos.toFixed(1)} | Volume: ${r.volume}`);
          lines.push('');
        }
    
        if (results.length > 15) {
          lines.push(`... and ${results.length - 15} more results.`);
        }
    
        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: ${msg}` }],
        };
      }
    },
  • Input schema definition using Zod. Validates 'name' (required string), 'type' (optional enum from EXCHANGE_TYPES), and 'league' (string with default 'Fate of the Vaal').
    inputSchema: {
      name: z.string().min(1).describe('Item name or partial match'),
      type: z
        .enum(EXCHANGE_TYPES)
        .optional()
        .describe('Exchange category to search. If omitted, searches all categories.'),
      league: z.string().default(DEFAULT_LEAGUE).describe('PoE2 league name'),
    },
  • Tool registration with server.registerTool. Defines metadata including title, description, inputSchema, and annotations (readOnly, idempotent, openWorld). Binds the handler function to the 'poe2_item_price' tool name.
      server.registerTool(
        'poe2_item_price',
        {
          title: 'PoE2 Item Price Lookup',
          description: `Look up the current market price of an item in Path of Exile 2 from poe.ninja.
    
    Searches by partial name match across exchange categories (Currency, Fragments, Essences, Soul Cores, Idols, Runes, etc.).
    
    Args:
      - name (string): Item name or partial name, e.g. "divine", "essence", "rune"
      - type (string): Exchange category to search. If omitted, searches all categories.
      - league (string): League name (default: "Fate of the Vaal")
    
    Returns: Matching items with chaos-equivalent values and trade volumes.
    
    Examples:
      - "How much is a Divine Orb?" → name="divine", type="Currency"
      - "Price of essences" → name="essence", type="Essences"
      - "Find rune prices" → name="rune", type="Runes"`,
          inputSchema: {
            name: z.string().min(1).describe('Item name or partial match'),
            type: z
              .enum(EXCHANGE_TYPES)
              .optional()
              .describe('Exchange category to search. If omitted, searches all categories.'),
            league: z.string().default(DEFAULT_LEAGUE).describe('PoE2 league name'),
          },
          annotations: {
            readOnlyHint: true,
            destructiveHint: false,
            idempotentHint: true,
            openWorldHint: true,
          },
        },
  • getNinjaExchangeOverview function - API helper that fetches exchange overview data from poe.ninja for a given league and type. Includes rate limiting and error handling.
    export async function getNinjaExchangeOverview(
      league: string,
      type: string,
    ): Promise<NinjaExchangeResponse> {
      const url = `${NINJA_POE2_BASE}/exchange/current/overview?league=${encodeURIComponent(league)}&type=${encodeURIComponent(type)}`;
      return fetchJson<NinjaExchangeResponse>(url, ninjaLimiter);
    }
  • displayName utility function - converts item IDs to title-case display names using a mapping from core item data.
    function displayName(id: string, coreNames: Map<string, string>): string {
      return coreNames.get(id) ?? id.charAt(0).toUpperCase() + id.slice(1);
    }
Behavior4/5

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

Annotations already indicate read-only, open-world, idempotent, and non-destructive behavior. The description adds valuable context beyond this: it specifies the data source ('poe.ninja'), mentions partial name matching, and notes that it returns 'chaos-equivalent values and trade volumes.' This enriches the agent's understanding without contradicting annotations.

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: the first sentence states the core purpose, followed by details on search behavior, parameters, returns, and examples. Every sentence adds value without redundancy, making it efficient for an agent 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 moderate complexity (3 parameters, no output schema), the description is mostly complete. It covers purpose, usage, parameters, and returns. However, without an output schema, it could benefit from more detail on the return format (e.g., structure of 'matching items'), though the mention of 'chaos-equivalent values and trade volumes' provides some insight.

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?

Schema description coverage is 100%, providing full parameter documentation. The description adds minimal extra semantics: it clarifies that 'name' supports partial matches and lists example categories, but this mostly repeats schema info. Baseline 3 is appropriate as the schema does the heavy lifting.

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 tool's purpose: 'Look up the current market price of an item in Path of Exile 2 from poe.ninja.' It specifies the verb ('look up'), resource ('market price of an item'), and source ('poe.ninja'), distinguishing it from sibling tools like 'poe2_currency_check' or 'poe2_wiki_search' which likely serve different functions.

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 usage: 'Searches by partial name match across exchange categories...' and includes examples that illustrate when to use specific parameters. However, it does not explicitly state when not to use this tool or name alternatives among siblings, such as 'poe2_currency_prices' for currency-specific queries.

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