@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.
Files changed (2) hide show
  1. package/dist/index.js +341 -211
  2. 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.json();
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)).json();
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)).json();
201
+ return safeParseJson(await fetchWithX402(url, x402Opts, wallet));
192
202
  }
193
- return r.json();
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.json();
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
- const jsonStr = JSON.stringify(output.json, null, 2);
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: ${JSON.stringify(result)}` },
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
- const params = new URLSearchParams();
499
- if (query)
500
- params.set("query", query);
501
- if (tag)
502
- params.set("tag", tag);
503
- if (provider)
504
- params.set("provider", provider);
505
- if (pricing_type)
506
- params.set("pricing_type", pricing_type);
507
- params.set("page", String(page));
508
- params.set("per_page", String(per_page));
509
- params.set("view", "agent");
510
- const data = await apiGet(`/api/tools?${params}`);
511
- let text = JSON.stringify(data, null, 2);
512
- if (pendingUpdate) {
513
- text += `\n\n[Update available: @caravo/mcp ${pendingUpdate.current} → ${pendingUpdate.latest}. Will auto-update on next MCP restart.]`;
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
- const data = await apiGet(`/api/tools/${tool_id.trim()}`);
534
- return {
535
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
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: ${JSON.stringify(result)}` },
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
- // Upvote mode
632
- if (upvote_review_id) {
633
- if (!execution_id) {
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: execution_id is required for upvoting. Each upvote consumes one tool execution." }],
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/upvote`, {
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.success) {
644
- const action = result.action === "already_upvoted" ? "Already upvoted" : "Upvoted";
645
- const upvotedToolId = result.tool_id || "";
646
- const lines = [`✓ ${action} review ${upvote_review_id}`];
647
- // Suggest favorite only when upvoting a 5-star review
648
- if (result.rating === 5 && upvotedToolId) {
649
- if (API_KEY && !registeredFavTools.has(upvotedToolId)) {
650
- lines.push(`This was a 5/5 review — consider favorite_tool(tool_id="${upvotedToolId}") if you plan to reuse it.`);
651
- }
652
- else if (!API_KEY) {
653
- lines.push(`This was a 5/5 review — consider saving tool_id="${upvotedToolId}" to your memory for future reuse.`);
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
- // New review mode
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: ${result.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
- const data = await apiGet("/api/tags");
875
- return {
876
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
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
- const data = await apiGet("/api/providers");
885
- return {
886
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
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
- const params = new URLSearchParams();
908
- params.set("status", status);
909
- params.set("page", String(page));
910
- params.set("per_page", String(per_page));
911
- const data = await apiGet(`/api/tool-requests?${params}`);
912
- return {
913
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
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
- const result = await apiPost("/api/tool-requests", {
929
- title,
930
- description,
931
- use_case,
932
- execution_id,
933
- agent_id,
934
- });
935
- if (result.error) {
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: ${result.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
- const result = await apiPost(`/api/tool-requests/${request_id}`, {
965
- execution_id,
966
- });
967
- if (result.error) {
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: ${result.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
- const result = await apiGet("/api/favorites");
995
- if (result.error) {
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: ${result.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
- const result = await apiPost("/api/favorites", { tool_id });
1042
- if (result.error) {
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: [{ type: "text", text: `Error: ${result.error}` }],
1045
- isError: true,
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
- // Dynamically register the new fav tool in this session
1049
- const tool = result.tool;
1050
- if (tool) {
1051
- registerFavTool(server, tool);
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
- const result = await apiDelete("/api/favorites", { tool_id });
1086
- if (result.error) {
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: [{ type: "text", text: `Error: ${result.error}` }],
1089
- isError: true,
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
- // Dynamically unregister the fav tool from this session
1093
- const registered = registeredFavTools.get(tool_id);
1094
- if (registered) {
1095
- registered.remove();
1096
- registeredFavTools.delete(tool_id);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caravo/mcp",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "The API marketplace built for autonomous AI agents. Search, execute, and pay for 200+ tools at $0.001–0.05 per call.",
5
5
  "type": "module",
6
6
  "bin": {