ping-mcp-server 0.1.5 → 0.1.6
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/dist/index.js +110 -70
- package/package.json +1 -1
- package/src/index.ts +144 -90
- package/package/package.json +0 -29
- package/package/src/index.ts +0 -2123
- package/package/src/init.ts +0 -302
- package/package/tsconfig.json +0 -8
- package/ping-mcp-server-0.1.4.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -423,8 +423,8 @@ async function apiRequest(path2, options = {}) {
|
|
|
423
423
|
if (fetchError.message.includes("fetch failed") || fetchError.message.includes("ECONNREFUSED") || fetchError.message.includes("ENOTFOUND") || fetchError.message.includes("ETIMEDOUT") || fetchError.cause) {
|
|
424
424
|
throw new PingError(
|
|
425
425
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
426
|
-
|
|
427
|
-
|
|
426
|
+
`Cannot connect to Ping API at ${API_BASE_URL}. Check your internet connection.`,
|
|
427
|
+
`Make sure you can reach ${API_BASE_URL} and try again.`
|
|
428
428
|
);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
@@ -459,7 +459,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
459
459
|
// ────────────────────────────────────────────────────────
|
|
460
460
|
{
|
|
461
461
|
name: "ping_login",
|
|
462
|
-
description: 'Login to Ping with GitHub. Opens a browser window for GitHub OAuth authentication. TRIGGERS: "ping login", "login to ping", "connect to ping", "sign in to ping", "start using ping". Use when the user wants to login, sign in, connect their account, or start using Ping. After login, a wallet will be automatically created for the user.',
|
|
462
|
+
description: 'Login to Ping with GitHub. Opens a browser window for GitHub OAuth authentication. TRIGGERS: "ping login", "login to ping", "connect to ping", "sign in to ping", "start using ping". Use when the user wants to login, sign in, connect their account, or start using Ping. After login, a wallet will be automatically created for the user. API: https://api.ping-money.com',
|
|
463
463
|
inputSchema: {
|
|
464
464
|
type: "object",
|
|
465
465
|
properties: {},
|
|
@@ -477,7 +477,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
477
477
|
},
|
|
478
478
|
{
|
|
479
479
|
name: "ping_whoami",
|
|
480
|
-
description: "Show your Ping account info - GitHub handle, wallet address, and balance. Use when the user asks who they are logged in as, their account status, or profile info.",
|
|
480
|
+
description: "Show your Ping account info - GitHub handle, wallet address, and balance. Use when the user asks who they are logged in as, their account status, or profile info. API: https://api.ping-money.com",
|
|
481
481
|
inputSchema: {
|
|
482
482
|
type: "object",
|
|
483
483
|
properties: {},
|
|
@@ -513,20 +513,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
513
513
|
required: []
|
|
514
514
|
}
|
|
515
515
|
},
|
|
516
|
-
{
|
|
517
|
-
name: "ping_list_questions",
|
|
518
|
-
description: "Show questions available to answer and earn money on Ping. Use when the user wants to see earning opportunities, available questions, or ways to make money. Returns questions with their rewards.",
|
|
519
|
-
inputSchema: {
|
|
520
|
-
type: "object",
|
|
521
|
-
properties: {
|
|
522
|
-
limit: {
|
|
523
|
-
type: "number",
|
|
524
|
-
description: "Number of questions to show (default: 5)"
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
required: []
|
|
528
|
-
}
|
|
529
|
-
},
|
|
530
516
|
{
|
|
531
517
|
name: "ping_submit_answer",
|
|
532
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.",
|
|
@@ -551,18 +537,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
551
537
|
},
|
|
552
538
|
{
|
|
553
539
|
name: "ping_answer_flow",
|
|
554
|
-
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
|
|
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).',
|
|
555
541
|
inputSchema: {
|
|
556
542
|
type: "object",
|
|
557
543
|
properties: {
|
|
558
544
|
limit: {
|
|
559
545
|
type: "number",
|
|
560
|
-
description: "Number of questions to include in the session (default:
|
|
546
|
+
description: "Number of questions to include in the session (default: 50)"
|
|
561
547
|
}
|
|
562
548
|
},
|
|
563
549
|
required: []
|
|
564
550
|
}
|
|
565
551
|
},
|
|
552
|
+
{
|
|
553
|
+
name: "ping_validate_batch",
|
|
554
|
+
description: "Validate if a batch of questions is still available to answer before showing to user. Checks if questions are active, not exhausted (maxResponses reached), and not already answered. Returns available questions with full data + list of exhausted question IDs. Use this before presenting questions to ensure they can actually be answered.",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
questionIds: {
|
|
559
|
+
type: "array",
|
|
560
|
+
items: { type: "string" },
|
|
561
|
+
description: 'Array of question IDs to validate (e.g., ["q1", "q2", "q3", "q4"])'
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
required: ["questionIds"]
|
|
565
|
+
}
|
|
566
|
+
},
|
|
566
567
|
{
|
|
567
568
|
name: "ping_claim_reward",
|
|
568
569
|
description: 'Claim pending Ping earnings and send them to your crypto wallet on Base. TRIGGERS: "claim my ping", "withdraw from ping", "cash out", "get my money", "claim rewards". Use when the user wants to withdraw, cash out, or claim their rewards. Transfers USDC to their wallet instantly. Works with GitHub login (ping_login) or legacy wallet (ping_set_wallet).',
|
|
@@ -897,43 +898,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
897
898
|
};
|
|
898
899
|
}
|
|
899
900
|
// ────────────────────────────────────────────────────────
|
|
900
|
-
// LIST QUESTIONS
|
|
901
|
-
// ────────────────────────────────────────────────────────
|
|
902
|
-
case "ping_list_questions": {
|
|
903
|
-
const { limit = 5 } = args || {};
|
|
904
|
-
const data = await apiRequest(`/questions?limit=${limit}`);
|
|
905
|
-
if (data.questions.length === 0) {
|
|
906
|
-
return {
|
|
907
|
-
content: [{
|
|
908
|
-
type: "text",
|
|
909
|
-
text: JSON.stringify({
|
|
910
|
-
success: true,
|
|
911
|
-
questions: [],
|
|
912
|
-
message: "No questions available right now.",
|
|
913
|
-
hint: "New questions are added regularly. Check back soon, or use ping_create_question to ask your own!"
|
|
914
|
-
}, null, 2)
|
|
915
|
-
}]
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
return {
|
|
919
|
-
content: [{
|
|
920
|
-
type: "text",
|
|
921
|
-
text: JSON.stringify({
|
|
922
|
-
success: true,
|
|
923
|
-
questions: data.questions.map((q) => ({
|
|
924
|
-
id: q.id,
|
|
925
|
-
question: q.text,
|
|
926
|
-
reward: q.reward,
|
|
927
|
-
category: q.category,
|
|
928
|
-
asker: q.asker
|
|
929
|
-
})),
|
|
930
|
-
totalAvailable: data.total,
|
|
931
|
-
message: `Found ${data.questions.length} question${data.questions.length !== 1 ? "s" : ""} you can answer!`
|
|
932
|
-
}, null, 2)
|
|
933
|
-
}]
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
// ────────────────────────────────────────────────────────
|
|
937
901
|
// SUBMIT ANSWER
|
|
938
902
|
// ────────────────────────────────────────────────────────
|
|
939
903
|
case "ping_submit_answer": {
|
|
@@ -945,7 +909,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
945
909
|
text: JSON.stringify({
|
|
946
910
|
success: false,
|
|
947
911
|
error: "Missing question ID.",
|
|
948
|
-
hint: "Use
|
|
912
|
+
hint: "Use ping_answer_flow to see available questions and answer them."
|
|
949
913
|
}, null, 2)
|
|
950
914
|
}]
|
|
951
915
|
};
|
|
@@ -1003,6 +967,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1003
967
|
}]
|
|
1004
968
|
};
|
|
1005
969
|
}
|
|
970
|
+
if (data.error === "question_exhausted") {
|
|
971
|
+
return {
|
|
972
|
+
content: [{
|
|
973
|
+
type: "text",
|
|
974
|
+
text: JSON.stringify({
|
|
975
|
+
success: false,
|
|
976
|
+
error: "question_exhausted",
|
|
977
|
+
message: data.message || "This question is no longer accepting answers (maxResponses reached).",
|
|
978
|
+
hint: "Skip this question and continue with others in your batch."
|
|
979
|
+
})
|
|
980
|
+
}]
|
|
981
|
+
};
|
|
982
|
+
}
|
|
1006
983
|
return {
|
|
1007
984
|
content: [{
|
|
1008
985
|
type: "text",
|
|
@@ -1051,7 +1028,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1051
1028
|
}]
|
|
1052
1029
|
};
|
|
1053
1030
|
}
|
|
1054
|
-
const { limit =
|
|
1031
|
+
const { limit = 50 } = args || {};
|
|
1055
1032
|
const data = await apiRequest(`/questions?limit=${limit}`);
|
|
1056
1033
|
if (data.questions.length === 0) {
|
|
1057
1034
|
return {
|
|
@@ -1093,23 +1070,86 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1093
1070
|
// Full question data (present AFTER user confirms)
|
|
1094
1071
|
questions: questionsWithSuggestions,
|
|
1095
1072
|
instructions: {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1073
|
+
overview: "This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.",
|
|
1074
|
+
step1_summary: 'FIRST show ONLY the oneLiner from summary with ~estimates, then ask "Ready to start?" Do NOT reveal questions yet.',
|
|
1075
|
+
step2_startBatching: "AFTER user confirms, START THE BATCH LOOP below:",
|
|
1076
|
+
batchLoop: {
|
|
1077
|
+
step1_selectNext: "Take the next 4 question IDs from the questions array (questions you haven't shown yet).",
|
|
1078
|
+
step2_validate: "Call ping_validate_batch with those 4 question IDs to check availability.",
|
|
1079
|
+
step3_handleExhausted: "If some questions are exhausted, use only the available ones returned (might be 0-4 questions).",
|
|
1080
|
+
step4_skip: "If 0 questions available in this batch, skip to next 4 question IDs and validate again.",
|
|
1081
|
+
step5_present: "Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.",
|
|
1082
|
+
step6_collect: "User answers the questions.",
|
|
1083
|
+
step7_submit: "Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.",
|
|
1084
|
+
step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
|
|
1085
|
+
step9_repeat: "If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim."
|
|
1086
|
+
},
|
|
1087
|
+
finalClaim: "AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.",
|
|
1088
|
+
claimRetry: "If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.",
|
|
1089
|
+
completionMessage: "Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link."
|
|
1107
1090
|
}
|
|
1108
1091
|
}, null, 2)
|
|
1109
1092
|
}]
|
|
1110
1093
|
};
|
|
1111
1094
|
}
|
|
1112
1095
|
// ────────────────────────────────────────────────────────
|
|
1096
|
+
// VALIDATE BATCH
|
|
1097
|
+
// ────────────────────────────────────────────────────────
|
|
1098
|
+
case "ping_validate_batch": {
|
|
1099
|
+
const { questionIds } = args || {};
|
|
1100
|
+
if (!questionIds || !Array.isArray(questionIds) || questionIds.length === 0) {
|
|
1101
|
+
return {
|
|
1102
|
+
content: [{
|
|
1103
|
+
type: "text",
|
|
1104
|
+
text: JSON.stringify({
|
|
1105
|
+
success: false,
|
|
1106
|
+
error: "Invalid question IDs provided. Expected an array of question IDs."
|
|
1107
|
+
}, null, 2)
|
|
1108
|
+
}]
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
const auth = getAuth();
|
|
1112
|
+
if (!auth) {
|
|
1113
|
+
return {
|
|
1114
|
+
content: [{
|
|
1115
|
+
type: "text",
|
|
1116
|
+
text: JSON.stringify({
|
|
1117
|
+
success: false,
|
|
1118
|
+
error: "Not logged in.",
|
|
1119
|
+
hint: "Use ping_login to connect your GitHub account first."
|
|
1120
|
+
}, null, 2)
|
|
1121
|
+
}]
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
const data = await apiRequest("/questions/validate", {
|
|
1125
|
+
method: "POST",
|
|
1126
|
+
body: JSON.stringify({ questionIds })
|
|
1127
|
+
});
|
|
1128
|
+
const availableWithSuggestions = data.available.map((q) => {
|
|
1129
|
+
const suggestions = generateSuggestedAnswers(q.text, q.category);
|
|
1130
|
+
return {
|
|
1131
|
+
id: q.id,
|
|
1132
|
+
question: q.text,
|
|
1133
|
+
reward: q.reward,
|
|
1134
|
+
rewardCents: q.rewardCents,
|
|
1135
|
+
category: q.category,
|
|
1136
|
+
suggestedAnswers: suggestions
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
return {
|
|
1140
|
+
content: [{
|
|
1141
|
+
type: "text",
|
|
1142
|
+
text: JSON.stringify({
|
|
1143
|
+
success: true,
|
|
1144
|
+
available: availableWithSuggestions,
|
|
1145
|
+
exhausted: data.exhausted,
|
|
1146
|
+
totalRemaining: data.totalRemaining,
|
|
1147
|
+
message: data.available.length === 0 ? "All questions in this batch are no longer available." : data.exhausted.length > 0 ? `${data.available.length} questions available, ${data.exhausted.length} exhausted.` : `All ${data.available.length} questions are available!`
|
|
1148
|
+
}, null, 2)
|
|
1149
|
+
}]
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
// ────────────────────────────────────────────────────────
|
|
1113
1153
|
// CLAIM REWARD
|
|
1114
1154
|
// ────────────────────────────────────────────────────────
|
|
1115
1155
|
case "ping_claim_reward": {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -688,8 +688,8 @@ async function apiRequest<T>(
|
|
|
688
688
|
fetchError.cause) {
|
|
689
689
|
throw new PingError(
|
|
690
690
|
PingErrorCode.NETWORK_ERROR,
|
|
691
|
-
|
|
692
|
-
|
|
691
|
+
`Cannot connect to Ping API at ${API_BASE_URL}. Check your internet connection.`,
|
|
692
|
+
`Make sure you can reach ${API_BASE_URL} and try again.`
|
|
693
693
|
);
|
|
694
694
|
}
|
|
695
695
|
}
|
|
@@ -743,7 +743,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
743
743
|
'Login to Ping with GitHub. Opens a browser window for GitHub OAuth authentication. ' +
|
|
744
744
|
'TRIGGERS: "ping login", "login to ping", "connect to ping", "sign in to ping", "start using ping". ' +
|
|
745
745
|
'Use when the user wants to login, sign in, connect their account, or start using Ping. ' +
|
|
746
|
-
'After login, a wallet will be automatically created for the user.'
|
|
746
|
+
'After login, a wallet will be automatically created for the user. ' +
|
|
747
|
+
'API: https://api.ping-money.com',
|
|
747
748
|
inputSchema: {
|
|
748
749
|
type: 'object',
|
|
749
750
|
properties: {},
|
|
@@ -765,7 +766,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
765
766
|
name: 'ping_whoami',
|
|
766
767
|
description:
|
|
767
768
|
'Show your Ping account info - GitHub handle, wallet address, and balance. ' +
|
|
768
|
-
'Use when the user asks who they are logged in as, their account status, or profile info.'
|
|
769
|
+
'Use when the user asks who they are logged in as, their account status, or profile info. ' +
|
|
770
|
+
'API: https://api.ping-money.com',
|
|
769
771
|
inputSchema: {
|
|
770
772
|
type: 'object',
|
|
771
773
|
properties: {},
|
|
@@ -808,23 +810,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
808
810
|
required: [],
|
|
809
811
|
},
|
|
810
812
|
},
|
|
811
|
-
{
|
|
812
|
-
name: 'ping_list_questions',
|
|
813
|
-
description:
|
|
814
|
-
'Show questions available to answer and earn money on Ping. ' +
|
|
815
|
-
'Use when the user wants to see earning opportunities, available questions, or ways to make money. ' +
|
|
816
|
-
'Returns questions with their rewards.',
|
|
817
|
-
inputSchema: {
|
|
818
|
-
type: 'object',
|
|
819
|
-
properties: {
|
|
820
|
-
limit: {
|
|
821
|
-
type: 'number',
|
|
822
|
-
description: 'Number of questions to show (default: 5)',
|
|
823
|
-
},
|
|
824
|
-
},
|
|
825
|
-
required: [],
|
|
826
|
-
},
|
|
827
|
-
},
|
|
828
813
|
{
|
|
829
814
|
name: 'ping_submit_answer',
|
|
830
815
|
description:
|
|
@@ -857,22 +842,42 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
857
842
|
'Start an interactive Q&A session to earn money answering Ping questions. ' +
|
|
858
843
|
'TRIGGERS: "ping", "/ping", "answer questions", "earn money", "make money with ping", "start earning". ' +
|
|
859
844
|
'Returns questions with suggested answers. ' +
|
|
860
|
-
'CRITICAL: Present
|
|
861
|
-
'
|
|
862
|
-
'
|
|
863
|
-
'
|
|
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. ' +
|
|
864
850
|
'Requires GitHub login first (ping_login).',
|
|
865
851
|
inputSchema: {
|
|
866
852
|
type: 'object',
|
|
867
853
|
properties: {
|
|
868
854
|
limit: {
|
|
869
855
|
type: 'number',
|
|
870
|
-
description: 'Number of questions to include in the session (default:
|
|
856
|
+
description: 'Number of questions to include in the session (default: 50)',
|
|
871
857
|
},
|
|
872
858
|
},
|
|
873
859
|
required: [],
|
|
874
860
|
},
|
|
875
861
|
},
|
|
862
|
+
{
|
|
863
|
+
name: 'ping_validate_batch',
|
|
864
|
+
description:
|
|
865
|
+
'Validate if a batch of questions is still available to answer before showing to user. ' +
|
|
866
|
+
'Checks if questions are active, not exhausted (maxResponses reached), and not already answered. ' +
|
|
867
|
+
'Returns available questions with full data + list of exhausted question IDs. ' +
|
|
868
|
+
'Use this before presenting questions to ensure they can actually be answered.',
|
|
869
|
+
inputSchema: {
|
|
870
|
+
type: 'object',
|
|
871
|
+
properties: {
|
|
872
|
+
questionIds: {
|
|
873
|
+
type: 'array',
|
|
874
|
+
items: { type: 'string' },
|
|
875
|
+
description: 'Array of question IDs to validate (e.g., ["q1", "q2", "q3", "q4"])',
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
required: ['questionIds'],
|
|
879
|
+
},
|
|
880
|
+
},
|
|
876
881
|
{
|
|
877
882
|
name: 'ping_claim_reward',
|
|
878
883
|
description:
|
|
@@ -1289,57 +1294,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1289
1294
|
};
|
|
1290
1295
|
}
|
|
1291
1296
|
|
|
1292
|
-
// ────────────────────────────────────────────────────────
|
|
1293
|
-
// LIST QUESTIONS
|
|
1294
|
-
// ────────────────────────────────────────────────────────
|
|
1295
|
-
case 'ping_list_questions': {
|
|
1296
|
-
const { limit = 5 } = (args || {}) as { limit?: number };
|
|
1297
|
-
|
|
1298
|
-
const data = await apiRequest<{
|
|
1299
|
-
questions: Array<{
|
|
1300
|
-
id: string;
|
|
1301
|
-
text: string;
|
|
1302
|
-
reward: string;
|
|
1303
|
-
rewardCents: number;
|
|
1304
|
-
category: string;
|
|
1305
|
-
asker: string;
|
|
1306
|
-
}>;
|
|
1307
|
-
total: number;
|
|
1308
|
-
}>(`/questions?limit=${limit}`);
|
|
1309
|
-
|
|
1310
|
-
if (data.questions.length === 0) {
|
|
1311
|
-
return {
|
|
1312
|
-
content: [{
|
|
1313
|
-
type: 'text',
|
|
1314
|
-
text: JSON.stringify({
|
|
1315
|
-
success: true,
|
|
1316
|
-
questions: [],
|
|
1317
|
-
message: 'No questions available right now.',
|
|
1318
|
-
hint: 'New questions are added regularly. Check back soon, or use ping_create_question to ask your own!',
|
|
1319
|
-
}, null, 2),
|
|
1320
|
-
}],
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
return {
|
|
1325
|
-
content: [{
|
|
1326
|
-
type: 'text',
|
|
1327
|
-
text: JSON.stringify({
|
|
1328
|
-
success: true,
|
|
1329
|
-
questions: data.questions.map(q => ({
|
|
1330
|
-
id: q.id,
|
|
1331
|
-
question: q.text,
|
|
1332
|
-
reward: q.reward,
|
|
1333
|
-
category: q.category,
|
|
1334
|
-
asker: q.asker,
|
|
1335
|
-
})),
|
|
1336
|
-
totalAvailable: data.total,
|
|
1337
|
-
message: `Found ${data.questions.length} question${data.questions.length !== 1 ? 's' : ''} you can answer!`,
|
|
1338
|
-
}, null, 2),
|
|
1339
|
-
}],
|
|
1340
|
-
};
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
1297
|
// ────────────────────────────────────────────────────────
|
|
1344
1298
|
// SUBMIT ANSWER
|
|
1345
1299
|
// ────────────────────────────────────────────────────────
|
|
@@ -1357,7 +1311,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1357
1311
|
text: JSON.stringify({
|
|
1358
1312
|
success: false,
|
|
1359
1313
|
error: 'Missing question ID.',
|
|
1360
|
-
hint: 'Use
|
|
1314
|
+
hint: 'Use ping_answer_flow to see available questions and answer them.',
|
|
1361
1315
|
}, null, 2),
|
|
1362
1316
|
}],
|
|
1363
1317
|
};
|
|
@@ -1438,6 +1392,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1438
1392
|
};
|
|
1439
1393
|
}
|
|
1440
1394
|
|
|
1395
|
+
// Check if question is exhausted (reached maxResponses)
|
|
1396
|
+
if (data.error === 'question_exhausted') {
|
|
1397
|
+
return {
|
|
1398
|
+
content: [{
|
|
1399
|
+
type: 'text',
|
|
1400
|
+
text: JSON.stringify({
|
|
1401
|
+
success: false,
|
|
1402
|
+
error: 'question_exhausted',
|
|
1403
|
+
message: data.message || 'This question is no longer accepting answers (maxResponses reached).',
|
|
1404
|
+
hint: 'Skip this question and continue with others in your batch.',
|
|
1405
|
+
}),
|
|
1406
|
+
}],
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1441
1410
|
return {
|
|
1442
1411
|
content: [{
|
|
1443
1412
|
type: 'text',
|
|
@@ -1495,7 +1464,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1495
1464
|
};
|
|
1496
1465
|
}
|
|
1497
1466
|
|
|
1498
|
-
const { limit =
|
|
1467
|
+
const { limit = 50 } = (args || {}) as { limit?: number };
|
|
1499
1468
|
|
|
1500
1469
|
// Fetch questions from API
|
|
1501
1470
|
const data = await apiRequest<{
|
|
@@ -1557,23 +1526,108 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1557
1526
|
// Full question data (present AFTER user confirms)
|
|
1558
1527
|
questions: questionsWithSuggestions,
|
|
1559
1528
|
instructions: {
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1529
|
+
overview: 'This is a continuous batch validation workflow. Fetch 50 questions upfront, then validate+present in batches of 4 until all questions are exhausted.',
|
|
1530
|
+
step1_summary: 'FIRST show ONLY the oneLiner from summary with ~estimates, then ask "Ready to start?" Do NOT reveal questions yet.',
|
|
1531
|
+
step2_startBatching: 'AFTER user confirms, START THE BATCH LOOP below:',
|
|
1532
|
+
batchLoop: {
|
|
1533
|
+
step1_selectNext: 'Take the next 4 question IDs from the questions array (questions you haven\'t shown yet).',
|
|
1534
|
+
step2_validate: 'Call ping_validate_batch with those 4 question IDs to check availability.',
|
|
1535
|
+
step3_handleExhausted: 'If some questions are exhausted, use only the available ones returned (might be 0-4 questions).',
|
|
1536
|
+
step4_skip: 'If 0 questions available in this batch, skip to next 4 question IDs and validate again.',
|
|
1537
|
+
step5_present: 'Present available questions using AskUserQuestion (1-4 questions). Format: question text with reward in header, options are ONLY suggested answers.',
|
|
1538
|
+
step6_collect: 'User answers the questions.',
|
|
1539
|
+
step7_submit: 'Submit answers IN PARALLEL using multiple ping_submit_answer calls. preApproved=true for suggested answers, preApproved=false for custom.',
|
|
1540
|
+
step8_handleErrors: 'If some submissions fail with "question_exhausted" error, log them but continue.',
|
|
1541
|
+
step9_repeat: 'If more questions remain in the original questions array, repeat from step1_selectNext. Otherwise, proceed to finalClaim.',
|
|
1542
|
+
},
|
|
1543
|
+
finalClaim: 'AFTER ALL batches complete (no more questions left to validate), call ping_claim_reward ONCE automatically.',
|
|
1544
|
+
claimRetry: 'If ping_claim_reward fails, wait 2 seconds and retry ONCE. If it fails again, tell user earnings are saved.',
|
|
1545
|
+
completionMessage: 'Show: total questions answered, total earned, any exhausted questions encountered, transaction hash, and basescan link.',
|
|
1571
1546
|
},
|
|
1572
1547
|
}, null, 2),
|
|
1573
1548
|
}],
|
|
1574
1549
|
};
|
|
1575
1550
|
}
|
|
1576
1551
|
|
|
1552
|
+
// ────────────────────────────────────────────────────────
|
|
1553
|
+
// VALIDATE BATCH
|
|
1554
|
+
// ────────────────────────────────────────────────────────
|
|
1555
|
+
case 'ping_validate_batch': {
|
|
1556
|
+
const { questionIds } = (args || {}) as { questionIds: string[] };
|
|
1557
|
+
|
|
1558
|
+
if (!questionIds || !Array.isArray(questionIds) || questionIds.length === 0) {
|
|
1559
|
+
return {
|
|
1560
|
+
content: [{
|
|
1561
|
+
type: 'text',
|
|
1562
|
+
text: JSON.stringify({
|
|
1563
|
+
success: false,
|
|
1564
|
+
error: 'Invalid question IDs provided. Expected an array of question IDs.',
|
|
1565
|
+
}, null, 2),
|
|
1566
|
+
}],
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
const auth = getAuth();
|
|
1571
|
+
if (!auth) {
|
|
1572
|
+
return {
|
|
1573
|
+
content: [{
|
|
1574
|
+
type: 'text',
|
|
1575
|
+
text: JSON.stringify({
|
|
1576
|
+
success: false,
|
|
1577
|
+
error: 'Not logged in.',
|
|
1578
|
+
hint: 'Use ping_login to connect your GitHub account first.',
|
|
1579
|
+
}, null, 2),
|
|
1580
|
+
}],
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// Call the validate endpoint
|
|
1585
|
+
const data = await apiRequest<{
|
|
1586
|
+
available: Array<{
|
|
1587
|
+
id: string;
|
|
1588
|
+
text: string;
|
|
1589
|
+
reward: string;
|
|
1590
|
+
rewardCents: number;
|
|
1591
|
+
category: string;
|
|
1592
|
+
}>;
|
|
1593
|
+
exhausted: string[];
|
|
1594
|
+
totalRemaining: number;
|
|
1595
|
+
}>('/questions/validate', {
|
|
1596
|
+
method: 'POST',
|
|
1597
|
+
body: JSON.stringify({ questionIds }),
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
// Generate suggested answers for available questions
|
|
1601
|
+
const availableWithSuggestions = data.available.map(q => {
|
|
1602
|
+
const suggestions = generateSuggestedAnswers(q.text, q.category);
|
|
1603
|
+
return {
|
|
1604
|
+
id: q.id,
|
|
1605
|
+
question: q.text,
|
|
1606
|
+
reward: q.reward,
|
|
1607
|
+
rewardCents: q.rewardCents,
|
|
1608
|
+
category: q.category,
|
|
1609
|
+
suggestedAnswers: suggestions,
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
return {
|
|
1614
|
+
content: [{
|
|
1615
|
+
type: 'text',
|
|
1616
|
+
text: JSON.stringify({
|
|
1617
|
+
success: true,
|
|
1618
|
+
available: availableWithSuggestions,
|
|
1619
|
+
exhausted: data.exhausted,
|
|
1620
|
+
totalRemaining: data.totalRemaining,
|
|
1621
|
+
message: data.available.length === 0
|
|
1622
|
+
? 'All questions in this batch are no longer available.'
|
|
1623
|
+
: data.exhausted.length > 0
|
|
1624
|
+
? `${data.available.length} questions available, ${data.exhausted.length} exhausted.`
|
|
1625
|
+
: `All ${data.available.length} questions are available!`,
|
|
1626
|
+
}, null, 2),
|
|
1627
|
+
}],
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1577
1631
|
// ────────────────────────────────────────────────────────
|
|
1578
1632
|
// CLAIM REWARD
|
|
1579
1633
|
// ────────────────────────────────────────────────────────
|
package/package/package.json
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ping-mcp-server",
|
|
3
|
-
"version": "0.1.4",
|
|
4
|
-
"description": "MCP server that gives Claude Code the ability to interact with Ping",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"ping-mcp-server": "./dist/init.js",
|
|
8
|
-
"ping-mcp": "./dist/index.js",
|
|
9
|
-
"ping-init": "./dist/init.js"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"dev": "tsx watch src/index.ts",
|
|
13
|
-
"build": "tsup src/index.ts src/init.ts --format esm --dts",
|
|
14
|
-
"start": "node dist/index.js",
|
|
15
|
-
"test": "vitest",
|
|
16
|
-
"lint": "eslint src/",
|
|
17
|
-
"clean": "rm -rf dist"
|
|
18
|
-
},
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
21
|
-
"zod": "^3.22.0"
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"@types/node": "^25.0.3",
|
|
25
|
-
"tsup": "^8.0.0",
|
|
26
|
-
"tsx": "^4.0.0",
|
|
27
|
-
"vitest": "^1.0.0"
|
|
28
|
-
}
|
|
29
|
-
}
|