@caravo/mcp 0.1.24 → 0.1.26
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 +310 -206
- 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,18 +189,18 @@ 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`);
|
|
186
196
|
const x402Opts = {
|
|
187
197
|
method: "POST",
|
|
188
|
-
headers:
|
|
198
|
+
headers: baseHeaders(), // Keep Authorization for user attribution on x402 fallback
|
|
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", {
|
|
@@ -897,20 +937,36 @@ function registerAllTools(server) {
|
|
|
897
937
|
description: "List all available tags/categories in the marketplace. Returns tag names, slugs, and tool counts.",
|
|
898
938
|
inputSchema: {},
|
|
899
939
|
}, async () => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
+
}
|
|
904
952
|
});
|
|
905
953
|
// ── List providers ───────────────────────────────────────────────────────────
|
|
906
954
|
server.registerTool("list_providers", {
|
|
907
955
|
description: "List all providers/vendors in the marketplace. Returns provider names, slugs, and tool counts.",
|
|
908
956
|
inputSchema: {},
|
|
909
957
|
}, async () => {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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
|
+
}
|
|
914
970
|
});
|
|
915
971
|
// ── Tool Requests ───────────────────────────────────────────────────────────
|
|
916
972
|
server.registerTool("list_tool_requests", {
|
|
@@ -930,14 +986,22 @@ function registerAllTools(server) {
|
|
|
930
986
|
if (per_page > 100) {
|
|
931
987
|
return { content: [{ type: "text", text: "Error: per_page must be at most 100" }], isError: true };
|
|
932
988
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
+
}
|
|
941
1005
|
});
|
|
942
1006
|
server.registerTool("request_tool", {
|
|
943
1007
|
description: "Submit a request for a tool that doesn't exist in the marketplace yet. " +
|
|
@@ -951,33 +1015,41 @@ function registerAllTools(server) {
|
|
|
951
1015
|
agent_id: z.string().optional().describe("Optional agent identifier"),
|
|
952
1016
|
},
|
|
953
1017
|
}, async ({ title, description, use_case, execution_id, agent_id }) => {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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) {
|
|
962
1048
|
return {
|
|
963
|
-
content: [{ type: "text", text: `Error: ${
|
|
1049
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
964
1050
|
isError: true,
|
|
965
1051
|
};
|
|
966
1052
|
}
|
|
967
|
-
return {
|
|
968
|
-
content: [
|
|
969
|
-
{
|
|
970
|
-
type: "text",
|
|
971
|
-
text: [
|
|
972
|
-
`✓ Tool request submitted: "${result.title}"`,
|
|
973
|
-
` Request ID: ${result.id}`,
|
|
974
|
-
` Status: ${result.status}`,
|
|
975
|
-
``,
|
|
976
|
-
`Other agents can upvote this request to signal demand.`,
|
|
977
|
-
].join("\n"),
|
|
978
|
-
},
|
|
979
|
-
],
|
|
980
|
-
};
|
|
981
1053
|
});
|
|
982
1054
|
server.registerTool("upvote_tool_request", {
|
|
983
1055
|
description: "Upvote an existing tool request to signal demand. " +
|
|
@@ -987,19 +1059,27 @@ function registerAllTools(server) {
|
|
|
987
1059
|
execution_id: z.string().optional().describe("Execution ID from a previous tool use (required if no API key)"),
|
|
988
1060
|
},
|
|
989
1061
|
}, async ({ request_id, execution_id }) => {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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) {
|
|
994
1078
|
return {
|
|
995
|
-
content: [{ type: "text", text: `Error: ${
|
|
1079
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
996
1080
|
isError: true,
|
|
997
1081
|
};
|
|
998
1082
|
}
|
|
999
|
-
const action = result.action === "already_upvoted" ? "Already upvoted" : "Upvoted";
|
|
1000
|
-
return {
|
|
1001
|
-
content: [{ type: "text", text: `✓ ${action} tool request ${request_id}` }],
|
|
1002
|
-
};
|
|
1003
1083
|
});
|
|
1004
1084
|
// ── Favorites management ─────────────────────────────────────────────────────
|
|
1005
1085
|
server.registerTool("list_favorites", {
|
|
@@ -1017,31 +1097,39 @@ function registerAllTools(server) {
|
|
|
1017
1097
|
isError: true,
|
|
1018
1098
|
};
|
|
1019
1099
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
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) {
|
|
1022
1128
|
return {
|
|
1023
|
-
content: [{ type: "text", text: `Error: ${
|
|
1129
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1024
1130
|
isError: true,
|
|
1025
1131
|
};
|
|
1026
1132
|
}
|
|
1027
|
-
const tools = result.data ?? [];
|
|
1028
|
-
return {
|
|
1029
|
-
content: [
|
|
1030
|
-
{
|
|
1031
|
-
type: "text",
|
|
1032
|
-
text: JSON.stringify({
|
|
1033
|
-
total: tools.length,
|
|
1034
|
-
favorites: tools.map((t) => ({
|
|
1035
|
-
tool_id: t.id,
|
|
1036
|
-
name: t.name,
|
|
1037
|
-
mcp_tool_name: `fav:${t.id}`,
|
|
1038
|
-
price_per_call: t.pricing.price_per_call,
|
|
1039
|
-
})),
|
|
1040
|
-
hint: "Favorited tools are registered as direct MCP tools named fav:<tool_id>.",
|
|
1041
|
-
}, null, 2),
|
|
1042
|
-
},
|
|
1043
|
-
],
|
|
1044
|
-
};
|
|
1045
1133
|
});
|
|
1046
1134
|
server.registerTool("favorite_tool", {
|
|
1047
1135
|
description: "Bookmark a tool you plan to reuse frequently — it appears as a direct fav:<tool_id> MCP tool. " +
|
|
@@ -1064,31 +1152,39 @@ function registerAllTools(server) {
|
|
|
1064
1152
|
isError: true,
|
|
1065
1153
|
};
|
|
1066
1154
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
+
}
|
|
1069
1168
|
return {
|
|
1070
|
-
content: [
|
|
1071
|
-
|
|
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
|
+
],
|
|
1072
1180
|
};
|
|
1073
1181
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
return {
|
|
1184
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1185
|
+
isError: true,
|
|
1186
|
+
};
|
|
1078
1187
|
}
|
|
1079
|
-
return {
|
|
1080
|
-
content: [
|
|
1081
|
-
{
|
|
1082
|
-
type: "text",
|
|
1083
|
-
text: [
|
|
1084
|
-
`★ Added "${tool?.name ?? tool_id}" to favorites!`,
|
|
1085
|
-
``,
|
|
1086
|
-
`It is now registered as a direct MCP tool: fav:${tool_id}`,
|
|
1087
|
-
`Call it directly with its input parameters — no need for use_tool.`,
|
|
1088
|
-
].join("\n"),
|
|
1089
|
-
},
|
|
1090
|
-
],
|
|
1091
|
-
};
|
|
1092
1188
|
});
|
|
1093
1189
|
server.registerTool("unfavorite_tool", {
|
|
1094
1190
|
description: "Remove a tool from your favorites. The fav:<tool_id> direct tool will be unregistered. " +
|
|
@@ -1108,29 +1204,37 @@ function registerAllTools(server) {
|
|
|
1108
1204
|
isError: true,
|
|
1109
1205
|
};
|
|
1110
1206
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
+
}
|
|
1113
1221
|
return {
|
|
1114
|
-
content: [
|
|
1115
|
-
|
|
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
|
+
],
|
|
1116
1230
|
};
|
|
1117
1231
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
return {
|
|
1234
|
+
content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
|
|
1235
|
+
isError: true,
|
|
1236
|
+
};
|
|
1123
1237
|
}
|
|
1124
|
-
return {
|
|
1125
|
-
content: [
|
|
1126
|
-
{
|
|
1127
|
-
type: "text",
|
|
1128
|
-
text: result.removed
|
|
1129
|
-
? `Removed "fav:${tool_id}" from favorites and unregistered it.`
|
|
1130
|
-
: `"${tool_id}" was not in your favorites.`,
|
|
1131
|
-
},
|
|
1132
|
-
],
|
|
1133
|
-
};
|
|
1134
1238
|
});
|
|
1135
1239
|
}
|
|
1136
1240
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED