@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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Tools - MCP tools for orderbook analysis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PolymarketSDK } from '@catalyst-team/poly-sdk';
|
|
6
|
+
import type {
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
GetOrderbookInput,
|
|
9
|
+
GetOrderbookOutput,
|
|
10
|
+
GetBestPricesInput,
|
|
11
|
+
GetBestPricesOutput,
|
|
12
|
+
EstimateExecutionInput,
|
|
13
|
+
EstimateExecutionOutput,
|
|
14
|
+
} from '../types.js';
|
|
15
|
+
import {
|
|
16
|
+
validateConditionId,
|
|
17
|
+
validateOutcome,
|
|
18
|
+
validateSide,
|
|
19
|
+
validatePositiveNumber,
|
|
20
|
+
wrapError,
|
|
21
|
+
McpToolError,
|
|
22
|
+
ErrorCode,
|
|
23
|
+
} from '../errors.js';
|
|
24
|
+
|
|
25
|
+
export const orderToolDefinitions: ToolDefinition[] = [
|
|
26
|
+
{
|
|
27
|
+
name: 'get_orderbook',
|
|
28
|
+
description: 'Get orderbook depth for a market outcome',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
conditionId: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Market condition ID',
|
|
35
|
+
},
|
|
36
|
+
outcome: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['Yes', 'No'],
|
|
39
|
+
description: "Which outcome's orderbook to fetch",
|
|
40
|
+
},
|
|
41
|
+
depth: {
|
|
42
|
+
type: 'number',
|
|
43
|
+
description: 'Number of price levels',
|
|
44
|
+
default: 10,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['conditionId', 'outcome'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'get_best_prices',
|
|
52
|
+
description: 'Get best bid/ask prices for both outcomes',
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
conditionId: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Market condition ID',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ['conditionId'],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'estimate_execution',
|
|
66
|
+
description: 'Estimate execution price and slippage for a trade',
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
conditionId: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
},
|
|
73
|
+
outcome: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
enum: ['Yes', 'No'],
|
|
76
|
+
},
|
|
77
|
+
side: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
enum: ['BUY', 'SELL'],
|
|
80
|
+
},
|
|
81
|
+
amount: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: 'Amount in USDC',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['conditionId', 'outcome', 'side', 'amount'],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export async function handleGetOrderbook(
|
|
92
|
+
sdk: PolymarketSDK,
|
|
93
|
+
input: GetOrderbookInput
|
|
94
|
+
): Promise<GetOrderbookOutput> {
|
|
95
|
+
validateConditionId(input.conditionId);
|
|
96
|
+
validateOutcome(input.outcome);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Get market to find the token ID
|
|
100
|
+
const market = await sdk.clobApi.getMarket(input.conditionId);
|
|
101
|
+
const isYes = input.outcome === 'Yes';
|
|
102
|
+
const token = market.tokens.find(
|
|
103
|
+
(t) => t.outcome.toLowerCase() === input.outcome.toLowerCase()
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!token) {
|
|
107
|
+
throw new McpToolError(
|
|
108
|
+
ErrorCode.MARKET_NOT_FOUND,
|
|
109
|
+
`Token for outcome ${input.outcome} not found`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Get the raw orderbook for this token
|
|
114
|
+
const orderbook = await sdk.clobApi.getOrderbook(token.tokenId);
|
|
115
|
+
const depth = input.depth || 10;
|
|
116
|
+
|
|
117
|
+
// Get market title
|
|
118
|
+
let marketTitle = input.conditionId;
|
|
119
|
+
try {
|
|
120
|
+
const unifiedMarket = await sdk.getMarket(input.conditionId);
|
|
121
|
+
marketTitle = unifiedMarket.question;
|
|
122
|
+
} catch {
|
|
123
|
+
// Use conditionId as fallback
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Calculate totals for each level
|
|
127
|
+
let bidTotal = 0;
|
|
128
|
+
let askTotal = 0;
|
|
129
|
+
|
|
130
|
+
const bids = orderbook.bids.slice(0, depth).map((level) => {
|
|
131
|
+
bidTotal += level.size * level.price;
|
|
132
|
+
return {
|
|
133
|
+
price: level.price,
|
|
134
|
+
size: level.size,
|
|
135
|
+
total: bidTotal,
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const asks = orderbook.asks.slice(0, depth).map((level) => {
|
|
140
|
+
askTotal += level.size * level.price;
|
|
141
|
+
return {
|
|
142
|
+
price: level.price,
|
|
143
|
+
size: level.size,
|
|
144
|
+
total: askTotal,
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const bestBid = bids[0]?.price || 0;
|
|
149
|
+
const bestAsk = asks[0]?.price || 1;
|
|
150
|
+
const spread = bestAsk - bestBid;
|
|
151
|
+
const spreadPercent = bestBid > 0 ? (spread / bestBid) * 100 : 0;
|
|
152
|
+
|
|
153
|
+
const bidDepth = orderbook.bids.slice(0, depth).reduce((sum, l) => sum + l.size, 0);
|
|
154
|
+
const askDepth = orderbook.asks.slice(0, depth).reduce((sum, l) => sum + l.size, 0);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
market: {
|
|
158
|
+
conditionId: input.conditionId,
|
|
159
|
+
title: marketTitle,
|
|
160
|
+
},
|
|
161
|
+
outcome: input.outcome,
|
|
162
|
+
tokenId: token.tokenId,
|
|
163
|
+
orderbook: {
|
|
164
|
+
bids,
|
|
165
|
+
asks,
|
|
166
|
+
},
|
|
167
|
+
summary: {
|
|
168
|
+
bestBid,
|
|
169
|
+
bestAsk,
|
|
170
|
+
spread,
|
|
171
|
+
spreadPercent,
|
|
172
|
+
bidDepth,
|
|
173
|
+
askDepth,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
} catch (err) {
|
|
177
|
+
throw wrapError(err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function handleGetBestPrices(
|
|
182
|
+
sdk: PolymarketSDK,
|
|
183
|
+
input: GetBestPricesInput
|
|
184
|
+
): Promise<GetBestPricesOutput> {
|
|
185
|
+
validateConditionId(input.conditionId);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const processedOrderbook = await sdk.clobApi.getProcessedOrderbook(input.conditionId);
|
|
189
|
+
|
|
190
|
+
// Get market title
|
|
191
|
+
let marketTitle = input.conditionId;
|
|
192
|
+
try {
|
|
193
|
+
const market = await sdk.getMarket(input.conditionId);
|
|
194
|
+
marketTitle = market.question;
|
|
195
|
+
} catch {
|
|
196
|
+
// Use conditionId as fallback
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const yes = processedOrderbook.yes;
|
|
200
|
+
const no = processedOrderbook.no;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
market: marketTitle,
|
|
204
|
+
yes: {
|
|
205
|
+
bestBid: yes.bid,
|
|
206
|
+
bestAsk: yes.ask,
|
|
207
|
+
midPrice: (yes.bid + yes.ask) / 2,
|
|
208
|
+
spread: yes.spread,
|
|
209
|
+
},
|
|
210
|
+
no: {
|
|
211
|
+
bestBid: no.bid,
|
|
212
|
+
bestAsk: no.ask,
|
|
213
|
+
midPrice: (no.bid + no.ask) / 2,
|
|
214
|
+
spread: no.spread,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
} catch (err) {
|
|
218
|
+
throw wrapError(err);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function handleEstimateExecution(
|
|
223
|
+
sdk: PolymarketSDK,
|
|
224
|
+
input: EstimateExecutionInput
|
|
225
|
+
): Promise<EstimateExecutionOutput> {
|
|
226
|
+
validateConditionId(input.conditionId);
|
|
227
|
+
validateOutcome(input.outcome);
|
|
228
|
+
validateSide(input.side);
|
|
229
|
+
validatePositiveNumber(input.amount, 'amount');
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// Get market to find the token ID
|
|
233
|
+
const market = await sdk.clobApi.getMarket(input.conditionId);
|
|
234
|
+
const token = market.tokens.find(
|
|
235
|
+
(t) => t.outcome.toLowerCase() === input.outcome.toLowerCase()
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (!token) {
|
|
239
|
+
throw new McpToolError(
|
|
240
|
+
ErrorCode.MARKET_NOT_FOUND,
|
|
241
|
+
`Token for outcome ${input.outcome} not found`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Get the raw orderbook
|
|
246
|
+
const orderbook = await sdk.clobApi.getOrderbook(token.tokenId);
|
|
247
|
+
|
|
248
|
+
// Get market title
|
|
249
|
+
let marketTitle = input.conditionId;
|
|
250
|
+
try {
|
|
251
|
+
const unifiedMarket = await sdk.getMarket(input.conditionId);
|
|
252
|
+
marketTitle = unifiedMarket.question;
|
|
253
|
+
} catch {
|
|
254
|
+
// Use conditionId as fallback
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Simulate execution
|
|
258
|
+
const isBuy = input.side === 'BUY';
|
|
259
|
+
const levels = isBuy ? orderbook.asks : orderbook.bids;
|
|
260
|
+
|
|
261
|
+
if (levels.length === 0) {
|
|
262
|
+
throw new McpToolError(
|
|
263
|
+
ErrorCode.MARKET_NOT_FOUND,
|
|
264
|
+
'No liquidity available for this order'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let remainingAmount = input.amount;
|
|
269
|
+
let totalShares = 0;
|
|
270
|
+
let worstPrice = levels[0].price;
|
|
271
|
+
|
|
272
|
+
for (const level of levels) {
|
|
273
|
+
const levelValue = level.size * level.price;
|
|
274
|
+
|
|
275
|
+
if (remainingAmount <= levelValue) {
|
|
276
|
+
// This level can fill the remaining order
|
|
277
|
+
const sharesToBuy = remainingAmount / level.price;
|
|
278
|
+
totalShares += sharesToBuy;
|
|
279
|
+
worstPrice = level.price;
|
|
280
|
+
remainingAmount = 0;
|
|
281
|
+
break;
|
|
282
|
+
} else {
|
|
283
|
+
// Consume entire level
|
|
284
|
+
totalShares += level.size;
|
|
285
|
+
remainingAmount -= levelValue;
|
|
286
|
+
worstPrice = level.price;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// If we couldn't fill the entire order
|
|
291
|
+
if (remainingAmount > 0) {
|
|
292
|
+
throw new McpToolError(
|
|
293
|
+
ErrorCode.INSUFFICIENT_BALANCE,
|
|
294
|
+
'Not enough liquidity to fill the entire order',
|
|
295
|
+
{ remainingAmount, totalShares }
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const avgPrice = input.amount / totalShares;
|
|
300
|
+
const bestPrice = levels[0].price;
|
|
301
|
+
const priceImpact = Math.abs(avgPrice - bestPrice) / bestPrice;
|
|
302
|
+
|
|
303
|
+
let warning: string | null = null;
|
|
304
|
+
if (priceImpact > 0.05) {
|
|
305
|
+
warning = `High price impact (${(priceImpact * 100).toFixed(1)}%)`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
market: marketTitle,
|
|
310
|
+
order: {
|
|
311
|
+
side: input.side,
|
|
312
|
+
outcome: input.outcome,
|
|
313
|
+
usdcAmount: input.amount,
|
|
314
|
+
},
|
|
315
|
+
estimate: {
|
|
316
|
+
avgPrice,
|
|
317
|
+
sharesReceived: totalShares,
|
|
318
|
+
priceImpact,
|
|
319
|
+
worstPrice,
|
|
320
|
+
},
|
|
321
|
+
warning,
|
|
322
|
+
};
|
|
323
|
+
} catch (err) {
|
|
324
|
+
throw wrapError(err);
|
|
325
|
+
}
|
|
326
|
+
}
|