Skip to main content
Glama

Frame0 MCP Server

by niklauslee
index.js36.2 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import * as response from "./response.js"; import { JsonRpcErrorCode } from "./response.js"; import { ARROWHEADS, convertArrowhead, command, filterPage, filterShape, trimObject, } from "./utils.js"; import packageJson from "../package.json" with { type: "json" }; const NAME = "frame0-mcp-server"; const VERSION = packageJson.version; // port number for the Frame0's API server (default: 58320) let apiPort = 58320; // command line argument parsing const args = process.argv.slice(2); const apiPortArg = args.find((arg) => arg.startsWith("--api-port=")); if (apiPortArg) { const port = apiPortArg.split("=")[1]; try { apiPort = parseInt(port, 10); if (isNaN(apiPort) || apiPort < 0 || apiPort > 65535) { throw new Error(`Invalid port number: ${port}`); } } catch (error) { console.error(`Invalid port number: ${port}`); process.exit(1); } } // Create an MCP server const server = new McpServer({ name: NAME, version: VERSION, }); server.tool("create_frame", "Create a frame shape in Frame0. Must add a new page before you create a new frame.", { frameType: z .enum(["phone", "tablet", "desktop", "browser", "watch", "tv"]) .describe("Type of the frame shape to create."), name: z.string().describe("Name of the frame shape."), fillColor: z .string() .optional() .default("#ffffff") .describe("Background color in hex code of the frame shape."), }, async ({ frameType, name, fillColor }) => { const FRAME_NAME = { phone: "Phone", tablet: "Tablet", desktop: "Desktop", browser: "Browser", watch: "Watch", tv: "TV", }; const FRAME_SIZE = { phone: { width: 320, height: 690 }, tablet: { width: 600, height: 800 }, desktop: { width: 800, height: 600 }, browser: { width: 800, height: 600 }, watch: { width: 198, height: 242 }, tv: { width: 960, height: 570 }, }; const FRAME_HEADER_HEIGHT = { phone: 0, tablet: 0, desktop: 32, browser: 76, watch: 0, tv: 0, }; try { // frame headers should be consider to calculate actual content area const frameHeaderHeight = FRAME_HEADER_HEIGHT[frameType]; const frameSize = FRAME_SIZE[frameType]; const frameName = FRAME_NAME[frameType]; const shapeId = await command(apiPort, "shape:create-shape-from-library-by-query", { query: `${frameName}&@Frame`, shapeProps: trimObject({ name, left: 0, top: -frameHeaderHeight, width: frameSize.width, height: frameSize.height + frameHeaderHeight, fillColor, }), convertColors: true, }); await command(apiPort, "view:fit-to-screen"); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created frame: " + JSON.stringify({ ...filterShape(data), top: -frameHeaderHeight, height: frameSize.height + frameHeaderHeight, })); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create frame: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_rectangle", `Create a rectangle shape in Frame0.`, { name: z.string().describe("Name of the rectangle shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), left: z .number() .describe("Left position of the rectangle shape in the absolute coordinate system."), top: z .number() .describe("Top position of the rectangle shape in the absolute coordinate system."), width: z.number().describe("Width of the rectangle shape."), height: z.number().describe("Height of the rectangle shape."), fillColor: z .string() .optional() .default("#ffffff") .describe("Fill color in hex code of the rectangle shape."), strokeColor: z .string() .optional() .default("#000000") .describe("Stroke color in hex code of the rectangle shape."), corners: z .array(z.number()) .length(4) .optional() .default([0, 0, 0, 0]) .describe("Corner radius of the rectangle shape. Must be in the form of [left-top, right-top, right-bottom, left-bottom]."), }, async ({ name, parentId, left, top, width, height, fillColor, strokeColor, corners, }) => { try { const shapeId = await command(apiPort, "shape:create-shape", { type: "Rectangle", shapeProps: trimObject({ name, left, top, width, height, fillColor, strokeColor, corners, }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created rectangle: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create rectangle: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_ellipse", `Create an ellipse shape in Frame0.`, { name: z.string().describe("Name of the ellipse shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), left: z .number() .describe("Left position of the ellipse shape in the absolute coordinate system."), top: z .number() .describe("Top position of the ellipse shape in the absolute coordinate system."), width: z.number().describe("Width of the ellipse shape."), height: z.number().describe("Height of the ellipse shape."), fillColor: z .string() .optional() .default("#ffffff") .describe("Fill color in hex code of the ellipse shape."), strokeColor: z .string() .optional() .default("#000000") .describe("Stroke color in hex code of the ellipse shape."), }, async ({ name, parentId, left, top, width, height, fillColor, strokeColor, }) => { try { const shapeId = await command(apiPort, "shape:create-shape", { type: "Ellipse", shapeProps: trimObject({ name, left, top, width, height, fillColor, strokeColor, }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created ellipse: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create ellipse: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_text", "Create a text shape in Frame0.", { type: z .enum(["label", "paragraph", "heading", "link", "normal"]) .optional() .describe("Type of the text shape to create. If type is 'paragraph', text width need to be updated using 'update_shape' tool."), name: z.string().describe("Name of the text shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), left: z .number() .describe("Left position of the text shape in the absolute coordinate system. Position need to be adjusted using 'move_shape' tool based on the width and height of the created text."), top: z .number() .describe("Top position of the text shape in the absolute coordinate system. Position need to be adjusted using 'move_shape' tool based on the width and height of the created text."), width: z .number() .optional() .describe("Width of the text shape. if the type is 'paragraph' recommend to set width."), text: z .string() .describe("Plain text content to display of the text shape. Use newline character (0x0A) instead of '\\n' for new line. Dont's use HTML and CSS code in the text content."), fontColor: z .string() .optional() .default("#000000") .describe("Font color in hex code of the text shape."), fontSize: z.number().optional().describe("Font size of the text shape."), }, async ({ type, name, parentId, left, top, width, text, fontColor, fontSize, }) => { try { const shapeId = await command(apiPort, "shape:create-shape", { type: "Text", shapeProps: trimObject({ name, left, width, top, text, fontColor, fontSize, wordWrap: type === "paragraph", }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created text: " + JSON.stringify({ ...filterShape(data), textType: type })); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create text: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_line", "Create a line shape in Frame0.", { name: z.string().describe("Name of the line shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), x1: z.number().describe("X coordinate of the first point."), y1: z.number().describe("Y coordinate of the first point."), x2: z.number().describe("X coordinate of the second point."), y2: z.number().describe("Y coordinate of the second point."), strokeColor: z .string() .optional() .default("#000000") .describe("Stroke color in hex code of the line shape. (e.g., black) - temp string type"), }, async ({ name, parentId, x1, y1, x2, y2, strokeColor }) => { try { const shapeId = await command(apiPort, "shape:create-shape", { type: "Line", shapeProps: trimObject({ name, path: [ [x1, y1], [x2, y2], ], tailEndType: "flat", headEndType: "flat", strokeColor, lineType: "straight", }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created line: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create line: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_polygon", "Create a polygon or polyline shape in Frame0.", { name: z.string().describe("Name of the polygon shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), points: z .array(z.object({ x: z.number().describe("X coordinate of the point."), y: z.number().describe("Y coordinate of the point."), })) .min(3) .describe("Array of points defining the polygon shape."), closed: z .boolean() .optional() .default(true) .describe("Whether the polygon shape is closed or not. Default is true."), fillColor: z .string() .optional() .default("#ffffff") .describe("Fill color in hex code of the polygon shape. (e.g., white) - temp string type"), strokeColor: z .string() .optional() .default("#000000") .describe("Stroke color in hex code of the line shape. (e.g., black) - temp string type"), }, async ({ name, parentId, points, closed, strokeColor }) => { try { const path = points.map((point) => [point.x, point.y]); const pathClosed = path[0][0] === path[path.length - 1][0] && path[0][1] === path[path.length - 1][1]; if (closed && !pathClosed) path.push(path[0]); const shapeId = await command(apiPort, "shape:create-shape", { type: "Line", shapeProps: trimObject({ name, path, tailEndType: "flat", headEndType: "flat", strokeColor, lineType: "straight", }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created line: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create line: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_connector", "Create a connector shape in Frame0.", { name: z.string().describe("Name of the line shape."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), startId: z.string().describe("ID of the start shape."), endId: z.string().describe("ID of the end shape."), startArrowhead: z .enum(ARROWHEADS) .optional() .default("none") .describe("Start arrowhead of the line shape."), endArrowhead: z .enum(ARROWHEADS) .optional() .default("none") .describe("End arrowhead of the line shape."), strokeColor: z .string() .optional() .default("#000000") .describe("Stroke color in hex code of the line. shape"), }, async ({ name, parentId, startId, endId, startArrowhead, endArrowhead, strokeColor, }) => { try { const shapeId = await command(apiPort, "shape:create-connector", { tailId: startId, headId: endId, shapeProps: trimObject({ name, tailEndType: convertArrowhead(startArrowhead || "none"), headEndType: convertArrowhead(endArrowhead || "none"), strokeColor, }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created connector: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create connector: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_icon", "Create an icon shape in Frame0.", { name: z .string() .describe("The name of the icon shape to create. The name should be one of the result of 'get_available_icons' tool."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), left: z .number() .describe("Left position of the icon shape in the absolute coordinate system."), top: z .number() .describe("Top position of the icon shape in the absolute coordinate system."), size: z .enum(["small", "medium", "large", "extra-large"]) .describe("Size of the icon shape. 'small' is 16 x 16, 'medium' is 24 x 24, 'large' is 32 x 32, 'extra-large' is 48 x 48."), strokeColor: z .string() .optional() .default("#000000") .describe(`Stroke color in hex code of the icon shape.`), }, async ({ name, parentId, left, top, size, strokeColor }) => { try { const sizeValue = { small: 16, medium: 24, large: 32, "extra-large": 48, }[size]; const shapeId = await command(apiPort, "shape:create-icon", { iconName: name, shapeProps: trimObject({ left, top, width: sizeValue ?? 24, height: sizeValue ?? 24, strokeColor, }), parentId, convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created icon: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create icon: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("create_image", "Create an image shape in Frame0.", { name: z.string().describe("The name of the image shape to create."), parentId: z .string() .optional() .describe("ID of the parent shape. Typically frame ID."), mimeType: z .enum(["image/png", "image/jpeg", "image/webp", "image/svg+xml"]) .describe("MIME type of the image."), imageData: z.string().describe("Base64 encoded image data."), left: z .number() .describe("Left position of the image shape in the absolute coordinate system."), top: z .number() .describe("Top position of the image shape in the absolute coordinate system."), }, async ({ name, parentId, mimeType, imageData, left, top }) => { try { const shapeId = await command(apiPort, "shape:create-image", { mimeType, imageData, shapeProps: trimObject({ name, left, top, }), parentId, }); const data = await command(apiPort, "shape:get-shape", { shapeId, }); return response.text("Created image: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to create image: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("update_shape", "Update properties of a shape in Frame0.", { shapeId: z.string().describe("ID of the shape to update"), name: z.string().optional().describe("Name of the shape."), width: z.number().optional().describe("Width of the shape."), height: z.number().optional().describe("Height of the shape."), fillColor: z .string() .optional() .describe("Fill color in hex code of the shape."), strokeColor: z .string() .optional() .describe("Stroke color in hex code of the shape."), fontColor: z .string() .optional() .describe("Font color in hex code of the text shape."), fontSize: z.number().optional().describe("Font size of the text shape."), corners: z .array(z.number()) .length(4) .optional() .describe("Corner radius of the rectangle shape. Must be in the form of [left-top, right-top, right-bottom, left-bottom]."), text: z .string() .optional() .describe("Plain text content to display of the text shape. Don't include escape sequences and HTML and CSS code in the text content."), }, async ({ shapeId, name, width, height, strokeColor, fillColor, fontColor, fontSize, corners, text, }) => { try { const updatedId = await command(apiPort, "shape:update-shape", { shapeId, shapeProps: trimObject({ name, width, height, fillColor, strokeColor, fontColor, fontSize, corners, text, }), convertColors: true, }); const data = await command(apiPort, "shape:get-shape", { shapeId: updatedId, }); return response.text("Updated shape: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to update shape: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("duplicate_shape", "Duplicate a shape in Frame0.", { shapeId: z.string().describe("ID of the shape to duplicate"), parentId: z .string() .optional() .describe("ID of the parent shape where the duplicated shape will be added. If not provided, the duplicated shape will be added to the current page."), dx: z .number() .optional() .describe("Delta X value by which the duplicated shape moves."), dy: z .number() .optional() .describe("Delta Y value by which the duplicated shape moves."), }, async ({ shapeId, parentId, dx, dy }) => { try { const duplicatedShapeIdArray = await command(apiPort, "edit:duplicate", { shapeIdArray: [shapeId], parentId, dx, dy, }); const duplicatedShapeId = duplicatedShapeIdArray[0]; const data = await command(apiPort, "shape:get-shape", { shapeId: duplicatedShapeId, }); return response.text("Duplicated shape: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to duplicate shape: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("delete_shape", "Delete a shape in Frame0.", { shapeId: z.string().describe("ID of the shape to delete") }, async ({ shapeId }) => { try { await command(apiPort, "edit:delete", { shapeIdArray: [shapeId], }); return response.text("Deleted shape of id: " + shapeId); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to delete shape: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("search_icons", "Search icon shapes available in Frame0.", { keyword: z .string() .optional() .describe("Search keyword to filter icon by name or tags (case-insensitive)"), }, async ({ keyword }) => { try { const data = await command(apiPort, "shape:get-available-icons", {}); const icons = Array.isArray(data) ? data : []; const filtered = keyword ? icons.filter((icon) => { if (typeof icon !== "object" || !icon.name || !Array.isArray(icon.tags)) { return false; } const searchLower = keyword.toLowerCase(); return (icon.name.toLowerCase().includes(searchLower) || icon.tags.some((tag) => tag.toLowerCase().includes(searchLower))); }) : icons; return response.text("Available icons: " + JSON.stringify(filtered)); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to search available icons: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("move_shape", "Move a shape in Frame0.", { shapeId: z.string().describe("ID of the shape to move"), dx: z.number().describe("Delta X"), dy: z.number().describe("Delta Y"), }, async ({ shapeId, dx, dy }) => { try { await command(apiPort, "shape:move", { shapeId, dx, dy, }); return response.text(`Moved shape (id: ${shapeId}) as (${dx}, ${dy})`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to move shape: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("align_shapes", "Align shapes in Frame0.", { alignType: z .enum([ "bring-to-front", "send-to-back", "align-left", "align-right", "align-horizontal-center", "align-top", "align-bottom", "align-vertical-center", "distribute-horizontally", "distribute-vertically", ]) .describe("Type of the alignment to apply."), shapeIdArray: z.array(z.string()).describe("Array of shape IDs to align"), }, async ({ alignType, shapeIdArray }) => { const COMMAND = { "bring-to-front": "align:bring-to-front", "send-to-back": "align:send-to-back", "align-left": "align:align-left", "align-right": "align:align-right", "align-horizontal-center": "align:align-center", "align-top": "align:align-top", "align-bottom": "align:align-bottom", "align-vertical-center": "align:align-middle", "distribute-horizontally": "align:horizontal-distribute", "distribute-vertically": "align:vertical-distribute", }; try { await command(apiPort, COMMAND[alignType], { shapeIdArray }); return response.text("Shapes are aligned."); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to align shapes: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("group", "Group shapes in Frame0.", { shapeIdArray: z.array(z.string()).describe("Array of shape IDs to group"), parentId: z .string() .optional() .describe("ID of the parent shape where the group will be added. If not provided, the group will be added to the current page."), }, async ({ shapeIdArray, parentId }) => { try { const groupId = await command(apiPort, "shape:group", { shapeIdArray, parentId, }); const data = await command(apiPort, "shape:get-shape", { shapeId: groupId, }); return response.text("Created group: " + JSON.stringify(filterShape(data))); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to group shapes: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("ungroup", "Ungroup a group in Frame0.", { groupId: z.string().describe("ID of the group to ungroup"), }, async ({ groupId }) => { try { await command(apiPort, "shape:ungroup", { shapeIdArray: [groupId], }); return response.text("Deleted group of id: " + groupId); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to ungroup shapes: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("set_link", "Set a link from a shape to a URL or a page in Frame0.", { shapeId: z.string().describe("ID of the shape to set link"), linkType: z .enum(["none", "web", "page", "action:backward"]) .describe("Type of the link to set."), url: z .string() .optional() .describe("URL to set. Required if linkType is 'web'."), pageId: z .string() .optional() .describe("ID of the page to set. Required if linkType is 'page'."), }, async ({ shapeId, linkType, url, pageId }) => { try { await command(apiPort, "shape:set-link", { shapeId, linkProps: trimObject({ linkType, url, pageId, }), }); return response.text(`A link is assigned to shape (id: ${shapeId})`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to set link: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("export_shape_as_image", "Export shape as image in Frame0.", { shapeId: z.string().describe("ID of the shape to export"), format: z .enum(["image/png", "image/jpeg", "image/webp"]) .optional() .default("image/png") .describe("Image format to export."), }, async ({ shapeId, format }) => { try { const data = await command(apiPort, "shape:get-shape", { shapeId, }); const image = await command(apiPort, "file:export-image", { pageId: data.pageId, shapeIdArray: [shapeId], format, fillBackground: true, }); return response.image(format, image); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to export shape as image: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("add_page", "Add a new page in Frame0. The added page becomes the current page.", { name: z.string().describe("Name of the page to add."), }, async ({ name }) => { try { const pageData = await command(apiPort, "page:add", { pageProps: trimObject({ name }), }); return response.text(`Added page: ${JSON.stringify(pageData)}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to add page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("update_page", "Update a page in Frame0.", { pageId: z.string().describe("ID of the page to update."), name: z.string().describe("Name of the page."), }, async ({ pageId, name }) => { try { const updatedPageId = await command(apiPort, "page:update", { pageId, pageProps: trimObject({ name }), }); const pageData = await command(apiPort, "page:get", { pageId: updatedPageId, }); return response.text(`Updated page: ${JSON.stringify(pageData)}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to update page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("duplicate_page", "Duplicate a page in Frame0.", { pageId: z.string().describe("ID of the page to duplicate"), name: z.string().optional().describe("Name of the duplicated page."), }, async ({ pageId, name }) => { try { const duplicatedPageId = await command(apiPort, "page:duplicate", { pageId, pageProps: trimObject({ name }), }); const pageData = await command(apiPort, "page:get", { pageId: duplicatedPageId, exportShapes: true, }); return response.text(`Duplicated page data: ${JSON.stringify(pageData)}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to duplicate page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("delete_page", "Delete a page in Frame0.", { pageId: z.string().describe("ID of the page to delete"), }, async ({ pageId }) => { try { await command(apiPort, "page:delete", { pageId, }); return response.text(`Deleted page ID is${pageId}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to delete page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("get_current_page_id", "Get ID of the current page in Frame0.", {}, async () => { try { const pageId = await command(apiPort, "page:get-current-page"); return response.text(`Current page ID is ${pageId},`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to get current page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("set_current_page_by_id", "Set current page by ID in Frame0.", { pageId: z.string().describe("ID of the page to set as current page."), }, async ({ pageId }) => { try { await command(apiPort, "page:set-current-page", { pageId, }); return response.text(`Current page ID is ${pageId}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to set current page: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("get_page", "Get page data in Frame0.", { pageId: z .string() .optional() .describe("ID of the page to get data. If not provided, the current page data is returned."), exportShapes: z .boolean() .optional() .default(true) .describe("Export shapes data included in the page."), }, async ({ pageId, exportShapes }) => { try { const pageData = await command(apiPort, "page:get", { pageId, exportShapes, }); return response.text(`The page data: ${JSON.stringify(filterPage(pageData))}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to get page data: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("get_all_pages", "Get all pages data in Frame0.", { exportShapes: z .boolean() .optional() .default(false) .describe("Export shapes data included in the page data."), }, async ({ exportShapes }) => { try { const docData = await command(apiPort, "doc:get", { exportPages: true, exportShapes, }); if (!Array.isArray(docData.children)) docData.children = []; const pageArray = docData.children.map((page) => filterPage(page)); return response.text(`The all pages data: ${JSON.stringify(pageArray)}`); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to get page data: ${error instanceof Error ? error.message : String(error)}`); } }); server.tool("export_page_as_image", "Export page as image in Frame0.", { pageId: z .string() .optional() .describe("ID of the page to export. If not provided, the current page is used."), format: z .enum(["image/png", "image/jpeg", "image/webp"]) .optional() .default("image/png") .describe("Image format to export."), }, async ({ pageId, format }) => { try { const image = await command(apiPort, "file:export-image", { pageId, format, fillBackground: true, }); return response.image(format, image); } catch (error) { console.error(error); return response.error(JsonRpcErrorCode.InternalError, `Failed to export page as image: ${error instanceof Error ? error.message : String(error)}`); } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Frame0 MCP Server running on stdio"); } main().catch((error) => { console.error("Error starting server:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/niklauslee/frame0-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server