@champz-llc/legends-mcp-server 1.1.2 → 1.3.2
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 +46 -0
- package/index.js +642 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,12 @@ Once configured, you can ask Claude:
|
|
|
58
58
|
- "How much CHAMPZ has been burned?"
|
|
59
59
|
- "How much USDC has been distributed to players?"
|
|
60
60
|
|
|
61
|
+
### Leaderboards
|
|
62
|
+
- "Show me the current cycle leaderboard for Legends"
|
|
63
|
+
- "Who's winning the weekly battle leaderboard?"
|
|
64
|
+
- "What are the all-time leaderboards for Legends of Champz?"
|
|
65
|
+
- "Show me the top 20 players in the current cycle"
|
|
66
|
+
|
|
61
67
|
### Reward Management
|
|
62
68
|
- "Check my claimable rewards in Legends"
|
|
63
69
|
- "Get my CHAMPZ claim data"
|
|
@@ -85,10 +91,50 @@ Get contract call data for claiming CHAMPZ tokens.
|
|
|
85
91
|
### `get_usdc_claim_data`
|
|
86
92
|
Get contract call data for claiming USDC rewards.
|
|
87
93
|
|
|
94
|
+
### `legends_leaderboard_current`
|
|
95
|
+
Get current cycle battle leaderboard (top players in active 12-hour cycle).
|
|
96
|
+
|
|
97
|
+
**Parameters**:
|
|
98
|
+
- `limit` (optional): Number of players to show (1-100, default: 10)
|
|
99
|
+
|
|
100
|
+
**Returns**:
|
|
101
|
+
- Current cycle ID and letter
|
|
102
|
+
- Cycle end time
|
|
103
|
+
- Top players with wins/losses, win rate, streaks
|
|
104
|
+
- Player tier badges and legend rarity
|
|
105
|
+
|
|
106
|
+
### `legends_leaderboard_weekly`
|
|
107
|
+
Get weekly battle leaderboard with USDC prize distribution.
|
|
108
|
+
|
|
109
|
+
**Parameters**:
|
|
110
|
+
- `limit` (optional): Number of players to show (1-100, default: 10)
|
|
111
|
+
|
|
112
|
+
**Returns**:
|
|
113
|
+
- Week number and cycle range
|
|
114
|
+
- Total USDC prize pool ($100)
|
|
115
|
+
- Top players with battle stats and USDC rewards
|
|
116
|
+
- Cycles remaining in current week
|
|
117
|
+
|
|
118
|
+
### `legends_leaderboard_alltime`
|
|
119
|
+
Get all-time leaderboards across 10 categories.
|
|
120
|
+
|
|
121
|
+
**Parameters**:
|
|
122
|
+
- `limit` (optional): Players per category (1-50, default: 10)
|
|
123
|
+
|
|
124
|
+
**Returns**:
|
|
125
|
+
- Most CHAMPZ earned
|
|
126
|
+
- Most USDC earned
|
|
127
|
+
- Most total wins
|
|
128
|
+
- Most guardian wins
|
|
129
|
+
- Highest win rate (min 100 battles)
|
|
130
|
+
- Longest win streak
|
|
131
|
+
- Elemental Charge high scores and stats
|
|
132
|
+
|
|
88
133
|
## 📡 API Endpoints
|
|
89
134
|
|
|
90
135
|
The server connects to:
|
|
91
136
|
- **Global Stats**: `https://api.champz.world/game/spore-trainer/global-stats`
|
|
137
|
+
- **Leaderboards**: `https://api.champz.world/game/spore-trainer/public-leaderboard`
|
|
92
138
|
- **Rewards**: (Coming soon - player-specific endpoints)
|
|
93
139
|
|
|
94
140
|
## 🔧 Development
|
package/index.js
CHANGED
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
9
|
import fetch from 'node-fetch';
|
|
10
10
|
|
|
11
|
+
// MCP Signature Authentication (optional - for player data access)
|
|
12
|
+
const WALLET = process.env.WALLET;
|
|
13
|
+
const SIGNATURE = process.env.SIGNATURE;
|
|
14
|
+
const SIGNED_AT = process.env.SIGNED_AT;
|
|
15
|
+
const hasWalletAuth = WALLET && SIGNATURE && SIGNED_AT;
|
|
16
|
+
|
|
11
17
|
// Hardcoded rewards data for demo
|
|
12
18
|
const DEMO_WALLET = '0xfbc159e35f56580d5d297af18a8c19f83d66088a';
|
|
13
19
|
|
|
@@ -100,8 +106,7 @@ const server = new Server(
|
|
|
100
106
|
|
|
101
107
|
// List available tools
|
|
102
108
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
103
|
-
|
|
104
|
-
tools: [
|
|
109
|
+
const tools = [
|
|
105
110
|
{
|
|
106
111
|
name: 'legends_global_stats',
|
|
107
112
|
description: 'Get overall Legends of Champz game statistics including total legends rolled, CHAMPZ burned, USDC distributed, total battles, and more',
|
|
@@ -137,9 +142,116 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
137
142
|
properties: {},
|
|
138
143
|
required: []
|
|
139
144
|
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'legends_leaderboard_current',
|
|
148
|
+
description: 'Get current cycle leaderboard for Legends of Champz. Shows top players in the active 12-hour cycle across battles, guardian (throne), or Elemental Charge mini-game.',
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
category: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'Leaderboard category: battles (default), guardian (throne holders), or ec (Elemental Charge)',
|
|
155
|
+
enum: ['battles', 'guardian', 'ec']
|
|
156
|
+
},
|
|
157
|
+
limit: {
|
|
158
|
+
type: 'number',
|
|
159
|
+
description: 'Number of top players to show (default: 10, max: 100)',
|
|
160
|
+
minimum: 1,
|
|
161
|
+
maximum: 100
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
required: []
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'legends_leaderboard_weekly',
|
|
169
|
+
description: 'Get weekly leaderboard for Legends of Champz. Shows top players for the current week (21 cycles) across battles (with USDC rewards) or guardian (throne competition).',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
category: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'Leaderboard category: battles (default - with USDC rewards) or guardian (throne holders). Note: ec not available for weekly.',
|
|
176
|
+
enum: ['battles', 'guardian']
|
|
177
|
+
},
|
|
178
|
+
limit: {
|
|
179
|
+
type: 'number',
|
|
180
|
+
description: 'Number of top players to show (default: 10, max: 100)',
|
|
181
|
+
minimum: 1,
|
|
182
|
+
maximum: 100
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
required: []
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'legends_leaderboard_alltime',
|
|
190
|
+
description: 'Get all-time leaderboards for Legends of Champz. Can show all categories or filter by battles, guardian, or ec (Elemental Charge) stats.',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
category: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: 'Filter by category: all (default - shows everything), battles (wins/earnings/streaks), guardian (throne wins), or ec (Elemental Charge scores)',
|
|
197
|
+
enum: ['all', 'battles', 'guardian', 'ec']
|
|
198
|
+
},
|
|
199
|
+
limit: {
|
|
200
|
+
type: 'number',
|
|
201
|
+
description: 'Number of top players per category (default: 10, max: 50)',
|
|
202
|
+
minimum: 1,
|
|
203
|
+
maximum: 50
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
required: []
|
|
207
|
+
},
|
|
140
208
|
}
|
|
141
|
-
]
|
|
142
|
-
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// Add player data tools if wallet is configured
|
|
212
|
+
if (hasWalletAuth) {
|
|
213
|
+
tools.push({
|
|
214
|
+
name: 'legends_player_data',
|
|
215
|
+
description: 'Get your personal Legends of Champz statistics including CHAMPZ spent on packs, legends owned, thrones, battle stats, claims history, and saved trainers. Requires wallet authentication.',
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: 'object',
|
|
218
|
+
properties: {},
|
|
219
|
+
required: []
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
tools.push({
|
|
224
|
+
name: 'show_legend',
|
|
225
|
+
description: 'Show details and image for a specific legend you own by ID. Use this when the user asks to see a particular legend (e.g., "show me legend #1010").',
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: 'object',
|
|
228
|
+
properties: {
|
|
229
|
+
legend_id: {
|
|
230
|
+
type: 'number',
|
|
231
|
+
description: 'The ID of the legend to display'
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
required: ['legend_id']
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
tools.push({
|
|
239
|
+
name: 'show_throne',
|
|
240
|
+
description: 'Show details and image for a specific throne you own by ID. Use this when the user asks to see a particular throne.',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: {
|
|
244
|
+
throne_id: {
|
|
245
|
+
type: 'number',
|
|
246
|
+
description: 'The ID of the throne to display'
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
required: ['throne_id']
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { tools };
|
|
143
255
|
});
|
|
144
256
|
|
|
145
257
|
// Handle tool calls
|
|
@@ -251,6 +363,532 @@ Last updated: ${new Date(stats.cached_at * 1000).toLocaleString()}`;
|
|
|
251
363
|
],
|
|
252
364
|
};
|
|
253
365
|
|
|
366
|
+
case 'legends_leaderboard_current':
|
|
367
|
+
try {
|
|
368
|
+
const category = request.params.arguments?.category || 'battles';
|
|
369
|
+
const limit = request.params.arguments?.limit || 10;
|
|
370
|
+
const response = await fetch(`https://api.champz.world/game/spore-trainer/public-leaderboard?type=current&category=${category}&limit=${limit}`);
|
|
371
|
+
const data = await response.json();
|
|
372
|
+
|
|
373
|
+
if (!data.success) {
|
|
374
|
+
throw new Error(data.error || 'Failed to fetch current cycle leaderboard');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let output = '';
|
|
378
|
+
const cycleInfo = `Cycle #${data.cycle_id} (${data.cycle_key}) - Ends: ${new Date(data.ends_at * 1000).toLocaleString()}\n`;
|
|
379
|
+
|
|
380
|
+
if (category === 'battles') {
|
|
381
|
+
output = `🏆 Legends of Champz - Current Cycle Battle Leaderboard\n\n`;
|
|
382
|
+
output += cycleInfo;
|
|
383
|
+
output += `Active Players: ${data.total_players.toLocaleString()}\n\n`;
|
|
384
|
+
output += `Top ${data.showing_top} Players:\n\n`;
|
|
385
|
+
|
|
386
|
+
data.leaderboard.forEach((player, index) => {
|
|
387
|
+
const tierEmoji = player.tier_emoji || '';
|
|
388
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...${player.wallet.slice(-4)}`;
|
|
389
|
+
const winRate = player.win_rate.toFixed(1);
|
|
390
|
+
const record = `${player.wins}W-${player.losses}L`;
|
|
391
|
+
|
|
392
|
+
output += `${index + 1}. ${tierEmoji} ${name}\n`;
|
|
393
|
+
output += ` ${record} (${winRate}% win rate)`;
|
|
394
|
+
if (player.streak > 0) {
|
|
395
|
+
output += ` 🔥 ${player.streak} streak`;
|
|
396
|
+
}
|
|
397
|
+
output += `\n`;
|
|
398
|
+
if (player.rarity) {
|
|
399
|
+
output += ` ${player.rarity.charAt(0).toUpperCase() + player.rarity.slice(1)} • ${player.elements.join('/')}\n`;
|
|
400
|
+
}
|
|
401
|
+
output += `\n`;
|
|
402
|
+
});
|
|
403
|
+
} else if (category === 'guardian') {
|
|
404
|
+
output = `👑 Legends of Champz - Current Cycle Guardian (Throne) Leaderboard\n\n`;
|
|
405
|
+
output += cycleInfo;
|
|
406
|
+
output += `Current Guardian: ${data.current_guardian ? data.current_guardian.slice(0, 6) + '...' : 'None'}\n`;
|
|
407
|
+
output += `Prize Pool: ${data.prize_pool.toLocaleString()} CHAMPZ\n\n`;
|
|
408
|
+
output += `Top ${data.showing_top} Guardian Holders:\n\n`;
|
|
409
|
+
|
|
410
|
+
data.leaderboard.forEach((player) => {
|
|
411
|
+
const tierEmoji = player.tier_emoji || '';
|
|
412
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...${player.wallet.slice(-4)}`;
|
|
413
|
+
const crown = player.is_current ? '👑 ' : '';
|
|
414
|
+
|
|
415
|
+
output += `${player.rank}. ${crown}${tierEmoji} ${name}\n`;
|
|
416
|
+
output += ` Held ${player.times_held}x • Sent ${player.total_sent.toLocaleString()} CHAMPZ\n\n`;
|
|
417
|
+
});
|
|
418
|
+
} else if (category === 'ec') {
|
|
419
|
+
output = `⚡ Legends of Champz - Current Cycle Elemental Charge Leaderboard\n\n`;
|
|
420
|
+
output += cycleInfo;
|
|
421
|
+
output += `Top ${data.showing_top} High Scores:\n\n`;
|
|
422
|
+
|
|
423
|
+
data.leaderboard.forEach((player) => {
|
|
424
|
+
const tierEmoji = player.tier_emoji || '';
|
|
425
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...${player.wallet.slice(-4)}`;
|
|
426
|
+
|
|
427
|
+
output += `${player.rank}. ${tierEmoji} ${name}\n`;
|
|
428
|
+
output += ` Score: ${player.best_score} • Rounds: ${player.best_rounds} • Streak: ${player.best_streak}\n\n`;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
content: [
|
|
434
|
+
{
|
|
435
|
+
type: 'text',
|
|
436
|
+
text: output,
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
};
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
content: [
|
|
443
|
+
{
|
|
444
|
+
type: 'text',
|
|
445
|
+
text: `Error fetching current cycle leaderboard: ${error.message}`,
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
case 'legends_leaderboard_weekly':
|
|
452
|
+
try {
|
|
453
|
+
const category = request.params.arguments?.category || 'battles';
|
|
454
|
+
const limit = request.params.arguments?.limit || 10;
|
|
455
|
+
const response = await fetch(`https://api.champz.world/game/spore-trainer/public-leaderboard?type=weekly&category=${category}&limit=${limit}`);
|
|
456
|
+
const data = await response.json();
|
|
457
|
+
|
|
458
|
+
if (!data.success) {
|
|
459
|
+
throw new Error(data.error || 'Failed to fetch weekly leaderboard');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let output = '';
|
|
463
|
+
const weekInfo = `Week ${data.week_number} (Cycles ${data.week_start_cycle}-${data.week_end_cycle})\nCurrent Cycle: ${data.current_cycle} | Cycles Remaining: ${data.cycles_remaining}\n`;
|
|
464
|
+
|
|
465
|
+
if (category === 'battles') {
|
|
466
|
+
output = `💰 Legends of Champz - Weekly Battle Leaderboard\n\n`;
|
|
467
|
+
output += weekInfo;
|
|
468
|
+
output += `Total Prize Pool: $${data.total_pool_usdc} USDC\n`;
|
|
469
|
+
output += `Active Players: ${data.total_players.toLocaleString()}\n\n`;
|
|
470
|
+
output += `Top ${data.showing_top} Players:\n\n`;
|
|
471
|
+
|
|
472
|
+
data.leaderboard.forEach((player, index) => {
|
|
473
|
+
const tierEmoji = player.tier_emoji || '';
|
|
474
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...${player.wallet.slice(-4)}`;
|
|
475
|
+
const winRate = player.win_rate.toFixed(1);
|
|
476
|
+
const record = `${player.wins}W-${player.losses}L`;
|
|
477
|
+
const reward = player.usdc_reward > 0 ? ` 💵 $${player.usdc_reward.toFixed(2)}` : '';
|
|
478
|
+
|
|
479
|
+
output += `${index + 1}. ${tierEmoji} ${name}\n`;
|
|
480
|
+
output += ` ${record} in ${player.battles} battles (${winRate}% WR)${reward}\n\n`;
|
|
481
|
+
});
|
|
482
|
+
} else if (category === 'guardian') {
|
|
483
|
+
output = `👑 Legends of Champz - Weekly Guardian (Throne) Leaderboard\n\n`;
|
|
484
|
+
output += weekInfo;
|
|
485
|
+
output += `\nTop ${data.showing_top} Guardian Competitors:\n\n`;
|
|
486
|
+
|
|
487
|
+
data.leaderboard.forEach((player) => {
|
|
488
|
+
const tierEmoji = player.tier_emoji || '';
|
|
489
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...${player.wallet.slice(-4)}`;
|
|
490
|
+
|
|
491
|
+
output += `${player.rank}. ${tierEmoji} ${name}\n`;
|
|
492
|
+
output += ` ${player.cycles_participated} cycles • ${player.total_sends} sends • ${player.total_sent.toLocaleString()} CHAMPZ\n\n`;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
content: [
|
|
498
|
+
{
|
|
499
|
+
type: 'text',
|
|
500
|
+
text: output,
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
};
|
|
504
|
+
} catch (error) {
|
|
505
|
+
return {
|
|
506
|
+
content: [
|
|
507
|
+
{
|
|
508
|
+
type: 'text',
|
|
509
|
+
text: `Error fetching weekly leaderboard: ${error.message}`,
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
case 'legends_leaderboard_alltime':
|
|
516
|
+
try {
|
|
517
|
+
const category = request.params.arguments?.category || 'all';
|
|
518
|
+
const limit = request.params.arguments?.limit || 10;
|
|
519
|
+
const response = await fetch(`https://api.champz.world/game/spore-trainer/public-leaderboard?type=alltime&category=${category}&limit=${limit}`);
|
|
520
|
+
const data = await response.json();
|
|
521
|
+
|
|
522
|
+
if (!data.success) {
|
|
523
|
+
throw new Error(data.error || 'Failed to fetch all-time leaderboards');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const boards = data.leaderboards;
|
|
527
|
+
let output = `🏅 Legends of Champz - All-Time Leaderboards`;
|
|
528
|
+
if (category !== 'all') {
|
|
529
|
+
output += ` (${category.toUpperCase()})`;
|
|
530
|
+
}
|
|
531
|
+
output += `\n\n`;
|
|
532
|
+
|
|
533
|
+
// Define categories to display based on what's available
|
|
534
|
+
const allCategories = [
|
|
535
|
+
{ key: 'champz_earned', title: '💰 Most CHAMPZ Earned', format: (v) => `${(v / 1000000).toFixed(2)}M` },
|
|
536
|
+
{ key: 'usdc_earned', title: '💵 Most USDC Earned', format: (v) => `$${v.toFixed(2)}` },
|
|
537
|
+
{ key: 'champz_spent', title: '🔥 Most CHAMPZ Spent', format: (v) => `${(v / 1000000).toFixed(2)}M` },
|
|
538
|
+
{ key: 'total_wins', title: '⚔️ Most Total Wins', format: (v) => v.toLocaleString() },
|
|
539
|
+
{ key: 'win_rate', title: '🎯 Highest Win Rate (100+ battles)', format: (v) => `${v.toFixed(1)}%` },
|
|
540
|
+
{ key: 'longest_streak', title: '🔥 Longest Win Streak', format: (v) => `${v} wins` },
|
|
541
|
+
{ key: 'guardian_wins', title: '👑 Most Guardian (Throne) Wins', format: (v) => v },
|
|
542
|
+
{ key: 'ec_best_score', title: '⚡ Best EC Score', format: (v) => v },
|
|
543
|
+
{ key: 'ec_total_rounds', title: '⚡ Most EC Rounds Completed', format: (v) => v.toLocaleString() },
|
|
544
|
+
{ key: 'ec_games_played', title: '⚡ Most EC Games Played', format: (v) => v },
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
allCategories.forEach(cat => {
|
|
548
|
+
const board = boards[cat.key];
|
|
549
|
+
if (board && board.length > 0) {
|
|
550
|
+
output += `${cat.title}\n`;
|
|
551
|
+
board.slice(0, 3).forEach((player, i) => {
|
|
552
|
+
const name = player.display_name || `${player.wallet.slice(0, 6)}...`;
|
|
553
|
+
const value = cat.format(player.value);
|
|
554
|
+
output += ` ${i + 1}. ${name} - ${value}`;
|
|
555
|
+
if (player.battles) {
|
|
556
|
+
output += ` (${player.wins}/${player.battles})`;
|
|
557
|
+
}
|
|
558
|
+
output += `\n`;
|
|
559
|
+
});
|
|
560
|
+
output += `\n`;
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
content: [
|
|
566
|
+
{
|
|
567
|
+
type: 'text',
|
|
568
|
+
text: output,
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
};
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return {
|
|
574
|
+
content: [
|
|
575
|
+
{
|
|
576
|
+
type: 'text',
|
|
577
|
+
text: `Error fetching all-time leaderboards: ${error.message}`,
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
case 'legends_player_data':
|
|
584
|
+
// Check if wallet authentication is configured
|
|
585
|
+
if (!hasWalletAuth) {
|
|
586
|
+
return {
|
|
587
|
+
content: [
|
|
588
|
+
{
|
|
589
|
+
type: 'text',
|
|
590
|
+
text: `To access your personal Legends of Champz data, you need to connect your wallet.
|
|
591
|
+
|
|
592
|
+
🔗 Setup Guide: https://legends.champz.world/mcp-setup
|
|
593
|
+
|
|
594
|
+
This one-time setup takes 2 minutes:
|
|
595
|
+
1. Visit the link above on your desktop
|
|
596
|
+
2. Connect your Coinbase Wallet
|
|
597
|
+
3. Sign a message to prove ownership
|
|
598
|
+
4. Copy the config to Claude Desktop
|
|
599
|
+
5. Restart Claude Desktop
|
|
600
|
+
|
|
601
|
+
You can still ask about:
|
|
602
|
+
• Global game stats (legends_global_stats)
|
|
603
|
+
• Leaderboards (legends_leaderboard_*)
|
|
604
|
+
• Current cycle information`,
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
try {
|
|
611
|
+
// Call player-data endpoint with signature authentication
|
|
612
|
+
const url = `https://api.champz.world/game/spore-trainer/player-data?wallet=${encodeURIComponent(WALLET)}&signature=${encodeURIComponent(SIGNATURE)}×tamp=${encodeURIComponent(SIGNED_AT)}`;
|
|
613
|
+
const response = await fetch(url);
|
|
614
|
+
const data = await response.json();
|
|
615
|
+
|
|
616
|
+
if (!data.success) {
|
|
617
|
+
throw new Error(data.error || 'Failed to fetch player data');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Format player data
|
|
621
|
+
const displayName = data.display_name || data.basename || `${data.wallet.slice(0, 8)}...`;
|
|
622
|
+
const stats = data.statistics;
|
|
623
|
+
const claims = data.claims;
|
|
624
|
+
|
|
625
|
+
let output = `Legends of Champz - Your Statistics\n\n`;
|
|
626
|
+
output += `Player: ${displayName}\n`;
|
|
627
|
+
output += `Wallet: ${data.wallet}\n`;
|
|
628
|
+
if (data.basename) {
|
|
629
|
+
output += `Basename: ${data.basename}\n`;
|
|
630
|
+
}
|
|
631
|
+
output += `Member since: ${new Date(stats.member_since).toLocaleDateString()}\n\n`;
|
|
632
|
+
|
|
633
|
+
output += `📦 Packs & Legends:\n`;
|
|
634
|
+
output += ` • Total packs opened: ${stats.total_packs_opened}\n`;
|
|
635
|
+
output += ` • CHAMPZ spent on packs: ${stats.champz_spent_on_packs.toLocaleString()}\n`;
|
|
636
|
+
output += ` • Legends owned: ${stats.legends_owned}\n`;
|
|
637
|
+
output += ` • Saved trainer slots: ${stats.saved_trainer_slots}\n\n`;
|
|
638
|
+
|
|
639
|
+
output += `⚔️ Battle Statistics:\n`;
|
|
640
|
+
output += ` • Total battles: ${stats.total_battles}\n`;
|
|
641
|
+
output += ` • Wins: ${stats.battles_won} (${(stats.win_rate * 100).toFixed(1)}% win rate)\n`;
|
|
642
|
+
output += ` • Losses: ${stats.battles_lost}\n`;
|
|
643
|
+
output += ` • Current streak: ${stats.current_win_streak}\n`;
|
|
644
|
+
output += ` • Best streak: ${stats.best_win_streak}\n\n`;
|
|
645
|
+
|
|
646
|
+
output += `👑 Thrones & Guardian:\n`;
|
|
647
|
+
output += ` • Thrones owned: ${stats.thrones_owned}\n`;
|
|
648
|
+
output += ` • Times held guardian: ${stats.times_held_guardian}\n`;
|
|
649
|
+
output += ` • CHAMPZ spent on guardian: ${stats.champz_spent_on_guardian.toLocaleString()}\n\n`;
|
|
650
|
+
|
|
651
|
+
output += `💰 Claims History:\n`;
|
|
652
|
+
output += ` • Total USDC claimed: $${claims.total_usdc_claimed}\n`;
|
|
653
|
+
output += ` • Total CHAMPZ claimed: ${claims.total_champz_claimed.toLocaleString()}\n`;
|
|
654
|
+
output += ` • Battle USDC (type 6): ${claims.by_type['6'].count} claims, $${claims.by_type['6'].total_amount}\n`;
|
|
655
|
+
output += ` • Battle CHAMPZ (type 5): ${claims.by_type['5'].count} claims, ${claims.by_type['5'].total_amount.toLocaleString()} tokens\n`;
|
|
656
|
+
output += ` • Guardian USDC (type 19): ${claims.by_type['19'].count} claims, $${claims.by_type['19'].total_amount}\n`;
|
|
657
|
+
output += ` • Guardian CHAMPZ (type 18): ${claims.by_type['18'].count} claims, ${claims.by_type['18'].total_amount.toLocaleString()} tokens\n\n`;
|
|
658
|
+
|
|
659
|
+
// Build content array with text and images
|
|
660
|
+
const content = [
|
|
661
|
+
{
|
|
662
|
+
type: 'text',
|
|
663
|
+
text: output,
|
|
664
|
+
},
|
|
665
|
+
];
|
|
666
|
+
|
|
667
|
+
// Add throne images (show all thrones)
|
|
668
|
+
if (data.thrones && data.thrones.length > 0) {
|
|
669
|
+
const throneText = `\n🏆 Throne Collection (${data.thrones.length}):\n`;
|
|
670
|
+
let throneList = '';
|
|
671
|
+
|
|
672
|
+
data.thrones.forEach((throne, i) => {
|
|
673
|
+
throneList += `${i + 1}. ${throne.name} (${throne.rarity.toUpperCase()}) - Cycle ${throne.cycle_id}\n`;
|
|
674
|
+
|
|
675
|
+
// Add throne image
|
|
676
|
+
const imageUrl = `https://img.champz.world${throne.image_path}`;
|
|
677
|
+
content.push({
|
|
678
|
+
type: 'image',
|
|
679
|
+
data: imageUrl,
|
|
680
|
+
mimeType: 'image/png',
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
content[0].text += throneText + throneList + '\n';
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Show legend summary (don't show all images by default - too many)
|
|
688
|
+
if (data.all_legends && data.all_legends.length > 0) {
|
|
689
|
+
const legendText = `\n🍄 All Legends Owned (${data.all_legends.length}):\n`;
|
|
690
|
+
let legendSummary = '';
|
|
691
|
+
|
|
692
|
+
// Group by rarity
|
|
693
|
+
const byRarity = {
|
|
694
|
+
unique: [],
|
|
695
|
+
legendary: [],
|
|
696
|
+
epic: [],
|
|
697
|
+
rare: [],
|
|
698
|
+
common: []
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
data.all_legends.forEach(legend => {
|
|
702
|
+
byRarity[legend.rarity]?.push(legend);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
Object.entries(byRarity).forEach(([rarity, legends]) => {
|
|
706
|
+
if (legends.length > 0) {
|
|
707
|
+
legendSummary += ` ${rarity.toUpperCase()}: ${legends.length} legends\n`;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
legendSummary += `\nTo view a specific legend, ask: "Show me legend #<ID>"\n`;
|
|
712
|
+
legendSummary += `Example: "Show me legend #1010"\n\n`;
|
|
713
|
+
|
|
714
|
+
content[0].text += legendText + legendSummary;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Add saved trainer images (premium collection)
|
|
718
|
+
if (data.saved_trainers && data.saved_trainers.length > 0) {
|
|
719
|
+
const trainerText = `⭐ Saved Trainers (${data.saved_trainers.length}):\n`;
|
|
720
|
+
let trainerList = '';
|
|
721
|
+
|
|
722
|
+
data.saved_trainers.forEach((trainer, i) => {
|
|
723
|
+
trainerList += `${i + 1}. ${trainer.rarity.toUpperCase()} Legend #${trainer.legend_id} - Power: ${trainer.total_power}\n`;
|
|
724
|
+
trainerList += ` ATK: ${trainer.attack} | DEF: ${trainer.defense} | SPD: ${trainer.speed}\n`;
|
|
725
|
+
trainerList += ` Elements: ${trainer.elements.join(', ')}\n\n`;
|
|
726
|
+
|
|
727
|
+
// Add trainer image
|
|
728
|
+
const imageUrl = `https://img.champz.world${trainer.image_path}`;
|
|
729
|
+
content.push({
|
|
730
|
+
type: 'image',
|
|
731
|
+
data: imageUrl,
|
|
732
|
+
mimeType: 'image/png',
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
content[0].text += trainerText + trainerList;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return { content };
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return {
|
|
742
|
+
content: [
|
|
743
|
+
{
|
|
744
|
+
type: 'text',
|
|
745
|
+
text: `Error fetching player data: ${error.message}\n\nMake sure your wallet is registered in Legends of Champz. Visit https://legends.champz.world to play!`,
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
case 'show_legend':
|
|
752
|
+
if (!hasWalletAuth) {
|
|
753
|
+
return {
|
|
754
|
+
content: [{ type: 'text', text: 'Authentication required. Visit https://legends.champz.world/mcp-setup' }],
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
const legendId = request.params.arguments?.legend_id;
|
|
760
|
+
if (!legendId) {
|
|
761
|
+
throw new Error('legend_id is required');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Fetch player data
|
|
765
|
+
const url = `https://api.champz.world/game/spore-trainer/player-data?wallet=${encodeURIComponent(WALLET)}&signature=${encodeURIComponent(SIGNATURE)}×tamp=${encodeURIComponent(SIGNED_AT)}`;
|
|
766
|
+
const response = await fetch(url);
|
|
767
|
+
const data = await response.json();
|
|
768
|
+
|
|
769
|
+
if (!data.success) {
|
|
770
|
+
throw new Error('Failed to fetch player data');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Find the legend in all_legends
|
|
774
|
+
const legend = data.all_legends?.find(l => l.legend_id === legendId);
|
|
775
|
+
|
|
776
|
+
if (!legend) {
|
|
777
|
+
return {
|
|
778
|
+
content: [
|
|
779
|
+
{
|
|
780
|
+
type: 'text',
|
|
781
|
+
text: `Legend #${legendId} not found in your collection. You own ${data.all_legends?.length || 0} legends total.`,
|
|
782
|
+
},
|
|
783
|
+
],
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Build output
|
|
788
|
+
let output = `Legend #${legend.legend_id} - ${legend.name}\n\n`;
|
|
789
|
+
output += `Rarity: ${legend.rarity.toUpperCase()}\n`;
|
|
790
|
+
output += `Total Power: ${legend.total_power}\n`;
|
|
791
|
+
output += `ATK: ${legend.attack} | DEF: ${legend.defense} | SPD: ${legend.speed}\n`;
|
|
792
|
+
output += `Elements: ${legend.elements.join(', ')}\n`;
|
|
793
|
+
output += `Rolled: ${new Date(legend.rolled_at).toLocaleDateString()}\n`;
|
|
794
|
+
output += `Saved: ${legend.is_saved ? 'Yes ⭐' : 'No'}\n`;
|
|
795
|
+
|
|
796
|
+
// Return with image
|
|
797
|
+
const imageUrl = `https://img.champz.world${legend.image_path}`;
|
|
798
|
+
|
|
799
|
+
return {
|
|
800
|
+
content: [
|
|
801
|
+
{
|
|
802
|
+
type: 'text',
|
|
803
|
+
text: output,
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
type: 'image',
|
|
807
|
+
data: imageUrl,
|
|
808
|
+
mimeType: 'image/png',
|
|
809
|
+
},
|
|
810
|
+
],
|
|
811
|
+
};
|
|
812
|
+
} catch (error) {
|
|
813
|
+
return {
|
|
814
|
+
content: [
|
|
815
|
+
{
|
|
816
|
+
type: 'text',
|
|
817
|
+
text: `Error showing legend: ${error.message}`,
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
case 'show_throne':
|
|
824
|
+
if (!hasWalletAuth) {
|
|
825
|
+
return {
|
|
826
|
+
content: [{ type: 'text', text: 'Authentication required. Visit https://legends.champz.world/mcp-setup' }],
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
try {
|
|
831
|
+
const throneId = request.params.arguments?.throne_id;
|
|
832
|
+
if (!throneId) {
|
|
833
|
+
throw new Error('throne_id is required');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Fetch player data
|
|
837
|
+
const url = `https://api.champz.world/game/spore-trainer/player-data?wallet=${encodeURIComponent(WALLET)}&signature=${encodeURIComponent(SIGNATURE)}×tamp=${encodeURIComponent(SIGNED_AT)}`;
|
|
838
|
+
const response = await fetch(url);
|
|
839
|
+
const data = await response.json();
|
|
840
|
+
|
|
841
|
+
if (!data.success) {
|
|
842
|
+
throw new Error('Failed to fetch player data');
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Find the throne
|
|
846
|
+
const throne = data.thrones?.find(t => t.throne_id === throneId);
|
|
847
|
+
|
|
848
|
+
if (!throne) {
|
|
849
|
+
return {
|
|
850
|
+
content: [
|
|
851
|
+
{
|
|
852
|
+
type: 'text',
|
|
853
|
+
text: `Throne #${throneId} not found in your collection. You own ${data.thrones?.length || 0} thrones total.`,
|
|
854
|
+
},
|
|
855
|
+
],
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Build output
|
|
860
|
+
let output = `Throne #${throne.throne_id} - ${throne.name}\n\n`;
|
|
861
|
+
output += `Rarity: ${throne.rarity.toUpperCase()}\n`;
|
|
862
|
+
output += `Earned in Cycle: ${throne.cycle_id}\n`;
|
|
863
|
+
output += `Earned: ${new Date(throne.earned_at).toLocaleDateString()}\n`;
|
|
864
|
+
|
|
865
|
+
// Return with image
|
|
866
|
+
const imageUrl = `https://img.champz.world${throne.image_path}`;
|
|
867
|
+
|
|
868
|
+
return {
|
|
869
|
+
content: [
|
|
870
|
+
{
|
|
871
|
+
type: 'text',
|
|
872
|
+
text: output,
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
type: 'image',
|
|
876
|
+
data: imageUrl,
|
|
877
|
+
mimeType: 'image/png',
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
};
|
|
881
|
+
} catch (error) {
|
|
882
|
+
return {
|
|
883
|
+
content: [
|
|
884
|
+
{
|
|
885
|
+
type: 'text',
|
|
886
|
+
text: `Error showing throne: ${error.message}`,
|
|
887
|
+
},
|
|
888
|
+
],
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
254
892
|
default:
|
|
255
893
|
throw new Error(`Unknown tool: ${name}`);
|
|
256
894
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@champz-llc/legends-mcp-server",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Legends of Champz - Query game stats and claim rewards through Claude Desktop",
|
|
3
|
+
"version": "1.3.2",
|
|
4
|
+
"description": "MCP server for Legends of Champz - Query game stats, access personal data with signature auth, and claim rewards through Claude Desktop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|