@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.
- package/dist/index.js +76 -4
- 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`,
|
|
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:
|
|
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