@caravo/mcp 0.1.23 → 0.1.25
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 +341 -211
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -169,7 +169,17 @@ function baseHeaders() {
|
|
|
169
169
|
}
|
|
170
170
|
async function apiGet(path) {
|
|
171
171
|
const r = await fetch(`${API_BASE}${path}`, { headers: baseHeaders() });
|
|
172
|
-
return r
|
|
172
|
+
return safeParseJson(r);
|
|
173
|
+
}
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
|
+
async function safeParseJson(r) {
|
|
176
|
+
try {
|
|
177
|
+
return await r.json();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
const text = await r.text().catch(() => "");
|
|
181
|
+
return { error: `Non-JSON response (${r.status}): ${text.slice(0, 200)}` };
|
|
182
|
+
}
|
|
173
183
|
}
|
|
174
184
|
async function apiPost(path, body) {
|
|
175
185
|
const url = `${API_BASE}${path}`;
|
|
@@ -179,7 +189,7 @@ async function apiPost(path, body) {
|
|
|
179
189
|
body: JSON.stringify(body),
|
|
180
190
|
};
|
|
181
191
|
if (!API_KEY)
|
|
182
|
-
return (await fetchWithX402(url, opts, wallet))
|
|
192
|
+
return safeParseJson(await fetchWithX402(url, opts, wallet));
|
|
183
193
|
const r = await fetch(url, opts);
|
|
184
194
|
if (r.status === 401 || r.status === 403 || r.status === 402) {
|
|
185
195
|
process.stderr.write(`[caravo] API key request failed (${r.status}), falling back to x402\n`);
|
|
@@ -188,9 +198,9 @@ async function apiPost(path, body) {
|
|
|
188
198
|
headers: { "Content-Type": "application/json" },
|
|
189
199
|
body: JSON.stringify(body),
|
|
190
200
|
};
|
|
191
|
-
return (await fetchWithX402(url, x402Opts, wallet))
|
|
201
|
+
return safeParseJson(await fetchWithX402(url, x402Opts, wallet));
|
|
192
202
|
}
|
|
193
|
-
return r
|
|
203
|
+
return safeParseJson(r);
|
|
194
204
|
}
|
|
195
205
|
async function apiDelete(path, body) {
|
|
196
206
|
const url = `${API_BASE}${path}`;
|
|
@@ -199,7 +209,15 @@ async function apiDelete(path, body) {
|
|
|
199
209
|
headers: baseHeaders(),
|
|
200
210
|
body: JSON.stringify(body),
|
|
201
211
|
});
|
|
202
|
-
return r
|
|
212
|
+
return safeParseJson(r);
|
|
213
|
+
}
|
|
214
|
+
const MAX_JSON_OUTPUT_CHARS = 20_000;
|
|
215
|
+
function safeJsonText(data, indent = true) {
|
|
216
|
+
const json = indent ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
217
|
+
if (json.length > MAX_JSON_OUTPUT_CHARS) {
|
|
218
|
+
return json.slice(0, MAX_JSON_OUTPUT_CHARS) + `\n... (truncated, ${json.length} chars total)`;
|
|
219
|
+
}
|
|
220
|
+
return json;
|
|
203
221
|
}
|
|
204
222
|
// ─── Input validation helpers ─────────────────────────────────────────────────
|
|
205
223
|
/** Validate tool_id format: only allow safe chars, no path traversal. */
|
|
@@ -271,9 +289,7 @@ function formatOutput(output) {
|
|
|
271
289
|
}
|
|
272
290
|
// JSON
|
|
273
291
|
if (output.json !== undefined) {
|
|
274
|
-
|
|
275
|
-
// Truncate large JSON to avoid context overload
|
|
276
|
-
lines.push(jsonStr.length > 4000 ? jsonStr.slice(0, 4000) + "\n... (truncated)" : jsonStr);
|
|
292
|
+
lines.push(safeJsonText(output.json));
|
|
277
293
|
}
|
|
278
294
|
return lines;
|
|
279
295
|
}
|
|
@@ -354,7 +370,7 @@ function makeFavToolHandler(tool) {
|
|
|
354
370
|
}
|
|
355
371
|
return {
|
|
356
372
|
content: [
|
|
357
|
-
{ type: "text", text: `Error: ${
|
|
373
|
+
{ type: "text", text: `Error: ${safeJsonText(result, false)}` },
|
|
358
374
|
],
|
|
359
375
|
isError: true,
|
|
360
376
|
};
|
|
@@ -495,26 +511,34 @@ function registerAllTools(server) {
|
|
|
495
511
|
if (per_page > 100) {
|
|
496
512
|
return { content: [{ type: "text", text: "Error: per_page must be at most 100" }], isError: true };
|
|
497
513
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
+
try {
|
|
515
|
+
const params = new URLSearchParams();
|
|
516
|
+
if (query)
|
|
517
|
+
params.set("query", query);
|
|
518
|
+
if (tag)
|
|
519
|
+
params.set("tag", tag);
|
|
520
|
+
if (provider)
|
|
521
|
+
params.set("provider", provider);
|
|
522
|
+
if (pricing_type)
|
|
523
|
+
params.set("pricing_type", pricing_type);
|
|
524
|
+
params.set("page", String(page));
|
|
525
|
+
params.set("per_page", String(per_page));
|
|
526
|
+
params.set("view", "agent");
|
|
527
|
+
const data = await apiGet(`/api/tools?${params}`);
|
|
528
|
+
let text = safeJsonText(data);
|
|
529
|
+
if (pendingUpdate) {
|
|
530
|
+
text += `\n\n[Update available: @caravo/mcp ${pendingUpdate.current} → ${pendingUpdate.latest}. Will auto-update on next MCP restart.]`;
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
content: [{ type: "text", text }],
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
return {
|
|
538
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
539
|
+
isError: true,
|
|
540
|
+
};
|
|
514
541
|
}
|
|
515
|
-
return {
|
|
516
|
-
content: [{ type: "text", text }],
|
|
517
|
-
};
|
|
518
542
|
});
|
|
519
543
|
// ── Get tool info ────────────────────────────────────────────────────────────
|
|
520
544
|
server.registerTool("get_tool_info", {
|
|
@@ -530,10 +554,18 @@ function registerAllTools(server) {
|
|
|
530
554
|
isError: true,
|
|
531
555
|
};
|
|
532
556
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
557
|
+
try {
|
|
558
|
+
const data = await apiGet(`/api/tools/${tool_id.trim()}`);
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: "text", text: safeJsonText(data) }],
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
catch (err) {
|
|
564
|
+
return {
|
|
565
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
566
|
+
isError: true,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
537
569
|
});
|
|
538
570
|
// ── use_tool (meta-tool) ─────────────────────────────────────────────────────
|
|
539
571
|
server.registerTool("use_tool", {
|
|
@@ -586,7 +618,7 @@ function registerAllTools(server) {
|
|
|
586
618
|
}
|
|
587
619
|
return {
|
|
588
620
|
content: [
|
|
589
|
-
{ type: "text", text: `Error: ${
|
|
621
|
+
{ type: "text", text: `Error: ${safeJsonText(result, false)}` },
|
|
590
622
|
],
|
|
591
623
|
isError: true,
|
|
592
624
|
};
|
|
@@ -628,82 +660,90 @@ function registerAllTools(server) {
|
|
|
628
660
|
isError: true,
|
|
629
661
|
};
|
|
630
662
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
if (
|
|
663
|
+
try {
|
|
664
|
+
// Upvote mode
|
|
665
|
+
if (upvote_review_id) {
|
|
666
|
+
if (!execution_id) {
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: "text", text: "Error: execution_id is required for upvoting. Each upvote consumes one tool execution." }],
|
|
669
|
+
isError: true,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
const result = await apiPost(`/api/reviews/upvote`, {
|
|
673
|
+
review_id: upvote_review_id,
|
|
674
|
+
execution_id,
|
|
675
|
+
});
|
|
676
|
+
if (result.success) {
|
|
677
|
+
const action = result.action === "already_upvoted" ? "Already upvoted" : "Upvoted";
|
|
678
|
+
const upvotedToolId = result.tool_id || "";
|
|
679
|
+
const lines = [`✓ ${action} review ${upvote_review_id}`];
|
|
680
|
+
// Suggest favorite only when upvoting a 5-star review
|
|
681
|
+
if (result.rating === 5 && upvotedToolId) {
|
|
682
|
+
if (API_KEY && !registeredFavTools.has(upvotedToolId)) {
|
|
683
|
+
lines.push(`This was a 5/5 review — consider favorite_tool(tool_id="${upvotedToolId}") if you plan to reuse it.`);
|
|
684
|
+
}
|
|
685
|
+
else if (!API_KEY) {
|
|
686
|
+
lines.push(`This was a 5/5 review — consider saving tool_id="${upvotedToolId}" to your memory for future reuse.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
content: [{ type: "text", text: result.error ? `Error: ${result.error}` : safeJsonText(result) }],
|
|
693
|
+
isError: true,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
// New review mode
|
|
697
|
+
if (rating == null || !comment) {
|
|
698
|
+
return {
|
|
699
|
+
content: [
|
|
700
|
+
{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: "Error: rating and comment are required for new reviews. To upvote an existing review, use upvote_review_id instead.",
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
isError: true,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
if (rating < 1 || rating > 5) {
|
|
634
709
|
return {
|
|
635
|
-
content: [{ type: "text", text: "Error:
|
|
710
|
+
content: [{ type: "text", text: "Error: rating must be between 1 and 5" }],
|
|
636
711
|
isError: true,
|
|
637
712
|
};
|
|
638
713
|
}
|
|
639
|
-
const result = await apiPost(`/api/reviews
|
|
640
|
-
review_id: upvote_review_id,
|
|
714
|
+
const result = await apiPost(`/api/reviews`, {
|
|
641
715
|
execution_id,
|
|
716
|
+
rating,
|
|
717
|
+
comment,
|
|
718
|
+
agent_id,
|
|
642
719
|
});
|
|
643
|
-
if (result.
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
720
|
+
if (result.error) {
|
|
721
|
+
return {
|
|
722
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
723
|
+
isError: true,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
// The API returns the review record with tool_id derived from execution
|
|
727
|
+
const reviewToolId = result.tool_id || "unknown";
|
|
728
|
+
const lines = [
|
|
729
|
+
`✓ Review submitted for ${reviewToolId} (${rating}/5)`,
|
|
730
|
+
];
|
|
731
|
+
if (rating === 5) {
|
|
732
|
+
if (API_KEY && !registeredFavTools.has(reviewToolId)) {
|
|
733
|
+
lines.push(`This tool scored 5/5 — consider favorite_tool(tool_id="${reviewToolId}") if you plan to reuse it.`);
|
|
734
|
+
}
|
|
735
|
+
else if (!API_KEY) {
|
|
736
|
+
lines.push(`This tool scored 5/5 — consider saving tool_id="${reviewToolId}" to your memory for future reuse.`);
|
|
655
737
|
}
|
|
656
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
657
738
|
}
|
|
658
|
-
return {
|
|
659
|
-
content: [{ type: "text", text: result.error ? `Error: ${result.error}` : JSON.stringify(result, null, 2) }],
|
|
660
|
-
isError: true,
|
|
661
|
-
};
|
|
739
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
662
740
|
}
|
|
663
|
-
|
|
664
|
-
if (rating == null || !comment) {
|
|
665
|
-
return {
|
|
666
|
-
content: [
|
|
667
|
-
{
|
|
668
|
-
type: "text",
|
|
669
|
-
text: "Error: rating and comment are required for new reviews. To upvote an existing review, use upvote_review_id instead.",
|
|
670
|
-
},
|
|
671
|
-
],
|
|
672
|
-
isError: true,
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
if (rating < 1 || rating > 5) {
|
|
676
|
-
return {
|
|
677
|
-
content: [{ type: "text", text: "Error: rating must be between 1 and 5" }],
|
|
678
|
-
isError: true,
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
const result = await apiPost(`/api/reviews`, {
|
|
682
|
-
execution_id,
|
|
683
|
-
rating,
|
|
684
|
-
comment,
|
|
685
|
-
agent_id,
|
|
686
|
-
});
|
|
687
|
-
if (result.error) {
|
|
741
|
+
catch (err) {
|
|
688
742
|
return {
|
|
689
|
-
content: [{ type: "text", text: `Error: ${
|
|
743
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
690
744
|
isError: true,
|
|
691
745
|
};
|
|
692
746
|
}
|
|
693
|
-
// The API returns the review record with tool_id derived from execution
|
|
694
|
-
const reviewToolId = result.tool_id || "unknown";
|
|
695
|
-
const lines = [
|
|
696
|
-
`✓ Review submitted for ${reviewToolId} (${rating}/5)`,
|
|
697
|
-
];
|
|
698
|
-
if (rating === 5) {
|
|
699
|
-
if (API_KEY && !registeredFavTools.has(reviewToolId)) {
|
|
700
|
-
lines.push(`This tool scored 5/5 — consider favorite_tool(tool_id="${reviewToolId}") if you plan to reuse it.`);
|
|
701
|
-
}
|
|
702
|
-
else if (!API_KEY) {
|
|
703
|
-
lines.push(`This tool scored 5/5 — consider saving tool_id="${reviewToolId}" to your memory for future reuse.`);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
707
747
|
});
|
|
708
748
|
// ── Wallet info ──────────────────────────────────────────────────────────────
|
|
709
749
|
server.registerTool("get_wallet_info", {
|
|
@@ -733,16 +773,42 @@ function registerAllTools(server) {
|
|
|
733
773
|
catch {
|
|
734
774
|
// ignore
|
|
735
775
|
}
|
|
776
|
+
const info = {
|
|
777
|
+
address: wallet.address,
|
|
778
|
+
network: "Base mainnet (eip155:8453)",
|
|
779
|
+
usdc_balance: balance,
|
|
780
|
+
};
|
|
781
|
+
// Check if wallet is empty and build funding guidance
|
|
782
|
+
const isWalletEmpty = balance === "0.000000 USDC" || balance === "unknown (check manually)";
|
|
783
|
+
if (isWalletEmpty && !API_KEY) {
|
|
784
|
+
info.funding_guide = [
|
|
785
|
+
"Your wallet has no USDC. Two options to get started:",
|
|
786
|
+
"",
|
|
787
|
+
"Option A — Caravo balance (recommended):",
|
|
788
|
+
' Run the "login" tool to connect your account, then top up at https://www.caravo.ai/dashboard',
|
|
789
|
+
"",
|
|
790
|
+
"Option B — USDC wallet (no registration):",
|
|
791
|
+
` Send USDC on Base to ${wallet.address}`,
|
|
792
|
+
"",
|
|
793
|
+
"Top up once to unlock 200+ tools at $0.001–$0.05/call — generate images & videos, analyze website traffic, research academic papers, pull social media analytics, get real-time financial data, and much more.",
|
|
794
|
+
].join("\n");
|
|
795
|
+
}
|
|
796
|
+
else if (isWalletEmpty && API_KEY) {
|
|
797
|
+
info.funding_guide = [
|
|
798
|
+
"Your wallet has no USDC.",
|
|
799
|
+
"",
|
|
800
|
+
"Top up your Caravo balance at https://www.caravo.ai/dashboard",
|
|
801
|
+
`Or send USDC on Base to ${wallet.address}`,
|
|
802
|
+
].join("\n");
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
info.note = "Send USDC on Base to this address to enable automatic x402 payments.";
|
|
806
|
+
}
|
|
736
807
|
return {
|
|
737
808
|
content: [
|
|
738
809
|
{
|
|
739
810
|
type: "text",
|
|
740
|
-
text: JSON.stringify(
|
|
741
|
-
address: wallet.address,
|
|
742
|
-
network: "Base mainnet (eip155:8453)",
|
|
743
|
-
usdc_balance: balance,
|
|
744
|
-
note: "Send USDC on Base to this address to enable automatic x402 payments.",
|
|
745
|
-
}, null, 2),
|
|
811
|
+
text: JSON.stringify(info, null, 2),
|
|
746
812
|
},
|
|
747
813
|
],
|
|
748
814
|
};
|
|
@@ -871,20 +937,36 @@ function registerAllTools(server) {
|
|
|
871
937
|
description: "List all available tags/categories in the marketplace. Returns tag names, slugs, and tool counts.",
|
|
872
938
|
inputSchema: {},
|
|
873
939
|
}, async () => {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
940
|
+
try {
|
|
941
|
+
const data = await apiGet("/api/tags");
|
|
942
|
+
return {
|
|
943
|
+
content: [{ type: "text", text: safeJsonText(data) }],
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
return {
|
|
948
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
949
|
+
isError: true,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
878
952
|
});
|
|
879
953
|
// ── List providers ───────────────────────────────────────────────────────────
|
|
880
954
|
server.registerTool("list_providers", {
|
|
881
955
|
description: "List all providers/vendors in the marketplace. Returns provider names, slugs, and tool counts.",
|
|
882
956
|
inputSchema: {},
|
|
883
957
|
}, async () => {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
958
|
+
try {
|
|
959
|
+
const data = await apiGet("/api/providers");
|
|
960
|
+
return {
|
|
961
|
+
content: [{ type: "text", text: safeJsonText(data) }],
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
catch (err) {
|
|
965
|
+
return {
|
|
966
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
967
|
+
isError: true,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
888
970
|
});
|
|
889
971
|
// ── Tool Requests ───────────────────────────────────────────────────────────
|
|
890
972
|
server.registerTool("list_tool_requests", {
|
|
@@ -904,14 +986,22 @@ function registerAllTools(server) {
|
|
|
904
986
|
if (per_page > 100) {
|
|
905
987
|
return { content: [{ type: "text", text: "Error: per_page must be at most 100" }], isError: true };
|
|
906
988
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
989
|
+
try {
|
|
990
|
+
const params = new URLSearchParams();
|
|
991
|
+
params.set("status", status);
|
|
992
|
+
params.set("page", String(page));
|
|
993
|
+
params.set("per_page", String(per_page));
|
|
994
|
+
const data = await apiGet(`/api/tool-requests?${params}`);
|
|
995
|
+
return {
|
|
996
|
+
content: [{ type: "text", text: safeJsonText(data) }],
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
1000
|
+
return {
|
|
1001
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1002
|
+
isError: true,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
915
1005
|
});
|
|
916
1006
|
server.registerTool("request_tool", {
|
|
917
1007
|
description: "Submit a request for a tool that doesn't exist in the marketplace yet. " +
|
|
@@ -925,33 +1015,41 @@ function registerAllTools(server) {
|
|
|
925
1015
|
agent_id: z.string().optional().describe("Optional agent identifier"),
|
|
926
1016
|
},
|
|
927
1017
|
}, async ({ title, description, use_case, execution_id, agent_id }) => {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1018
|
+
try {
|
|
1019
|
+
const result = await apiPost("/api/tool-requests", {
|
|
1020
|
+
title,
|
|
1021
|
+
description,
|
|
1022
|
+
use_case,
|
|
1023
|
+
execution_id,
|
|
1024
|
+
agent_id,
|
|
1025
|
+
});
|
|
1026
|
+
if (result.error) {
|
|
1027
|
+
return {
|
|
1028
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
1029
|
+
isError: true,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
return {
|
|
1033
|
+
content: [
|
|
1034
|
+
{
|
|
1035
|
+
type: "text",
|
|
1036
|
+
text: [
|
|
1037
|
+
`✓ Tool request submitted: "${result.title}"`,
|
|
1038
|
+
` Request ID: ${result.id}`,
|
|
1039
|
+
` Status: ${result.status}`,
|
|
1040
|
+
``,
|
|
1041
|
+
`Other agents can upvote this request to signal demand.`,
|
|
1042
|
+
].join("\n"),
|
|
1043
|
+
},
|
|
1044
|
+
],
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
catch (err) {
|
|
936
1048
|
return {
|
|
937
|
-
content: [{ type: "text", text: `Error: ${
|
|
1049
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
938
1050
|
isError: true,
|
|
939
1051
|
};
|
|
940
1052
|
}
|
|
941
|
-
return {
|
|
942
|
-
content: [
|
|
943
|
-
{
|
|
944
|
-
type: "text",
|
|
945
|
-
text: [
|
|
946
|
-
`✓ Tool request submitted: "${result.title}"`,
|
|
947
|
-
` Request ID: ${result.id}`,
|
|
948
|
-
` Status: ${result.status}`,
|
|
949
|
-
``,
|
|
950
|
-
`Other agents can upvote this request to signal demand.`,
|
|
951
|
-
].join("\n"),
|
|
952
|
-
},
|
|
953
|
-
],
|
|
954
|
-
};
|
|
955
1053
|
});
|
|
956
1054
|
server.registerTool("upvote_tool_request", {
|
|
957
1055
|
description: "Upvote an existing tool request to signal demand. " +
|
|
@@ -961,19 +1059,27 @@ function registerAllTools(server) {
|
|
|
961
1059
|
execution_id: z.string().optional().describe("Execution ID from a previous tool use (required if no API key)"),
|
|
962
1060
|
},
|
|
963
1061
|
}, async ({ request_id, execution_id }) => {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1062
|
+
try {
|
|
1063
|
+
const result = await apiPost(`/api/tool-requests/${request_id}`, {
|
|
1064
|
+
execution_id,
|
|
1065
|
+
});
|
|
1066
|
+
if (result.error) {
|
|
1067
|
+
return {
|
|
1068
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
1069
|
+
isError: true,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
const action = result.action === "already_upvoted" ? "Already upvoted" : "Upvoted";
|
|
1073
|
+
return {
|
|
1074
|
+
content: [{ type: "text", text: `✓ ${action} tool request ${request_id}` }],
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
catch (err) {
|
|
968
1078
|
return {
|
|
969
|
-
content: [{ type: "text", text: `Error: ${
|
|
1079
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
970
1080
|
isError: true,
|
|
971
1081
|
};
|
|
972
1082
|
}
|
|
973
|
-
const action = result.action === "already_upvoted" ? "Already upvoted" : "Upvoted";
|
|
974
|
-
return {
|
|
975
|
-
content: [{ type: "text", text: `✓ ${action} tool request ${request_id}` }],
|
|
976
|
-
};
|
|
977
1083
|
});
|
|
978
1084
|
// ── Favorites management ─────────────────────────────────────────────────────
|
|
979
1085
|
server.registerTool("list_favorites", {
|
|
@@ -991,31 +1097,39 @@ function registerAllTools(server) {
|
|
|
991
1097
|
isError: true,
|
|
992
1098
|
};
|
|
993
1099
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1100
|
+
try {
|
|
1101
|
+
const result = await apiGet("/api/favorites");
|
|
1102
|
+
if (result.error) {
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
1105
|
+
isError: true,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
const tools = result.data ?? [];
|
|
1109
|
+
return {
|
|
1110
|
+
content: [
|
|
1111
|
+
{
|
|
1112
|
+
type: "text",
|
|
1113
|
+
text: safeJsonText({
|
|
1114
|
+
total: tools.length,
|
|
1115
|
+
favorites: tools.map((t) => ({
|
|
1116
|
+
tool_id: t.id,
|
|
1117
|
+
name: t.name,
|
|
1118
|
+
mcp_tool_name: `fav:${t.id}`,
|
|
1119
|
+
price_per_call: t.pricing.price_per_call,
|
|
1120
|
+
})),
|
|
1121
|
+
hint: "Favorited tools are registered as direct MCP tools named fav:<tool_id>.",
|
|
1122
|
+
}),
|
|
1123
|
+
},
|
|
1124
|
+
],
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
catch (err) {
|
|
996
1128
|
return {
|
|
997
|
-
content: [{ type: "text", text: `Error: ${
|
|
1129
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
998
1130
|
isError: true,
|
|
999
1131
|
};
|
|
1000
1132
|
}
|
|
1001
|
-
const tools = result.data ?? [];
|
|
1002
|
-
return {
|
|
1003
|
-
content: [
|
|
1004
|
-
{
|
|
1005
|
-
type: "text",
|
|
1006
|
-
text: JSON.stringify({
|
|
1007
|
-
total: tools.length,
|
|
1008
|
-
favorites: tools.map((t) => ({
|
|
1009
|
-
tool_id: t.id,
|
|
1010
|
-
name: t.name,
|
|
1011
|
-
mcp_tool_name: `fav:${t.id}`,
|
|
1012
|
-
price_per_call: t.pricing.price_per_call,
|
|
1013
|
-
})),
|
|
1014
|
-
hint: "Favorited tools are registered as direct MCP tools named fav:<tool_id>.",
|
|
1015
|
-
}, null, 2),
|
|
1016
|
-
},
|
|
1017
|
-
],
|
|
1018
|
-
};
|
|
1019
1133
|
});
|
|
1020
1134
|
server.registerTool("favorite_tool", {
|
|
1021
1135
|
description: "Bookmark a tool you plan to reuse frequently — it appears as a direct fav:<tool_id> MCP tool. " +
|
|
@@ -1038,31 +1152,39 @@ function registerAllTools(server) {
|
|
|
1038
1152
|
isError: true,
|
|
1039
1153
|
};
|
|
1040
1154
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1155
|
+
try {
|
|
1156
|
+
const result = await apiPost("/api/favorites", { tool_id });
|
|
1157
|
+
if (result.error) {
|
|
1158
|
+
return {
|
|
1159
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
1160
|
+
isError: true,
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
// Dynamically register the new fav tool in this session
|
|
1164
|
+
const tool = result.tool;
|
|
1165
|
+
if (tool) {
|
|
1166
|
+
registerFavTool(server, tool);
|
|
1167
|
+
}
|
|
1043
1168
|
return {
|
|
1044
|
-
content: [
|
|
1045
|
-
|
|
1169
|
+
content: [
|
|
1170
|
+
{
|
|
1171
|
+
type: "text",
|
|
1172
|
+
text: [
|
|
1173
|
+
`★ Added "${tool?.name ?? tool_id}" to favorites!`,
|
|
1174
|
+
``,
|
|
1175
|
+
`It is now registered as a direct MCP tool: fav:${tool_id}`,
|
|
1176
|
+
`Call it directly with its input parameters — no need for use_tool.`,
|
|
1177
|
+
].join("\n"),
|
|
1178
|
+
},
|
|
1179
|
+
],
|
|
1046
1180
|
};
|
|
1047
1181
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
return {
|
|
1184
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1185
|
+
isError: true,
|
|
1186
|
+
};
|
|
1052
1187
|
}
|
|
1053
|
-
return {
|
|
1054
|
-
content: [
|
|
1055
|
-
{
|
|
1056
|
-
type: "text",
|
|
1057
|
-
text: [
|
|
1058
|
-
`★ Added "${tool?.name ?? tool_id}" to favorites!`,
|
|
1059
|
-
``,
|
|
1060
|
-
`It is now registered as a direct MCP tool: fav:${tool_id}`,
|
|
1061
|
-
`Call it directly with its input parameters — no need for use_tool.`,
|
|
1062
|
-
].join("\n"),
|
|
1063
|
-
},
|
|
1064
|
-
],
|
|
1065
|
-
};
|
|
1066
1188
|
});
|
|
1067
1189
|
server.registerTool("unfavorite_tool", {
|
|
1068
1190
|
description: "Remove a tool from your favorites. The fav:<tool_id> direct tool will be unregistered. " +
|
|
@@ -1082,29 +1204,37 @@ function registerAllTools(server) {
|
|
|
1082
1204
|
isError: true,
|
|
1083
1205
|
};
|
|
1084
1206
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1207
|
+
try {
|
|
1208
|
+
const result = await apiDelete("/api/favorites", { tool_id });
|
|
1209
|
+
if (result.error) {
|
|
1210
|
+
return {
|
|
1211
|
+
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
1212
|
+
isError: true,
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
// Dynamically unregister the fav tool from this session
|
|
1216
|
+
const registered = registeredFavTools.get(tool_id);
|
|
1217
|
+
if (registered) {
|
|
1218
|
+
registered.remove();
|
|
1219
|
+
registeredFavTools.delete(tool_id);
|
|
1220
|
+
}
|
|
1087
1221
|
return {
|
|
1088
|
-
content: [
|
|
1089
|
-
|
|
1222
|
+
content: [
|
|
1223
|
+
{
|
|
1224
|
+
type: "text",
|
|
1225
|
+
text: result.removed
|
|
1226
|
+
? `Removed "fav:${tool_id}" from favorites and unregistered it.`
|
|
1227
|
+
: `"${tool_id}" was not in your favorites.`,
|
|
1228
|
+
},
|
|
1229
|
+
],
|
|
1090
1230
|
};
|
|
1091
1231
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
return {
|
|
1234
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1235
|
+
isError: true,
|
|
1236
|
+
};
|
|
1097
1237
|
}
|
|
1098
|
-
return {
|
|
1099
|
-
content: [
|
|
1100
|
-
{
|
|
1101
|
-
type: "text",
|
|
1102
|
-
text: result.removed
|
|
1103
|
-
? `Removed "fav:${tool_id}" from favorites and unregistered it.`
|
|
1104
|
-
: `"${tool_id}" was not in your favorites.`,
|
|
1105
|
-
},
|
|
1106
|
-
],
|
|
1107
|
-
};
|
|
1108
1238
|
});
|
|
1109
1239
|
}
|
|
1110
1240
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED