@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,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trader Tools - MCP tools for trader/wallet analysis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PolymarketSDK } from '@catalyst-team/poly-sdk';
|
|
6
|
+
import type {
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
GetTraderPositionsInput,
|
|
9
|
+
GetTraderPositionsOutput,
|
|
10
|
+
GetTraderTradesInput,
|
|
11
|
+
GetTraderTradesOutput,
|
|
12
|
+
GetTraderProfileInput,
|
|
13
|
+
GetTraderProfileOutput,
|
|
14
|
+
GetLeaderboardInput,
|
|
15
|
+
GetLeaderboardOutput,
|
|
16
|
+
} from '../types.js';
|
|
17
|
+
import { validateAddress, wrapError } from '../errors.js';
|
|
18
|
+
|
|
19
|
+
export const traderToolDefinitions: ToolDefinition[] = [
|
|
20
|
+
{
|
|
21
|
+
name: 'get_trader_positions',
|
|
22
|
+
description: 'Get all positions held by a trader with PnL breakdown',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
address: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Trader wallet address (0x...)',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ['address'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'get_trader_trades',
|
|
36
|
+
description: 'Get recent trading activity for a trader',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
address: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Trader wallet address',
|
|
43
|
+
},
|
|
44
|
+
limit: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
description: 'Maximum number of trades to return',
|
|
47
|
+
default: 20,
|
|
48
|
+
},
|
|
49
|
+
side: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: ['BUY', 'SELL'],
|
|
52
|
+
description: 'Filter by trade side',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ['address'],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'get_trader_profile',
|
|
60
|
+
description: 'Get comprehensive trader profile with performance metrics',
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
address: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Trader wallet address',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ['address'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'get_leaderboard',
|
|
74
|
+
description: 'Get top traders by PnL',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
limit: {
|
|
79
|
+
type: 'number',
|
|
80
|
+
description: 'Number of traders to return',
|
|
81
|
+
default: 10,
|
|
82
|
+
},
|
|
83
|
+
offset: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
description: 'Pagination offset',
|
|
86
|
+
default: 0,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
export async function handleGetTraderPositions(
|
|
94
|
+
sdk: PolymarketSDK,
|
|
95
|
+
input: GetTraderPositionsInput
|
|
96
|
+
): Promise<GetTraderPositionsOutput> {
|
|
97
|
+
validateAddress(input.address);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const positions = await sdk.wallets.getWalletPositions(input.address);
|
|
101
|
+
|
|
102
|
+
const totalUnrealizedPnl = positions.reduce((sum, p) => sum + (p.cashPnl || 0), 0);
|
|
103
|
+
const totalRealizedPnl = positions.reduce((sum, p) => sum + (p.realizedPnl || 0), 0);
|
|
104
|
+
const winningPositions = positions.filter((p) => (p.cashPnl || 0) > 0).length;
|
|
105
|
+
const losingPositions = positions.filter((p) => (p.cashPnl || 0) < 0).length;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
trader: {
|
|
109
|
+
address: input.address,
|
|
110
|
+
displayName: null, // Will be enriched if available
|
|
111
|
+
},
|
|
112
|
+
positions: positions.map((p) => ({
|
|
113
|
+
market: {
|
|
114
|
+
conditionId: p.conditionId,
|
|
115
|
+
title: p.title,
|
|
116
|
+
slug: p.slug,
|
|
117
|
+
},
|
|
118
|
+
holding: {
|
|
119
|
+
outcome: p.outcome,
|
|
120
|
+
size: p.size,
|
|
121
|
+
avgPrice: p.avgPrice,
|
|
122
|
+
curPrice: p.curPrice,
|
|
123
|
+
},
|
|
124
|
+
pnl: {
|
|
125
|
+
unrealized: p.cashPnl,
|
|
126
|
+
unrealizedPercent: p.percentPnl,
|
|
127
|
+
realized: p.realizedPnl,
|
|
128
|
+
},
|
|
129
|
+
status: {
|
|
130
|
+
redeemable: p.redeemable,
|
|
131
|
+
endDate: p.endDate,
|
|
132
|
+
},
|
|
133
|
+
})),
|
|
134
|
+
summary: {
|
|
135
|
+
totalPositions: positions.length,
|
|
136
|
+
totalUnrealizedPnl,
|
|
137
|
+
totalRealizedPnl,
|
|
138
|
+
winningPositions,
|
|
139
|
+
losingPositions,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
} catch (err) {
|
|
143
|
+
throw wrapError(err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function handleGetTraderTrades(
|
|
148
|
+
sdk: PolymarketSDK,
|
|
149
|
+
input: GetTraderTradesInput
|
|
150
|
+
): Promise<GetTraderTradesOutput> {
|
|
151
|
+
validateAddress(input.address);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const activityResult = await sdk.wallets.getWalletActivity(
|
|
155
|
+
input.address,
|
|
156
|
+
input.limit || 100
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
let trades = activityResult.activities.filter((a) => a.type === 'TRADE');
|
|
160
|
+
|
|
161
|
+
// Filter by side if specified
|
|
162
|
+
if (input.side) {
|
|
163
|
+
trades = trades.filter((a) => a.side === input.side);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Apply limit
|
|
167
|
+
const limit = input.limit || 20;
|
|
168
|
+
trades = trades.slice(0, limit);
|
|
169
|
+
|
|
170
|
+
const buyTrades = trades.filter((t) => t.side === 'BUY');
|
|
171
|
+
const sellTrades = trades.filter((t) => t.side === 'SELL');
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
trader: {
|
|
175
|
+
address: input.address,
|
|
176
|
+
displayName: null,
|
|
177
|
+
},
|
|
178
|
+
trades: trades.map((t) => ({
|
|
179
|
+
type: t.type,
|
|
180
|
+
side: t.side,
|
|
181
|
+
market: {
|
|
182
|
+
conditionId: t.conditionId,
|
|
183
|
+
title: t.title || '',
|
|
184
|
+
slug: t.slug,
|
|
185
|
+
},
|
|
186
|
+
outcome: t.outcome,
|
|
187
|
+
execution: {
|
|
188
|
+
size: t.size,
|
|
189
|
+
price: t.price,
|
|
190
|
+
usdcValue: t.usdcSize || t.size * t.price,
|
|
191
|
+
},
|
|
192
|
+
timestamp: new Date(t.timestamp).toISOString(),
|
|
193
|
+
txHash: t.transactionHash,
|
|
194
|
+
})),
|
|
195
|
+
summary: {
|
|
196
|
+
totalTrades: trades.length,
|
|
197
|
+
buyCount: buyTrades.length,
|
|
198
|
+
sellCount: sellTrades.length,
|
|
199
|
+
buyVolume: buyTrades.reduce((sum, t) => sum + (t.usdcSize || t.size * t.price), 0),
|
|
200
|
+
sellVolume: sellTrades.reduce((sum, t) => sum + (t.usdcSize || t.size * t.price), 0),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
} catch (err) {
|
|
204
|
+
throw wrapError(err);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function handleGetTraderProfile(
|
|
209
|
+
sdk: PolymarketSDK,
|
|
210
|
+
input: GetTraderProfileInput
|
|
211
|
+
): Promise<GetTraderProfileOutput> {
|
|
212
|
+
validateAddress(input.address);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const profile = await sdk.wallets.getWalletProfile(input.address);
|
|
216
|
+
|
|
217
|
+
// Try to get leaderboard data for rank and official PnL
|
|
218
|
+
let rank: number | null = null;
|
|
219
|
+
let leaderboardPnl: number | null = null;
|
|
220
|
+
let leaderboardVolume: number | null = null;
|
|
221
|
+
let displayName: string | null = null;
|
|
222
|
+
let verified = false;
|
|
223
|
+
try {
|
|
224
|
+
const leaderboard = await sdk.wallets.getLeaderboard(0, 100);
|
|
225
|
+
const entry = leaderboard.entries.find(
|
|
226
|
+
(e) => e.address.toLowerCase() === input.address.toLowerCase()
|
|
227
|
+
);
|
|
228
|
+
if (entry) {
|
|
229
|
+
rank = entry.rank;
|
|
230
|
+
leaderboardPnl = entry.pnl;
|
|
231
|
+
leaderboardVolume = entry.volume;
|
|
232
|
+
displayName = entry.userName || null;
|
|
233
|
+
verified = entry.verifiedBadge || false;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
// Ignore leaderboard errors
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const winningCount = profile.positionCount > 0
|
|
240
|
+
? Math.round(profile.positionCount * (profile.avgPercentPnL > 0 ? 0.6 : 0.4))
|
|
241
|
+
: 0;
|
|
242
|
+
const winRate = profile.positionCount > 0
|
|
243
|
+
? winningCount / profile.positionCount
|
|
244
|
+
: 0;
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
trader: {
|
|
248
|
+
address: input.address,
|
|
249
|
+
displayName,
|
|
250
|
+
xUsername: null,
|
|
251
|
+
verified,
|
|
252
|
+
profileImage: null,
|
|
253
|
+
},
|
|
254
|
+
ranking: {
|
|
255
|
+
rank,
|
|
256
|
+
totalTraders: 10000, // Approximate
|
|
257
|
+
},
|
|
258
|
+
performance: {
|
|
259
|
+
// Official PnL from leaderboard (used for ranking)
|
|
260
|
+
officialPnl: leaderboardPnl,
|
|
261
|
+
// Volume from leaderboard
|
|
262
|
+
totalVolume: leaderboardVolume ?? (profile.totalPnL + profile.unrealizedPnL),
|
|
263
|
+
// Calculated PnL from profile positions
|
|
264
|
+
unrealizedPnl: profile.unrealizedPnL,
|
|
265
|
+
realizedPnl: profile.realizedPnL,
|
|
266
|
+
},
|
|
267
|
+
stats: {
|
|
268
|
+
positionCount: profile.positionCount,
|
|
269
|
+
winRate,
|
|
270
|
+
avgPercentPnl: profile.avgPercentPnL,
|
|
271
|
+
smartScore: profile.smartScore,
|
|
272
|
+
},
|
|
273
|
+
activity: {
|
|
274
|
+
lastTradeAt: profile.lastActiveAt.getTime() > 0
|
|
275
|
+
? profile.lastActiveAt.toISOString()
|
|
276
|
+
: null,
|
|
277
|
+
isActive: Date.now() - profile.lastActiveAt.getTime() < 7 * 24 * 60 * 60 * 1000,
|
|
278
|
+
},
|
|
279
|
+
// Explain potential PnL differences
|
|
280
|
+
notes: leaderboardPnl !== null && Math.abs((leaderboardPnl || 0) - (profile.unrealizedPnL + profile.realizedPnL)) > 100
|
|
281
|
+
? 'Note: officialPnl (from leaderboard) may differ from unrealizedPnl + realizedPnl (from positions) due to different calculation methods. Leaderboard PnL includes historical settled positions.'
|
|
282
|
+
: undefined,
|
|
283
|
+
};
|
|
284
|
+
} catch (err) {
|
|
285
|
+
throw wrapError(err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function handleGetLeaderboard(
|
|
290
|
+
sdk: PolymarketSDK,
|
|
291
|
+
input: GetLeaderboardInput
|
|
292
|
+
): Promise<GetLeaderboardOutput> {
|
|
293
|
+
const limit = input.limit || 10;
|
|
294
|
+
const offset = input.offset || 0;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const page = Math.floor(offset / 50);
|
|
298
|
+
const leaderboard = await sdk.wallets.getLeaderboard(page, 50);
|
|
299
|
+
|
|
300
|
+
// Apply offset and limit within the page
|
|
301
|
+
const startIdx = offset % 50;
|
|
302
|
+
const entries = leaderboard.entries.slice(startIdx, startIdx + limit);
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
traders: entries.map((e) => ({
|
|
306
|
+
rank: e.rank,
|
|
307
|
+
address: e.address,
|
|
308
|
+
displayName: e.userName || null,
|
|
309
|
+
pnl: e.pnl,
|
|
310
|
+
volume: e.volume,
|
|
311
|
+
verified: e.verifiedBadge || false,
|
|
312
|
+
})),
|
|
313
|
+
pagination: {
|
|
314
|
+
total: leaderboard.total,
|
|
315
|
+
offset,
|
|
316
|
+
limit,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
} catch (err) {
|
|
320
|
+
throw wrapError(err);
|
|
321
|
+
}
|
|
322
|
+
}
|