@wavespeed/mcp 1.0.12 → 1.0.14
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/README.md +5 -1
- package/dist/lib/api.js +42 -0
- package/dist/lib/delete-predictions.js +34 -0
- package/dist/server.js +67 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,11 @@ Built on the same design as the open-source [`@wavespeed/cli`](https://github.co
|
|
|
35
35
|
| `get_model_schema` | A model's real input schema (required, properties, defaults) |
|
|
36
36
|
| `run_model` | Submit + wait; rejects input keys the model doesn't accept; `@path` inputs upload automatically; returns output URLs |
|
|
37
37
|
| `get_price` | Cost estimate with `unpriced_inputs` / `at_base_price` disclosure |
|
|
38
|
-
| `get_balance` | Account credit balance |
|
|
38
|
+
| `get_balance` | Account credit balance, with the top-up link |
|
|
39
|
+
| `list_predictions` | Run history (default last 24h); filter by model, status, time range |
|
|
40
|
+
| `list_billings` | Itemized charges and refunds (needs a Billing-access key) |
|
|
41
|
+
| `get_usage` | Spend, request counts and per-model breakdown (default last 7 days; needs a Billing-access key) |
|
|
42
|
+
| `delete_predictions` | Delete finished runs from the history (up to 100; needs `confirm: true`, cannot be undone from here) |
|
|
39
43
|
| `upload_file` | Local file → hosted URL (24h content-hash dedupe) |
|
|
40
44
|
| `get_prediction` | Status/outputs of any run by id; optional `wait_seconds` to wait for it |
|
|
41
45
|
|
package/dist/lib/api.js
CHANGED
|
@@ -162,6 +162,44 @@ export async function fetchModels(opts = {}) {
|
|
|
162
162
|
export async function fetchBalance() {
|
|
163
163
|
return apiGet('/api/v3/balance');
|
|
164
164
|
}
|
|
165
|
+
export const PREDICTION_STATUSES = ['created', 'processing', 'completed', 'failed', 'cancelled', 'timeout', 'deleted'];
|
|
166
|
+
// Same contract as wavespeed-cli `history`: the endpoint needs a time window,
|
|
167
|
+
// so default to the last 24 hours.
|
|
168
|
+
export async function fetchHistory(query = {}) {
|
|
169
|
+
const now = new Date();
|
|
170
|
+
const body = {
|
|
171
|
+
page: query.page ?? 1,
|
|
172
|
+
page_size: query.pageSize ?? 20,
|
|
173
|
+
created_after: query.createdAfter ?? new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString(),
|
|
174
|
+
created_before: query.createdBefore ?? now.toISOString(),
|
|
175
|
+
};
|
|
176
|
+
if (query.model)
|
|
177
|
+
body.model = query.model;
|
|
178
|
+
if (query.status)
|
|
179
|
+
body.status = query.status;
|
|
180
|
+
return apiPost('/api/v3/predictions', body, { timeoutMs: GET_TIMEOUT_MS });
|
|
181
|
+
}
|
|
182
|
+
// Needs a key with Billing access; otherwise the platform's own error is surfaced.
|
|
183
|
+
export async function fetchBillings(query = {}) {
|
|
184
|
+
const body = { page: query.page ?? 1, page_size: query.pageSize ?? 20 };
|
|
185
|
+
if (query.type)
|
|
186
|
+
body.billing_type = query.type;
|
|
187
|
+
if (query.startTime)
|
|
188
|
+
body.start_time = query.startTime;
|
|
189
|
+
if (query.endTime)
|
|
190
|
+
body.end_time = query.endTime;
|
|
191
|
+
if (query.model)
|
|
192
|
+
body.model_uuid = query.model;
|
|
193
|
+
if (query.accessKey)
|
|
194
|
+
body.access_key_uuid = query.accessKey;
|
|
195
|
+
return apiPost('/api/v3/billings/search', body, { timeoutMs: GET_TIMEOUT_MS });
|
|
196
|
+
}
|
|
197
|
+
export async function fetchUsageStats(startTime, endTime, model) {
|
|
198
|
+
const body = { start_time: startTime, end_time: endTime };
|
|
199
|
+
if (model)
|
|
200
|
+
body.model_uuid = model;
|
|
201
|
+
return apiPost('/api/v3/user/usage_stats', body, { timeoutMs: GET_TIMEOUT_MS });
|
|
202
|
+
}
|
|
165
203
|
export async function fetchPricing(modelId, inputs) {
|
|
166
204
|
// /model/price is the documented pricing endpoint.
|
|
167
205
|
return apiPost('/api/v3/model/price', { model_id: modelId, inputs });
|
|
@@ -218,3 +256,7 @@ export async function waitForPrediction(id, opts = {}) {
|
|
|
218
256
|
await new Promise((r) => setTimeout(r, interval));
|
|
219
257
|
}
|
|
220
258
|
}
|
|
259
|
+
// Deletes within the key's own organization; the response lists the ids actually deleted.
|
|
260
|
+
export async function deletePredictions(ids) {
|
|
261
|
+
return apiPost('/api/v3/predictions/delete', { ids }, { timeoutMs: GET_TIMEOUT_MS });
|
|
262
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// delete_predictions: the checks and the report around POST /api/v3/predictions/delete.
|
|
2
|
+
// The API only touches the key's own organization and returns the ids it deleted.
|
|
3
|
+
export const MAX_DELETE_IDS = 100;
|
|
4
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
5
|
+
/** The ids to send: trimmed, unique, valid, and only once the user agreed (confirm). */
|
|
6
|
+
export function prepareDelete(ids, confirm) {
|
|
7
|
+
const unique = [];
|
|
8
|
+
for (const raw of ids) {
|
|
9
|
+
const id = raw.trim();
|
|
10
|
+
if (!ID_PATTERN.test(id))
|
|
11
|
+
throw new Error(`Invalid prediction id "${raw}".`);
|
|
12
|
+
if (!unique.includes(id))
|
|
13
|
+
unique.push(id);
|
|
14
|
+
}
|
|
15
|
+
if (unique.length === 0 || unique.length > MAX_DELETE_IDS) {
|
|
16
|
+
throw new Error(`Pass between 1 and ${MAX_DELETE_IDS} prediction ids.`);
|
|
17
|
+
}
|
|
18
|
+
if (!confirm) {
|
|
19
|
+
throw new Error(`Nothing was deleted. This removes ${unique.length} prediction(s) from the history and cannot be undone here: ask the user, then call again with confirm: true.`);
|
|
20
|
+
}
|
|
21
|
+
return unique;
|
|
22
|
+
}
|
|
23
|
+
export function summarizeDelete(requested, deletedIds) {
|
|
24
|
+
const deleted = deletedIds ?? [];
|
|
25
|
+
const notDeleted = requested.filter((id) => !deleted.includes(id));
|
|
26
|
+
return {
|
|
27
|
+
deleted,
|
|
28
|
+
deleted_count: deleted.length,
|
|
29
|
+
requested: requested.length,
|
|
30
|
+
...(notDeleted.length > 0
|
|
31
|
+
? { not_deleted: notDeleted, note: "These ids were not deleted: they are not in this account's history, are still running, or were already deleted." }
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -15,14 +15,25 @@ import { createRequire } from 'node:module';
|
|
|
15
15
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { Client } from 'wavespeed';
|
|
18
|
-
import { fetchModels, fetchBalance, fetchPricing, submitPrediction, waitForPrediction, MCP_CLIENT_NAME, TERMINAL_FAILURES, } from './lib/api.js';
|
|
18
|
+
import { fetchModels, fetchBalance, fetchHistory, fetchBillings, fetchUsageStats, deletePredictions, PREDICTION_STATUSES, fetchPricing, submitPrediction, waitForPrediction, MCP_CLIENT_NAME, TERMINAL_FAILURES, } from './lib/api.js';
|
|
19
19
|
import { getApiKey, getBaseUrl } from './lib/config.js';
|
|
20
|
+
import { MAX_DELETE_IDS, prepareDelete, summarizeDelete } from './lib/delete-predictions.js';
|
|
21
|
+
// Where to add credit; handed to the assistant next to the balance.
|
|
22
|
+
const TOP_UP_URL = 'https://wavespeed.ai/top-up';
|
|
20
23
|
import { resolveLocalFiles } from './lib/local-files.js';
|
|
21
24
|
import { uploadWithCache } from './lib/upload-cache.js';
|
|
22
25
|
import { missingPriceVars, isFloorQuote } from './lib/pricing-vars.js';
|
|
23
26
|
import { findUnknownInputs } from './lib/validate-inputs.js';
|
|
24
27
|
const require = createRequire(import.meta.url);
|
|
25
28
|
const { version: PKG_VERSION } = require('../package.json');
|
|
29
|
+
function toIso(value, name) {
|
|
30
|
+
if (!value)
|
|
31
|
+
return undefined;
|
|
32
|
+
const t = Date.parse(value);
|
|
33
|
+
if (Number.isNaN(t))
|
|
34
|
+
throw new Error(`Invalid ${name} "${value}": use ISO 8601 or YYYY-MM-DD.`);
|
|
35
|
+
return new Date(t).toISOString();
|
|
36
|
+
}
|
|
26
37
|
const PRICE_DISCLAIMER = 'Estimate only, for reference — the amount actually charged for a run is authoritative.';
|
|
27
38
|
function requestSchema(m) {
|
|
28
39
|
return m.api_schema?.api_schemas?.[0]?.request_schema;
|
|
@@ -232,8 +243,61 @@ export function createServer() {
|
|
|
232
243
|
disclaimer: PRICE_DISCLAIMER,
|
|
233
244
|
});
|
|
234
245
|
});
|
|
235
|
-
server.tool('get_balance', 'Show the WaveSpeed account credit balance.', {}, { readOnlyHint: true, openWorldHint: true }, async () => {
|
|
236
|
-
return ok(await fetchBalance());
|
|
246
|
+
server.tool('get_balance', 'Show the WaveSpeed account credit balance, with the link to top up.', {}, { readOnlyHint: true, openWorldHint: true }, async () => {
|
|
247
|
+
return ok({ ...(await fetchBalance()), top_up_url: TOP_UP_URL });
|
|
248
|
+
});
|
|
249
|
+
server.tool('list_predictions', 'List past predictions (run history), newest window first. Defaults to the last 24 hours; pass since/until (ISO or YYYY-MM-DD) for other ranges. Use get_prediction with an id for one task in full.', {
|
|
250
|
+
page: z.number().int().min(1).default(1),
|
|
251
|
+
limit: z.number().int().min(1).max(100).default(20).describe('Page size'),
|
|
252
|
+
model: z.string().optional().describe('Filter by model ID'),
|
|
253
|
+
status: z.enum(PREDICTION_STATUSES).optional(),
|
|
254
|
+
since: z.string().optional().describe('Created after this time (ISO or YYYY-MM-DD)'),
|
|
255
|
+
until: z.string().optional().describe('Created before this time (ISO or YYYY-MM-DD)'),
|
|
256
|
+
}, { readOnlyHint: true, openWorldHint: true }, async ({ page, limit, model, status, since, until }) => {
|
|
257
|
+
return ok(await fetchHistory({
|
|
258
|
+
page,
|
|
259
|
+
pageSize: limit,
|
|
260
|
+
model,
|
|
261
|
+
status,
|
|
262
|
+
createdAfter: toIso(since, 'since'),
|
|
263
|
+
createdBefore: toIso(until, 'until'),
|
|
264
|
+
}));
|
|
265
|
+
});
|
|
266
|
+
server.tool('list_billings', 'List itemized charges and refunds (billing records). Requires an API key with Billing access.', {
|
|
267
|
+
page: z.number().int().min(1).default(1),
|
|
268
|
+
limit: z.number().int().min(1).max(100).default(20).describe('Page size'),
|
|
269
|
+
type: z.enum(['deduct', 'refund']).optional(),
|
|
270
|
+
model: z.string().optional().describe('Filter by model uuid'),
|
|
271
|
+
key: z.string().optional().describe('Filter by the API key uuid that generated the charge'),
|
|
272
|
+
since: z.string().optional().describe('Records after this time (ISO or YYYY-MM-DD)'),
|
|
273
|
+
until: z.string().optional().describe('Records before this time (ISO or YYYY-MM-DD)'),
|
|
274
|
+
}, { readOnlyHint: true, openWorldHint: true }, async ({ page, limit, type, model, key, since, until }) => {
|
|
275
|
+
return ok(await fetchBillings({
|
|
276
|
+
page,
|
|
277
|
+
pageSize: limit,
|
|
278
|
+
type,
|
|
279
|
+
model,
|
|
280
|
+
accessKey: key,
|
|
281
|
+
startTime: toIso(since, 'since'),
|
|
282
|
+
endTime: toIso(until, 'until'),
|
|
283
|
+
}));
|
|
284
|
+
});
|
|
285
|
+
server.tool('get_usage', 'Usage statistics: spend, request counts, daily series and per-model breakdown. Defaults to the last 7 days. Requires an API key with Billing access.', {
|
|
286
|
+
since: z.string().optional().describe('Start (ISO or YYYY-MM-DD); default 7 days ago'),
|
|
287
|
+
until: z.string().optional().describe('End (ISO or YYYY-MM-DD); default now'),
|
|
288
|
+
model: z.string().optional().describe('Filter by model uuid'),
|
|
289
|
+
}, { readOnlyHint: true, openWorldHint: true }, async ({ since, until, model }) => {
|
|
290
|
+
const end = toIso(until, 'until') ?? new Date().toISOString();
|
|
291
|
+
const start = toIso(since, 'since') ?? new Date(Date.parse(end) - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
292
|
+
return ok(await fetchUsageStats(start, end, model));
|
|
293
|
+
});
|
|
294
|
+
server.tool('delete_predictions', `Delete finished predictions from this account's run history, up to ${MAX_DELETE_IDS} at a time. They stop appearing in list_predictions and get_prediction, and this tool cannot bring them back. Runs that are still going are left alone, and only the key's own organization's records are deleted. Ask the user first, and pass confirm: true only after they agreed to delete these ids.`, {
|
|
295
|
+
ids: z.array(z.string()).min(1).max(MAX_DELETE_IDS).describe('Prediction ids to delete'),
|
|
296
|
+
confirm: z.boolean().describe('Must be true: the user has agreed to delete these predictions'),
|
|
297
|
+
}, { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }, async ({ ids, confirm }) => {
|
|
298
|
+
const toDelete = prepareDelete(ids, confirm);
|
|
299
|
+
const res = await deletePredictions(toDelete);
|
|
300
|
+
return ok(summarizeDelete(toDelete, res.deleted_ids));
|
|
237
301
|
});
|
|
238
302
|
server.tool('upload_file', 'Upload a local file to WaveSpeed and get its hosted URL (identical bytes reuse the same upload for 24h). Usually unnecessary — run_model handles "@./path" inputs itself.', { path: z.string().describe('Local file path') }, { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }, async ({ path: filePath }) => {
|
|
239
303
|
return ok(await uploadFile(filePath));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavespeed/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "WaveSpeed MCP server — run any model on the live WaveSpeed catalog (image, video, audio, 3D) with schema introspection, local-file upload, and price quotes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|