@simpligen/mcp 0.1.1 → 0.1.3
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/client.js +8 -0
- package/src/tools.js +59 -1
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -46,6 +46,7 @@ export class ControlApiClient {
|
|
|
46
46
|
getStatus() { return this.#req('GET', '/status'); }
|
|
47
47
|
listProjects() { return this.#req('GET', '/projects'); }
|
|
48
48
|
createProject(name) { return this.#req('POST', '/projects', { name }); }
|
|
49
|
+
listLoras() { return this.#req('GET', '/loras'); }
|
|
49
50
|
generate(args) { return this.#req('POST', '/generate', args); }
|
|
50
51
|
getJob(id) { return this.#req('GET', `/jobs/${encodeURIComponent(id)}`); }
|
|
51
52
|
listJobs() { return this.#req('GET', '/jobs'); }
|
|
@@ -67,6 +68,13 @@ export class ControlApiClient {
|
|
|
67
68
|
regenerateCharacterBase(id, args) { return this.#req('POST', `/characters/${encodeURIComponent(id)}/regenerate-base`, args); }
|
|
68
69
|
removeCharacter(id) { return this.#req('DELETE', `/characters/${encodeURIComponent(id)}`); }
|
|
69
70
|
|
|
71
|
+
listRecipeBlocks() { return this.#req('GET', '/recipe-blocks'); }
|
|
72
|
+
composeRecipe(args) { return this.#req('POST', '/recipes', args); }
|
|
73
|
+
validateRecipe(args) { return this.#req('POST', '/recipes/validate', args); }
|
|
74
|
+
listRecipes() { return this.#req('GET', '/recipes'); }
|
|
75
|
+
getRecipe(id) { return this.#req('GET', `/recipes/${encodeURIComponent(id)}`); }
|
|
76
|
+
publishRecipe(id, args) { return this.#req('POST', `/recipes/${encodeURIComponent(id)}/publish`, args); }
|
|
77
|
+
|
|
70
78
|
async uploadFile(filePath) {
|
|
71
79
|
let buf;
|
|
72
80
|
try {
|
package/src/tools.js
CHANGED
|
@@ -7,12 +7,13 @@ import { buildResultImageContent } from './resultImage.js';
|
|
|
7
7
|
|
|
8
8
|
export const TOOL_NAMES = [
|
|
9
9
|
'list_capabilities', 'get_status', 'list_projects', 'create_project',
|
|
10
|
-
'upload_file', 'generate', 'get_job', 'wait_for_result', 'list_jobs',
|
|
10
|
+
'upload_file', 'generate', 'list_loras', 'get_job', 'wait_for_result', 'list_jobs',
|
|
11
11
|
'cancel_job', 'prepare_preset', 'get_result_image',
|
|
12
12
|
'list_characters', 'get_character', 'list_character_features', 'list_character_presets',
|
|
13
13
|
'remove_character', 'create_character', 'generate_character_image', 'generate_character_video',
|
|
14
14
|
'generate_character_frame', 'animate_character', 'set_character_preset',
|
|
15
15
|
'enhance_character_media', 'regenerate_character_base',
|
|
16
|
+
'list_recipe_blocks', 'compose_recipe', 'validate_recipe', 'list_recipes', 'get_recipe', 'publish_recipe',
|
|
16
17
|
];
|
|
17
18
|
|
|
18
19
|
const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
@@ -50,11 +51,16 @@ export function registerTools(server, client) {
|
|
|
50
51
|
image: z.string().optional().describe('upload_file handle'),
|
|
51
52
|
referenceImages: z.array(z.string()).optional().describe('upload_file handles'),
|
|
52
53
|
video: z.string().optional().describe('upload_file handle (driving video)'),
|
|
54
|
+
loras: z.array(z.object({ name: z.string().describe('LoRA display name from list_loras'), weight: z.number().optional().describe('Strength; defaults to the LoRA default') })).optional().describe('LoRAs to apply. Local backend only, and only for presets where inputs.supportsLoras is true. Use the LoRA trigger words in the prompt so it activates.'),
|
|
53
55
|
options: z.object({ resolution: z.string().optional(), aspectRatio: z.string().optional().describe('Aspect ratio, e.g. "1:1", "16:9", "9:16" (portrait/vertical), "4:3", "3:4". See list_capabilities -> options.aspectRatio for the values each preset accepts. Defaults to 1:1 for images (16:9 for video) if omitted.'), durationSeconds: z.number().optional(), steps: z.number().optional(), cfg: z.number().optional(), seed: z.number().optional() }).optional(),
|
|
54
56
|
},
|
|
55
57
|
},
|
|
56
58
|
wrap((args) => client.generate(args)));
|
|
57
59
|
|
|
60
|
+
server.registerTool('list_loras',
|
|
61
|
+
{ title: 'List LoRAs', description: 'List the installed user LoRAs (name, base model, trigger words) so you can pass them in generate -> loras.', inputSchema: {} },
|
|
62
|
+
wrap(() => client.listLoras()));
|
|
63
|
+
|
|
58
64
|
server.registerTool('get_job',
|
|
59
65
|
{ title: 'Get job', description: 'Status + result of a generation job. resultPath is the local file when completed.', inputSchema: { jobId: z.string() } },
|
|
60
66
|
wrap(({ jobId }) => client.getJob(jobId)));
|
|
@@ -302,4 +308,56 @@ export function registerTools(server, client) {
|
|
|
302
308
|
}
|
|
303
309
|
} catch (e) { return fail(e); }
|
|
304
310
|
});
|
|
311
|
+
|
|
312
|
+
// --- Recipe authoring: compose multi-step recipes out of packs/presets ---
|
|
313
|
+
|
|
314
|
+
const recipeStep = z.object({
|
|
315
|
+
preset: z.string().describe('The preset this step runs, as "packId:presetId" (from list_recipe_blocks).'),
|
|
316
|
+
entry: z.string().optional().describe('Entry point: create-image | create-video | edit-image | upscale. Inferred from the preset when omitted.'),
|
|
317
|
+
promptInput: z.object({ key: z.string().optional(), label: z.string().optional() }).optional()
|
|
318
|
+
.describe('Name this step\'s prompt input, or set key to an existing input to SHARE a prompt. Default: a dedicated prompt input per step.'),
|
|
319
|
+
negativePrompt: z.string().optional(),
|
|
320
|
+
params: z.record(z.any()).optional().describe('Param bindings, literal or { from: "input:<key>" } (e.g. aspectRatio, seed, duration).'),
|
|
321
|
+
}).passthrough();
|
|
322
|
+
|
|
323
|
+
const recipeInput = z.object({
|
|
324
|
+
key: z.string().describe('Unique slug referenced as input:<key>.'),
|
|
325
|
+
kind: z.string().describe('subject | text | image | video | reference | audio | select | color | number | toggle'),
|
|
326
|
+
label: z.string().optional(),
|
|
327
|
+
required: z.boolean().optional(),
|
|
328
|
+
}).passthrough();
|
|
329
|
+
|
|
330
|
+
const recipeSpec = z.object({
|
|
331
|
+
name: z.string().describe('Recipe name shown to end users.'),
|
|
332
|
+
description: z.string().optional(),
|
|
333
|
+
category: z.string().optional().describe('e.g. product | ugc'),
|
|
334
|
+
nsfw: z.boolean().optional(),
|
|
335
|
+
version: z.string().optional().describe('Creator-facing semver, default 1.0.0.'),
|
|
336
|
+
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
|
+
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.'),
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
server.registerTool('list_recipe_blocks',
|
|
341
|
+
{ title: 'List recipe blocks', description: 'The building blocks for authoring a recipe: entry points (create-image/video, edit-image, upscale) and every preset usable as a step, each with its port contract (inputs it needs, what it produces, which entry points it fits). Call this before compose_recipe to choose presets.', inputSchema: {} },
|
|
342
|
+
wrap(() => client.listRecipeBlocks()));
|
|
343
|
+
|
|
344
|
+
server.registerTool('compose_recipe',
|
|
345
|
+
{ title: 'Compose a recipe', description: 'Author a new recipe from a high-level spec and save it as a draft. You give an ordered list of steps (each pinned to a preset from list_recipe_blocks) plus any extra inputs; SimpliGen auto-wires the ports, media chain, and per-step prompt inputs and validates it. Returns { recipeId, valid, errors }. Anyone can compose; publishing later needs a connected account.', inputSchema: { spec: recipeSpec } },
|
|
346
|
+
wrap(({ spec }) => client.composeRecipe({ spec })));
|
|
347
|
+
|
|
348
|
+
server.registerTool('validate_recipe',
|
|
349
|
+
{ title: 'Validate a recipe', description: 'Dry-run without saving: pass a `spec` to compose+validate it, or a `config` to validate an existing recipe config. Returns precise errors (unbound ports, missing presets, type mismatches, cycles) to fix before compose_recipe.', inputSchema: { spec: recipeSpec.optional(), config: z.record(z.any()).optional() } },
|
|
350
|
+
wrap((args) => client.validateRecipe(args)));
|
|
351
|
+
|
|
352
|
+
server.registerTool('list_recipes',
|
|
353
|
+
{ title: 'List recipes', description: 'List the recipe drafts saved on this device (id, name, version, category).', inputSchema: {} },
|
|
354
|
+
wrap(() => client.listRecipes()));
|
|
355
|
+
|
|
356
|
+
server.registerTool('get_recipe',
|
|
357
|
+
{ 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
|
+
wrap(({ id }) => client.getRecipe(id)));
|
|
359
|
+
|
|
360
|
+
server.registerTool('publish_recipe',
|
|
361
|
+
{ title: 'Publish a recipe', description: 'Submit a saved recipe to the SimpliGen Creator Marketplace (queued for human review). Requires a connected account: if none, returns not_connected and the user connects once in Settings, then this succeeds on the same recipe. priceTokens sets an optional one-time price (0 or omitted = free). Every preset the recipe uses must be a distributable first-party pack.', inputSchema: { id: z.string().describe('Recipe id to publish'), changelog: z.string().optional(), priceTokens: z.number().optional().describe('One-time price in tokens (0/omitted = free).') } },
|
|
362
|
+
wrap(({ id, changelog, priceTokens }) => client.publishRecipe(id, { changelog, priceTokens })));
|
|
305
363
|
}
|