slides-studio-mcp 0.1.0-beta.2 → 0.1.0-beta.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/mcp-server.mjs +26 -25
package/package.json
CHANGED
package/src/mcp-server.mjs
CHANGED
|
@@ -109,9 +109,10 @@ export async function createSlideStudioMcpServer(companion) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function register(name, description, inputSchema, handler, annotations = {}) {
|
|
112
|
+
const normalizedAnnotations = { openWorldHint: false, ...annotations };
|
|
112
113
|
const guidanceExempt = new Set(["select_editor", "open_project", "set_view", "show_notification"]);
|
|
113
|
-
definitions.set(name, { inputSchema, handler, mutating: !
|
|
114
|
-
server.registerTool(name, { title: name.split("_").map((part) => part[0].toUpperCase() + part.slice(1)).join(" "), description, inputSchema, annotations }, async (args, context) => {
|
|
114
|
+
definitions.set(name, { inputSchema, handler, mutating: !normalizedAnnotations.readOnlyHint && !guidanceExempt.has(name) });
|
|
115
|
+
server.registerTool(name, { title: name.split("_").map((part) => part[0].toUpperCase() + part.slice(1)).join(" "), description, inputSchema, annotations: normalizedAnnotations }, async (args, context) => {
|
|
115
116
|
await identify(context);
|
|
116
117
|
if (definitions.get(name).mutating && !guidanceRead) throw new Error("Call get_design_guidance before changing slides. This one-time step prevents avoidable clipping and unattractive defaults.");
|
|
117
118
|
const value = await handler(args, context);
|
|
@@ -136,36 +137,36 @@ export async function createSlideStudioMcpServer(companion) {
|
|
|
136
137
|
}, { readOnlyHint: true, idempotentHint: true });
|
|
137
138
|
|
|
138
139
|
register("list_editors", "List connected browser tabs and show which tab this MCP session targets.", z.object({}).strict(), () => companion.call("list_editors"), { readOnlyHint: true });
|
|
139
|
-
register("select_editor", "Select a connected browser tab for this MCP session.", z.object({ editorId: id }).strict(), ({ editorId }) => companion.call("select_editor", { editorId }), { idempotentHint: true });
|
|
140
|
+
register("select_editor", "Select a connected browser tab for this MCP session.", z.object({ editorId: id }).strict(), ({ editorId }) => companion.call("select_editor", { editorId }), { destructiveHint: false, idempotentHint: true });
|
|
140
141
|
register("inspect_editor", "Inspect projects, slides, assets, and every text/image layer without returning image bytes.", z.object({ ...targetSlide, includeAllProjects: z.boolean().default(true) }).strict(), (args) => browserOperation(companion, "inspect_editor", args), { readOnlyHint: true });
|
|
141
|
-
register("show_notification", "Show a short visual notification in the connected editor for status or marketing demos.", z.object({ message: z.string().min(1).max(240), tone: z.enum(["agent", "success", "info", "error"]).default("agent") }).strict(), (args) => companion.call("notify", args), { idempotentHint: false });
|
|
142
|
+
register("show_notification", "Show a short visual notification in the connected editor for status or marketing demos.", z.object({ message: z.string().min(1).max(240), tone: z.enum(["agent", "success", "info", "error"]).default("agent") }).strict(), (args) => companion.call("notify", args), { destructiveHint: false, idempotentHint: false });
|
|
142
143
|
|
|
143
|
-
register("create_project", "Create and open an empty project.", z.object({ name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "create_project", args));
|
|
144
|
-
register("open_project", "Open a project and optionally a specific slide without changing content.", z.object({ projectId: id, slideId: optionalId }).strict(), (args) => browserOperation(companion, "open_project", args), { idempotentHint: true });
|
|
145
|
-
register("update_project", "Rename a project.", z.object({ ...targetProject, name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "update_project", args));
|
|
144
|
+
register("create_project", "Create and open an empty project.", z.object({ name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "create_project", args), { destructiveHint: false });
|
|
145
|
+
register("open_project", "Open a project and optionally a specific slide without changing content.", z.object({ projectId: id, slideId: optionalId }).strict(), (args) => browserOperation(companion, "open_project", args), { destructiveHint: false, idempotentHint: true });
|
|
146
|
+
register("update_project", "Rename a project.", z.object({ ...targetProject, name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "update_project", args), { destructiveHint: true });
|
|
146
147
|
register("delete_project", "Delete a project from browser storage.", z.object({ ...targetProject, projectId: id }).strict(), (args) => browserOperation(companion, "delete_project", args), { destructiveHint: true });
|
|
147
148
|
|
|
148
|
-
register("add_slide", "Add and open a slide using a solid color or local background image path.", z.object({ ...targetProject, name: z.string().max(160).optional(), index: z.number().int().min(0).optional(), backgroundColor: color.optional(), backgroundPath: z.string().min(1).optional() }).strict(), (args) => browserOperation(companion, "add_slide", args));
|
|
149
|
-
register("update_slide", "Rename a slide, replace its background, or change background pan/zoom. The browser opens this slide.", z.object({ ...targetSlide, name: z.string().max(160).optional(), backgroundColor: color.optional(), backgroundPath: z.string().min(1).optional(), imageScale: z.number().min(1).max(3).optional(), imageX: unit.optional(), imageY: unit.optional() }).strict(), (args) => browserOperation(companion, "update_slide", args));
|
|
150
|
-
register("duplicate_slide", "Duplicate a slide with all layers and open the copy.", z.object({ ...targetSlide, name: z.string().max(160).optional() }).strict(), (args) => browserOperation(companion, "duplicate_slide", args));
|
|
151
|
-
register("reorder_slides", "Set the complete slide order using every slide ID exactly once.", z.object({ ...targetProject, slideIds: z.array(id).min(1) }).strict(), (args) => browserOperation(companion, "reorder_slides", args));
|
|
149
|
+
register("add_slide", "Add and open a slide using a solid color or local background image path.", z.object({ ...targetProject, name: z.string().max(160).optional(), index: z.number().int().min(0).optional(), backgroundColor: color.optional(), backgroundPath: z.string().min(1).optional() }).strict(), (args) => browserOperation(companion, "add_slide", args), { destructiveHint: false });
|
|
150
|
+
register("update_slide", "Rename a slide, replace its background, or change background pan/zoom. The browser opens this slide.", z.object({ ...targetSlide, name: z.string().max(160).optional(), backgroundColor: color.optional(), backgroundPath: z.string().min(1).optional(), imageScale: z.number().min(1).max(3).optional(), imageX: unit.optional(), imageY: unit.optional() }).strict(), (args) => browserOperation(companion, "update_slide", args), { destructiveHint: true });
|
|
151
|
+
register("duplicate_slide", "Duplicate a slide with all layers and open the copy.", z.object({ ...targetSlide, name: z.string().max(160).optional() }).strict(), (args) => browserOperation(companion, "duplicate_slide", args), { destructiveHint: false });
|
|
152
|
+
register("reorder_slides", "Set the complete slide order using every slide ID exactly once.", z.object({ ...targetProject, slideIds: z.array(id).min(1) }).strict(), (args) => browserOperation(companion, "reorder_slides", args), { destructiveHint: true });
|
|
152
153
|
register("delete_slide", "Delete one slide.", z.object({ ...targetSlide, slideId: id }).strict(), (args) => browserOperation(companion, "delete_slide", args), { destructiveHint: true });
|
|
153
154
|
|
|
154
|
-
register("add_text", "Add a text layer. Coordinates and dimensions are normalized to the 9:16 canvas; attractive defaults use generous bounds and per-line boxes.", z.object({ ...targetSlide, ...textFields, text: z.string().min(1).max(4000) }).strict(), (args) => browserOperation(companion, "add_text", args));
|
|
155
|
-
register("update_text", "Update one or more text layers, including content, geometry, color, style, alignment, rotation, and stacking.", z.object({ ...targetSlide, updates: z.array(z.object({ id, ...textFields }).strict()).min(1).max(100) }).strict(), (args) => browserOperation(companion, "update_text", args));
|
|
155
|
+
register("add_text", "Add a text layer. Coordinates and dimensions are normalized to the 9:16 canvas; attractive defaults use generous bounds and per-line boxes.", z.object({ ...targetSlide, ...textFields, text: z.string().min(1).max(4000) }).strict(), (args) => browserOperation(companion, "add_text", args), { destructiveHint: false });
|
|
156
|
+
register("update_text", "Update one or more text layers, including content, geometry, color, style, alignment, rotation, and stacking.", z.object({ ...targetSlide, updates: z.array(z.object({ id, ...textFields }).strict()).min(1).max(100) }).strict(), (args) => browserOperation(companion, "update_text", args), { destructiveHint: true });
|
|
156
157
|
|
|
157
|
-
register("import_asset", "Import a local image file into the active project's reusable asset library. Image bytes stay local.", z.object({ ...targetSlide, path: z.string().min(1), name: z.string().max(160).optional() }).strict(), (args) => browserOperation(companion, "import_asset", args));
|
|
158
|
-
register("update_asset", "Rename a reusable image asset.", z.object({ ...targetProject, assetId: id, name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "update_asset", args));
|
|
158
|
+
register("import_asset", "Import a local image file into the active project's reusable asset library. Image bytes stay local.", z.object({ ...targetSlide, path: z.string().min(1), name: z.string().max(160).optional() }).strict(), (args) => browserOperation(companion, "import_asset", args), { destructiveHint: false });
|
|
159
|
+
register("update_asset", "Rename a reusable image asset.", z.object({ ...targetProject, assetId: id, name: z.string().min(1).max(160) }).strict(), (args) => browserOperation(companion, "update_asset", args), { destructiveHint: true });
|
|
159
160
|
register("delete_asset", "Delete an asset and every placed instance that references it.", z.object({ ...targetProject, assetId: id }).strict(), (args) => browserOperation(companion, "delete_asset", args), { destructiveHint: true });
|
|
160
|
-
register("add_image", "Place an imported asset as an image layer and optionally set geometry, crop, rotation, and stacking.", z.object({ ...targetSlide, assetId: id, ...imageFields }).strict(), (args) => browserOperation(companion, "add_image", args));
|
|
161
|
-
register("update_image", "Update one or more placed image layers, including geometry, crop, rotation, and stacking.", z.object({ ...targetSlide, updates: z.array(z.object({ id, ...imageFields }).strict()).min(1).max(100) }).strict(), (args) => browserOperation(companion, "update_image", args));
|
|
161
|
+
register("add_image", "Place an imported asset as an image layer and optionally set geometry, crop, rotation, and stacking.", z.object({ ...targetSlide, assetId: id, ...imageFields }).strict(), (args) => browserOperation(companion, "add_image", args), { destructiveHint: false });
|
|
162
|
+
register("update_image", "Update one or more placed image layers, including geometry, crop, rotation, and stacking.", z.object({ ...targetSlide, updates: z.array(z.object({ id, ...imageFields }).strict()).min(1).max(100) }).strict(), (args) => browserOperation(companion, "update_image", args), { destructiveHint: true });
|
|
162
163
|
|
|
163
164
|
register("delete_layers", "Delete text and/or image layers by ID.", z.object({ ...targetSlide, layerIds: z.array(id).min(1).max(200) }).strict(), (args) => browserOperation(companion, "delete_layers", args), { destructiveHint: true });
|
|
164
|
-
register("duplicate_layers", "Duplicate text and/or image layers with an optional normalized offset.", z.object({ ...targetSlide, layerIds: z.array(id).min(1).max(100), offsetX: z.number().min(-1).max(1).optional(), offsetY: z.number().min(-1).max(1).optional() }).strict(), (args) => browserOperation(companion, "duplicate_layers", args));
|
|
165
|
-
register("reorder_layers", "Set the complete back-to-front layer order using every layer ID exactly once.", z.object({ ...targetSlide, layerIds: z.array(id).min(1).max(300) }).strict(), (args) => browserOperation(companion, "reorder_layers", args));
|
|
166
|
-
register("undo", "Undo the latest project edit.", z.object(targetSlide).strict(), (args) => browserOperation(companion, "undo", args));
|
|
167
|
-
register("redo", "Redo the latest undone project edit.", z.object(targetSlide).strict(), (args) => browserOperation(companion, "redo", args));
|
|
168
|
-
register("set_view", "Open a project/slide and control editor-only canvas zoom or TikTok safe-area overlay.", z.object({ ...targetSlide, canvasZoom: z.number().min(0.2).max(3).optional(), showTikTokOverlay: z.boolean().optional() }).strict(), (args) => browserOperation(companion, "set_view", args), { idempotentHint: true });
|
|
165
|
+
register("duplicate_layers", "Duplicate text and/or image layers with an optional normalized offset.", z.object({ ...targetSlide, layerIds: z.array(id).min(1).max(100), offsetX: z.number().min(-1).max(1).optional(), offsetY: z.number().min(-1).max(1).optional() }).strict(), (args) => browserOperation(companion, "duplicate_layers", args), { destructiveHint: false });
|
|
166
|
+
register("reorder_layers", "Set the complete back-to-front layer order using every layer ID exactly once.", z.object({ ...targetSlide, layerIds: z.array(id).min(1).max(300) }).strict(), (args) => browserOperation(companion, "reorder_layers", args), { destructiveHint: true });
|
|
167
|
+
register("undo", "Undo the latest project edit.", z.object(targetSlide).strict(), (args) => browserOperation(companion, "undo", args), { destructiveHint: true });
|
|
168
|
+
register("redo", "Redo the latest undone project edit.", z.object(targetSlide).strict(), (args) => browserOperation(companion, "redo", args), { destructiveHint: true });
|
|
169
|
+
register("set_view", "Open a project/slide and control editor-only canvas zoom or TikTok safe-area overlay.", z.object({ ...targetSlide, canvasZoom: z.number().min(0.2).max(3).optional(), showTikTokOverlay: z.boolean().optional() }).strict(), (args) => browserOperation(companion, "set_view", args), { destructiveHint: false, idempotentHint: true });
|
|
169
170
|
|
|
170
171
|
register("render_slide", "Render and return the actual slide image for visual inspection. This does not persist the rendered file.", z.object({ ...targetSlide, width: z.number().int().min(180).max(1080).default(540), format: z.enum(["png", "jpeg"]).default("png"), quality: z.number().min(0.4).max(1).default(0.9) }).strict(), async (args) => {
|
|
171
172
|
const rendered = await browserOperation(companion, "render_slide", args);
|
|
@@ -181,7 +182,7 @@ export async function createSlideStudioMcpServer(companion) {
|
|
|
181
182
|
const path = absolutePath(outputPath);
|
|
182
183
|
await mkdir(dirname(path), { recursive: true });
|
|
183
184
|
return companion.call("write_export", { path, data: rendered.data, overwrite });
|
|
184
|
-
});
|
|
185
|
+
}, { destructiveHint: true });
|
|
185
186
|
|
|
186
187
|
register("export_project", "Render every slide at full resolution into a local directory. Existing files are protected unless overwrite=true.", z.object({ ...targetProject, outputDirectory: z.string().min(1), overwrite: z.boolean().default(false) }).strict(), async ({ outputDirectory, overwrite, ...target }) => {
|
|
187
188
|
const inspected = await browserOperation(companion, "inspect_editor", { ...target, includeAllProjects: false });
|
|
@@ -196,7 +197,7 @@ export async function createSlideStudioMcpServer(companion) {
|
|
|
196
197
|
files.push(await companion.call("write_export", { path, data: rendered.data, overwrite }));
|
|
197
198
|
}
|
|
198
199
|
return { projectId: inspected.project.id, outputDirectory: directory, fileCount: files.length, files };
|
|
199
|
-
});
|
|
200
|
+
}, { destructiveHint: true });
|
|
200
201
|
|
|
201
202
|
const batchTools = [...definitions.entries()].filter(([, definition]) => definition.mutating).map(([name]) => name).filter((name) => !["export_slide", "export_project"].includes(name));
|
|
202
203
|
register("apply_operations", "Apply many ordered editing operations in one compact tool call. Each edit still appears live in the browser.", z.object({ operations: z.array(z.object({ tool: z.enum(batchTools), arguments: z.record(z.string(), z.unknown()).default({}) }).strict()).min(1).max(100) }).strict(), async ({ operations }) => {
|
|
@@ -208,7 +209,7 @@ export async function createSlideStudioMcpServer(companion) {
|
|
|
208
209
|
items.push({ operation: await prepareOperation(companion, item.tool, args), label: operationLabel(item.tool) });
|
|
209
210
|
}
|
|
210
211
|
return companion.call("batch", { items });
|
|
211
|
-
});
|
|
212
|
+
}, { destructiveHint: true });
|
|
212
213
|
|
|
213
214
|
return server;
|
|
214
215
|
}
|