@wavespeed/mcp 1.2.3 → 1.2.5
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/assets/result.html +17 -1
- package/dist/lib/app.js +36 -2
- package/dist/server.js +18 -11
- package/package.json +1 -1
package/assets/result.html
CHANGED
|
@@ -300,13 +300,29 @@
|
|
|
300
300
|
return said;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
// Ask the server for the run through the host: the standard bridge first (tools/call), then ChatGPT's own
|
|
304
|
+
// window.openai.callTool when the standard call is refused. Both answer with the tool result (ChatGPT's may come back
|
|
305
|
+
// as {structuredContent}, {content} or {result: "<json text>"}).
|
|
306
|
+
function callServerTool(name, toolArgs) {
|
|
307
|
+
return request("tools/call", { name: name, arguments: toolArgs }).catch(function (err) {
|
|
308
|
+
var o = window.openai;
|
|
309
|
+
if (!o || typeof o.callTool !== "function" || (err && err.message === "timeout")) throw err;
|
|
310
|
+
return Promise.resolve(o.callTool(name, toolArgs)).then(function (r) {
|
|
311
|
+
if (r && typeof r.result === "string" && !r.structuredContent && !r.content) {
|
|
312
|
+
try { return { structuredContent: JSON.parse(r.result) }; } catch (e) { return { content: [{ type: "text", text: r.result }] }; }
|
|
313
|
+
}
|
|
314
|
+
return r;
|
|
315
|
+
}, function () { throw err; });
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
303
319
|
// Keep asking the server while the run is going: this is what turns a video's wait into a card that fills in.
|
|
304
320
|
function poll(id, token) {
|
|
305
321
|
var asked = 0;
|
|
306
322
|
(function again() {
|
|
307
323
|
if (token !== pollToken) return;
|
|
308
324
|
asked++;
|
|
309
|
-
|
|
325
|
+
callServerTool("get_prediction", { id: id, wait_seconds: 25 }).then(function (res) {
|
|
310
326
|
if (token !== pollToken) return;
|
|
311
327
|
var d = res && res.structuredContent;
|
|
312
328
|
if (!d) {
|
package/dist/lib/app.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
// the metadata and show the text as before. The view is the same file the
|
|
4
4
|
// remote server (api-server /mcp) serves; keep the two copies identical.
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
|
-
|
|
6
|
+
// A host keeps a UI resource by its URI, so a changed card only reaches it under a new URI: bump the number.
|
|
7
|
+
export const RESULT_VIEW_URI = 'ui://wavespeed/result-v2.html';
|
|
7
8
|
export const APP_MIME_TYPE = 'text/html;profile=mcp-app';
|
|
8
9
|
export const TOP_UP_URL = 'https://wavespeed.ai/top-up';
|
|
10
|
+
// get_prediction is not one of them: the card asks it for itself while the run goes on, and an assistant that polls it
|
|
11
|
+
// would draw a second card for the same result. It is marked callable from the card instead (see toolMeta).
|
|
9
12
|
export const UI_TOOLS = new Set([
|
|
10
13
|
'run_model',
|
|
11
|
-
'get_prediction',
|
|
12
14
|
'generate_image',
|
|
13
15
|
'edit_image',
|
|
14
16
|
'generate_video',
|
|
@@ -18,6 +20,38 @@ export const UI_TOOLS = new Set([
|
|
|
18
20
|
// "https:" alone should allow every https source, but a host may only honour named hosts when it builds the
|
|
19
21
|
// card's CSP, so the usual media hosts are named too, as wildcards.
|
|
20
22
|
export const VIEW_RESOURCE_DOMAINS = ['https:', 'https://*.cloudfront.net', 'https://*.wavespeed.ai', 'https://*.amazonaws.com'];
|
|
23
|
+
// The one-line status ChatGPT shows while a tool runs and after it ran (it allows 64 characters).
|
|
24
|
+
const TOOL_STATUS = {
|
|
25
|
+
run_model: ['Starting the run…', 'Run started'],
|
|
26
|
+
generate_image: ['Starting the run…', 'Run started'],
|
|
27
|
+
edit_image: ['Starting the run…', 'Run started'],
|
|
28
|
+
generate_video: ['Starting the run…', 'Run started'],
|
|
29
|
+
edit_video: ['Starting the run…', 'Run started'],
|
|
30
|
+
extend_video: ['Starting the run…', 'Run started'],
|
|
31
|
+
get_prediction: ['Checking the run…', 'Run checked'],
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The tool's _meta for the card: the run tools open it; get_prediction does not but says the card may call it
|
|
35
|
+
* (ChatGPT only lets a widget call a tool marked openai/widgetAccessible; MCP Apps hosts read ui.visibility, whose
|
|
36
|
+
* default already includes "app"). Same as the remote server (api-server mcp_app.go).
|
|
37
|
+
*/
|
|
38
|
+
export function toolMeta(name) {
|
|
39
|
+
const linked = UI_TOOLS.has(name);
|
|
40
|
+
if (!linked && name !== 'get_prediction')
|
|
41
|
+
return undefined;
|
|
42
|
+
const meta = {
|
|
43
|
+
ui: linked ? { resourceUri: RESULT_VIEW_URI, visibility: ['model', 'app'] } : { visibility: ['model', 'app'] },
|
|
44
|
+
'openai/widgetAccessible': true,
|
|
45
|
+
};
|
|
46
|
+
if (linked)
|
|
47
|
+
meta['openai/outputTemplate'] = RESULT_VIEW_URI;
|
|
48
|
+
const status = TOOL_STATUS[name];
|
|
49
|
+
if (status) {
|
|
50
|
+
meta['openai/toolInvocation/invoking'] = status[0];
|
|
51
|
+
meta['openai/toolInvocation/invoked'] = status[1];
|
|
52
|
+
}
|
|
53
|
+
return meta;
|
|
54
|
+
}
|
|
21
55
|
export function loadView() {
|
|
22
56
|
return readFileSync(new URL('../../assets/result.html', import.meta.url), 'utf8');
|
|
23
57
|
}
|
package/dist/server.js
CHANGED
|
@@ -24,7 +24,7 @@ import { MAX_DELETE_IDS, prepareDelete, summarizeDelete } from './lib/delete-pre
|
|
|
24
24
|
// Where to add credit; handed to the assistant next to the balance.
|
|
25
25
|
const TOP_UP_URL = 'https://wavespeed.ai/top-up';
|
|
26
26
|
import { resolveLocalFiles } from './lib/local-files.js';
|
|
27
|
-
import { APP_MIME_TYPE, RESULT_VIEW_URI,
|
|
27
|
+
import { APP_MIME_TYPE, RESULT_VIEW_URI, toolMeta, VIEW_RESOURCE_DOMAINS, insufficientCreditsResult, isInsufficientCredits, loadView, } from './lib/app.js';
|
|
28
28
|
import { uploadWithCache } from './lib/upload-cache.js';
|
|
29
29
|
import { findUnknownInputs } from './lib/validate-inputs.js';
|
|
30
30
|
import { IMAGE_MODEL_KEYS, VIDEO_MODEL_KEYS } from './lib/media-models.js';
|
|
@@ -203,8 +203,9 @@ export function createServer() {
|
|
|
203
203
|
}
|
|
204
204
|
};
|
|
205
205
|
const tool = registerTool(...args);
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
const meta = toolMeta(name);
|
|
207
|
+
if (meta)
|
|
208
|
+
tool.update({ _meta: meta });
|
|
208
209
|
return tool;
|
|
209
210
|
};
|
|
210
211
|
server.registerResource('result-card', RESULT_VIEW_URI, {
|
|
@@ -217,7 +218,13 @@ export function createServer() {
|
|
|
217
218
|
uri: uri.href,
|
|
218
219
|
mimeType: APP_MIME_TYPE,
|
|
219
220
|
text: loadView(),
|
|
220
|
-
_meta: {
|
|
221
|
+
_meta: {
|
|
222
|
+
ui: { csp: { resourceDomains: VIEW_RESOURCE_DOMAINS, connectDomains: [] }, prefersBorder: true },
|
|
223
|
+
// The same, in the names ChatGPT's own extensions use, for versions that read only those.
|
|
224
|
+
'openai/widgetCSP': { resource_domains: VIEW_RESOURCE_DOMAINS, connect_domains: [] },
|
|
225
|
+
'openai/widgetPrefersBorder': true,
|
|
226
|
+
'openai/widgetDescription': 'Shows the images, videos and audio of a WaveSpeed run and follows it until it finishes.',
|
|
227
|
+
},
|
|
221
228
|
},
|
|
222
229
|
],
|
|
223
230
|
}));
|
|
@@ -229,7 +236,7 @@ export function createServer() {
|
|
|
229
236
|
.describe('Filter by modality type, e.g. text-to-image, image-to-video'),
|
|
230
237
|
limit: z.number().int().min(1).max(200).default(30).describe('Max results'),
|
|
231
238
|
refresh: z.boolean().default(false).describe('Bypass the 1h catalog cache'),
|
|
232
|
-
}, { readOnlyHint: true, openWorldHint: true }, async ({ query, type, limit, refresh }) => {
|
|
239
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ query, type, limit, refresh }) => {
|
|
233
240
|
const models = await fetchModels({ refresh });
|
|
234
241
|
const q = query?.toLowerCase();
|
|
235
242
|
const filtered = models.filter((m) => {
|
|
@@ -246,7 +253,7 @@ export function createServer() {
|
|
|
246
253
|
models: filtered.slice(0, limit).map(compactModel),
|
|
247
254
|
});
|
|
248
255
|
});
|
|
249
|
-
server.tool('get_model_schema', "Get a model's real input schema (required fields, properties, defaults). Call this before run_model so inputs match what the model actually accepts.", { model: z.string().describe('Model ID from list_models, e.g. bytedance/seedream-v5.0-pro') }, { readOnlyHint: true, openWorldHint: true }, async ({ model }) => {
|
|
256
|
+
server.tool('get_model_schema', "Get a model's real input schema (required fields, properties, defaults). Call this before run_model so inputs match what the model actually accepts.", { model: z.string().describe('Model ID from list_models, e.g. bytedance/seedream-v5.0-pro') }, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ model }) => {
|
|
250
257
|
const models = await fetchModels();
|
|
251
258
|
const meta = models.find((m) => m.model_id === model);
|
|
252
259
|
if (!meta) {
|
|
@@ -436,7 +443,7 @@ export function createServer() {
|
|
|
436
443
|
const data = await fetchPricing(model, priceInput);
|
|
437
444
|
return ok({ ...data, estimate: true, disclaimer: PRICE_DISCLAIMER, playground_url: modelPlaygroundUrl(model) });
|
|
438
445
|
});
|
|
439
|
-
server.tool('get_balance', 'Show the WaveSpeed account credit balance, with the link to top up.', {}, { readOnlyHint: true, openWorldHint: true }, async () => {
|
|
446
|
+
server.tool('get_balance', 'Show the WaveSpeed account credit balance, with the link to top up.', {}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async () => {
|
|
440
447
|
return ok({ ...(await fetchBalance()), top_up_url: TOP_UP_URL });
|
|
441
448
|
});
|
|
442
449
|
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.', {
|
|
@@ -446,7 +453,7 @@ export function createServer() {
|
|
|
446
453
|
status: z.enum(PREDICTION_STATUSES).optional(),
|
|
447
454
|
since: z.string().optional().describe('Created after this time (ISO or YYYY-MM-DD)'),
|
|
448
455
|
until: z.string().optional().describe('Created before this time (ISO or YYYY-MM-DD)'),
|
|
449
|
-
}, { readOnlyHint: true, openWorldHint: true }, async ({ page, limit, model, status, since, until }) => {
|
|
456
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ page, limit, model, status, since, until }) => {
|
|
450
457
|
return ok(await fetchHistory({
|
|
451
458
|
page,
|
|
452
459
|
pageSize: limit,
|
|
@@ -464,7 +471,7 @@ export function createServer() {
|
|
|
464
471
|
key: z.string().optional().describe('Filter by the API key uuid that generated the charge'),
|
|
465
472
|
since: z.string().optional().describe('Records after this time (ISO or YYYY-MM-DD)'),
|
|
466
473
|
until: z.string().optional().describe('Records before this time (ISO or YYYY-MM-DD)'),
|
|
467
|
-
}, { readOnlyHint: true, openWorldHint: true }, async ({ page, limit, type, model, key, since, until }) => {
|
|
474
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ page, limit, type, model, key, since, until }) => {
|
|
468
475
|
return ok(await fetchBillings({
|
|
469
476
|
page,
|
|
470
477
|
pageSize: limit,
|
|
@@ -479,7 +486,7 @@ export function createServer() {
|
|
|
479
486
|
since: z.string().optional().describe('Start (ISO or YYYY-MM-DD); default 7 days ago'),
|
|
480
487
|
until: z.string().optional().describe('End (ISO or YYYY-MM-DD); default now'),
|
|
481
488
|
model: z.string().optional().describe('Filter by model uuid'),
|
|
482
|
-
}, { readOnlyHint: true, openWorldHint: true }, async ({ since, until, model }) => {
|
|
489
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ since, until, model }) => {
|
|
483
490
|
const end = toIso(until, 'until') ?? new Date().toISOString();
|
|
484
491
|
const start = toIso(since, 'since') ?? new Date(Date.parse(end) - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
485
492
|
return ok(await fetchUsageStats(start, end, model));
|
|
@@ -504,7 +511,7 @@ export function createServer() {
|
|
|
504
511
|
.max(1800)
|
|
505
512
|
.default(0)
|
|
506
513
|
.describe('Max seconds to wait for a terminal status; 0 = return the current status now'),
|
|
507
|
-
}, { readOnlyHint: true, openWorldHint: true }, async ({ id, wait_seconds }, extra) => {
|
|
514
|
+
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: true }, async ({ id, wait_seconds }, extra) => {
|
|
508
515
|
const started = Date.now();
|
|
509
516
|
const { item, done } = await waitForPrediction(id, {
|
|
510
517
|
intervalMs: POLL_INTERVAL_MS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wavespeed/mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
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": {
|