@champz-llc/legends-mcp-server 1.6.1 → 1.7.1

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.
Files changed (2) hide show
  1. package/index.js +280 -21
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -10,7 +10,7 @@ import fetch from 'node-fetch';
10
10
  import sharp from 'sharp';
11
11
 
12
12
  // Helper function to fetch image, resize, and convert to base64
13
- async function fetchImageAsBase64(imageUrl) {
13
+ async function fetchImageAsBase64(imageUrl, options = {}) {
14
14
  try {
15
15
  console.error(`[MCP] Fetching image: ${imageUrl}`);
16
16
  const response = await fetch(imageUrl);
@@ -27,24 +27,63 @@ async function fetchImageAsBase64(imageUrl) {
27
27
  const originalBuffer = Buffer.from(arrayBuffer);
28
28
  console.error(`[MCP] Original image size: ${Math.round(originalBuffer.length / 1024)}KB`);
29
29
 
30
- // Resize image to max 200x200 (perfect for chat) and convert to JPEG for smaller size
31
- const resizedBuffer = await sharp(originalBuffer)
32
- .resize(200, 200, {
30
+ // Default options
31
+ const maxSize = options.maxSize || 300; // Increased from 200 to 300
32
+ const quality = options.quality || 95; // Increased from 80 to 95
33
+ const format = options.format || 'jpeg'; // Can override to 'png'
34
+
35
+ // Resize image
36
+ let processor = sharp(originalBuffer)
37
+ .resize(maxSize, maxSize, {
33
38
  fit: 'inside',
34
39
  withoutEnlargement: true
35
- })
36
- .jpeg({ quality: 80, mozjpeg: true }) // JPEG is much smaller than PNG
37
- .toBuffer();
40
+ });
41
+
42
+ // Apply format
43
+ if (format === 'png') {
44
+ processor = processor.png({ quality, compressionLevel: 6 });
45
+ } else {
46
+ processor = processor.jpeg({ quality, mozjpeg: true });
47
+ }
38
48
 
49
+ const resizedBuffer = await processor.toBuffer();
39
50
  const base64 = resizedBuffer.toString('base64');
40
51
  console.error(`[MCP] Resized image size: ${Math.round(resizedBuffer.length / 1024)}KB, base64: ${Math.round(base64.length / 1024)}KB`);
41
- return base64;
52
+ return { base64, format };
42
53
  } catch (error) {
43
54
  console.error(`[MCP] Error processing image ${imageUrl}:`, error.message);
44
55
  return null;
45
56
  }
46
57
  }
47
58
 
59
+ // Helper function to fetch token icons (smaller, simpler images)
60
+ async function fetchTokenIcon(iconUrl) {
61
+ try {
62
+ console.error(`[MCP] Fetching token icon: ${iconUrl}`);
63
+ const response = await fetch(iconUrl);
64
+ if (!response.ok) {
65
+ console.error(`[MCP] Icon fetch failed: ${response.status}`);
66
+ return null;
67
+ }
68
+
69
+ const contentType = response.headers.get('content-type');
70
+ const arrayBuffer = await response.arrayBuffer();
71
+ const buffer = Buffer.from(arrayBuffer);
72
+
73
+ // For small icons, keep original or resize to max 64x64, use PNG for transparency
74
+ const format = contentType?.includes('svg') ? 'png' : 'png';
75
+ const resizedBuffer = await sharp(buffer)
76
+ .resize(64, 64, { fit: 'inside', withoutEnlargement: false })
77
+ .png({ quality: 100 })
78
+ .toBuffer();
79
+
80
+ return resizedBuffer.toString('base64');
81
+ } catch (error) {
82
+ console.error(`[MCP] Error fetching token icon:`, error.message);
83
+ return null;
84
+ }
85
+ }
86
+
48
87
  // MCP Signature Authentication (optional - for player data access)
49
88
  const WALLET = process.env.WALLET;
50
89
  const SIGNATURE = process.env.SIGNATURE;
@@ -382,15 +421,231 @@ Last updated: ${new Date(stats.cached_at * 1000).toLocaleString()}`;
382
421
  };
383
422
  }
384
423
 
385
- let output = 'šŸ„ Legends of Champz - Claimable Rewards\n\n';
386
- output += `${data.message}\n\n`;
387
- output += `šŸ’° **Total Claimable:**\n`;
388
- if (data.total_champz !== '0.00 CHAMPZ') output += ` šŸ„ ${data.total_champz}\n`;
389
- if (data.total_usdc !== '0.00 USDC') output += ` šŸ’µ ${data.total_usdc}\n`;
390
- output += `\nšŸ“ Ready to execute on Base L2 (Chain ID: ${data.chain_id})\n`;
424
+ // Fetch and embed token icons as base64
425
+ const champzIconBase64 = await fetchTokenIcon('https://img.champz.world/img/icon_small.png');
426
+ const usdcIconBase64 = await fetchTokenIcon('https://img.champz.world/img/loots/usdc.svg');
427
+
428
+ const champzIcon = champzIconBase64 ? `data:image/png;base64,${champzIconBase64}` : '';
429
+ const usdcIcon = usdcIconBase64 ? `data:image/png;base64,${usdcIconBase64}` : '';
430
+
431
+ let claimsHTML = '';
432
+
433
+ // CHAMPZ claims
434
+ if (data.champz_claims.length > 0) {
435
+ data.champz_claims.forEach((claim, idx) => {
436
+ const typeIcon = claim.claim_type === 18 ? 'šŸ‘‘' : 'āš”ļø';
437
+ const typeColor = claim.claim_type === 18 ? '#9D4EDD' : '#4EA8DE';
438
+ claimsHTML += `
439
+ <div class="claim-card">
440
+ <div class="claim-header">
441
+ <span class="claim-number">#${idx + 1}</span>
442
+ <span class="claim-type" style="background: ${typeColor};">${typeIcon} ${claim.claim_type_name}</span>
443
+ </div>
444
+ <div class="claim-amount">
445
+ <img src="${champzIcon}" alt="CHAMPZ" class="token-icon">
446
+ <span class="amount">${claim.amount_human}</span>
447
+ </div>
448
+ <div class="claim-footer">
449
+ <span class="expires">Expires: ${new Date(claim.expires_at).toLocaleDateString()}</span>
450
+ </div>
451
+ </div>
452
+ `;
453
+ });
454
+ }
455
+
456
+ // USDC claims
457
+ if (data.usdc_claims.length > 0) {
458
+ const champzClaimsCount = data.champz_claims.length;
459
+ data.usdc_claims.forEach((claim, idx) => {
460
+ const typeIcon = claim.claim_type === 5 ? 'āš”ļø' : claim.claim_type === 6 ? 'šŸ‘‘' : 'šŸ¤–';
461
+ const typeColor = claim.claim_type === 5 ? '#4EA8DE' : claim.claim_type === 6 ? '#9D4EDD' : '#FFD700';
462
+ claimsHTML += `
463
+ <div class="claim-card">
464
+ <div class="claim-header">
465
+ <span class="claim-number">#${champzClaimsCount + idx + 1}</span>
466
+ <span class="claim-type" style="background: ${typeColor};">${typeIcon} ${claim.claim_type_name}</span>
467
+ </div>
468
+ <div class="claim-amount">
469
+ <img src="${usdcIcon}" alt="USDC" class="token-icon">
470
+ <span class="amount">${claim.amount_human}</span>
471
+ </div>
472
+ <div class="claim-footer">
473
+ <span class="expires">Expires: ${new Date(claim.expires_at).toLocaleDateString()}</span>
474
+ </div>
475
+ </div>
476
+ `;
477
+ });
478
+ }
479
+
480
+ const html = `<!DOCTYPE html>
481
+ <html>
482
+ <head>
483
+ <meta charset="UTF-8">
484
+ <style>
485
+ * { margin: 0; padding: 0; box-sizing: border-box; }
486
+ body {
487
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
488
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
489
+ padding: 20px;
490
+ min-height: 100vh;
491
+ }
492
+ .container {
493
+ max-width: 600px;
494
+ margin: 0 auto;
495
+ }
496
+ .header {
497
+ text-align: center;
498
+ margin-bottom: 24px;
499
+ padding-bottom: 16px;
500
+ border-bottom: 2px solid rgba(255,255,255,0.1);
501
+ }
502
+ .header h1 {
503
+ color: #fff;
504
+ font-size: 24px;
505
+ margin-bottom: 8px;
506
+ }
507
+ .header .subtitle {
508
+ color: #aaa;
509
+ font-size: 14px;
510
+ }
511
+ .claim-card {
512
+ background: linear-gradient(145deg, #2a2a3e 0%, #1f1f2e 100%);
513
+ border-radius: 12px;
514
+ padding: 16px;
515
+ margin-bottom: 12px;
516
+ border: 1px solid rgba(255,255,255,0.1);
517
+ transition: transform 0.2s;
518
+ }
519
+ .claim-card:hover {
520
+ transform: translateY(-2px);
521
+ border-color: rgba(157, 78, 221, 0.5);
522
+ }
523
+ .claim-header {
524
+ display: flex;
525
+ justify-content: space-between;
526
+ align-items: center;
527
+ margin-bottom: 12px;
528
+ }
529
+ .claim-number {
530
+ background: rgba(255,255,255,0.1);
531
+ color: #fff;
532
+ padding: 4px 10px;
533
+ border-radius: 12px;
534
+ font-size: 12px;
535
+ font-weight: bold;
536
+ }
537
+ .claim-type {
538
+ padding: 4px 12px;
539
+ border-radius: 12px;
540
+ font-size: 12px;
541
+ font-weight: bold;
542
+ color: #fff;
543
+ }
544
+ .claim-amount {
545
+ display: flex;
546
+ align-items: center;
547
+ gap: 12px;
548
+ margin-bottom: 12px;
549
+ }
550
+ .token-icon {
551
+ width: 32px;
552
+ height: 32px;
553
+ border-radius: 50%;
554
+ background: rgba(255,255,255,0.1);
555
+ padding: 2px;
556
+ }
557
+ .amount {
558
+ color: #fff;
559
+ font-size: 20px;
560
+ font-weight: bold;
561
+ }
562
+ .claim-footer {
563
+ display: flex;
564
+ justify-content: space-between;
565
+ padding-top: 8px;
566
+ border-top: 1px solid rgba(255,255,255,0.05);
567
+ }
568
+ .expires {
569
+ color: #888;
570
+ font-size: 11px;
571
+ }
572
+ .summary {
573
+ background: linear-gradient(135deg, rgba(157, 78, 221, 0.2), rgba(77, 144, 254, 0.2));
574
+ border: 2px solid #9D4EDD;
575
+ border-radius: 12px;
576
+ padding: 16px;
577
+ margin-top: 16px;
578
+ text-align: center;
579
+ }
580
+ .summary-title {
581
+ color: #aaa;
582
+ font-size: 12px;
583
+ margin-bottom: 8px;
584
+ text-transform: uppercase;
585
+ letter-spacing: 1px;
586
+ }
587
+ .summary-amount {
588
+ display: flex;
589
+ align-items: center;
590
+ justify-content: center;
591
+ gap: 8px;
592
+ margin: 8px 0;
593
+ }
594
+ .summary-amount img {
595
+ width: 24px;
596
+ height: 24px;
597
+ }
598
+ .summary-amount span {
599
+ color: #fff;
600
+ font-size: 18px;
601
+ font-weight: bold;
602
+ }
603
+ .footer-note {
604
+ text-align: center;
605
+ color: #888;
606
+ font-size: 12px;
607
+ margin-top: 16px;
608
+ padding: 12px;
609
+ background: rgba(255,255,255,0.05);
610
+ border-radius: 8px;
611
+ }
612
+ </style>
613
+ </head>
614
+ <body>
615
+ <div class="container">
616
+ <div class="header">
617
+ <h1>šŸ„ Claimable Rewards</h1>
618
+ <div class="subtitle">${data.message}</div>
619
+ </div>
620
+
621
+ ${claimsHTML}
622
+
623
+ <div class="summary">
624
+ <div class="summary-title">Total Claimable</div>
625
+ ${data.total_champz !== '0 CHAMPZ' ? `
626
+ <div class="summary-amount">
627
+ <img src="${champzIcon}" alt="CHAMPZ">
628
+ <span>${data.total_champz}</span>
629
+ </div>
630
+ ` : ''}
631
+ ${data.total_usdc !== '0.00 USDC' ? `
632
+ <div class="summary-amount">
633
+ <img src="${usdcIcon}" alt="USDC">
634
+ <span>${data.total_usdc}</span>
635
+ </div>
636
+ ` : ''}
637
+ </div>
638
+
639
+ <div class="footer-note">
640
+ šŸ“ Ready to execute on Base L2 (Chain ID: ${data.chain_id})<br>
641
+ šŸ’” Claims are executed one at a time through Base MCP
642
+ </div>
643
+ </div>
644
+ </body>
645
+ </html>`;
391
646
 
392
647
  return {
393
- content: [{ type: 'text', text: output }],
648
+ content: [{ type: 'text', text: html }],
394
649
  };
395
650
  } catch (error) {
396
651
  return {
@@ -933,9 +1188,11 @@ You can still ask about:
933
1188
  };
934
1189
  }
935
1190
 
936
- // Fetch image
1191
+ // Fetch image with higher quality settings
937
1192
  const imageUrl = `https://img.champz.world${legend.image_path}`;
938
- const base64Data = await fetchImageAsBase64(imageUrl);
1193
+ const imageData = await fetchImageAsBase64(imageUrl, { maxSize: 300, quality: 95 });
1194
+ const base64Data = imageData?.base64;
1195
+ const imageFormat = imageData?.format || 'jpeg';
939
1196
 
940
1197
  // Build HTML card with embedded image
941
1198
  const rarityColors = {
@@ -1104,7 +1361,7 @@ You can still ask about:
1104
1361
  </div>
1105
1362
  ${base64Data ? `
1106
1363
  <div class="image-container">
1107
- <img src="data:image/jpeg;base64,${base64Data}" alt="${legend.name}" class="legend-image">
1364
+ <img src="data:image/${imageFormat};base64,${base64Data}" alt="${legend.name}" class="legend-image">
1108
1365
  </div>` : ''}
1109
1366
  <div class="elements">${elementIcons}</div>
1110
1367
  <div class="stats">
@@ -1188,9 +1445,11 @@ You can still ask about:
1188
1445
  };
1189
1446
  }
1190
1447
 
1191
- // Fetch and convert image to base64
1448
+ // Fetch and convert image to base64 with higher quality settings
1192
1449
  const imageUrl = `https://img.champz.world${throne.image_path}`;
1193
- const base64Data = await fetchImageAsBase64(imageUrl);
1450
+ const imageData = await fetchImageAsBase64(imageUrl, { maxSize: 300, quality: 95 });
1451
+ const base64Data = imageData?.base64;
1452
+ const imageFormat = imageData?.format || 'jpeg';
1194
1453
 
1195
1454
  // Build HTML card with embedded image
1196
1455
  const rarityColors = {
@@ -1334,7 +1593,7 @@ You can still ask about:
1334
1593
  </div>
1335
1594
  ${base64Data ? `
1336
1595
  <div class="image-container">
1337
- <img src="data:image/jpeg;base64,${base64Data}" alt="${throne.name}" class="throne-image">
1596
+ <img src="data:image/${imageFormat};base64,${base64Data}" alt="${throne.name}" class="throne-image">
1338
1597
  </div>` : ''}
1339
1598
  <div class="info">
1340
1599
  <div class="info-row">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@champz-llc/legends-mcp-server",
3
- "version": "1.6.1",
3
+ "version": "1.7.1",
4
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",