ping-mcp-server 0.1.9 → 0.1.11

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 (3) hide show
  1. package/dist/index.js +108 -79
  2. package/package.json +1 -1
  3. package/src/index.ts +156 -86
package/dist/index.js CHANGED
@@ -515,7 +515,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
515
515
  },
516
516
  {
517
517
  name: "ping_submit_answer",
518
- description: "Submit an answer to a Ping question and earn the reward. Use when the user provides their answer to one of the available questions. Set preApproved=true when the user selects a suggested answer (skips AI review for instant approval). Requires GitHub login (ping_login) or wallet address.",
518
+ description: "Submit a SINGLE answer. DEPRECATED: Use ping_submit_batch instead for better UX. Only use this if you need to submit exactly one answer.",
519
519
  inputSchema: {
520
520
  type: "object",
521
521
  properties: {
@@ -535,9 +535,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
535
535
  required: ["questionId", "answer"]
536
536
  }
537
537
  },
538
+ {
539
+ name: "ping_submit_batch",
540
+ description: "Submit multiple answers at once. ALWAYS use this instead of ping_submit_answer. Returns a fun receipt-style summary. Much cleaner UX than individual submissions.",
541
+ inputSchema: {
542
+ type: "object",
543
+ properties: {
544
+ answers: {
545
+ type: "array",
546
+ items: {
547
+ type: "object",
548
+ properties: {
549
+ questionId: { type: "string" },
550
+ answer: { type: "string" },
551
+ preApproved: { type: "boolean" }
552
+ },
553
+ required: ["questionId", "answer"]
554
+ },
555
+ description: "Array of answers to submit"
556
+ }
557
+ },
558
+ required: ["answers"]
559
+ }
560
+ },
538
561
  {
539
562
  name: "ping_answer_flow",
540
- description: 'Start an interactive Q&A session to earn money answering Ping questions. TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". Returns questions with suggested answers. CRITICAL: Present questions in BATCHES OF 4 using AskUserQuestion (tool limit). If more than 4 questions, present first 4, submit answers, then IMMEDIATELY present next 4 WITHOUT claiming. Continue batching until all questions are answered. Submit answers IN PARALLEL for each batch. AUTO-CLAIM: ONLY call ping_claim_reward AFTER the FINAL batch is submitted, not between batches. Requires GitHub login first (ping_login).',
563
+ description: 'Start an interactive Q&A session to earn money answering Ping questions. TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". Returns questions with suggested answers. CRITICAL: Use ping_submit_batch to submit answers (NOT ping_submit_answer). Present questions in batches of 4, collect answers, then call ping_submit_batch ONCE. AUTO-CLAIM: Call ping_claim_reward AFTER all batches submitted. Requires GitHub login first (ping_login).',
541
564
  inputSchema: {
542
565
  type: "object",
543
566
  properties: {
@@ -775,11 +798,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
775
798
  return {
776
799
  content: [{
777
800
  type: "text",
778
- text: JSON.stringify({
779
- loggedIn: false,
780
- message: "You're not logged in to Ping.",
781
- hint: "Use ping_login to connect with GitHub."
782
- }, null, 2)
801
+ text: "\u274C Not logged in. Use ping_login to connect with GitHub."
783
802
  }]
784
803
  };
785
804
  }
@@ -789,31 +808,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
789
808
  return {
790
809
  content: [{
791
810
  type: "text",
792
- text: JSON.stringify({
793
- loggedIn: true,
794
- handle: userData.handle,
795
- authProvider: userData.authProvider,
796
- walletAddress: userData.walletAddress || "Not set yet",
797
- balance: {
798
- available: userData.availableBalance,
799
- pending: userData.pendingBalance,
800
- claimed: userData.claimedBalance
801
- },
802
- message: `Logged in as ${userData.handle}`
803
- }, null, 2)
811
+ text: `\u2705 Logged in as @${userData.handle}`
804
812
  }]
805
813
  };
806
814
  } catch (err) {
807
815
  return {
808
816
  content: [{
809
817
  type: "text",
810
- text: JSON.stringify({
811
- loggedIn: true,
812
- handle: `@${auth.handle}`,
813
- authProvider: "github",
814
- message: `Logged in as @${auth.handle}`,
815
- note: "Could not fetch balance (API may be unavailable)"
816
- }, null, 2)
818
+ text: `\u2705 Logged in as @${auth.handle} (offline mode)`
817
819
  }]
818
820
  };
819
821
  }
@@ -821,13 +823,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
821
823
  return {
822
824
  content: [{
823
825
  type: "text",
824
- text: JSON.stringify({
825
- loggedIn: false,
826
- legacyMode: true,
827
- walletAddress: wallet,
828
- message: "Using legacy wallet-only mode (not logged in with GitHub).",
829
- hint: "Use ping_login to connect with GitHub for the full experience."
830
- }, null, 2)
826
+ text: `\u26A0\uFE0F Legacy mode: wallet ${wallet?.slice(0, 10)}... (Use ping_login for full features)`
831
827
  }]
832
828
  };
833
829
  }
@@ -1021,6 +1017,58 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1021
1017
  };
1022
1018
  }
1023
1019
  // ────────────────────────────────────────────────────────
1020
+ // BATCH SUBMIT (Submit multiple answers at once)
1021
+ // ────────────────────────────────────────────────────────
1022
+ case "ping_submit_batch": {
1023
+ const auth = getAuth();
1024
+ if (!auth) {
1025
+ return {
1026
+ content: [{
1027
+ type: "text",
1028
+ text: JSON.stringify({
1029
+ success: false,
1030
+ error: "Not logged in.",
1031
+ hint: "Use ping_login to connect your GitHub account first."
1032
+ }, null, 2)
1033
+ }]
1034
+ };
1035
+ }
1036
+ const { answers } = args || {};
1037
+ if (!answers || answers.length === 0) {
1038
+ return {
1039
+ content: [{
1040
+ type: "text",
1041
+ text: JSON.stringify({
1042
+ success: false,
1043
+ error: "No answers provided.",
1044
+ hint: "Provide an array of answers with questionId and answer fields."
1045
+ }, null, 2)
1046
+ }]
1047
+ };
1048
+ }
1049
+ const data = await apiRequest("/answers/batch", {
1050
+ method: "POST",
1051
+ body: JSON.stringify({ answers })
1052
+ });
1053
+ const { summary } = data;
1054
+ const checkmark = summary.approved > 0 ? "\u2705" : "\u2B1C";
1055
+ const statusLine = summary.errors > 0 ? `${summary.approved}/${summary.submitted} approved, ${summary.errors} error(s)` : `${summary.approved}/${summary.submitted} approved`;
1056
+ const receipt = `
1057
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1058
+ \u2502 \u{1F9FE} PING RECEIPT \u2502
1059
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1060
+ \u2502 ${checkmark} ${statusLine.padEnd(24)}\u2502
1061
+ \u2502 \u{1F4B0} ${summary.totalEarned.padEnd(24)}\u2502
1062
+ \u2502 \u{1F4CA} Balance: ${summary.pendingBalance.padEnd(14)}\u2502
1063
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`;
1064
+ return {
1065
+ content: [{
1066
+ type: "text",
1067
+ text: receipt.trim() + "\n\n" + (summary.approved > 0 ? "\u{1F389} Nice work! Your earnings are ready to claim." : "Keep trying - quality answers get approved!")
1068
+ }]
1069
+ };
1070
+ }
1071
+ // ────────────────────────────────────────────────────────
1024
1072
  // ANSWER FLOW (Interactive Q&A Session)
1025
1073
  // ────────────────────────────────────────────────────────
1026
1074
  case "ping_answer_flow": {
@@ -1040,15 +1088,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1040
1088
  const { limit = 50 } = args || {};
1041
1089
  const data = await apiRequest(`/questions?limit=${limit}`);
1042
1090
  if (data.questions.length === 0) {
1091
+ const noQuestionsMessage = `
1092
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1093
+ \u2502 \u{1F4ED} No questions available \u2502
1094
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1095
+ \u2502 All caught up! Check back soon \u2502
1096
+ \u2502 for new earning opportunities. \u2502
1097
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
1098
+
1099
+ \u{1F4A1} Tip: Questions are added throughout the day. Try again later!`;
1043
1100
  return {
1044
1101
  content: [{
1045
1102
  type: "text",
1046
- text: JSON.stringify({
1047
- success: true,
1048
- questions: [],
1049
- message: "No questions available right now.",
1050
- hint: "New questions are added regularly. Check back soon to start earning!"
1051
- }, null, 2)
1103
+ text: noQuestionsMessage.trim()
1052
1104
  }]
1053
1105
  };
1054
1106
  }
@@ -1062,41 +1114,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1062
1114
  suggestedAnswers: suggestions
1063
1115
  };
1064
1116
  });
1117
+ const summaryCard = `
1118
+ \u{1F4EC} ${questionsWithSuggestions.length} questions available \xB7 Earn up to ${data.totalPotentialEarnings}
1119
+
1120
+ Ready to start answering!`;
1121
+ const flowData = {
1122
+ _meta: "INTERNAL: Do not display this JSON to user. Use AskUserQuestion to present questions.",
1123
+ questions: questionsWithSuggestions,
1124
+ instructions: "Present questions in batches of 4 using AskUserQuestion. Use ping_validate_batch first, then ping_submit_batch after user answers. Auto-claim at end."
1125
+ };
1065
1126
  return {
1066
1127
  content: [{
1067
1128
  type: "text",
1068
- text: JSON.stringify({
1069
- success: true,
1070
- flow: "answer_session",
1071
- handle: `@${auth.handle}`,
1072
- // Summary stats for initial presentation
1073
- summary: {
1074
- questionCount: questionsWithSuggestions.length,
1075
- totalPotentialEarnings: data.totalPotentialEarnings,
1076
- totalAnswersToday: data.totalAnswersToday,
1077
- oneLiner: `\u{1F4EC} ${questionsWithSuggestions.length} questions available \xB7 Earn up to ${data.totalPotentialEarnings} \xB7 ${data.totalAnswersToday} answers submitted today`
1078
- },
1079
- // Full question data for immediate presentation
1080
- questions: questionsWithSuggestions,
1081
- instructions: {
1082
- overview: "This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.",
1083
- step1_startImmediately: "Show the oneLiner summary briefly, then IMMEDIATELY start the batch loop. Do NOT ask for confirmation - user already chose to answer questions.",
1084
- batchLoop: {
1085
- step1_selectNext: "Take the next 4 question IDs from the questions array (questions you haven't shown yet).",
1086
- step2_validate: "Call ping_validate_batch with those 4 question IDs to check availability.",
1087
- step3_handleExhausted: "If some questions are exhausted, use only the available ones returned (might be 0-4 questions).",
1088
- step4_skip: "If 0 questions available in this batch, skip to next 4 question IDs and validate again.",
1089
- step5_present: "Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.",
1090
- step6_collect: "User answers the questions.",
1091
- step7_submit: "Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.",
1092
- step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
1093
- step9_repeat: "If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim."
1094
- },
1095
- finalClaim: "AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.",
1096
- claimRetry: "If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.",
1097
- completionMessage: "Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link."
1098
- }
1099
- }, null, 2)
1129
+ text: summaryCard.trim() + "\n\n<!-- FLOW_DATA:" + JSON.stringify(flowData) + " -->"
1100
1130
  }]
1101
1131
  };
1102
1132
  }
@@ -1226,17 +1256,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1226
1256
  // ────────────────────────────────────────────────────────
1227
1257
  case "ping_stats": {
1228
1258
  const data = await apiRequest("/stats");
1259
+ const statsLine = `\u{1F4CA} ${data.questions.active} questions \xB7 ${data.responses.today} answers today \xB7 ${data.rewards.claimedToday} claimed`;
1229
1260
  return {
1230
1261
  content: [{
1231
1262
  type: "text",
1232
- text: JSON.stringify({
1233
- success: true,
1234
- stats: {
1235
- questionsAvailable: data.questions.active,
1236
- answersToday: data.responses.today,
1237
- rewardsClaimedToday: data.rewards.claimedToday
1238
- }
1239
- }, null, 2)
1263
+ text: statsLine + `
1264
+ <!-- STATS:${JSON.stringify({
1265
+ questionsAvailable: data.questions.active,
1266
+ answersToday: data.responses.today,
1267
+ rewardsClaimedToday: data.rewards.claimedToday
1268
+ })} -->`
1240
1269
  }]
1241
1270
  };
1242
1271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ping-mcp-server",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server that gives Claude Code the ability to interact with Ping",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -813,10 +813,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
813
813
  {
814
814
  name: 'ping_submit_answer',
815
815
  description:
816
- 'Submit an answer to a Ping question and earn the reward. ' +
817
- 'Use when the user provides their answer to one of the available questions. ' +
818
- 'Set preApproved=true when the user selects a suggested answer (skips AI review for instant approval). ' +
819
- 'Requires GitHub login (ping_login) or wallet address.',
816
+ 'Submit a SINGLE answer. DEPRECATED: Use ping_submit_batch instead for better UX. ' +
817
+ 'Only use this if you need to submit exactly one answer.',
820
818
  inputSchema: {
821
819
  type: 'object',
822
820
  properties: {
@@ -836,17 +834,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
836
834
  required: ['questionId', 'answer'],
837
835
  },
838
836
  },
837
+ {
838
+ name: 'ping_submit_batch',
839
+ description:
840
+ 'Submit multiple answers at once. ALWAYS use this instead of ping_submit_answer. ' +
841
+ 'Returns a fun receipt-style summary. Much cleaner UX than individual submissions.',
842
+ inputSchema: {
843
+ type: 'object',
844
+ properties: {
845
+ answers: {
846
+ type: 'array',
847
+ items: {
848
+ type: 'object',
849
+ properties: {
850
+ questionId: { type: 'string' },
851
+ answer: { type: 'string' },
852
+ preApproved: { type: 'boolean' },
853
+ },
854
+ required: ['questionId', 'answer'],
855
+ },
856
+ description: 'Array of answers to submit',
857
+ },
858
+ },
859
+ required: ['answers'],
860
+ },
861
+ },
839
862
  {
840
863
  name: 'ping_answer_flow',
841
864
  description:
842
865
  'Start an interactive Q&A session to earn money answering Ping questions. ' +
843
866
  'TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". ' +
844
867
  'Returns questions with suggested answers. ' +
845
- 'CRITICAL: Present questions in BATCHES OF 4 using AskUserQuestion (tool limit). ' +
846
- 'If more than 4 questions, present first 4, submit answers, then IMMEDIATELY present next 4 WITHOUT claiming. ' +
847
- 'Continue batching until all questions are answered. ' +
848
- 'Submit answers IN PARALLEL for each batch. ' +
849
- 'AUTO-CLAIM: ONLY call ping_claim_reward AFTER the FINAL batch is submitted, not between batches. ' +
868
+ 'CRITICAL: Use ping_submit_batch to submit answers (NOT ping_submit_answer). ' +
869
+ 'Present questions in batches of 4, collect answers, then call ping_submit_batch ONCE. ' +
870
+ 'AUTO-CLAIM: Call ping_claim_reward AFTER all batches submitted. ' +
850
871
  'Requires GitHub login first (ping_login).',
851
872
  inputSchema: {
852
873
  type: 'object',
@@ -1135,11 +1156,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1135
1156
  return {
1136
1157
  content: [{
1137
1158
  type: 'text',
1138
- text: JSON.stringify({
1139
- loggedIn: false,
1140
- message: "You're not logged in to Ping.",
1141
- hint: 'Use ping_login to connect with GitHub.',
1142
- }, null, 2),
1159
+ text: '❌ Not logged in. Use ping_login to connect with GitHub.',
1143
1160
  }],
1144
1161
  };
1145
1162
  }
@@ -1164,18 +1181,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1164
1181
  return {
1165
1182
  content: [{
1166
1183
  type: 'text',
1167
- text: JSON.stringify({
1168
- loggedIn: true,
1169
- handle: userData.handle,
1170
- authProvider: userData.authProvider,
1171
- walletAddress: userData.walletAddress || 'Not set yet',
1172
- balance: {
1173
- available: userData.availableBalance,
1174
- pending: userData.pendingBalance,
1175
- claimed: userData.claimedBalance,
1176
- },
1177
- message: `Logged in as ${userData.handle}`,
1178
- }, null, 2),
1184
+ text: `✅ Logged in as @${userData.handle}`,
1179
1185
  }],
1180
1186
  };
1181
1187
  } catch (err) {
@@ -1183,13 +1189,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1183
1189
  return {
1184
1190
  content: [{
1185
1191
  type: 'text',
1186
- text: JSON.stringify({
1187
- loggedIn: true,
1188
- handle: `@${auth.handle}`,
1189
- authProvider: 'github',
1190
- message: `Logged in as @${auth.handle}`,
1191
- note: 'Could not fetch balance (API may be unavailable)',
1192
- }, null, 2),
1192
+ text: `✅ Logged in as @${auth.handle} (offline mode)`,
1193
1193
  }],
1194
1194
  };
1195
1195
  }
@@ -1199,13 +1199,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1199
1199
  return {
1200
1200
  content: [{
1201
1201
  type: 'text',
1202
- text: JSON.stringify({
1203
- loggedIn: false,
1204
- legacyMode: true,
1205
- walletAddress: wallet,
1206
- message: 'Using legacy wallet-only mode (not logged in with GitHub).',
1207
- hint: 'Use ping_login to connect with GitHub for the full experience.',
1208
- }, null, 2),
1202
+ text: `⚠️ Legacy mode: wallet ${wallet?.slice(0, 10)}... (Use ping_login for full features)`,
1209
1203
  }],
1210
1204
  };
1211
1205
  }
@@ -1456,6 +1450,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1456
1450
  };
1457
1451
  }
1458
1452
 
1453
+ // ────────────────────────────────────────────────────────
1454
+ // BATCH SUBMIT (Submit multiple answers at once)
1455
+ // ────────────────────────────────────────────────────────
1456
+ case 'ping_submit_batch': {
1457
+ const auth = getAuth();
1458
+
1459
+ if (!auth) {
1460
+ return {
1461
+ content: [{
1462
+ type: 'text',
1463
+ text: JSON.stringify({
1464
+ success: false,
1465
+ error: 'Not logged in.',
1466
+ hint: 'Use ping_login to connect your GitHub account first.',
1467
+ }, null, 2),
1468
+ }],
1469
+ };
1470
+ }
1471
+
1472
+ const { answers } = (args || {}) as {
1473
+ answers: Array<{
1474
+ questionId: string;
1475
+ answer: string;
1476
+ preApproved?: boolean;
1477
+ }>;
1478
+ };
1479
+
1480
+ if (!answers || answers.length === 0) {
1481
+ return {
1482
+ content: [{
1483
+ type: 'text',
1484
+ text: JSON.stringify({
1485
+ success: false,
1486
+ error: 'No answers provided.',
1487
+ hint: 'Provide an array of answers with questionId and answer fields.',
1488
+ }, null, 2),
1489
+ }],
1490
+ };
1491
+ }
1492
+
1493
+ // Call the batch endpoint
1494
+ const data = await apiRequest<{
1495
+ success: boolean;
1496
+ summary: {
1497
+ submitted: number;
1498
+ approved: number;
1499
+ rejected: number;
1500
+ errors: number;
1501
+ totalEarned: string;
1502
+ totalEarnedCents: number;
1503
+ pendingBalance: string;
1504
+ };
1505
+ results: Array<{
1506
+ questionId: string;
1507
+ status: string;
1508
+ earned?: string;
1509
+ error?: string;
1510
+ }>;
1511
+ }>('/answers/batch', {
1512
+ method: 'POST',
1513
+ body: JSON.stringify({ answers }),
1514
+ });
1515
+
1516
+ // Build a fun receipt-style ASCII art response
1517
+ const { summary } = data;
1518
+ const checkmark = summary.approved > 0 ? '✅' : '⬜';
1519
+ const statusLine = summary.errors > 0
1520
+ ? `${summary.approved}/${summary.submitted} approved, ${summary.errors} error(s)`
1521
+ : `${summary.approved}/${summary.submitted} approved`;
1522
+
1523
+ const receipt = `
1524
+ ┌─────────────────────────────┐
1525
+ │ 🧾 PING RECEIPT │
1526
+ ├─────────────────────────────┤
1527
+ │ ${checkmark} ${statusLine.padEnd(24)}│
1528
+ │ 💰 ${summary.totalEarned.padEnd(24)}│
1529
+ │ 📊 Balance: ${summary.pendingBalance.padEnd(14)}│
1530
+ └─────────────────────────────┘`;
1531
+
1532
+ return {
1533
+ content: [{
1534
+ type: 'text',
1535
+ text: receipt.trim() + '\n\n' + (summary.approved > 0
1536
+ ? '🎉 Nice work! Your earnings are ready to claim.'
1537
+ : 'Keep trying - quality answers get approved!'),
1538
+ }],
1539
+ };
1540
+ }
1541
+
1459
1542
  // ────────────────────────────────────────────────────────
1460
1543
  // ANSWER FLOW (Interactive Q&A Session)
1461
1544
  // ────────────────────────────────────────────────────────
@@ -1494,15 +1577,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1494
1577
  }>(`/questions?limit=${limit}`);
1495
1578
 
1496
1579
  if (data.questions.length === 0) {
1580
+ const noQuestionsMessage = `
1581
+ ┌─────────────────────────────────────┐
1582
+ │ 📭 No questions available │
1583
+ ├─────────────────────────────────────┤
1584
+ │ All caught up! Check back soon │
1585
+ │ for new earning opportunities. │
1586
+ └─────────────────────────────────────┘
1587
+
1588
+ 💡 Tip: Questions are added throughout the day. Try again later!`;
1589
+
1497
1590
  return {
1498
1591
  content: [{
1499
1592
  type: 'text',
1500
- text: JSON.stringify({
1501
- success: true,
1502
- questions: [],
1503
- message: 'No questions available right now.',
1504
- hint: 'New questions are added regularly. Check back soon to start earning!',
1505
- }, null, 2),
1593
+ text: noQuestionsMessage.trim(),
1506
1594
  }],
1507
1595
  };
1508
1596
  }
@@ -1520,41 +1608,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1520
1608
  };
1521
1609
  });
1522
1610
 
1611
+ // Build a nice human-readable summary
1612
+ const summaryCard = `
1613
+ 📬 ${questionsWithSuggestions.length} questions available · Earn up to ${data.totalPotentialEarnings}
1614
+
1615
+ Ready to start answering!`;
1616
+
1617
+ // Compact data for Claude to process (not for user display)
1618
+ const flowData = {
1619
+ _meta: 'INTERNAL: Do not display this JSON to user. Use AskUserQuestion to present questions.',
1620
+ questions: questionsWithSuggestions,
1621
+ instructions: 'Present questions in batches of 4 using AskUserQuestion. Use ping_validate_batch first, then ping_submit_batch after user answers. Auto-claim at end.',
1622
+ };
1623
+
1523
1624
  return {
1524
1625
  content: [{
1525
1626
  type: 'text',
1526
- text: JSON.stringify({
1527
- success: true,
1528
- flow: 'answer_session',
1529
- handle: `@${auth.handle}`,
1530
- // Summary stats for initial presentation
1531
- summary: {
1532
- questionCount: questionsWithSuggestions.length,
1533
- totalPotentialEarnings: data.totalPotentialEarnings,
1534
- totalAnswersToday: data.totalAnswersToday,
1535
- oneLiner: `📬 ${questionsWithSuggestions.length} questions available · Earn up to ${data.totalPotentialEarnings} · ${data.totalAnswersToday} answers submitted today`,
1536
- },
1537
- // Full question data for immediate presentation
1538
- questions: questionsWithSuggestions,
1539
- instructions: {
1540
- overview: 'This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.',
1541
- step1_startImmediately: 'Show the oneLiner summary briefly, then IMMEDIATELY start the batch loop. Do NOT ask for confirmation - user already chose to answer questions.',
1542
- batchLoop: {
1543
- step1_selectNext: 'Take the next 4 question IDs from the questions array (questions you haven\'t shown yet).',
1544
- step2_validate: 'Call ping_validate_batch with those 4 question IDs to check availability.',
1545
- step3_handleExhausted: 'If some questions are exhausted, use only the available ones returned (might be 0-4 questions).',
1546
- step4_skip: 'If 0 questions available in this batch, skip to next 4 question IDs and validate again.',
1547
- step5_present: 'Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.',
1548
- step6_collect: 'User answers the questions.',
1549
- step7_submit: 'Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.',
1550
- step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
1551
- step9_repeat: 'If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim.',
1552
- },
1553
- finalClaim: 'AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.',
1554
- claimRetry: 'If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.',
1555
- completionMessage: 'Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link.',
1556
- },
1557
- }, null, 2),
1627
+ text: summaryCard.trim() + '\n\n<!-- FLOW_DATA:' + JSON.stringify(flowData) + ' -->',
1558
1628
  }],
1559
1629
  };
1560
1630
  }
@@ -1730,17 +1800,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1730
1800
  rewards: { claimedToday: string; claimedTodayCents: number };
1731
1801
  }>('/stats');
1732
1802
 
1803
+ // Human-readable summary with parseable data for banner
1804
+ const statsLine = `📊 ${data.questions.active} questions · ${data.responses.today} answers today · ${data.rewards.claimedToday} claimed`;
1805
+
1733
1806
  return {
1734
1807
  content: [{
1735
1808
  type: 'text',
1736
- text: JSON.stringify({
1737
- success: true,
1738
- stats: {
1739
- questionsAvailable: data.questions.active,
1740
- answersToday: data.responses.today,
1741
- rewardsClaimedToday: data.rewards.claimedToday,
1742
- },
1743
- }, null, 2),
1809
+ text: statsLine + `\n<!-- STATS:${JSON.stringify({
1810
+ questionsAvailable: data.questions.active,
1811
+ answersToday: data.responses.today,
1812
+ rewardsClaimedToday: data.rewards.claimedToday,
1813
+ })} -->`,
1744
1814
  }],
1745
1815
  };
1746
1816
  }