@simpligen/mcp 0.1.3 → 0.1.4
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/package.json +1 -1
- package/src/bin.js +1 -1
- package/src/client.js +8 -0
- package/src/tools.js +74 -2
package/package.json
CHANGED
package/src/bin.js
CHANGED
package/src/client.js
CHANGED
|
@@ -74,6 +74,14 @@ export class ControlApiClient {
|
|
|
74
74
|
listRecipes() { return this.#req('GET', '/recipes'); }
|
|
75
75
|
getRecipe(id) { return this.#req('GET', `/recipes/${encodeURIComponent(id)}`); }
|
|
76
76
|
publishRecipe(id, args) { return this.#req('POST', `/recipes/${encodeURIComponent(id)}/publish`, args); }
|
|
77
|
+
runRecipe(id, args) { return this.#req('POST', `/recipes/${encodeURIComponent(id)}/run`, args); }
|
|
78
|
+
listRecipeRuns(id) { return this.#req('GET', `/recipes/${encodeURIComponent(id)}/runs`); }
|
|
79
|
+
getRecipeRun(runId) { return this.#req('GET', `/recipe-runs/${encodeURIComponent(runId)}`); }
|
|
80
|
+
cancelRecipeRun(runId) { return this.#req('POST', `/recipe-runs/${encodeURIComponent(runId)}/cancel`, {}); }
|
|
81
|
+
listSubjects() { return this.#req('GET', '/subjects'); }
|
|
82
|
+
listProducts() { return this.#req('GET', '/products'); }
|
|
83
|
+
createProduct(args) { return this.#req('POST', '/products', args); }
|
|
84
|
+
removeProduct(id) { return this.#req('DELETE', `/products/${encodeURIComponent(id)}`); }
|
|
77
85
|
|
|
78
86
|
async uploadFile(filePath) {
|
|
79
87
|
let buf;
|
package/src/tools.js
CHANGED
|
@@ -14,6 +14,8 @@ export const TOOL_NAMES = [
|
|
|
14
14
|
'generate_character_frame', 'animate_character', 'set_character_preset',
|
|
15
15
|
'enhance_character_media', 'regenerate_character_base',
|
|
16
16
|
'list_recipe_blocks', 'compose_recipe', 'validate_recipe', 'list_recipes', 'get_recipe', 'publish_recipe',
|
|
17
|
+
'run_recipe', 'get_recipe_run', 'wait_for_recipe_run', 'list_recipe_runs', 'cancel_recipe_run',
|
|
18
|
+
'list_subjects', 'list_products', 'create_product', 'remove_product',
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
@@ -331,7 +333,11 @@ export function registerTools(server, client) {
|
|
|
331
333
|
name: z.string().describe('Recipe name shown to end users.'),
|
|
332
334
|
description: z.string().optional(),
|
|
333
335
|
category: z.string().optional().describe('e.g. product | ugc'),
|
|
334
|
-
|
|
336
|
+
mature: z.boolean().optional().describe('Creator-declared: this recipe produces adult content.'),
|
|
337
|
+
// Accepted, not for the benefit of third parties (breaking them is fine), but
|
|
338
|
+
// because an agent composing a recipe mid-transition would otherwise DROP the
|
|
339
|
+
// classification, which publishes adult content as unclassified (TASK-472).
|
|
340
|
+
nsfw: z.boolean().optional().describe('Deprecated alias for `mature`.'),
|
|
335
341
|
version: z.string().optional().describe('Creator-facing semver, default 1.0.0.'),
|
|
336
342
|
inputs: z.array(recipeInput).optional().describe('Extra user-facing inputs (subjects, choices, media holes). A prompt input is auto-created per step, so you rarely list prompts here.'),
|
|
337
343
|
steps: z.array(recipeStep).min(1).describe('The pipeline in order. Each step pins a preset; media output flows into the next step automatically, and a subject input seeds a reference port.'),
|
|
@@ -350,9 +356,75 @@ export function registerTools(server, client) {
|
|
|
350
356
|
wrap((args) => client.validateRecipe(args)));
|
|
351
357
|
|
|
352
358
|
server.registerTool('list_recipes',
|
|
353
|
-
{ title: 'List recipes', description: 'List
|
|
359
|
+
{ title: 'List recipes', description: 'List every recipe on this device: the ones installed from the Store into UGC Studio / Product Studio (category ugc | product) and drafts authored here. Each entry carries its `inputs` (key, kind, label, required, options) so you know what run_recipe needs: a `subject` input takes a character id from list_characters (or a product id), image/video/audio inputs take upload_file handles, text/select/number inputs take plain values.', inputSchema: {} },
|
|
354
360
|
wrap(() => client.listRecipes()));
|
|
355
361
|
|
|
362
|
+
// --- Running an installed recipe (what drives the UGC + Product Studios) ---
|
|
363
|
+
|
|
364
|
+
server.registerTool('run_recipe',
|
|
365
|
+
{
|
|
366
|
+
title: 'Run a recipe',
|
|
367
|
+
description: 'Run an installed recipe (from list_recipes) with its inputs filled: the same multi-step pipeline UGC Studio / Product Studio runs when a person clicks Generate, so results land in the Gallery grouped the same way. `inputs` is keyed by input key; a subject input takes a character/product id, media inputs take upload_file handles. engine: local (default when every step is installed) | cloud | auto. Returns a runId; poll with wait_for_recipe_run or get_recipe_run. Cloud runs are priced up front and count against this agent\'s spend cap.',
|
|
368
|
+
inputSchema: {
|
|
369
|
+
id: z.string().describe('Recipe id from list_recipes'),
|
|
370
|
+
inputs: z.record(z.any()).describe('Input values keyed by the recipe\'s input keys (see list_recipes -> inputs).'),
|
|
371
|
+
engine: z.enum(['local', 'cloud', 'auto']).optional().describe('Where the steps render. auto = local if every step is installed, else cloud when connected.'),
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
wrap(({ id, inputs, engine }) => client.runRecipe(id, { inputs: inputs || {}, ...(engine ? { engine } : {}) })));
|
|
375
|
+
|
|
376
|
+
server.registerTool('get_recipe_run',
|
|
377
|
+
{ title: 'Get a recipe run', description: 'Status of a recipe run: overall status (running | completed | failed | cancelled), each step with its status and resultPath, and resultPath = the final output once done.', inputSchema: { runId: z.string().describe('Run id from run_recipe') } },
|
|
378
|
+
wrap(({ runId }) => client.getRecipeRun(runId)));
|
|
379
|
+
|
|
380
|
+
server.registerTool('wait_for_recipe_run',
|
|
381
|
+
{ title: 'Wait for a recipe run', description: 'Block until a recipe run completes, fails, is cancelled, or the timeout passes. Returns the final run (resultPath when completed). Multi-step video recipes can take several minutes; call again if it returns timeout_still_running.', inputSchema: { runId: z.string(), timeoutSeconds: z.number().optional(), pollMs: z.number().optional() } },
|
|
382
|
+
wrap(async ({ runId, timeoutSeconds = 600, pollMs = 3000 }) => {
|
|
383
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
384
|
+
// eslint-disable-next-line no-constant-condition
|
|
385
|
+
while (true) {
|
|
386
|
+
const { run } = await client.getRecipeRun(runId);
|
|
387
|
+
if (run && ['completed', 'failed', 'cancelled'].includes(run.status)) return { run };
|
|
388
|
+
if (Date.now() >= deadline) return { run, note: 'timeout_still_running' };
|
|
389
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
390
|
+
}
|
|
391
|
+
}));
|
|
392
|
+
|
|
393
|
+
server.registerTool('list_recipe_runs',
|
|
394
|
+
{ title: 'List recipe runs', description: 'Recent runs of a recipe (newest first) with their status and results.', inputSchema: { id: z.string().describe('Recipe id') } },
|
|
395
|
+
wrap(({ id }) => client.listRecipeRuns(id)));
|
|
396
|
+
|
|
397
|
+
// --- Products + subjects: what a recipe's `subject` inputs take ---
|
|
398
|
+
|
|
399
|
+
server.registerTool('list_subjects',
|
|
400
|
+
{ title: 'List subjects', description: 'Every subject a recipe\'s `subject` input can take: characters and products (id, type, name). Use a character id from list_characters or a product id from list_products / create_product.', inputSchema: {} },
|
|
401
|
+
wrap(() => client.listSubjects()));
|
|
402
|
+
|
|
403
|
+
server.registerTool('list_products',
|
|
404
|
+
{ title: 'List products', description: 'The products saved on this device (a product = a named thing with a base photo, used by Product Studio and UGC recipes).', inputSchema: {} },
|
|
405
|
+
wrap(() => client.listProducts()));
|
|
406
|
+
|
|
407
|
+
server.registerTool('create_product',
|
|
408
|
+
{
|
|
409
|
+
title: 'Create a product',
|
|
410
|
+
description: 'Save a product from a photo so recipes can use it: upload the photo with upload_file first, then pass its handle. The description and category are woven into recipe prompts, so describe what it is and how it looks. Returns the productId to pass as a `subject` input in run_recipe.',
|
|
411
|
+
inputSchema: {
|
|
412
|
+
name: z.string().describe('Product name shown in the app'),
|
|
413
|
+
image: z.string().describe('upload_file handle of the product photo (clean, well-lit, product centred)'),
|
|
414
|
+
description: z.string().optional().describe('What it is and looks like, e.g. "a matte black ceramic coffee mug with a wooden lid"'),
|
|
415
|
+
category: z.string().optional().describe('e.g. skincare | beverage | electronics'),
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
wrap(({ name, image, description, category }) => client.createProduct({ name, image, description, category })));
|
|
419
|
+
|
|
420
|
+
server.registerTool('remove_product',
|
|
421
|
+
{ title: 'Remove a product', description: 'Delete a product and its photo from this device.', inputSchema: { id: z.string() } },
|
|
422
|
+
wrap(({ id }) => client.removeProduct(id)));
|
|
423
|
+
|
|
424
|
+
server.registerTool('cancel_recipe_run',
|
|
425
|
+
{ title: 'Cancel a recipe run', description: 'Stop a running recipe: no further steps start; a local in-flight step is interrupted (a cloud step already running finishes server-side and is saved).', inputSchema: { runId: z.string() } },
|
|
426
|
+
wrap(({ runId }) => client.cancelRecipeRun(runId)));
|
|
427
|
+
|
|
356
428
|
server.registerTool('get_recipe',
|
|
357
429
|
{ title: 'Get a recipe', description: 'Get a saved recipe and its full config by id.', inputSchema: { id: z.string().describe('Recipe id from compose_recipe or list_recipes') } },
|
|
358
430
|
wrap(({ id }) => client.getRecipe(id)));
|