import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
// ============================================================================
// TYPE DEFINITIONS - Polymarket API Response Types
// ============================================================================
interface Market {
id: string
question: string
conditionId: string
slug: string
description?: string
outcomePrices: string[]
outcomes: string[]
volume: string
liquidity: string
active: boolean
closed: boolean
endDate: string
startDate?: string
category?: string
volume24hr?: string
bestBid?: string
bestAsk?: string
events?: Event[]
tags?: Tag[]
}
interface Event {
id: string
slug: string
title: string
description?: string
startDate?: string
endDate?: string
active: boolean
closed: boolean
archived: boolean
liquidity: string
volume: string
markets?: Market[]
tags?: Tag[]
}
interface Tag {
id: string
label: string
slug: string
}
interface Trade {
id: string
market: string
asset: string
side: "BUY" | "SELL"
size: string
price: string
timestamp: number
transactionHash: string
outcome?: string
marketTitle?: string
}
// ============================================================================
// API CLIENT - Polymarket Gamma & Data API
// ============================================================================
const GAMMA_API_BASE = "https://gamma-api.polymarket.com"
const DATA_API_BASE = "https://data-api.polymarket.com"
// Search API response types
interface SearchMarket {
id: string
question: string
conditionId: string
slug: string
description?: string
outcomes: string // JSON string
outcomePrices: string // JSON string
volume: string
liquidity: string
volume24hr?: number
volumeNum?: number
liquidityNum?: number
active: boolean
closed: boolean
endDate: string
startDate?: string
category?: string
bestBid?: number
bestAsk?: number
}
interface SearchEvent {
id: string
slug: string
title: string
description?: string
startDate?: string
endDate?: string
active: boolean
closed: boolean
archived: boolean
liquidity: number
volume: number
volume24hr?: number
markets?: SearchMarket[]
tags?: Tag[]
}
interface SearchResponse {
events: SearchEvent[]
tags?: Tag[]
profiles?: unknown[]
pagination?: {
hasMore: boolean
totalResults: number
}
}
class PolymarketClient {
/**
* Normalize search market to Market format
*/
private normalizeMarket(market: SearchMarket): Market {
// Parse JSON strings for outcomes and outcomePrices
let outcomes: string[] = []
let outcomePrices: string[] = []
try {
if (market.outcomes) {
outcomes = typeof market.outcomes === 'string'
? JSON.parse(market.outcomes)
: market.outcomes
}
} catch (e) {
// If parsing fails, use empty array
outcomes = []
}
try {
if (market.outcomePrices) {
outcomePrices = typeof market.outcomePrices === 'string'
? JSON.parse(market.outcomePrices)
: market.outcomePrices
}
} catch (e) {
// If parsing fails, use empty array
outcomePrices = []
}
return {
id: market.id,
question: market.question,
conditionId: market.conditionId,
slug: market.slug,
description: market.description,
outcomes,
outcomePrices,
volume: market.volume,
liquidity: market.liquidity,
active: market.active,
closed: market.closed,
endDate: market.endDate,
startDate: market.startDate,
category: market.category,
volume24hr: market.volume24hr?.toString() || market.volumeNum?.toString(),
bestBid: market.bestBid?.toString(),
bestAsk: market.bestAsk?.toString(),
}
}
/**
* Search for markets using the public-search endpoint
*/
async searchMarkets(params: {
query?: string
limit?: number
offset?: number
closed?: boolean
tag_id?: number
liquidity_min?: number
volume_min?: number
order?: string
ascending?: boolean
}): Promise<Market[]> {
const queryParams = new URLSearchParams()
// Map to public-search API parameters
if (params.query) queryParams.append("q", params.query)
if (params.limit !== undefined) queryParams.append("limit_per_type", params.limit.toString())
if (params.offset !== undefined) queryParams.append("page", Math.floor(params.offset / (params.limit || 10)).toString())
if (params.closed !== undefined) {
queryParams.append("events_status", params.closed ? "closed" : "open")
queryParams.append("keep_closed_markets", params.closed ? "1" : "0")
}
if (params.tag_id !== undefined) {
// Note: public-search API uses events_tag (array of tag slugs), not tag_id
// For now, we'll filter client-side after fetching results
// TODO: Could enhance by fetching tag slug from tag_id first
}
if (params.order) queryParams.append("sort", params.order)
if (params.ascending !== undefined) queryParams.append("ascending", params.ascending.toString())
// Only search events (not profiles)
queryParams.append("search_profiles", "false")
const url = `${GAMMA_API_BASE}/public-search?${queryParams.toString()}`
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Polymarket API error: ${response.status} ${response.statusText}`)
}
const data: SearchResponse = await response.json()
// Extract and normalize markets from events, tracking event info for tag filtering
const markets: Market[] = []
const marketEventMap = new Map<string, SearchEvent>() // Map market ID to its event
if (data.events && Array.isArray(data.events)) {
for (const event of data.events) {
// Filter events by tag_id if specified
if (params.tag_id !== undefined && event.tags) {
const hasTag = event.tags.some(tag => tag.id === params.tag_id!.toString())
if (!hasTag) {
continue // Skip events without the specified tag
}
}
if (event.markets && Array.isArray(event.markets)) {
for (const market of event.markets) {
const normalizedMarket = this.normalizeMarket(market)
markets.push(normalizedMarket)
marketEventMap.set(market.id, event)
}
}
}
}
// Apply additional filters that weren't handled by the API
let filteredMarkets = markets
if (params.liquidity_min !== undefined) {
filteredMarkets = filteredMarkets.filter(m => {
const liquidity = parseFloat(m.liquidity)
return !isNaN(liquidity) && liquidity >= params.liquidity_min!
})
}
if (params.volume_min !== undefined) {
filteredMarkets = filteredMarkets.filter(m => {
const volume = parseFloat(m.volume)
return !isNaN(volume) && volume >= params.volume_min!
})
}
// Apply limit after filtering
if (params.limit !== undefined && filteredMarkets.length > params.limit) {
filteredMarkets = filteredMarkets.slice(0, params.limit)
}
return filteredMarkets
}
/**
* Get a specific market by slug
*/
async getMarket(slug: string): Promise<Market> {
const url = `${GAMMA_API_BASE}/markets/slug/${slug}`
const response = await fetch(url)
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Market not found: ${slug}`)
}
throw new Error(`Polymarket API error: ${response.status} ${response.statusText}`)
}
return await response.json()
}
/**
* Search for events with various filters
*/
async searchEvents(params: {
limit?: number
offset?: number
closed?: boolean
tag_id?: number
featured?: boolean
order?: string
ascending?: boolean
}): Promise<Event[]> {
const queryParams = new URLSearchParams()
if (params.limit !== undefined) queryParams.append("limit", params.limit.toString())
if (params.offset !== undefined) queryParams.append("offset", params.offset.toString())
if (params.closed !== undefined) queryParams.append("closed", params.closed.toString())
if (params.tag_id !== undefined) queryParams.append("tag_id", params.tag_id.toString())
if (params.featured !== undefined) queryParams.append("featured", params.featured.toString())
if (params.order) queryParams.append("order", params.order)
if (params.ascending !== undefined) queryParams.append("ascending", params.ascending.toString())
const url = `${GAMMA_API_BASE}/events?${queryParams.toString()}`
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Polymarket API error: ${response.status} ${response.statusText}`)
}
return await response.json()
}
/**
* Get a specific event by slug
*/
async getEvent(slug: string): Promise<Event> {
const url = `${GAMMA_API_BASE}/events/slug/${slug}`
const response = await fetch(url)
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Event not found: ${slug}`)
}
throw new Error(`Polymarket API error: ${response.status} ${response.statusText}`)
}
return await response.json()
}
/**
* List all available tags for categorization
*/
async listTags(params?: {
limit?: number
offset?: number
}): Promise<Tag[]> {
const queryParams = new URLSearchParams()
if (params?.limit !== undefined) queryParams.append("limit", params.limit.toString())
if (params?.offset !== undefined) queryParams.append("offset", params.offset.toString())
const url = `${GAMMA_API_BASE}/tags?${queryParams.toString()}`
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Polymarket API error: ${response.status} ${response.statusText}`)
}
return await response.json()
}
/**
* Get recent trades from the Data API
*/
async getTrades(params: {
limit?: number
offset?: number
market?: string
eventId?: string
side?: "BUY" | "SELL"
}): Promise<Trade[]> {
const queryParams = new URLSearchParams()
if (params.limit !== undefined) queryParams.append("limit", params.limit.toString())
if (params.offset !== undefined) queryParams.append("offset", params.offset.toString())
if (params.market) queryParams.append("market", params.market)
if (params.eventId) queryParams.append("eventId", params.eventId)
if (params.side) queryParams.append("side", params.side)
const url = `${DATA_API_BASE}/trades?${queryParams.toString()}`
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Polymarket Data API error: ${response.status} ${response.statusText}`)
}
return await response.json()
}
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Format a market for Claude-friendly analysis
*/
function formatMarketAnalysis(market: Market): string {
const probability = market.outcomePrices && market.outcomePrices.length > 0 && market.outcomePrices[0]
? (parseFloat(market.outcomePrices[0]) * 100).toFixed(1)
: "N/A"
const volume24h = market.volume24hr ? `$${(parseFloat(market.volume24hr) / 1000).toFixed(1)}k` : "N/A"
let analysis = `π **${market.question}**\n\n`
analysis += `**Current Probability:** ${probability}% (${market.outcomes && market.outcomes.length > 0 ? market.outcomes[0] : 'Yes'})\n`
analysis += `**Status:** ${market.closed ? 'π΄ Closed' : market.active ? 'π’ Active' : 'βͺ Inactive'}\n`
analysis += `**Volume (24h):** ${volume24h}\n`
const totalVolume = market.volume ? (parseFloat(market.volume) / 1000).toFixed(1) : "0.0"
const liquidity = market.liquidity ? (parseFloat(market.liquidity) / 1000).toFixed(1) : "0.0"
analysis += `**Total Volume:** $${totalVolume}k\n`
analysis += `**Liquidity:** $${liquidity}k\n`
if (market.endDate) {
analysis += `**End Date:** ${new Date(market.endDate).toLocaleDateString()}\n`
}
if (market.outcomes && market.outcomePrices && market.outcomes.length === market.outcomePrices.length) {
analysis += `\n**Outcomes & Prices:**\n`
market.outcomes.forEach((outcome, i) => {
const price = (parseFloat(market.outcomePrices[i]) * 100).toFixed(1)
analysis += ` β’ ${outcome}: ${price}%\n`
})
}
return analysis
}
/**
* Format trades for analysis
*/
function formatTradesSummary(trades: Trade[]): string {
const buyTrades = trades.filter(t => t.side === "BUY").length
const sellTrades = trades.filter(t => t.side === "SELL").length
const totalVolume = trades.reduce((sum, t) => sum + parseFloat(t.size || "0"), 0)
let summary = `π **Recent Trading Activity**\n\n`
summary += `**Total Trades:** ${trades.length}\n`
if (trades.length > 0) {
summary += `**Buy Orders:** ${buyTrades} (${((buyTrades / trades.length) * 100).toFixed(1)}%)\n`
summary += `**Sell Orders:** ${sellTrades} (${((sellTrades / trades.length) * 100).toFixed(1)}%)\n`
} else {
summary += `**Buy Orders:** 0 (0%)\n`
summary += `**Sell Orders:** 0 (0%)\n`
}
summary += `**Total Volume:** ${totalVolume.toFixed(2)} shares\n`
if (trades.length > 0) {
const latestTrade = trades[0]
summary += `\n**Latest Trade:**\n`
summary += ` β’ Side: ${latestTrade.side}\n`
summary += ` β’ Price: $${latestTrade.price}\n`
summary += ` β’ Size: ${latestTrade.size} shares\n`
summary += ` β’ Time: ${new Date(latestTrade.timestamp * 1000).toLocaleString()}\n`
}
return summary
}
// ============================================================================
// MCP SERVER CONFIGURATION
// ============================================================================
export const configSchema = z.object({
debug: z.boolean().default(false).describe("Enable debug logging"),
})
export default function createServer({
config,
}: {
config: z.infer<typeof configSchema>
}) {
const server = new McpServer({
name: "Polymarket",
version: "1.0.0",
})
const client = new PolymarketClient()
// ==========================================================================
// TOOL: search_markets
// ==========================================================================
server.registerTool(
"search_markets",
{
title: "Search Markets",
description: "Search Polymarket prediction markets with filters. Find active markets, filter by tags, volume, liquidity, and more. Perfect for market discovery and analysis.",
inputSchema: {
query: z.string().optional().describe("Search query to filter markets by question text (e.g., 'Bitcoin $100k', 'Trump wins')"),
limit: z.number().optional().default(10).describe("Number of results (max 100)"),
offset: z.number().optional().default(0).describe("Pagination offset"),
closed: z.boolean().optional().default(false).describe("Filter by closed status (false = only active markets, true = include closed)"),
tag_id: z.number().optional().describe("Filter by tag ID (use list_tags to discover)"),
liquidity_min: z.number().optional().describe("Minimum liquidity in USD"),
volume_min: z.number().optional().describe("Minimum volume in USD"),
order: z.string().optional().describe("Field to order by (e.g., 'volume', 'liquidity', 'volume24hr')"),
ascending: z.boolean().optional().describe("Sort direction (true = ascending, false = descending)"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async (params) => {
try {
const markets = await client.searchMarkets(params)
if (markets.length === 0) {
let response = `No markets found matching your criteria.\n\n`
if (params.query) {
response += `**Search query:** "${params.query}"\n\n`
response += `π‘ **Suggestions:**\n`
response += `β’ Try different keywords or variations (e.g., "BTC" instead of "Bitcoin")\n`
response += `β’ Remove the query parameter to see all active markets\n`
response += `β’ Try searching with \`closed: true\` to include closed markets\n`
response += `β’ Use \`list_tags\` to find relevant categories, then filter by tag_id\n`
} else {
response += `π‘ Try adding a \`query\` parameter to search for specific markets, or adjust your filters.`
}
return {
content: [{ type: "text", text: response }],
}
}
let response = `Found ${markets.length} markets:\n\n`
markets.forEach((market, idx) => {
const prob = market.outcomePrices && market.outcomePrices.length > 0 && market.outcomePrices[0]
? (parseFloat(market.outcomePrices[0]) * 100).toFixed(1)
: "N/A"
const volume = market.volume ? (parseFloat(market.volume) / 1000).toFixed(1) : "0.0"
response += `${idx + 1}. **${market.question}**\n`
response += ` Slug: \`${market.slug}\`\n`
response += ` Probability: ${prob}% | Volume: $${volume}k\n`
response += ` Status: ${market.closed ? 'Closed' : market.active ? 'Active' : 'Inactive'}\n\n`
})
response += `\nπ‘ Use \`get_market\` with a slug for detailed analysis.`
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: get_market
// ==========================================================================
server.registerTool(
"get_market",
{
title: "Get Market Details",
description: "Get detailed information about a specific market by slug. Returns probabilities, volume, liquidity, outcomes, and full market data.",
inputSchema: {
slug: z.string().describe("Market slug (from URL or search results)"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async ({ slug }) => {
try {
const market = await client.getMarket(slug)
const analysis = formatMarketAnalysis(market)
let response = analysis
if (market.description) {
response += `\n**Description:** ${market.description}\n`
}
if (market.tags && market.tags.length > 0) {
response += `\n**Tags:** ${market.tags.map(t => t.label).join(', ')}\n`
}
response += `\n**Market Slug:** \`${market.slug}\``
response += `\n**Condition ID:** \`${market.conditionId}\``
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: search_events
// ==========================================================================
server.registerTool(
"search_events",
{
title: "Search Events",
description: "Search Polymarket events. Events group related markets together (e.g., 'Presidential Election 2024' contains multiple markets). Great for discovering market clusters.",
inputSchema: {
limit: z.number().optional().default(10).describe("Number of results (max 100)"),
offset: z.number().optional().default(0).describe("Pagination offset"),
closed: z.boolean().optional().describe("Filter by closed status"),
tag_id: z.number().optional().describe("Filter by tag ID"),
featured: z.boolean().optional().describe("Show only featured events"),
order: z.string().optional().describe("Field to order by"),
ascending: z.boolean().optional().describe("Sort direction"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async (params) => {
try {
const events = await client.searchEvents(params)
let response = `Found ${events.length} events:\n\n`
events.forEach((event, idx) => {
response += `${idx + 1}. **${event.title}**\n`
response += ` Slug: \`${event.slug}\`\n`
response += ` Markets: ${event.markets?.length || 0}\n`
response += ` Volume: $${(parseFloat(event.volume) / 1000).toFixed(1)}k\n`
response += ` Status: ${event.closed ? 'Closed' : event.active ? 'Active' : 'Inactive'}\n\n`
})
response += `\nπ‘ Use \`get_event\` with a slug for detailed event info.`
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: get_event
// ==========================================================================
server.registerTool(
"get_event",
{
title: "Get Event Details",
description: "Get detailed information about a specific event by slug, including all related markets.",
inputSchema: {
slug: z.string().describe("Event slug (from URL or search results)"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async ({ slug }) => {
try {
const event = await client.getEvent(slug)
let response = `π― **${event.title}**\n\n`
if (event.description) {
response += `**Description:** ${event.description}\n\n`
}
response += `**Status:** ${event.closed ? 'π΄ Closed' : event.active ? 'π’ Active' : 'βͺ Inactive'}\n`
response += `**Total Volume:** $${(parseFloat(event.volume) / 1000).toFixed(1)}k\n`
response += `**Liquidity:** $${(parseFloat(event.liquidity) / 1000).toFixed(1)}k\n`
if (event.endDate) {
response += `**End Date:** ${new Date(event.endDate).toLocaleDateString()}\n`
}
if (event.markets && event.markets.length > 0) {
response += `\n**Markets (${event.markets.length}):**\n\n`
event.markets.forEach((market, idx) => {
const prob = market.outcomePrices && market.outcomePrices.length > 0 && market.outcomePrices[0]
? (parseFloat(market.outcomePrices[0]) * 100).toFixed(1)
: "N/A"
response += `${idx + 1}. ${market.question}\n`
response += ` Probability: ${prob}% | Slug: \`${market.slug}\`\n\n`
})
}
if (event.tags && event.tags.length > 0) {
response += `**Tags:** ${event.tags.map(t => t.label).join(', ')}\n`
}
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: list_tags
// ==========================================================================
server.registerTool(
"list_tags",
{
title: "List Tags",
description: "List all available tags/categories for filtering markets and events. Use tag IDs with search_markets or search_events.",
inputSchema: {
limit: z.number().optional().default(50).describe("Number of tags to return"),
offset: z.number().optional().default(0).describe("Pagination offset"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async (params) => {
try {
const tags = await client.listTags(params)
let response = `π **Available Tags (${tags.length}):**\n\n`
tags.forEach((tag, idx) => {
response += `${idx + 1}. **${tag.label}** (ID: ${tag.id})\n`
response += ` Slug: \`${tag.slug}\`\n\n`
})
response += `π‘ Use the tag ID with \`search_markets\` or \`search_events\` to filter by category.`
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: get_trades
// ==========================================================================
server.registerTool(
"get_trades",
{
title: "Get Recent Trades",
description: "Get recent trade activity from Polymarket's Data API. Analyze trading patterns, volume, and market sentiment.",
inputSchema: {
limit: z.number().optional().default(20).describe("Number of trades to fetch (max 100)"),
offset: z.number().optional().default(0).describe("Pagination offset"),
market: z.string().optional().describe("Filter by market condition ID"),
eventId: z.string().optional().describe("Filter by event ID"),
side: z.enum(["BUY", "SELL"]).optional().describe("Filter by trade side"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async (params) => {
try {
const trades = await client.getTrades(params)
if (trades.length === 0) {
return {
content: [{ type: "text", text: "No trades found matching the criteria." }],
}
}
const summary = formatTradesSummary(trades)
let response = summary
response += `\n\n**Recent Trades:**\n`
trades.slice(0, 10).forEach((trade, idx) => {
response += `\n${idx + 1}. ${trade.side === "BUY" ? "π’ BUY" : "π΄ SELL"} ${trade.size} @ $${trade.price}\n`
if (trade.marketTitle) {
response += ` Market: ${trade.marketTitle}\n`
}
response += ` Time: ${new Date(trade.timestamp * 1000).toLocaleString()}\n`
})
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// TOOL: analyze_market
// ==========================================================================
server.registerTool(
"analyze_market",
{
title: "Analyze Market",
description: "Get comprehensive market analysis including probabilities, trading activity, and AI-friendly insights. Combines market data with recent trades.",
inputSchema: {
slug: z.string().describe("Market slug to analyze"),
include_trades: z.boolean().optional().default(true).describe("Include recent trading activity"),
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
},
},
async ({ slug, include_trades }) => {
try {
const market = await client.getMarket(slug)
let response = formatMarketAnalysis(market)
// Add detailed outcome analysis
if (market.outcomes && market.outcomePrices && market.outcomePrices.length > 0) {
response += `\n\n**π Probability Analysis:**\n`
const prices = market.outcomePrices.map(p => parseFloat(p) * 100)
const maxProb = Math.max(...prices)
const minProb = Math.min(...prices)
response += `β’ Highest: ${maxProb.toFixed(1)}%\n`
response += `β’ Lowest: ${minProb.toFixed(1)}%\n`
response += `β’ Spread: ${(maxProb - minProb).toFixed(1)}%\n`
if (maxProb > 75) {
response += `\nπ― **Market Sentiment:** Strong consensus (${maxProb.toFixed(1)}% probability)\n`
} else if (maxProb < 60) {
response += `\nβοΈ **Market Sentiment:** Uncertain / Divided opinion\n`
} else {
response += `\nπ **Market Sentiment:** Moderate confidence\n`
}
}
// Include trading activity if requested
if (include_trades && market.conditionId) {
try {
const trades = await client.getTrades({ market: market.conditionId, limit: 20 })
if (trades.length > 0) {
response += `\n\n${formatTradesSummary(trades)}`
}
} catch (e) {
// Silently fail if trades not available
}
}
// Add market health indicators
const liquidityNum = market.liquidity ? parseFloat(market.liquidity) : 0
const volumeNum = market.volume ? parseFloat(market.volume) : 0
response += `\n\n**π‘ Market Health:**\n`
if (liquidityNum > 100000) {
response += `β’ Liquidity: π’ High (Easy to trade)\n`
} else if (liquidityNum > 10000) {
response += `β’ Liquidity: π‘ Moderate\n`
} else {
response += `β’ Liquidity: π΄ Low (May have slippage)\n`
}
if (volumeNum > 500000) {
response += `β’ Activity: π’ Very Active\n`
} else if (volumeNum > 50000) {
response += `β’ Activity: π‘ Moderate\n`
} else {
response += `β’ Activity: π΄ Low Volume\n`
}
return {
content: [{ type: "text", text: response }],
}
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
isError: true,
}
}
}
)
// ==========================================================================
// PROMPTS - Common use cases
// ==========================================================================
server.registerPrompt(
"analyze_market",
{
title: "Analyze Specific Market",
description: "Get comprehensive analysis of a specific Polymarket prediction market",
argsSchema: {
market_slug: z.string().describe("The market slug (e.g., trump-wins-2024)"),
},
},
async ({ market_slug }) => {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please analyze the Polymarket prediction market "${market_slug}". Use the analyze_market tool to get comprehensive data including probabilities, trading activity, market health, and sentiment. Provide insights on what the market is predicting and how confident traders are.`,
},
},
],
}
}
)
server.registerPrompt(
"find_trending",
{
title: "Find Trending Markets",
description: "Discover the most active and high-volume prediction markets",
argsSchema: {
category: z.string().optional().describe("Optional category/tag to filter by (e.g., politics, sports, crypto)"),
},
},
async ({ category }) => {
const categoryText = category ? ` in the ${category} category` : ""
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Find the most active and trending prediction markets${categoryText}. Use search_markets with appropriate filters to find high-volume, high-liquidity markets that are currently active. Order by volume and show me the top 10. For each market, provide the current probability, volume, and a brief analysis of what it's predicting.`,
},
},
],
}
}
)
server.registerPrompt(
"compare_event",
{
title: "Compare Markets in Event",
description: "Analyze and compare all markets within a specific event",
argsSchema: {
event_slug: z.string().describe("The event slug (e.g., presidential-election-2024)"),
},
},
async ({ event_slug }) => {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Analyze the Polymarket event "${event_slug}" and compare all markets within it. Use get_event to retrieve the event and all its markets. For each market, show the current probabilities and trading volume. Identify any interesting patterns or contradictions between related markets. Summarize the overall prediction for this event.`,
},
},
],
}
}
)
server.registerPrompt(
"market_discovery",
{
title: "Discover Markets by Category",
description: "Explore prediction markets in a specific category or topic",
argsSchema: {
category: z.string().describe("Category or topic to explore (e.g., politics, sports, crypto, economics)"),
},
},
async ({ category }) => {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Help me discover prediction markets related to "${category}". First, use list_tags to find relevant category tags. Then use search_markets with the appropriate tag_id to find active markets in this category. Show me the most interesting markets with their current probabilities, volume, and what they're predicting. Highlight any markets with strong consensus (>75% probability) or divided opinion (<60% probability).`,
},
},
],
}
}
)
// ==========================================================================
// RESOURCES - Expose useful market data
// ==========================================================================
server.registerResource(
"trending-markets",
"polymarket://trending",
{
title: "Trending Markets",
description: "Currently trending prediction markets with high volume and activity",
mimeType: "application/json",
},
async () => {
try {
const markets = await client.searchMarkets({
limit: 20,
closed: false,
order: "volume24hr",
ascending: false,
})
return {
contents: [
{
uri: "polymarket://trending",
mimeType: "application/json",
text: JSON.stringify(markets, null, 2),
},
],
}
} catch (error) {
return {
contents: [
{
uri: "polymarket://trending",
mimeType: "text/plain",
text: `Error fetching trending markets: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
}
}
}
)
server.registerResource(
"market-categories",
"polymarket://categories",
{
title: "Market Categories",
description: "All available categories/tags for filtering Polymarket prediction markets",
mimeType: "application/json",
},
async () => {
try {
const tags = await client.listTags({ limit: 100 })
return {
contents: [
{
uri: "polymarket://categories",
mimeType: "application/json",
text: JSON.stringify(tags, null, 2),
},
],
}
} catch (error) {
return {
contents: [
{
uri: "polymarket://categories",
mimeType: "text/plain",
text: `Error fetching categories: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
}
}
}
)
server.registerResource(
"featured-events",
"polymarket://featured",
{
title: "Featured Events",
description: "Featured prediction market events with multiple related markets",
mimeType: "application/json",
},
async () => {
try {
const events = await client.searchEvents({
limit: 10,
featured: true,
closed: false,
})
return {
contents: [
{
uri: "polymarket://featured",
mimeType: "application/json",
text: JSON.stringify(events, null, 2),
},
],
}
} catch (error) {
return {
contents: [
{
uri: "polymarket://featured",
mimeType: "text/plain",
text: `Error fetching featured events: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
}
}
}
)
return server.server
}