@c0pilot/mcp-polymarket 1.0.2 → 1.0.3
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/client.d.ts +1 -0
- package/build/client.js +4 -0
- package/build/tools/account.js +98 -45
- package/build/tools/markets.js +41 -5
- package/build/tools/trading.js +70 -4
- package/build/types.d.ts +6 -0
- package/package.json +1 -1
package/build/client.d.ts
CHANGED
package/build/client.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Wallet } from "ethers";
|
|
|
3
3
|
import { getConfig } from "./config.js";
|
|
4
4
|
const CLOB_API_URL = "https://clob.polymarket.com";
|
|
5
5
|
const GAMMA_API_URL = "https://gamma-api.polymarket.com";
|
|
6
|
+
const DATA_API_URL = "https://data-api.polymarket.com";
|
|
6
7
|
export class ClobClientWrapper {
|
|
7
8
|
client = null;
|
|
8
9
|
config;
|
|
@@ -67,6 +68,9 @@ export class ClobClientWrapper {
|
|
|
67
68
|
getClobApiUrl() {
|
|
68
69
|
return CLOB_API_URL;
|
|
69
70
|
}
|
|
71
|
+
getDataApiUrl() {
|
|
72
|
+
return DATA_API_URL;
|
|
73
|
+
}
|
|
70
74
|
getFunder() {
|
|
71
75
|
return this.config.funder;
|
|
72
76
|
}
|
package/build/tools/account.js
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AssetType } from "@polymarket/clob-client";
|
|
3
3
|
const GetBalanceSchema = z.object({});
|
|
4
|
-
const GetPositionsSchema = z.object({
|
|
4
|
+
const GetPositionsSchema = z.object({
|
|
5
|
+
redeemable: z.boolean().optional().default(false),
|
|
6
|
+
market: z.string().optional(),
|
|
7
|
+
limit: z.number().min(1).max(500).optional().default(100),
|
|
8
|
+
});
|
|
9
|
+
const UpdateAllowanceSchema = z.object({});
|
|
10
|
+
async function fetchPositions(clientWrapper, redeemable, market, limit = 100) {
|
|
11
|
+
const baseUrl = clientWrapper.getDataApiUrl();
|
|
12
|
+
const funder = clientWrapper.getFunder();
|
|
13
|
+
const params = new URLSearchParams({
|
|
14
|
+
user: funder,
|
|
15
|
+
sizeThreshold: "0",
|
|
16
|
+
limit: limit.toString(),
|
|
17
|
+
sortBy: "CURRENT",
|
|
18
|
+
sortDirection: "DESC",
|
|
19
|
+
});
|
|
20
|
+
if (redeemable) {
|
|
21
|
+
params.set("redeemable", "true");
|
|
22
|
+
}
|
|
23
|
+
if (market) {
|
|
24
|
+
params.set("market", market);
|
|
25
|
+
}
|
|
26
|
+
const url = `${baseUrl}/positions?${params.toString()}`;
|
|
27
|
+
const response = await fetch(url);
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
throw new Error(`Failed to fetch positions: ${response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return Array.isArray(data) ? data : [];
|
|
33
|
+
}
|
|
5
34
|
export function registerAccountTools(server, clientWrapper) {
|
|
6
35
|
server.tool("polymarket_get_balance", "Get the USDC balance and allowance for the configured wallet on Polymarket.", GetBalanceSchema.shape, async () => {
|
|
7
36
|
try {
|
|
8
37
|
const client = clientWrapper.getClient();
|
|
9
38
|
const funder = clientWrapper.getFunder();
|
|
10
|
-
// Get balance allowance for USDC collateral
|
|
11
39
|
const balanceData = await client.getBalanceAllowance({
|
|
12
40
|
asset_type: AssetType.COLLATERAL,
|
|
13
41
|
});
|
|
@@ -40,55 +68,80 @@ export function registerAccountTools(server, clientWrapper) {
|
|
|
40
68
|
};
|
|
41
69
|
}
|
|
42
70
|
});
|
|
43
|
-
server.tool("polymarket_get_positions",
|
|
71
|
+
server.tool("polymarket_get_positions", `Get real positions (token holdings) for the configured wallet from Polymarket Data API.
|
|
72
|
+
|
|
73
|
+
Returns actual token balances with P&L, not just open orders.
|
|
74
|
+
|
|
75
|
+
**Parameters:**
|
|
76
|
+
- redeemable: Filter to only show redeemable (resolved) positions (default: false)
|
|
77
|
+
- market: Filter by condition ID (optional)
|
|
78
|
+
- limit: Max results 1-500 (default: 100)
|
|
79
|
+
|
|
80
|
+
**Response includes:**
|
|
81
|
+
- token_id, condition_id, outcome, size, avg_price, current_price
|
|
82
|
+
- pnl (cash P&L), pnl_percent
|
|
83
|
+
- redeemable/mergeable flags
|
|
84
|
+
- market title, slug, end_date`, GetPositionsSchema.shape, async (args) => {
|
|
44
85
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const tokenId = order.asset_id;
|
|
62
|
-
const existing = positionMap.get(tokenId);
|
|
63
|
-
if (!existing) {
|
|
64
|
-
positionMap.set(tokenId, {
|
|
65
|
-
token_id: tokenId,
|
|
66
|
-
market: order.market || "Unknown",
|
|
67
|
-
outcome: order.outcome || "Unknown",
|
|
68
|
-
size: order.original_size,
|
|
69
|
-
avg_price: order.price,
|
|
70
|
-
current_price: order.price,
|
|
71
|
-
pnl: "0",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const positions = Array.from(positionMap.values());
|
|
76
|
-
// Also return raw open orders for transparency
|
|
77
|
-
const formattedOrders = openOrders.map((o) => ({
|
|
78
|
-
id: o.id,
|
|
79
|
-
token_id: o.asset_id,
|
|
80
|
-
side: o.side,
|
|
81
|
-
price: o.price,
|
|
82
|
-
size: o.original_size,
|
|
83
|
-
filled: o.size_matched,
|
|
86
|
+
const { redeemable, market, limit } = GetPositionsSchema.parse(args);
|
|
87
|
+
const positions = await fetchPositions(clientWrapper, redeemable, market, limit);
|
|
88
|
+
const formatted = positions.map((p) => ({
|
|
89
|
+
token_id: p.asset,
|
|
90
|
+
condition_id: p.conditionId,
|
|
91
|
+
market: p.title || "Unknown",
|
|
92
|
+
outcome: p.outcome || "Unknown",
|
|
93
|
+
size: p.size,
|
|
94
|
+
avg_price: p.avgPrice,
|
|
95
|
+
current_price: p.curPrice,
|
|
96
|
+
pnl: p.cashPnl,
|
|
97
|
+
pnl_percent: p.percentPnl,
|
|
98
|
+
redeemable: p.redeemable,
|
|
99
|
+
mergeable: p.mergeable,
|
|
100
|
+
slug: p.slug || "",
|
|
101
|
+
end_date: p.endDate || "",
|
|
84
102
|
}));
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({ positions: formatted, count: formatted.length }, null, 2),
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `Error fetching positions: ${message}`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
server.tool("polymarket_update_allowance", `Trigger a refresh of the USDC allowance for trading on Polymarket.
|
|
126
|
+
|
|
127
|
+
Use this when orders fail due to insufficient allowance. This tells the CLOB server to re-check and update the on-chain allowance state.`, UpdateAllowanceSchema.shape, async () => {
|
|
128
|
+
try {
|
|
129
|
+
const client = clientWrapper.getClient();
|
|
130
|
+
await client.updateBalanceAllowance({
|
|
131
|
+
asset_type: AssetType.COLLATERAL,
|
|
132
|
+
});
|
|
133
|
+
// Fetch updated balance to confirm
|
|
134
|
+
const balanceData = await client.getBalanceAllowance({
|
|
135
|
+
asset_type: AssetType.COLLATERAL,
|
|
136
|
+
});
|
|
85
137
|
return {
|
|
86
138
|
content: [
|
|
87
139
|
{
|
|
88
140
|
type: "text",
|
|
89
141
|
text: JSON.stringify({
|
|
90
|
-
|
|
91
|
-
|
|
142
|
+
status: "updated",
|
|
143
|
+
balance: balanceData.balance || "0",
|
|
144
|
+
allowance: balanceData.allowance || "0",
|
|
92
145
|
}, null, 2),
|
|
93
146
|
},
|
|
94
147
|
],
|
|
@@ -100,7 +153,7 @@ export function registerAccountTools(server, clientWrapper) {
|
|
|
100
153
|
content: [
|
|
101
154
|
{
|
|
102
155
|
type: "text",
|
|
103
|
-
text: `Error
|
|
156
|
+
text: `Error updating allowance: ${message}`,
|
|
104
157
|
},
|
|
105
158
|
],
|
|
106
159
|
isError: true,
|
package/build/tools/markets.js
CHANGED
|
@@ -5,7 +5,8 @@ const GetMarketsSchema = z.object({
|
|
|
5
5
|
search: z.string().optional(),
|
|
6
6
|
});
|
|
7
7
|
const GetMarketSchema = z.object({
|
|
8
|
-
condition_id: z.string().
|
|
8
|
+
condition_id: z.string().optional(),
|
|
9
|
+
slug: z.string().optional(),
|
|
9
10
|
});
|
|
10
11
|
async function fetchGammaMarkets(clientWrapper, limit, offset, search) {
|
|
11
12
|
const baseUrl = clientWrapper.getGammaApiUrl();
|
|
@@ -37,6 +38,22 @@ async function fetchGammaMarket(clientWrapper, conditionId) {
|
|
|
37
38
|
}
|
|
38
39
|
return await response.json();
|
|
39
40
|
}
|
|
41
|
+
async function fetchGammaMarketBySlug(clientWrapper, slug) {
|
|
42
|
+
const baseUrl = clientWrapper.getGammaApiUrl();
|
|
43
|
+
const params = new URLSearchParams({
|
|
44
|
+
slug: slug,
|
|
45
|
+
limit: "1",
|
|
46
|
+
});
|
|
47
|
+
const url = `${baseUrl}/markets?${params.toString()}`;
|
|
48
|
+
const response = await fetch(url);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
throw new Error(`Failed to fetch market by slug: ${response.statusText}`);
|
|
51
|
+
}
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
if (!Array.isArray(data) || data.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
return data[0];
|
|
56
|
+
}
|
|
40
57
|
function formatMarket(market) {
|
|
41
58
|
const tokens = [];
|
|
42
59
|
const outcomes = market.outcomes || ["Yes", "No"];
|
|
@@ -87,16 +104,35 @@ export function registerMarketTools(server, clientWrapper) {
|
|
|
87
104
|
};
|
|
88
105
|
}
|
|
89
106
|
});
|
|
90
|
-
server.tool("polymarket_get_market",
|
|
107
|
+
server.tool("polymarket_get_market", `Get detailed information about a specific prediction market including token IDs, current prices, and market status.
|
|
108
|
+
|
|
109
|
+
Provide either condition_id or slug to look up a market.`, GetMarketSchema.shape, async (args) => {
|
|
91
110
|
try {
|
|
92
|
-
const { condition_id } = GetMarketSchema.parse(args);
|
|
93
|
-
|
|
111
|
+
const { condition_id, slug } = GetMarketSchema.parse(args);
|
|
112
|
+
if (!condition_id && !slug) {
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: "Either condition_id or slug is required",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
let market = null;
|
|
124
|
+
if (condition_id) {
|
|
125
|
+
market = await fetchGammaMarket(clientWrapper, condition_id);
|
|
126
|
+
}
|
|
127
|
+
else if (slug) {
|
|
128
|
+
market = await fetchGammaMarketBySlug(clientWrapper, slug);
|
|
129
|
+
}
|
|
94
130
|
if (!market) {
|
|
95
131
|
return {
|
|
96
132
|
content: [
|
|
97
133
|
{
|
|
98
134
|
type: "text",
|
|
99
|
-
text: `Market not found: ${condition_id}`,
|
|
135
|
+
text: `Market not found: ${condition_id || slug}`,
|
|
100
136
|
},
|
|
101
137
|
],
|
|
102
138
|
isError: true,
|
package/build/tools/trading.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Side as ClobSide } from "@polymarket/clob-client";
|
|
2
|
+
import { Side as ClobSide, OrderType } from "@polymarket/clob-client";
|
|
3
3
|
const PlaceOrderSchema = z.object({
|
|
4
4
|
token_id: z.string().min(1),
|
|
5
5
|
side: z.enum(["BUY", "SELL"]),
|
|
@@ -12,6 +12,15 @@ const PlaceOrderSchema = z.object({
|
|
|
12
12
|
return !isNaN(num) && num > 0 && num < 1;
|
|
13
13
|
}, "Price must be between 0 and 1 (exclusive)"),
|
|
14
14
|
});
|
|
15
|
+
const PlaceMarketOrderSchema = z.object({
|
|
16
|
+
token_id: z.string().min(1),
|
|
17
|
+
side: z.enum(["BUY", "SELL"]),
|
|
18
|
+
amount: z.string().refine((val) => {
|
|
19
|
+
const num = parseFloat(val);
|
|
20
|
+
return !isNaN(num) && num > 0;
|
|
21
|
+
}, "Amount must be a positive number"),
|
|
22
|
+
order_type: z.enum(["FOK", "FAK"]).optional().default("FOK"),
|
|
23
|
+
});
|
|
15
24
|
const CancelOrderSchema = z.object({
|
|
16
25
|
order_id: z.string().min(1),
|
|
17
26
|
});
|
|
@@ -21,7 +30,6 @@ const GetTradesSchema = z.object({
|
|
|
21
30
|
async function getMarketInfoForToken(clientWrapper, tokenId) {
|
|
22
31
|
const client = clientWrapper.getClient();
|
|
23
32
|
try {
|
|
24
|
-
// The CLOB client should have a method to get market info
|
|
25
33
|
const marketInfo = (await client.getMarket(tokenId));
|
|
26
34
|
return {
|
|
27
35
|
tickSize: marketInfo.minimum_tick_size || 0.01,
|
|
@@ -29,7 +37,6 @@ async function getMarketInfoForToken(clientWrapper, tokenId) {
|
|
|
29
37
|
};
|
|
30
38
|
}
|
|
31
39
|
catch {
|
|
32
|
-
// Default values if we can't fetch market info
|
|
33
40
|
return {
|
|
34
41
|
tickSize: 0.01,
|
|
35
42
|
negRisk: false,
|
|
@@ -80,7 +87,7 @@ export function registerTradingTools(server, clientWrapper, includeWriteTools) {
|
|
|
80
87
|
});
|
|
81
88
|
// Only register write tools if not in readonly mode
|
|
82
89
|
if (!includeWriteTools) {
|
|
83
|
-
console.error("Readonly mode: trading tools (place_order, cancel_order) disabled");
|
|
90
|
+
console.error("Readonly mode: trading tools (place_order, cancel_order, etc.) disabled");
|
|
84
91
|
return;
|
|
85
92
|
}
|
|
86
93
|
server.tool("polymarket_place_order", "Place a limit order on Polymarket. CAUTION: This executes a real trade with real funds. Price must be between 0 and 1, size in shares.", PlaceOrderSchema.shape, async (args) => {
|
|
@@ -138,6 +145,65 @@ export function registerTradingTools(server, clientWrapper, includeWriteTools) {
|
|
|
138
145
|
};
|
|
139
146
|
}
|
|
140
147
|
});
|
|
148
|
+
server.tool("polymarket_place_market_order", `Place a market order on Polymarket for immediate execution.
|
|
149
|
+
|
|
150
|
+
CAUTION: This executes a REAL trade with REAL funds at market price!
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
- token_id: The token to trade
|
|
154
|
+
- side: "BUY" or "SELL"
|
|
155
|
+
- amount: For BUY — USD amount to spend. For SELL — number of shares to sell.
|
|
156
|
+
- order_type: "FOK" (Fill or Kill, default) or "FAK" (Fill and Kill — allows partial fills)
|
|
157
|
+
|
|
158
|
+
**Examples:**
|
|
159
|
+
- BUY $10 worth of Yes tokens: side="BUY", amount="10"
|
|
160
|
+
- SELL 5 shares at market: side="SELL", amount="5"`, PlaceMarketOrderSchema.shape, async (args) => {
|
|
161
|
+
try {
|
|
162
|
+
clientWrapper.ensureWriteAccess();
|
|
163
|
+
const { token_id, side, amount, order_type } = PlaceMarketOrderSchema.parse(args);
|
|
164
|
+
const client = clientWrapper.getClient();
|
|
165
|
+
const marketOrderType = order_type === "FAK" ? OrderType.FAK : OrderType.FOK;
|
|
166
|
+
const orderArgs = {
|
|
167
|
+
tokenID: token_id,
|
|
168
|
+
side: mapSide(side),
|
|
169
|
+
amount: parseFloat(amount),
|
|
170
|
+
orderType: marketOrderType,
|
|
171
|
+
};
|
|
172
|
+
const signedOrder = await client.createMarketOrder(orderArgs);
|
|
173
|
+
const response = (await client.postOrder(signedOrder, orderArgs.orderType));
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: "text",
|
|
178
|
+
text: JSON.stringify({
|
|
179
|
+
order_id: response.orderID || "",
|
|
180
|
+
status: response.status || "unknown",
|
|
181
|
+
message: response.errorMsg,
|
|
182
|
+
order_details: {
|
|
183
|
+
token_id,
|
|
184
|
+
side,
|
|
185
|
+
amount,
|
|
186
|
+
order_type,
|
|
187
|
+
type: "MARKET",
|
|
188
|
+
},
|
|
189
|
+
}, null, 2),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
196
|
+
return {
|
|
197
|
+
content: [
|
|
198
|
+
{
|
|
199
|
+
type: "text",
|
|
200
|
+
text: `Error placing market order: ${message}`,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
isError: true,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
141
207
|
server.tool("polymarket_cancel_order", "Cancel an existing order on Polymarket.", CancelOrderSchema.shape, async (args) => {
|
|
142
208
|
try {
|
|
143
209
|
clientWrapper.ensureWriteAccess();
|
package/build/types.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface Position {
|
|
|
29
29
|
avg_price: string;
|
|
30
30
|
current_price: string;
|
|
31
31
|
pnl: string;
|
|
32
|
+
pnl_percent: string;
|
|
33
|
+
redeemable: boolean;
|
|
34
|
+
mergeable: boolean;
|
|
35
|
+
condition_id: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
end_date: string;
|
|
32
38
|
}
|
|
33
39
|
export interface TradeInfo {
|
|
34
40
|
id: string;
|
package/package.json
CHANGED