@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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools Index
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Re-export all tool definitions and handlers
|
|
6
|
+
export {
|
|
7
|
+
guideToolDefinitions,
|
|
8
|
+
createGuideHandlers,
|
|
9
|
+
} from './guide.js';
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
traderToolDefinitions,
|
|
13
|
+
handleGetTraderPositions,
|
|
14
|
+
handleGetTraderTrades,
|
|
15
|
+
handleGetTraderProfile,
|
|
16
|
+
handleGetLeaderboard,
|
|
17
|
+
} from './trader.js';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
marketToolDefinitions,
|
|
21
|
+
handleGetMarket,
|
|
22
|
+
handleSearchMarkets,
|
|
23
|
+
handleGetTrendingMarkets,
|
|
24
|
+
handleGetMarketTrades,
|
|
25
|
+
} from './market.js';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
orderToolDefinitions,
|
|
29
|
+
handleGetOrderbook,
|
|
30
|
+
handleGetBestPrices,
|
|
31
|
+
handleEstimateExecution,
|
|
32
|
+
} from './order.js';
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
tradeToolDefinitions,
|
|
36
|
+
handlePlaceLimitOrder,
|
|
37
|
+
handlePlaceMarketOrder,
|
|
38
|
+
handleCancelOrder,
|
|
39
|
+
handleGetMyOrders,
|
|
40
|
+
} from './trade.js';
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
walletToolDefinitions,
|
|
44
|
+
handleGetSupportedAssets,
|
|
45
|
+
handleGetDepositAddresses,
|
|
46
|
+
handleDepositUsdc,
|
|
47
|
+
handleCheckAllowances,
|
|
48
|
+
handleApproveTrading,
|
|
49
|
+
handleSwap,
|
|
50
|
+
handleSwapAndDeposit,
|
|
51
|
+
handleGetTokenBalances,
|
|
52
|
+
handleGetWalletBalances,
|
|
53
|
+
handleGetSwapQuote,
|
|
54
|
+
handleGetAvailablePools,
|
|
55
|
+
} from './wallet.js';
|
|
56
|
+
|
|
57
|
+
// Combined tool definitions
|
|
58
|
+
import { guideToolDefinitions } from './guide.js';
|
|
59
|
+
import { traderToolDefinitions } from './trader.js';
|
|
60
|
+
import { marketToolDefinitions } from './market.js';
|
|
61
|
+
import { orderToolDefinitions } from './order.js';
|
|
62
|
+
import { tradeToolDefinitions } from './trade.js';
|
|
63
|
+
import { walletToolDefinitions } from './wallet.js';
|
|
64
|
+
|
|
65
|
+
// Guide tool FIRST so AI sees it prominently
|
|
66
|
+
export const allToolDefinitions = [
|
|
67
|
+
...guideToolDefinitions,
|
|
68
|
+
...traderToolDefinitions,
|
|
69
|
+
...marketToolDefinitions,
|
|
70
|
+
...orderToolDefinitions,
|
|
71
|
+
...tradeToolDefinitions,
|
|
72
|
+
...walletToolDefinitions,
|
|
73
|
+
];
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Market Tools - MCP tools for market discovery and analysis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PolymarketSDK } from '@catalyst-team/poly-sdk';
|
|
6
|
+
import type {
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
GetMarketInput,
|
|
9
|
+
GetMarketOutput,
|
|
10
|
+
SearchMarketsInput,
|
|
11
|
+
SearchMarketsOutput,
|
|
12
|
+
GetTrendingMarketsInput,
|
|
13
|
+
GetTrendingMarketsOutput,
|
|
14
|
+
GetMarketTradesInput,
|
|
15
|
+
GetMarketTradesOutput,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
import { validateConditionId, wrapError, McpToolError, ErrorCode } from '../errors.js';
|
|
18
|
+
|
|
19
|
+
export const marketToolDefinitions: ToolDefinition[] = [
|
|
20
|
+
{
|
|
21
|
+
name: 'get_market',
|
|
22
|
+
description: 'Get market details including prices, volume, and status',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
identifier: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: "Market slug (e.g., 'us-recession-in-2025') or conditionId (0x...)",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ['identifier'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'search_markets',
|
|
36
|
+
description:
|
|
37
|
+
'Search for markets by keyword. Searches in question text and slug. Returns markets sorted by 24h volume.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
query: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description:
|
|
44
|
+
'Search keyword (e.g., "Trump", "Bitcoin", "recession"). Multi-word queries match any word.',
|
|
45
|
+
},
|
|
46
|
+
active: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Only return active markets',
|
|
49
|
+
default: true,
|
|
50
|
+
},
|
|
51
|
+
limit: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Maximum results',
|
|
54
|
+
default: 10,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['query'],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'get_trending_markets',
|
|
62
|
+
description: 'Get trending markets sorted by volume or liquidity',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
limit: {
|
|
67
|
+
type: 'number',
|
|
68
|
+
default: 10,
|
|
69
|
+
},
|
|
70
|
+
sortBy: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
enum: ['volume', 'liquidity', 'newest'],
|
|
73
|
+
default: 'volume',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'get_market_trades',
|
|
80
|
+
description: 'Get recent trades for a specific market',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
conditionId: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: 'Market condition ID',
|
|
87
|
+
},
|
|
88
|
+
limit: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
default: 20,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ['conditionId'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
export async function handleGetMarket(
|
|
99
|
+
sdk: PolymarketSDK,
|
|
100
|
+
input: GetMarketInput
|
|
101
|
+
): Promise<GetMarketOutput> {
|
|
102
|
+
if (!input.identifier) {
|
|
103
|
+
throw new McpToolError(ErrorCode.INVALID_INPUT, 'Market identifier is required');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const market = await sdk.getMarket(input.identifier);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
market: {
|
|
111
|
+
conditionId: market.conditionId,
|
|
112
|
+
question: market.question,
|
|
113
|
+
slug: market.slug,
|
|
114
|
+
description: market.description,
|
|
115
|
+
},
|
|
116
|
+
prices: {
|
|
117
|
+
yes: market.tokens.yes.price,
|
|
118
|
+
no: market.tokens.no.price,
|
|
119
|
+
spread: market.spread,
|
|
120
|
+
},
|
|
121
|
+
tokens: {
|
|
122
|
+
yes: {
|
|
123
|
+
tokenId: market.tokens.yes.tokenId,
|
|
124
|
+
price: market.tokens.yes.price,
|
|
125
|
+
},
|
|
126
|
+
no: {
|
|
127
|
+
tokenId: market.tokens.no.tokenId,
|
|
128
|
+
price: market.tokens.no.price,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
stats: {
|
|
132
|
+
volume: market.volume,
|
|
133
|
+
liquidity: market.liquidity,
|
|
134
|
+
},
|
|
135
|
+
status: {
|
|
136
|
+
active: market.active,
|
|
137
|
+
closed: market.closed,
|
|
138
|
+
acceptingOrders: market.acceptingOrders,
|
|
139
|
+
endDate: market.endDate?.toISOString(),
|
|
140
|
+
},
|
|
141
|
+
trading: {
|
|
142
|
+
minTickSize: undefined, // From CLOB if needed
|
|
143
|
+
minOrderSize: undefined,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
} catch (err) {
|
|
147
|
+
throw wrapError(err);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function handleSearchMarkets(
|
|
152
|
+
sdk: PolymarketSDK,
|
|
153
|
+
input: SearchMarketsInput
|
|
154
|
+
): Promise<SearchMarketsOutput> {
|
|
155
|
+
if (!input.query) {
|
|
156
|
+
throw new McpToolError(ErrorCode.INVALID_INPUT, 'Search query is required');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
// Fetch active markets sorted by 24h volume for better relevance
|
|
161
|
+
// Note: Gamma API doesn't support server-side text search,
|
|
162
|
+
// so we fetch a larger set and filter client-side
|
|
163
|
+
const allMarkets = await sdk.gammaApi.getMarkets({
|
|
164
|
+
active: input.active !== false,
|
|
165
|
+
closed: false,
|
|
166
|
+
order: 'volume24hr',
|
|
167
|
+
ascending: false,
|
|
168
|
+
limit: 500, // Fetch more markets for comprehensive search
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const queryLower = input.query.toLowerCase();
|
|
172
|
+
const queryWords = queryLower.split(/\s+/).filter((w) => w.length > 0);
|
|
173
|
+
|
|
174
|
+
// Filter by query - match any word in question or slug
|
|
175
|
+
const filtered = allMarkets.filter((m) => {
|
|
176
|
+
const questionLower = m.question.toLowerCase();
|
|
177
|
+
const slugLower = m.slug.toLowerCase();
|
|
178
|
+
|
|
179
|
+
// Match if any query word is found in question or slug
|
|
180
|
+
return queryWords.some(
|
|
181
|
+
(word) => questionLower.includes(word) || slugLower.includes(word)
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Sort by relevance: prioritize exact phrase matches, then by volume
|
|
186
|
+
filtered.sort((a, b) => {
|
|
187
|
+
const aQuestion = a.question.toLowerCase();
|
|
188
|
+
const bQuestion = b.question.toLowerCase();
|
|
189
|
+
const aExact = aQuestion.includes(queryLower);
|
|
190
|
+
const bExact = bQuestion.includes(queryLower);
|
|
191
|
+
|
|
192
|
+
// Exact phrase matches first
|
|
193
|
+
if (aExact && !bExact) return -1;
|
|
194
|
+
if (!aExact && bExact) return 1;
|
|
195
|
+
|
|
196
|
+
// Then by 24h volume
|
|
197
|
+
return (b.volume24hr || 0) - (a.volume24hr || 0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Apply limit
|
|
201
|
+
const results = filtered.slice(0, input.limit || 10);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
markets: results.map((m) => {
|
|
205
|
+
// Safely handle endDate - it may be invalid
|
|
206
|
+
let endDateStr: string | undefined;
|
|
207
|
+
try {
|
|
208
|
+
if (m.endDate && !isNaN(m.endDate.getTime())) {
|
|
209
|
+
endDateStr = m.endDate.toISOString();
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Ignore invalid date
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
conditionId: m.conditionId,
|
|
217
|
+
question: m.question,
|
|
218
|
+
slug: m.slug,
|
|
219
|
+
prices: {
|
|
220
|
+
yes: m.outcomePrices[0] || 0.5,
|
|
221
|
+
no: m.outcomePrices[1] || 0.5,
|
|
222
|
+
},
|
|
223
|
+
volume: m.volume,
|
|
224
|
+
volume24h: m.volume24hr,
|
|
225
|
+
endDate: endDateStr,
|
|
226
|
+
};
|
|
227
|
+
}),
|
|
228
|
+
total: filtered.length,
|
|
229
|
+
};
|
|
230
|
+
} catch (err) {
|
|
231
|
+
throw wrapError(err);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function handleGetTrendingMarkets(
|
|
236
|
+
sdk: PolymarketSDK,
|
|
237
|
+
input: GetTrendingMarketsInput
|
|
238
|
+
): Promise<GetTrendingMarketsOutput> {
|
|
239
|
+
try {
|
|
240
|
+
const sortBy = input.sortBy || 'volume';
|
|
241
|
+
let orderBy: string;
|
|
242
|
+
const ascending = false;
|
|
243
|
+
|
|
244
|
+
switch (sortBy) {
|
|
245
|
+
case 'volume':
|
|
246
|
+
orderBy = 'volume24hr';
|
|
247
|
+
break;
|
|
248
|
+
case 'liquidity':
|
|
249
|
+
orderBy = 'liquidity';
|
|
250
|
+
break;
|
|
251
|
+
case 'newest':
|
|
252
|
+
orderBy = 'startDate';
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
orderBy = 'volume24hr';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Fetch more markets than requested to allow filtering
|
|
259
|
+
const requestedLimit = input.limit || 10;
|
|
260
|
+
const markets = await sdk.gammaApi.getMarkets({
|
|
261
|
+
active: true,
|
|
262
|
+
closed: false, // Explicitly exclude closed markets
|
|
263
|
+
limit: requestedLimit * 2, // Fetch extra in case some need filtering
|
|
264
|
+
order: orderBy,
|
|
265
|
+
ascending,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Filter out markets that are closed or not accepting orders
|
|
269
|
+
// This handles edge cases where API returns recently-settled markets
|
|
270
|
+
const activeMarkets = markets.filter((m) => {
|
|
271
|
+
// Skip if explicitly marked as closed
|
|
272
|
+
if (m.closed) return false;
|
|
273
|
+
// Skip if market has ended (endDate in past)
|
|
274
|
+
if (m.endDate) {
|
|
275
|
+
const endTime = m.endDate instanceof Date ? m.endDate.getTime() : new Date(m.endDate).getTime();
|
|
276
|
+
if (!isNaN(endTime) && endTime < Date.now()) return false;
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Take only the requested number after filtering
|
|
282
|
+
const finalMarkets = activeMarkets.slice(0, requestedLimit);
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
markets: finalMarkets.map((m) => ({
|
|
286
|
+
conditionId: m.conditionId,
|
|
287
|
+
question: m.question,
|
|
288
|
+
slug: m.slug,
|
|
289
|
+
volume24h: m.volume24hr,
|
|
290
|
+
priceChange24h: m.oneDayPriceChange,
|
|
291
|
+
prices: {
|
|
292
|
+
yes: m.outcomePrices[0] || 0.5,
|
|
293
|
+
no: m.outcomePrices[1] || 0.5,
|
|
294
|
+
},
|
|
295
|
+
// Add status fields for transparency
|
|
296
|
+
status: {
|
|
297
|
+
active: m.active,
|
|
298
|
+
closed: m.closed,
|
|
299
|
+
endDate: m.endDate ? (m.endDate instanceof Date ? m.endDate.toISOString() : String(m.endDate)) : undefined,
|
|
300
|
+
},
|
|
301
|
+
})),
|
|
302
|
+
};
|
|
303
|
+
} catch (err) {
|
|
304
|
+
throw wrapError(err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function handleGetMarketTrades(
|
|
309
|
+
sdk: PolymarketSDK,
|
|
310
|
+
input: GetMarketTradesInput
|
|
311
|
+
): Promise<GetMarketTradesOutput> {
|
|
312
|
+
validateConditionId(input.conditionId);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const trades = await sdk.dataApi.getTradesByMarket(
|
|
316
|
+
input.conditionId,
|
|
317
|
+
input.limit || 20
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Get market info for the title
|
|
321
|
+
let marketTitle = '';
|
|
322
|
+
try {
|
|
323
|
+
const market = await sdk.getMarket(input.conditionId);
|
|
324
|
+
marketTitle = market.question;
|
|
325
|
+
} catch {
|
|
326
|
+
// Use conditionId as fallback
|
|
327
|
+
marketTitle = input.conditionId;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
const oneDayAgo = now - 24 * 60 * 60 * 1000;
|
|
332
|
+
|
|
333
|
+
const recentTrades = trades.filter((t) => t.timestamp >= oneDayAgo);
|
|
334
|
+
const buyTrades = recentTrades.filter((t) => t.side === 'BUY');
|
|
335
|
+
const sellTrades = recentTrades.filter((t) => t.side === 'SELL');
|
|
336
|
+
|
|
337
|
+
const buyVolume24h = buyTrades.reduce((sum, t) => sum + t.size * t.price, 0);
|
|
338
|
+
const sellVolume24h = sellTrades.reduce((sum, t) => sum + t.size * t.price, 0);
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
market: {
|
|
342
|
+
conditionId: input.conditionId,
|
|
343
|
+
title: marketTitle,
|
|
344
|
+
},
|
|
345
|
+
trades: trades.slice(0, input.limit || 20).map((t) => ({
|
|
346
|
+
trader: t.proxyWallet || '',
|
|
347
|
+
traderName: t.name,
|
|
348
|
+
side: t.side,
|
|
349
|
+
outcome: t.outcome,
|
|
350
|
+
size: t.size,
|
|
351
|
+
price: t.price,
|
|
352
|
+
timestamp: new Date(t.timestamp).toISOString(),
|
|
353
|
+
})),
|
|
354
|
+
summary: {
|
|
355
|
+
buyVolume24h,
|
|
356
|
+
sellVolume24h,
|
|
357
|
+
netFlow: buyVolume24h - sellVolume24h,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
} catch (err) {
|
|
361
|
+
throw wrapError(err);
|
|
362
|
+
}
|
|
363
|
+
}
|