Skip to main content
Glama
PaulieB14

graph-polymarket-mcp

get_market_open_interest

Retrieve top Polymarket prediction markets ranked by open interest (USDC locked in positions). Uses a unique subgraph for accurate open interest data.

Instructions

Get the top Polymarket markets ranked by open interest (USDC locked in outstanding positions). This data is unique to the Open Interest subgraph — no other Polymarket subgraph tracks OI.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
firstNoNumber of markets to return (1-100)
orderByNoField to rank markets byamount
orderDirectionNoSort directiondesc

Implementation Reference

  • src/index.ts:590-653 (registration)
    Registration of the 'get_market_open_interest' tool via server.registerTool(). Defines the tool name, description, input schema (zod), and the handler callback.
    // ---------------------------------------------------------------------------
    // Tool 13: get_market_open_interest
    // ---------------------------------------------------------------------------
    server.registerTool(
      "get_market_open_interest",
      {
        description:
          "Get the top Polymarket markets ranked by open interest (USDC locked in outstanding positions). This data is unique to the Open Interest subgraph — no other Polymarket subgraph tracks OI.",
        inputSchema: {
          first: z.number().min(1).max(100).default(10).describe("Number of markets to return (1-100)"),
          orderBy: z
            .enum(["amount", "splitCount", "mergeCount", "lastUpdatedTimestamp"])
            .default("amount")
            .describe("Field to rank markets by"),
          orderDirection: z.enum(["asc", "desc"]).default("desc").describe("Sort direction"),
        },
      },
      async ({ first, orderBy, orderDirection }) => {
        try {
          const oiQuery = `{
            marketOpenInterests(first: ${first}, orderBy: ${orderBy}, orderDirection: ${orderDirection}) {
              id conditionId amount amountRaw splitCount mergeCount redemptionCount
              createdAtTimestamp lastUpdatedTimestamp
            }
          }`;
          const oiData = await querySubgraph(SUBGRAPHS.open_interest.ipfsHash, oiQuery);
          const od = oiData as { marketOpenInterests?: Array<{ conditionId: string; amount: string }> };
          const conditionIds = (od.marketOpenInterests ?? []).map((m) => m.conditionId).filter(Boolean);
          // Cross-ref Main subgraph: payoutDenominator > 0 means market is resolved
          const resolutionMap = new Map<string, boolean>();
          if (conditionIds.length > 0) {
            const ids = conditionIds.map((id) => `"${id}"`).join(", ");
            const mainQuery = `{ conditions(where: { id_in: [${ids}] }) { id payoutDenominator } }`;
            const mainData = await querySubgraph(SUBGRAPHS.main.ipfsHash, mainQuery).catch(() => null);
            if (mainData) {
              const md = mainData as { conditions?: Array<{ id: string; payoutDenominator: string }> };
              for (const c of md.conditions ?? []) {
                resolutionMap.set(c.id, parseInt(c.payoutDenominator || "0") > 0);
              }
            }
          }
          const annotated = (od.marketOpenInterests ?? []).map((m) => {
            if (resolutionMap.get(m.conditionId)) {
              return {
                ...m,
                warning: `⚠ dead money — market resolved. $${parseFloat(m.amount).toFixed(2)} OI represents worthless losing-side tokens that will never be redeemed on-chain.`,
              };
            }
            return m;
          });
          const deadMoneyTotal = (od.marketOpenInterests ?? [])
            .filter((m) => resolutionMap.get(m.conditionId))
            .reduce((sum, m) => sum + parseFloat(m.amount || "0"), 0);
          return textResult({
            markets: annotated,
            ...(deadMoneyTotal > 0
              ? { deadMoneyOI: `$${deadMoneyTotal.toFixed(2)} of displayed OI is from resolved markets (losing tokens — not redeemable on-chain)` }
              : {}),
          });
        } catch (error) {
          return errorResult(error);
        }
      }
    );
  • Input schema for get_market_open_interest: defines 'first' (number 1-100, default 10), 'orderBy' (enum of amount/splitCount/mergeCount/lastUpdatedTimestamp, default amount), and 'orderDirection' (asc/desc, default desc).
    inputSchema: {
      first: z.number().min(1).max(100).default(10).describe("Number of markets to return (1-100)"),
      orderBy: z
        .enum(["amount", "splitCount", "mergeCount", "lastUpdatedTimestamp"])
        .default("amount")
        .describe("Field to rank markets by"),
      orderDirection: z.enum(["asc", "desc"]).default("desc").describe("Sort direction"),
    },
  • Handler function for get_market_open_interest. Queries the Open Interest subgraph for marketOpenInterests, cross-references the Main subgraph to detect resolved markets (dead money), annotates results with warnings, and returns annotated data.
      async ({ first, orderBy, orderDirection }) => {
        try {
          const oiQuery = `{
            marketOpenInterests(first: ${first}, orderBy: ${orderBy}, orderDirection: ${orderDirection}) {
              id conditionId amount amountRaw splitCount mergeCount redemptionCount
              createdAtTimestamp lastUpdatedTimestamp
            }
          }`;
          const oiData = await querySubgraph(SUBGRAPHS.open_interest.ipfsHash, oiQuery);
          const od = oiData as { marketOpenInterests?: Array<{ conditionId: string; amount: string }> };
          const conditionIds = (od.marketOpenInterests ?? []).map((m) => m.conditionId).filter(Boolean);
          // Cross-ref Main subgraph: payoutDenominator > 0 means market is resolved
          const resolutionMap = new Map<string, boolean>();
          if (conditionIds.length > 0) {
            const ids = conditionIds.map((id) => `"${id}"`).join(", ");
            const mainQuery = `{ conditions(where: { id_in: [${ids}] }) { id payoutDenominator } }`;
            const mainData = await querySubgraph(SUBGRAPHS.main.ipfsHash, mainQuery).catch(() => null);
            if (mainData) {
              const md = mainData as { conditions?: Array<{ id: string; payoutDenominator: string }> };
              for (const c of md.conditions ?? []) {
                resolutionMap.set(c.id, parseInt(c.payoutDenominator || "0") > 0);
              }
            }
          }
          const annotated = (od.marketOpenInterests ?? []).map((m) => {
            if (resolutionMap.get(m.conditionId)) {
              return {
                ...m,
                warning: `⚠ dead money — market resolved. $${parseFloat(m.amount).toFixed(2)} OI represents worthless losing-side tokens that will never be redeemed on-chain.`,
              };
            }
            return m;
          });
          const deadMoneyTotal = (od.marketOpenInterests ?? [])
            .filter((m) => resolutionMap.get(m.conditionId))
            .reduce((sum, m) => sum + parseFloat(m.amount || "0"), 0);
          return textResult({
            markets: annotated,
            ...(deadMoneyTotal > 0
              ? { deadMoneyOI: `$${deadMoneyTotal.toFixed(2)} of displayed OI is from resolved markets (losing tokens — not redeemable on-chain)` }
              : {}),
          });
        } catch (error) {
          return errorResult(error);
        }
      }
    );
  • Configuration for the 'open_interest' subgraph used by get_market_open_interest. Contains the ipfsHash (QmbT2MmS2VGbGihiTUmWk6GMc2QYqoT9ZhiupUicYMWt6H) and description explaining OI tracking.
    open_interest: {
      name: "Open Interest",
      ipfsHash: "QmbT2MmS2VGbGihiTUmWk6GMc2QYqoT9ZhiupUicYMWt6H",
      description:
        "The only Polymarket subgraph dedicated to open interest. Tracks USDC currently locked in outstanding YES/NO positions per market, with hourly snapshots for time-series analysis. OI is computed from PositionSplit (increases) and PositionsMerge (decreases) events on the ConditionalTokens contract. IMPORTANT: Polymarket does NOT use on-chain PayoutRedemption — winners sell shares on the orderbook or merge positions instead. This means resolved markets will still show residual OI from losing-side tokens that will never be redeemed. High OI on a resolved market = dead money (worthless losing tokens), not unclaimed winnings. Best for: identifying markets with the most capital at risk, charting OI trends over time, and detecting capital flow shifts across markets.",
      keyEntities: [
        "MarketOpenInterest (amount in USDC, splitCount, mergeCount — cross-reference with main subgraph for resolution status)",
        "OISnapshot (hourly bucketed OI per market — amount, timestamp, blockNumber)",
        "GlobalOpenInterest (total OI across all markets, marketCount)",
      ],
    },
Behavior2/5

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

No annotations are provided, so the description must carry the full burden of behavioral disclosure. It only states that the data is from the Open Interest subgraph, but does not mention data freshness, rate limits, pagination, or any other behavioral traits. For a read operation, more details are expected.

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 highly concise with two sentences. It immediately states the primary function and then adds a distinguishing remark. Every sentence adds value without unnecessary words.

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

Completeness2/5

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

The tool has no output schema, so the description should cover what the response contains. It does not explain the return format, pagination, or structure of the market data. For a tool returning a list of markets, this lack of completeness is a significant gap.

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?

The input schema has 100% description coverage for all three parameters, so the schema already explains each parameter. The tool description adds no additional semantic value beyond what is in the schema, earning the baseline score of 3.

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 action ('Get the top Polymarket markets') and the specific metric ('ranked by open interest'). It also distinguishes this tool from siblings by noting that no other Polymarket subgraph tracks open interest, 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 Guidelines3/5

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

The description implies when to use this tool (when open interest data is needed) but does not explicitly state when not to use it or provide alternatives. Given the sibling list includes 'get_global_open_interest', more guidance on the distinction would improve score.

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/PaulieB14/graph-polymarket-mcp'

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