@c0pilot/mcp-polymarket 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ import { registerMarketTools } from "./markets.js";
2
2
  import { registerOrderbookTools } from "./orderbook.js";
3
3
  import { registerAccountTools } from "./account.js";
4
4
  import { registerTradingTools } from "./trading.js";
5
+ import { registerNewsTools } from "./news.js";
5
6
  export function registerAllTools(server, clientWrapper) {
6
7
  const isReadonly = clientWrapper.isReadonly();
7
8
  console.error(`Registering tools (readonly: ${isReadonly})`);
@@ -9,6 +10,7 @@ export function registerAllTools(server, clientWrapper) {
9
10
  registerMarketTools(server, clientWrapper);
10
11
  registerOrderbookTools(server, clientWrapper);
11
12
  registerAccountTools(server, clientWrapper);
13
+ registerNewsTools(server);
12
14
  // Register trading tools (write tools are conditionally included)
13
15
  registerTradingTools(server, clientWrapper, !isReadonly);
14
16
  console.error("All tools registered successfully");
@@ -7,13 +7,33 @@ const GetMarketsSchema = z.object({
7
7
  const GetMarketSchema = z.object({
8
8
  condition_id: z.string().optional(),
9
9
  slug: z.string().optional(),
10
+ url: z.string().optional(),
10
11
  });
12
+ /**
13
+ * Gamma API sometimes returns array fields as JSON strings instead of arrays.
14
+ * e.g. clobTokenIds: '["abc","def"]' instead of ["abc","def"]
15
+ */
16
+ function ensureArray(val) {
17
+ if (Array.isArray(val))
18
+ return val;
19
+ if (typeof val === "string") {
20
+ try {
21
+ const parsed = JSON.parse(val);
22
+ if (Array.isArray(parsed))
23
+ return parsed;
24
+ }
25
+ catch { /* not valid JSON */ }
26
+ }
27
+ return [];
28
+ }
11
29
  async function fetchGammaMarkets(clientWrapper, limit, offset, search) {
12
30
  const baseUrl = clientWrapper.getGammaApiUrl();
13
31
  const params = new URLSearchParams({
14
32
  limit: limit.toString(),
15
33
  offset: offset.toString(),
16
34
  active: "true",
35
+ order: "volume24hr",
36
+ ascending: "false",
17
37
  });
18
38
  if (search) {
19
39
  params.set("slug_contains", search.toLowerCase());
@@ -54,30 +74,45 @@ async function fetchGammaMarketBySlug(clientWrapper, slug) {
54
74
  return null;
55
75
  return data[0];
56
76
  }
77
+ /**
78
+ * Extract a Polymarket event slug from a URL.
79
+ * Handles: https://polymarket.com/event/slug, polymarket.com/event/slug?params, raw slug
80
+ */
81
+ function extractSlugFromUrl(url) {
82
+ const match = url.match(/(?:polymarket\.com\/event\/)([^/?#]+)/);
83
+ return match ? match[1] : url.replace(/^\/+/, "");
84
+ }
57
85
  function formatMarket(market) {
58
86
  const tokens = [];
59
- const outcomes = market.outcomes || ["Yes", "No"];
60
- const prices = market.outcomePrices || [];
61
- const tokenIds = market.clobTokenIds || [];
62
- for (let i = 0; i < outcomes.length; i++) {
87
+ const outcomes = ensureArray(market.outcomes);
88
+ const prices = ensureArray(market.outcomePrices);
89
+ const tokenIds = ensureArray(market.clobTokenIds);
90
+ // Fall back to ["Yes", "No"] if no outcomes
91
+ const outcomeNames = outcomes.length > 0 ? outcomes : ["Yes", "No"];
92
+ for (let i = 0; i < outcomeNames.length; i++) {
63
93
  tokens.push({
64
94
  token_id: tokenIds[i] || "",
65
- outcome: outcomes[i],
95
+ outcome: outcomeNames[i],
66
96
  price: prices[i] ? parseFloat(prices[i]) : 0,
67
97
  });
68
98
  }
69
99
  return {
70
100
  condition_id: market.conditionId,
71
101
  question: market.question,
102
+ slug: market.slug,
103
+ url: market.slug ? `https://polymarket.com/event/${market.slug}` : undefined,
104
+ description: market.description ? market.description.slice(0, 500) : undefined,
72
105
  tokens,
73
106
  volume: market.volume || "0",
74
- end_date: market.endDate || "",
107
+ liquidity: market.liquidityNum,
108
+ end_date: market.endDateIso || market.endDate || "",
75
109
  active: market.active,
76
110
  closed: market.closed,
111
+ accepting_orders: market.acceptingOrders,
77
112
  };
78
113
  }
79
114
  export function registerMarketTools(server, clientWrapper) {
80
- server.tool("polymarket_get_markets", "List available prediction markets on Polymarket. Returns market question, current prices for Yes/No outcomes, and trading volume.", GetMarketsSchema.shape, async (args) => {
115
+ server.tool("polymarket_get_markets", "List available prediction markets on Polymarket, sorted by volume. Returns market question, current prices for Yes/No outcomes, token IDs, volume, liquidity, and Polymarket URL.", GetMarketsSchema.shape, async (args) => {
81
116
  try {
82
117
  const { limit, offset, search } = GetMarketsSchema.parse(args);
83
118
  const markets = await fetchGammaMarkets(clientWrapper, limit, offset, search);
@@ -104,17 +139,21 @@ export function registerMarketTools(server, clientWrapper) {
104
139
  };
105
140
  }
106
141
  });
107
- server.tool("polymarket_get_market", `Get detailed information about a specific prediction market including token IDs, current prices, and market status.
142
+ server.tool("polymarket_get_market", `Get detailed information about a specific prediction market including token IDs, current prices, description, liquidity, and market status.
108
143
 
109
- Provide either condition_id or slug to look up a market.`, GetMarketSchema.shape, async (args) => {
144
+ Provide one of: condition_id, slug, or a full Polymarket URL (e.g. https://polymarket.com/event/btc-updown-15m-1770647400).`, GetMarketSchema.shape, async (args) => {
110
145
  try {
111
- const { condition_id, slug } = GetMarketSchema.parse(args);
146
+ let { condition_id, slug, url } = GetMarketSchema.parse(args);
147
+ // Extract slug from URL if provided
148
+ if (url) {
149
+ slug = extractSlugFromUrl(url);
150
+ }
112
151
  if (!condition_id && !slug) {
113
152
  return {
114
153
  content: [
115
154
  {
116
155
  type: "text",
117
- text: "Either condition_id or slug is required",
156
+ text: "Provide one of: condition_id, slug, or url",
118
157
  },
119
158
  ],
120
159
  isError: true,
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerNewsTools(server: McpServer): void;
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ const GetNewsSchema = z.object({
3
+ query: z.string().min(1).describe("News search query (e.g., 'Bitcoin price', 'Trump tariffs', 'NBA playoffs')"),
4
+ limit: z.number().min(1).max(10).optional().default(5),
5
+ });
6
+ /**
7
+ * Fetch news headlines from Google News RSS (free, no API key needed).
8
+ */
9
+ async function fetchNewsHeadlines(query, maxResults) {
10
+ const encoded = encodeURIComponent(query);
11
+ const url = `https://news.google.com/rss/search?q=${encoded}&hl=en&gl=US&ceid=US:en`;
12
+ const response = await fetch(url, {
13
+ signal: AbortSignal.timeout(10_000),
14
+ headers: { "User-Agent": "Mozilla/5.0 (compatible; mcp-polymarket/1.0)" },
15
+ });
16
+ if (!response.ok) {
17
+ throw new Error(`Google News returned ${response.status}`);
18
+ }
19
+ const xml = await response.text();
20
+ return parseRssItems(xml, maxResults);
21
+ }
22
+ function parseRssItems(xml, maxResults) {
23
+ const headlines = [];
24
+ const itemRegex = /<item>([\s\S]*?)<\/item>/g;
25
+ let match;
26
+ while ((match = itemRegex.exec(xml)) !== null && headlines.length < maxResults) {
27
+ const item = match[1];
28
+ const title = extractTag(item, "title");
29
+ const source = extractTag(item, "source");
30
+ const pubDate = extractTag(item, "pubDate");
31
+ const link = extractTag(item, "link");
32
+ if (title) {
33
+ headlines.push({
34
+ title: decodeXmlEntities(title),
35
+ source: source || "Unknown",
36
+ pubDate: pubDate || "",
37
+ link: link || "",
38
+ });
39
+ }
40
+ }
41
+ return headlines;
42
+ }
43
+ function extractTag(xml, tag) {
44
+ const regex = new RegExp(`<${tag}[^>]*>(.*?)</${tag}>`, "s");
45
+ const match = xml.match(regex);
46
+ return match ? match[1] : null;
47
+ }
48
+ function decodeXmlEntities(text) {
49
+ return text
50
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")
51
+ .replace(/&amp;/g, "&")
52
+ .replace(/&lt;/g, "<")
53
+ .replace(/&gt;/g, ">")
54
+ .replace(/&quot;/g, '"')
55
+ .replace(/&#39;/g, "'");
56
+ }
57
+ export function registerNewsTools(server) {
58
+ server.tool("polymarket_get_news", "Fetch recent news headlines for a topic via Google News RSS. Useful for understanding current events and market context when analyzing prediction markets. No API key required.", GetNewsSchema.shape, async (args) => {
59
+ try {
60
+ const { query, limit } = GetNewsSchema.parse(args);
61
+ const headlines = await fetchNewsHeadlines(query, limit);
62
+ if (headlines.length === 0) {
63
+ return {
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: `No recent news found for "${query}"`,
68
+ },
69
+ ],
70
+ };
71
+ }
72
+ return {
73
+ content: [
74
+ {
75
+ type: "text",
76
+ text: JSON.stringify({ count: headlines.length, headlines }, null, 2),
77
+ },
78
+ ],
79
+ };
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ return {
84
+ content: [
85
+ {
86
+ type: "text",
87
+ text: `Error fetching news: ${message}`,
88
+ },
89
+ ],
90
+ isError: true,
91
+ };
92
+ }
93
+ });
94
+ }
package/build/types.d.ts CHANGED
@@ -6,11 +6,16 @@ export interface TokenInfo {
6
6
  export interface MarketInfo {
7
7
  condition_id: string;
8
8
  question: string;
9
+ slug?: string;
10
+ url?: string;
11
+ description?: string;
9
12
  tokens: TokenInfo[];
10
13
  volume: string;
14
+ liquidity?: number;
11
15
  end_date: string;
12
16
  active: boolean;
13
17
  closed: boolean;
18
+ accepting_orders?: boolean;
14
19
  }
15
20
  export interface OrderbookEntry {
16
21
  price: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c0pilot/mcp-polymarket",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "MCP server and client library for Polymarket prediction markets - trade, browse markets, manage positions",
5
5
  "type": "module",
6
6
  "main": "./build/index.js",