@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.
- package/build/tools/index.js +2 -0
- package/build/tools/markets.js +50 -11
- package/build/tools/news.d.ts +2 -0
- package/build/tools/news.js +94 -0
- package/build/types.d.ts +5 -0
- package/package.json +1 -1
package/build/tools/index.js
CHANGED
|
@@ -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");
|
package/build/tools/markets.js
CHANGED
|
@@ -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
|
|
60
|
-
const prices = market.outcomePrices
|
|
61
|
-
const tokenIds = market.clobTokenIds
|
|
62
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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: "
|
|
156
|
+
text: "Provide one of: condition_id, slug, or url",
|
|
118
157
|
},
|
|
119
158
|
],
|
|
120
159
|
isError: true,
|
|
@@ -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(/&/g, "&")
|
|
52
|
+
.replace(/</g, "<")
|
|
53
|
+
.replace(/>/g, ">")
|
|
54
|
+
.replace(/"/g, '"')
|
|
55
|
+
.replace(/'/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