Skip to main content

Prerequisites

  • Node.js 18+ installed (node --version)
  • An EventGraph API key (get one free)

Step 1 — Create the MCP server

Create a new folder and save this file as server.js:
mkdir eventgraph-mcp && cd eventgraph-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
Create server.js:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const API_BASE = "https://app.eventgraph.ai";
const API_KEY = process.env.EVENTGRAPH_API_KEY;

if (!API_KEY) {
  process.stderr.write("Error: EVENTGRAPH_API_KEY environment variable is required\n");
  process.exit(1);
}

async function apiCall(path, params = {}) {
  const url = new URL(`${API_BASE}${path}`);
  for (const [k, v] of Object.entries(params)) {
    if (v !== undefined && v !== null && v !== "") {
      url.searchParams.set(k, String(v));
    }
  }
  const res = await fetch(url.toString(), {
    headers: { "X-API-Key": API_KEY },
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`API error ${res.status}: ${err?.detail?.message || res.statusText}`);
  }
  const json = await res.json();
  return json.data ?? json;
}

const server = new McpServer({
  name: "eventgraph",
  version: "1.0.0",
});

// ── Free tier tools ──────────────────────────────────────────────────────────

server.tool(
  "search_markets",
  "Search prediction markets across Polymarket, Kalshi, Limitless, and OpinionTrade. Returns live market data including prices, volumes, and platform details.",
  {
    query: z.string().optional().describe("Keyword search query"),
    platform: z.enum(["polymarket", "kalshi", "limitless", "opiniontrade"]).optional().describe("Filter by platform"),
    status: z.enum(["active", "resolved", "all"]).optional().default("active").describe("Market status filter"),
    category: z.string().optional().describe("Category filter e.g. 'politics', 'sports', 'crypto'"),
    page_size: z.number().int().min(1).max(100).optional().default(20).describe("Number of results"),
  },
  async (params) => {
    const data = await apiCall("/api/v1/markets", params);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

server.tool(
  "get_market",
  "Get full details for a specific prediction market including current prices, volume, liquidity, and metadata.",
  {
    market_id: z.string().describe("The market ID (from search_markets results)"),
  },
  async ({ market_id }) => {
    const data = await apiCall(`/api/v1/markets/${market_id}`);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

server.tool(
  "search_events",
  "Search prediction market events. Events are groups of related markets (e.g. 'US 2026 Midterms' is an event containing many individual markets).",
  {
    query: z.string().optional().describe("Keyword search query"),
    category: z.string().optional().describe("Category filter"),
    page_size: z.number().int().min(1).max(100).optional().default(20),
  },
  async (params) => {
    const data = await apiCall("/api/v1/events", params);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

server.tool(
  "get_api_status",
  "Check EventGraph API health and data freshness. Shows when data was last updated for each platform.",
  {},
  async () => {
    const data = await apiCall("/api/v1/status");
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

// ── Pro tier tools ───────────────────────────────────────────────────────────

server.tool(
  "find_arbitrage",
  "Find cross-platform arbitrage opportunities — markets where the same event is priced differently on different platforms. Pro tier required.",
  {
    min_spread: z.number().optional().describe("Minimum spread percentage to return (e.g. 2 for 2%)"),
    page_size: z.number().int().min(1).max(100).optional().default(20),
  },
  async (params) => {
    const data = await apiCall("/api/v1/arbitrage", params);
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

server.tool(
  "compare_markets",
  "Compare how the same event is priced across multiple prediction market platforms side by side. Pro tier required.",
  {
    event_id: z.string().describe("The event ID to compare across platforms (from search_events results)"),
  },
  async ({ event_id }) => {
    const data = await apiCall("/api/v1/compare", { event_id });
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

server.tool(
  "get_market_history",
  "Get historical price time series data for a prediction market. Pro tier required.",
  {
    market_id: z.string().describe("The market ID"),
    interval: z.enum(["1h", "4h", "1d", "1w"]).optional().default("1d").describe("Time interval between data points"),
  },
  async ({ market_id, interval }) => {
    const data = await apiCall(`/api/v1/markets/${market_id}/history`, { interval });
    return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
  }
);

// ── Start server ─────────────────────────────────────────────────────────────

const transport = new StdioServerTransport();
await server.connect(transport);
Add "type": "module" to your package.json:
{
  "type": "module",
  ...
}

Step 2 — Test it locally

EVENTGRAPH_API_KEY=eg_live_your_key node server.js
The server starts and waits for MCP client connections. Now connect a client:

Claude Desktop

Add to Claude Desktop config

Cursor / Windsurf

Add to your IDE’s MCP config

ChatGPT

Create a Custom GPT with Actions

Verify it’s working

Once connected to Claude or Cursor, try asking:
“What are the top active prediction markets right now?”
“Find me any arbitrage opportunities across Polymarket and Kalshi.”
“What markets exist around the 2026 US midterm elections?”
The AI will call your MCP server and return live data.
API key tier matters. The find_arbitrage, compare_markets, and get_market_history tools require a Pro key. Free tier keys will return a 403 on those tools. Upgrade to Pro →