@caravo/mcp 0.1.20 → 0.1.21

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 +76 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -267,8 +267,13 @@ function buildPostExecPrompt(execId, toolId) {
267
267
  }
268
268
  function makeFavToolHandler(tool) {
269
269
  return async (args) => {
270
+ // Extract dry_run before passing remaining args to the API
271
+ const { dry_run, ...toolInput } = args;
272
+ if (dry_run) {
273
+ return dryRunProbe(tool.id, toolInput);
274
+ }
270
275
  try {
271
- const result = await apiPost(`/api/tools/${tool.id}/execute`, args);
276
+ const result = await apiPost(`/api/tools/${tool.id}/execute`, toolInput);
272
277
  if (result.success) {
273
278
  const execId = result.execution_id || null;
274
279
  const reviewLines = buildPostExecPrompt(execId, tool.id);
@@ -315,10 +320,12 @@ function registerFavTool(server, tool) {
315
320
  const priceLabel = tool.pricing.price_per_call > 0
316
321
  ? `$${tool.pricing.price_per_call}/call`
317
322
  : "Free";
323
+ const schema = buildSchemaShape(tool);
324
+ schema.dry_run = z.boolean().optional().describe("Preview cost without executing");
318
325
  const registered = server.registerTool(`fav:${tool.id}`, {
319
326
  title: `★ ${tool.name}`,
320
327
  description: `[${tool.provider}] ${tool.description} | ${priceLabel} | Tags: ${tool.tags.join(", ")}`,
321
- inputSchema: buildSchemaShape(tool),
328
+ inputSchema: schema,
322
329
  }, makeFavToolHandler(tool));
323
330
  registeredFavTools.set(tool.id, registered);
324
331
  }
@@ -343,6 +350,63 @@ async function loadFavoriteTools(server) {
343
350
  process.stderr.write(`[caravo] warning: could not load favorites: ${e}\n`);
344
351
  }
345
352
  }
353
+ // ─── Dry-run helper ─────────────────────────────────────────────────────────
354
+ async function dryRunProbe(toolId, input) {
355
+ try {
356
+ // Send a plain POST with no auth/payment headers to trigger a 402 for paid tools
357
+ const url = `${API_BASE}/api/tools/${toolId}/execute`;
358
+ const resp = await fetch(url, {
359
+ method: "POST",
360
+ headers: { "Content-Type": "application/json" },
361
+ body: JSON.stringify(input),
362
+ });
363
+ if (resp.status === 402) {
364
+ // Parse cost from 402 response
365
+ let cost = "unknown";
366
+ try {
367
+ const body = await resp.json();
368
+ const amount = body?.accepts?.[0]?.maxAmountRequired ?? body?.accepts?.[0]?.amount;
369
+ if (amount) {
370
+ cost = `$${(parseInt(amount) / 1e6).toFixed(6)}`;
371
+ }
372
+ }
373
+ catch {
374
+ // Header fallback
375
+ const header = resp.headers.get("payment-required");
376
+ if (header) {
377
+ try {
378
+ const pr = JSON.parse(atob(header));
379
+ const amount = pr?.accepts?.[0]?.maxAmountRequired ?? pr?.accepts?.[0]?.amount;
380
+ if (amount)
381
+ cost = `$${(parseInt(amount) / 1e6).toFixed(6)}`;
382
+ }
383
+ catch { /* ignore */ }
384
+ }
385
+ }
386
+ return {
387
+ content: [{ type: "text", text: `Preview: ${toolId} costs ${cost} per call (no payment was made)` }],
388
+ };
389
+ }
390
+ if (resp.ok) {
391
+ return {
392
+ content: [{ type: "text", text: `Preview: ${toolId} is free ($0.00 per call)` }],
393
+ };
394
+ }
395
+ // Other error (e.g. 400 bad input)
396
+ const body = await resp.json().catch(() => ({}));
397
+ const errorMsg = body?.error ?? `HTTP ${resp.status}`;
398
+ return {
399
+ content: [{ type: "text", text: `Dry-run failed: ${errorMsg}` }],
400
+ isError: true,
401
+ };
402
+ }
403
+ catch (err) {
404
+ return {
405
+ content: [{ type: "text", text: `Dry-run error: ${err instanceof Error ? err.message : String(err)}` }],
406
+ isError: true,
407
+ };
408
+ }
409
+ }
346
410
  // ─── Static management + meta tools ───────────────────────────────────────────
347
411
  function registerAllTools(server) {
348
412
  // ── Core workflow tools (registered first for visibility) ──────────────────
@@ -359,10 +423,11 @@ function registerAllTools(server) {
359
423
  query: z.string().optional().describe("Search query"),
360
424
  tag: z.string().optional().describe("Filter by tag (name or slug)"),
361
425
  provider: z.string().optional().describe("Filter by provider slug"),
426
+ pricing_type: z.enum(["free", "paid"]).optional().describe("Filter by pricing: 'free' or 'paid'"),
362
427
  page: z.number().optional().describe("Page number (default 1)"),
363
428
  per_page: z.number().optional().describe("Results per page (default 10)"),
364
429
  },
365
- }, async ({ query, tag, provider, page = 1, per_page = 10 }) => {
430
+ }, async ({ query, tag, provider, pricing_type, page = 1, per_page = 10 }) => {
366
431
  if (!Number.isInteger(page) || page < 1) {
367
432
  return { content: [{ type: "text", text: "Error: page must be a positive integer" }], isError: true };
368
433
  }
@@ -379,6 +444,8 @@ function registerAllTools(server) {
379
444
  params.set("tag", tag);
380
445
  if (provider)
381
446
  params.set("provider", provider);
447
+ if (pricing_type)
448
+ params.set("pricing_type", pricing_type);
382
449
  params.set("page", String(page));
383
450
  params.set("per_page", String(per_page));
384
451
  params.set("view", "agent");
@@ -416,8 +483,9 @@ function registerAllTools(server) {
416
483
  input: z
417
484
  .record(z.string(), z.unknown())
418
485
  .describe("Input parameters for the tool (see get_tool_info for schema)"),
486
+ dry_run: z.boolean().optional().describe("Preview execution cost without actually running the tool or making a payment"),
419
487
  },
420
- }, async ({ tool_id, input }) => {
488
+ }, async ({ tool_id, input, dry_run }) => {
421
489
  const validationError = validateToolId(tool_id);
422
490
  if (validationError) {
423
491
  return {
@@ -426,6 +494,10 @@ function registerAllTools(server) {
426
494
  };
427
495
  }
428
496
  const cleanInput = stripDangerousFields(input);
497
+ // Dry-run mode: probe cost without executing or paying
498
+ if (dry_run) {
499
+ return dryRunProbe(tool_id.trim(), cleanInput);
500
+ }
429
501
  try {
430
502
  const result = await apiPost(`/api/tools/${tool_id.trim()}/execute`, cleanInput);
431
503
  if (result.success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caravo/mcp",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
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": {