@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 CHANGED
@@ -10,6 +10,7 @@ export declare class ClobClientWrapper {
10
10
  ensureWriteAccess(): void;
11
11
  getGammaApiUrl(): string;
12
12
  getClobApiUrl(): string;
13
+ getDataApiUrl(): string;
13
14
  getFunder(): string;
14
15
  }
15
16
  export declare function getClientWrapper(): Promise<ClobClientWrapper>;
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
  }
@@ -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", "Get all open orders and positions for the configured wallet, including P&L calculations.", GetPositionsSchema.shape, async () => {
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 client = clientWrapper.getClient();
46
- // Get open orders to derive positions
47
- const openOrders = (await client.getOpenOrders());
48
- if (!openOrders || openOrders.length === 0) {
49
- return {
50
- content: [
51
- {
52
- type: "text",
53
- text: JSON.stringify({ positions: [], open_orders: [] }, null, 2),
54
- },
55
- ],
56
- };
57
- }
58
- // Group orders by token to calculate positions
59
- const positionMap = new Map();
60
- for (const order of openOrders) {
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
- positions,
91
- open_orders: formattedOrders,
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 fetching positions: ${message}`,
156
+ text: `Error updating allowance: ${message}`,
104
157
  },
105
158
  ],
106
159
  isError: true,
@@ -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().min(1),
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", "Get detailed information about a specific prediction market including token IDs, current prices, and market status.", GetMarketSchema.shape, async (args) => {
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
- const market = await fetchGammaMarket(clientWrapper, condition_id);
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,
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c0pilot/mcp-polymarket",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",