@catalyst-team/poly-mcp 0.1.0
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/README.md +317 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +86 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +155 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/guide.d.ts +12 -0
- package/dist/tools/guide.d.ts.map +1 -0
- package/dist/tools/guide.js +801 -0
- package/dist/tools/guide.js.map +1 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +27 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/market.d.ts +11 -0
- package/dist/tools/market.d.ts.map +1 -0
- package/dist/tools/market.js +314 -0
- package/dist/tools/market.js.map +1 -0
- package/dist/tools/order.d.ts +10 -0
- package/dist/tools/order.d.ts.map +1 -0
- package/dist/tools/order.js +258 -0
- package/dist/tools/order.js.map +1 -0
- package/dist/tools/trade.d.ts +38 -0
- package/dist/tools/trade.d.ts.map +1 -0
- package/dist/tools/trade.js +313 -0
- package/dist/tools/trade.js.map +1 -0
- package/dist/tools/trader.d.ts +11 -0
- package/dist/tools/trader.d.ts.map +1 -0
- package/dist/tools/trader.js +277 -0
- package/dist/tools/trader.js.map +1 -0
- package/dist/tools/wallet.d.ts +274 -0
- package/dist/tools/wallet.d.ts.map +1 -0
- package/dist/tools/wallet.js +579 -0
- package/dist/tools/wallet.js.map +1 -0
- package/dist/types.d.ts +413 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/docs/01-mcp.md +2075 -0
- package/package.json +55 -0
- package/src/errors.ts +124 -0
- package/src/index.ts +309 -0
- package/src/server.ts +183 -0
- package/src/tools/guide.ts +821 -0
- package/src/tools/index.ts +73 -0
- package/src/tools/market.ts +363 -0
- package/src/tools/order.ts +326 -0
- package/src/tools/trade.ts +417 -0
- package/src/tools/trader.ts +322 -0
- package/src/tools/wallet.ts +683 -0
- package/src/types.ts +472 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trade Tools - MCP tools for trading execution
|
|
3
|
+
*
|
|
4
|
+
* These tools enable actual trading on Polymarket when private key is configured.
|
|
5
|
+
* API credentials are automatically derived from the private key using createOrDeriveApiKey().
|
|
6
|
+
*
|
|
7
|
+
* Required environment variable:
|
|
8
|
+
* - POLY_PRIVATE_KEY: Wallet private key for signing transactions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PolymarketSDK } from '@catalyst-team/poly-sdk';
|
|
12
|
+
import type { ToolDefinition } from '../types.js';
|
|
13
|
+
import {
|
|
14
|
+
validateConditionId,
|
|
15
|
+
validateOutcome,
|
|
16
|
+
validateSide,
|
|
17
|
+
validatePrice,
|
|
18
|
+
validatePositiveNumber,
|
|
19
|
+
wrapError,
|
|
20
|
+
McpToolError,
|
|
21
|
+
ErrorCode,
|
|
22
|
+
} from '../errors.js';
|
|
23
|
+
import { TradingClient, RateLimiter } from '@catalyst-team/poly-sdk';
|
|
24
|
+
import type { OrderResult } from '@catalyst-team/poly-sdk';
|
|
25
|
+
|
|
26
|
+
// Singleton trading client instance (lazily initialized)
|
|
27
|
+
let tradingClient: TradingClient | null = null;
|
|
28
|
+
let tradingClientInitialized = false;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get or create the TradingClient instance
|
|
32
|
+
* Uses the signer from SDK's clobApi
|
|
33
|
+
*/
|
|
34
|
+
async function getTradingClient(sdk: PolymarketSDK): Promise<TradingClient> {
|
|
35
|
+
// Check if signer is available
|
|
36
|
+
const sdkAny = sdk as unknown as { clobApi?: { signer?: unknown; config?: { chainId?: number } } };
|
|
37
|
+
const signer = sdkAny.clobApi?.signer;
|
|
38
|
+
|
|
39
|
+
if (!signer) {
|
|
40
|
+
throw new McpToolError(
|
|
41
|
+
ErrorCode.AUTH_REQUIRED,
|
|
42
|
+
'Trading requires a private key. Configure POLY_PRIVATE_KEY environment variable.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create trading client if not exists
|
|
47
|
+
if (!tradingClient) {
|
|
48
|
+
const signerAny = signer as { privateKey?: string };
|
|
49
|
+
const privateKey = signerAny.privateKey;
|
|
50
|
+
|
|
51
|
+
if (!privateKey) {
|
|
52
|
+
throw new McpToolError(
|
|
53
|
+
ErrorCode.AUTH_REQUIRED,
|
|
54
|
+
'Cannot extract private key from signer. Ensure POLY_PRIVATE_KEY is configured correctly.'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const chainId = sdkAny.clobApi?.config?.chainId || 137;
|
|
59
|
+
const rateLimiter = new RateLimiter();
|
|
60
|
+
|
|
61
|
+
tradingClient = new TradingClient(rateLimiter, {
|
|
62
|
+
privateKey,
|
|
63
|
+
chainId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize if needed
|
|
68
|
+
if (!tradingClientInitialized) {
|
|
69
|
+
await tradingClient.initialize();
|
|
70
|
+
tradingClientInitialized = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return tradingClient;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const tradeToolDefinitions: ToolDefinition[] = [
|
|
77
|
+
{
|
|
78
|
+
name: 'place_limit_order',
|
|
79
|
+
description: 'Place a limit order on a market (requires API key)',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
conditionId: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
},
|
|
86
|
+
outcome: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
enum: ['Yes', 'No'],
|
|
89
|
+
},
|
|
90
|
+
side: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
enum: ['BUY', 'SELL'],
|
|
93
|
+
},
|
|
94
|
+
price: {
|
|
95
|
+
type: 'number',
|
|
96
|
+
description: 'Limit price (0.001 to 0.999)',
|
|
97
|
+
},
|
|
98
|
+
size: {
|
|
99
|
+
type: 'number',
|
|
100
|
+
description: 'Number of shares',
|
|
101
|
+
},
|
|
102
|
+
orderType: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
enum: ['GTC', 'GTD'],
|
|
105
|
+
default: 'GTC',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
required: ['conditionId', 'outcome', 'side', 'price', 'size'],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'place_market_order',
|
|
113
|
+
description: 'Place a market order on a market (requires API key)',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
conditionId: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
},
|
|
120
|
+
outcome: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
enum: ['Yes', 'No'],
|
|
123
|
+
},
|
|
124
|
+
side: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
enum: ['BUY', 'SELL'],
|
|
127
|
+
},
|
|
128
|
+
amount: {
|
|
129
|
+
type: 'number',
|
|
130
|
+
description: 'Amount in USDC',
|
|
131
|
+
},
|
|
132
|
+
maxSlippage: {
|
|
133
|
+
type: 'number',
|
|
134
|
+
description: 'Maximum acceptable slippage (e.g., 0.02 for 2%)',
|
|
135
|
+
default: 0.02,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
required: ['conditionId', 'outcome', 'side', 'amount'],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'cancel_order',
|
|
143
|
+
description: 'Cancel an open order (requires API key)',
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {
|
|
147
|
+
orderId: {
|
|
148
|
+
type: 'string',
|
|
149
|
+
description: 'Order ID to cancel',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['orderId'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'get_my_orders',
|
|
157
|
+
description: 'Get your open orders (requires API key)',
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
status: {
|
|
162
|
+
type: 'string',
|
|
163
|
+
enum: ['LIVE', 'FILLED', 'CANCELLED'],
|
|
164
|
+
default: 'LIVE',
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// Check if trading is configured (basic check)
|
|
172
|
+
function checkTradingEnabled(sdk: PolymarketSDK): void {
|
|
173
|
+
if (!sdk.clobApi) {
|
|
174
|
+
throw new McpToolError(
|
|
175
|
+
ErrorCode.AUTH_REQUIRED,
|
|
176
|
+
'Trading requires SDK to be properly initialized'
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check if signer is available
|
|
181
|
+
const sdkAny = sdk as unknown as { clobApi?: { signer?: unknown } };
|
|
182
|
+
if (!sdkAny.clobApi?.signer) {
|
|
183
|
+
throw new McpToolError(
|
|
184
|
+
ErrorCode.AUTH_REQUIRED,
|
|
185
|
+
'Trading requires a private key. Configure POLY_PRIVATE_KEY environment variable.'
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface PlaceLimitOrderInput {
|
|
191
|
+
conditionId: string;
|
|
192
|
+
outcome: 'Yes' | 'No';
|
|
193
|
+
side: 'BUY' | 'SELL';
|
|
194
|
+
price: number;
|
|
195
|
+
size: number;
|
|
196
|
+
orderType?: 'GTC' | 'GTD';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface PlaceMarketOrderInput {
|
|
200
|
+
conditionId: string;
|
|
201
|
+
outcome: 'Yes' | 'No';
|
|
202
|
+
side: 'BUY' | 'SELL';
|
|
203
|
+
amount: number;
|
|
204
|
+
maxSlippage?: number;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface CancelOrderInput {
|
|
208
|
+
orderId: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface GetMyOrdersInput {
|
|
212
|
+
status?: 'LIVE' | 'FILLED' | 'CANCELLED';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function handlePlaceLimitOrder(
|
|
216
|
+
sdk: PolymarketSDK,
|
|
217
|
+
input: PlaceLimitOrderInput
|
|
218
|
+
): Promise<unknown> {
|
|
219
|
+
checkTradingEnabled(sdk);
|
|
220
|
+
validateConditionId(input.conditionId);
|
|
221
|
+
validateOutcome(input.outcome);
|
|
222
|
+
validateSide(input.side);
|
|
223
|
+
validatePrice(input.price);
|
|
224
|
+
validatePositiveNumber(input.size, 'size');
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
// Get token ID for the outcome
|
|
228
|
+
const market = await sdk.clobApi.getMarket(input.conditionId);
|
|
229
|
+
const token = market.tokens.find(
|
|
230
|
+
(t) => t.outcome.toLowerCase() === input.outcome.toLowerCase()
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (!token) {
|
|
234
|
+
throw new McpToolError(
|
|
235
|
+
ErrorCode.MARKET_NOT_FOUND,
|
|
236
|
+
`Token for outcome ${input.outcome} not found`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Get trading client (lazily initialized)
|
|
241
|
+
const client = await getTradingClient(sdk);
|
|
242
|
+
|
|
243
|
+
// Place the limit order
|
|
244
|
+
const result: OrderResult = await client.createOrder({
|
|
245
|
+
tokenId: token.tokenId,
|
|
246
|
+
side: input.side,
|
|
247
|
+
price: input.price,
|
|
248
|
+
size: input.size,
|
|
249
|
+
orderType: input.orderType || 'GTC',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
success: result.success,
|
|
254
|
+
orderId: result.orderId,
|
|
255
|
+
orderIds: result.orderIds,
|
|
256
|
+
market: {
|
|
257
|
+
conditionId: input.conditionId,
|
|
258
|
+
question: market.question,
|
|
259
|
+
outcome: input.outcome,
|
|
260
|
+
},
|
|
261
|
+
order: {
|
|
262
|
+
side: input.side,
|
|
263
|
+
price: input.price,
|
|
264
|
+
size: input.size,
|
|
265
|
+
orderType: input.orderType || 'GTC',
|
|
266
|
+
},
|
|
267
|
+
transactionHashes: result.transactionHashes,
|
|
268
|
+
errorMsg: result.errorMsg,
|
|
269
|
+
};
|
|
270
|
+
} catch (err) {
|
|
271
|
+
throw wrapError(err);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function handlePlaceMarketOrder(
|
|
276
|
+
sdk: PolymarketSDK,
|
|
277
|
+
input: PlaceMarketOrderInput
|
|
278
|
+
): Promise<unknown> {
|
|
279
|
+
checkTradingEnabled(sdk);
|
|
280
|
+
validateConditionId(input.conditionId);
|
|
281
|
+
validateOutcome(input.outcome);
|
|
282
|
+
validateSide(input.side);
|
|
283
|
+
validatePositiveNumber(input.amount, 'amount');
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
// Get token ID for the outcome
|
|
287
|
+
const market = await sdk.clobApi.getMarket(input.conditionId);
|
|
288
|
+
const token = market.tokens.find(
|
|
289
|
+
(t) => t.outcome.toLowerCase() === input.outcome.toLowerCase()
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (!token) {
|
|
293
|
+
throw new McpToolError(
|
|
294
|
+
ErrorCode.MARKET_NOT_FOUND,
|
|
295
|
+
`Token for outcome ${input.outcome} not found`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Get trading client (lazily initialized)
|
|
300
|
+
const client = await getTradingClient(sdk);
|
|
301
|
+
|
|
302
|
+
// Get current price for slippage calculation
|
|
303
|
+
const currentPrice = token.price;
|
|
304
|
+
const maxSlippage = input.maxSlippage || 0.02; // Default 2%
|
|
305
|
+
|
|
306
|
+
// Calculate price limit based on side and slippage
|
|
307
|
+
// For BUY: max price we're willing to pay = current * (1 + slippage)
|
|
308
|
+
// For SELL: min price we're willing to accept = current * (1 - slippage)
|
|
309
|
+
const priceLimit = input.side === 'BUY'
|
|
310
|
+
? Math.min(currentPrice * (1 + maxSlippage), 0.999)
|
|
311
|
+
: Math.max(currentPrice * (1 - maxSlippage), 0.001);
|
|
312
|
+
|
|
313
|
+
// Place the market order
|
|
314
|
+
const result: OrderResult = await client.createMarketOrder({
|
|
315
|
+
tokenId: token.tokenId,
|
|
316
|
+
side: input.side,
|
|
317
|
+
amount: input.amount,
|
|
318
|
+
price: priceLimit,
|
|
319
|
+
orderType: 'FOK', // Fill or Kill for market orders
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
success: result.success,
|
|
324
|
+
orderId: result.orderId,
|
|
325
|
+
orderIds: result.orderIds,
|
|
326
|
+
market: {
|
|
327
|
+
conditionId: input.conditionId,
|
|
328
|
+
question: market.question,
|
|
329
|
+
outcome: input.outcome,
|
|
330
|
+
},
|
|
331
|
+
order: {
|
|
332
|
+
side: input.side,
|
|
333
|
+
amount: input.amount,
|
|
334
|
+
priceLimit,
|
|
335
|
+
maxSlippage,
|
|
336
|
+
},
|
|
337
|
+
transactionHashes: result.transactionHashes,
|
|
338
|
+
errorMsg: result.errorMsg,
|
|
339
|
+
};
|
|
340
|
+
} catch (err) {
|
|
341
|
+
throw wrapError(err);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export async function handleCancelOrder(
|
|
346
|
+
sdk: PolymarketSDK,
|
|
347
|
+
input: CancelOrderInput
|
|
348
|
+
): Promise<unknown> {
|
|
349
|
+
checkTradingEnabled(sdk);
|
|
350
|
+
|
|
351
|
+
if (!input.orderId) {
|
|
352
|
+
throw new McpToolError(ErrorCode.INVALID_INPUT, 'Order ID is required');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
// Get trading client (lazily initialized)
|
|
357
|
+
const client = await getTradingClient(sdk);
|
|
358
|
+
|
|
359
|
+
// Cancel the order
|
|
360
|
+
const result: OrderResult = await client.cancelOrder(input.orderId);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
success: result.success,
|
|
364
|
+
orderId: input.orderId,
|
|
365
|
+
message: result.success
|
|
366
|
+
? `Order ${input.orderId} cancelled successfully`
|
|
367
|
+
: `Failed to cancel order ${input.orderId}`,
|
|
368
|
+
};
|
|
369
|
+
} catch (err) {
|
|
370
|
+
throw wrapError(err);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export async function handleGetMyOrders(
|
|
375
|
+
sdk: PolymarketSDK,
|
|
376
|
+
input: GetMyOrdersInput
|
|
377
|
+
): Promise<unknown> {
|
|
378
|
+
checkTradingEnabled(sdk);
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
// Get trading client (lazily initialized)
|
|
382
|
+
const client = await getTradingClient(sdk);
|
|
383
|
+
|
|
384
|
+
// Note: TradingClient only supports getting open (LIVE) orders
|
|
385
|
+
// For FILLED/CANCELLED, we would need to query trades history
|
|
386
|
+
const status = input.status || 'LIVE';
|
|
387
|
+
|
|
388
|
+
if (status !== 'LIVE') {
|
|
389
|
+
// For non-LIVE orders, return a note about limitation
|
|
390
|
+
return {
|
|
391
|
+
orders: [],
|
|
392
|
+
status,
|
|
393
|
+
note: `Currently only LIVE orders are supported. Use get_trader_trades for historical order information.`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Get open orders
|
|
398
|
+
const orders = await client.getOpenOrders();
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
orders: orders.map(o => ({
|
|
402
|
+
id: o.id,
|
|
403
|
+
status: o.status,
|
|
404
|
+
tokenId: o.tokenId,
|
|
405
|
+
side: o.side,
|
|
406
|
+
price: o.price,
|
|
407
|
+
originalSize: o.originalSize,
|
|
408
|
+
remainingSize: o.remainingSize,
|
|
409
|
+
createdAt: o.createdAt,
|
|
410
|
+
})),
|
|
411
|
+
count: orders.length,
|
|
412
|
+
status: 'LIVE',
|
|
413
|
+
};
|
|
414
|
+
} catch (err) {
|
|
415
|
+
throw wrapError(err);
|
|
416
|
+
}
|
|
417
|
+
}
|