/**
* Validation script for all MCP tools.
* Run: npx pnpm tsx test/validate-tools.ts
*/
const GAMMA_BASE = "https://gamma-api.polymarket.com";
const CLOB_BASE = "https://clob.polymarket.com";
let passed = 0;
let failed = 0;
async function test(name: string, fn: () => Promise<void>) {
try {
await fn();
passed++;
console.log(` ✓ ${name}`);
} catch (e: any) {
failed++;
console.log(` ✗ ${name}: ${e.message}`);
}
}
function assert(cond: boolean, msg: string) {
if (!cond) throw new Error(msg);
}
async function fetchJSON(url: string) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
async function main() {
console.log("Validating Polymarket MCP tools...\n");
// 1. Search markets
console.log("Search & Discovery:");
await test("search_markets returns results", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?_q=bitcoin&limit=5&active=true&closed=false`);
assert(Array.isArray(data), "Expected array");
assert(data.length > 0, "Expected results");
assert(data[0].question, "Expected question field");
});
// 2. List active markets
await test("list_active_markets returns sorted results", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?limit=5&closed=false&order=volume24hr&ascending=false`);
assert(Array.isArray(data), "Expected array");
assert(data.length > 0, "Expected results");
// Check descending volume
for (let i = 1; i < data.length; i++) {
const prev = Number.parseFloat(data[i - 1].volume24hr ?? "0");
const curr = Number.parseFloat(data[i].volume24hr ?? "0");
assert(prev >= curr, `Volume not descending: ${prev} < ${curr}`);
}
});
// 3. Get market by slug
await test("get_market_by_slug returns valid market", async () => {
// First get a real slug
const markets = await fetchJSON(`${GAMMA_BASE}/markets?limit=1&closed=false&order=volume24hr&ascending=false`);
const slug = markets[0].slug;
const data = await fetchJSON(`${GAMMA_BASE}/markets?slug=${slug}`);
assert(Array.isArray(data) && data.length > 0, "Expected market");
assert(data[0].slug === slug, "Slug mismatch");
});
// 4. Get all tags
await test("get_all_tags returns tags", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/tags`);
assert(Array.isArray(data), "Expected array");
assert(data.length > 0, "Expected tags");
});
// 5. Trending markets (volume sorted)
console.log("\nMarket Discovery:");
await test("get_trending_markets returns high-volume markets", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?limit=5&order=volume24hr&ascending=false&active=true&closed=false`);
assert(data.length > 0, "Expected results");
assert(Number.parseFloat(data[0].volume24hr ?? "0") > 0, "Expected non-zero volume");
});
// 6. Closing soon markets
await test("get_closing_soon_markets returns future endDates", async () => {
const now = new Date().toISOString();
const future = new Date(Date.now() + 48 * 3600 * 1000).toISOString();
const data = await fetchJSON(
`${GAMMA_BASE}/markets?limit=5&order=endDate&ascending=true&active=true&closed=false&end_date_min=${now}&end_date_max=${future}`,
);
// May be empty if no markets closing soon, that's ok
if (data.length > 0) {
assert(new Date(data[0].endDate) > new Date(), "endDate should be in future");
}
});
// 7. Featured markets
await test("get_featured_markets returns results", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?limit=5&order=volume24hr&ascending=false&active=true&closed=false`);
assert(data.length > 0, "Expected featured/popular markets");
});
// 8. Category filter
await test("filter_markets_by_category works", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?limit=5&tag_slug=politics&order=volume24hr&ascending=false&active=true&closed=false`);
assert(Array.isArray(data), "Expected array");
});
// 9. Compare markets
await test("compare_markets fetches multiple", async () => {
const markets = await fetchJSON(`${GAMMA_BASE}/markets?limit=2&closed=false&order=volume24hr&ascending=false`);
assert(markets.length >= 2, "Need at least 2 markets");
const [m1, m2] = markets;
assert(m1.slug && m2.slug, "Markets should have slugs");
});
// 10. CLOB order book
console.log("\nMarket Analysis:");
await test("get_order_book returns book data", async () => {
// Get a token ID from an active market
const markets = await fetchJSON(`${GAMMA_BASE}/markets?limit=1&closed=false&order=volume24hr&ascending=false`);
const tokenIds = JSON.parse(markets[0].clobTokenIds);
const tokenId = tokenIds[0];
const book = await fetchJSON(`${CLOB_BASE}/book?token_id=${encodeURIComponent(tokenId)}`);
assert(book.bids || book.asks, "Expected bids or asks");
});
// 11. CLOB price
await test("get_current_price returns price", async () => {
const markets = await fetchJSON(`${GAMMA_BASE}/markets?limit=1&closed=false&order=volume24hr&ascending=false`);
const tokenIds = JSON.parse(markets[0].clobTokenIds);
const tokenId = tokenIds[0];
const price = await fetchJSON(`${CLOB_BASE}/price?token_id=${encodeURIComponent(tokenId)}&side=buy`);
assert(price.price !== undefined, "Expected price field");
});
// 12. Freshness check
console.log("\nFreshness:");
await test("active markets have recent createdAt", async () => {
const data = await fetchJSON(`${GAMMA_BASE}/markets?limit=5&closed=false&order=volume24hr&ascending=false`);
const cutoff = new Date("2024-01-01");
// At least some should be created after 2024
const recent = data.filter((m: any) => new Date(m.createdAt) > cutoff);
assert(recent.length > 0, "Expected markets created after 2024");
});
// 13. Rate limit test
console.log("\nRate Limiting:");
await test("rapid sequential calls don't error", async () => {
const promises = Array.from({ length: 5 }, () =>
fetchJSON(`${GAMMA_BASE}/markets?limit=1&closed=false`),
);
const results = await Promise.allSettled(promises);
const successes = results.filter((r) => r.status === "fulfilled");
assert(successes.length >= 3, `Only ${successes.length}/5 succeeded`);
});
// Summary
console.log(`\n${"=".repeat(40)}`);
console.log(`Results: ${passed} passed, ${failed} failed`);
if (failed > 0) process.exit(1);
}
main().catch((e) => {
console.error("Fatal:", e);
process.exit(1);
});