@open-pencil/mcp 0.7.0 → 0.8.0
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/dist/server.js +29 -1
- package/package.json +2 -2
- package/src/server.ts +45 -6
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { ALL_TOOLS, FigmaAPI, parseFigFile, computeAllLayouts, SceneGraph } from '@open-pencil/core';
|
|
5
|
+
import { ALL_TOOLS, FigmaAPI, parseFigFile, computeAllLayouts, SceneGraph, renderNodesToImage, SkiaRenderer } from '@open-pencil/core';
|
|
6
6
|
function ok(data) {
|
|
7
7
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
8
8
|
}
|
|
@@ -30,6 +30,16 @@ function paramToZod(param) {
|
|
|
30
30
|
const schema = typeMap[param.type]();
|
|
31
31
|
return param.required ? schema : schema.optional();
|
|
32
32
|
}
|
|
33
|
+
let ckInstance = null;
|
|
34
|
+
async function getCanvasKit() {
|
|
35
|
+
if (ckInstance)
|
|
36
|
+
return ckInstance;
|
|
37
|
+
const CanvasKitInit = (await import('canvaskit-wasm/full')).default;
|
|
38
|
+
const ckPath = import.meta.resolve('canvaskit-wasm/full');
|
|
39
|
+
const binDir = new URL('.', ckPath).pathname;
|
|
40
|
+
ckInstance = await CanvasKitInit({ locateFile: (file) => binDir + file });
|
|
41
|
+
return ckInstance;
|
|
42
|
+
}
|
|
33
43
|
export function createServer(version, options = {}) {
|
|
34
44
|
const server = new McpServer({ name: 'open-pencil', version });
|
|
35
45
|
const enableEval = options.enableEval ?? true;
|
|
@@ -44,6 +54,19 @@ export function createServer(version, options = {}) {
|
|
|
44
54
|
const api = new FigmaAPI(graph);
|
|
45
55
|
if (currentPageId)
|
|
46
56
|
api.currentPage = api.wrapNode(currentPageId);
|
|
57
|
+
api.exportImage = async (nodeIds, opts) => {
|
|
58
|
+
const ck = await getCanvasKit();
|
|
59
|
+
const surface = ck.MakeSurface(1, 1);
|
|
60
|
+
const renderer = new SkiaRenderer(ck, surface);
|
|
61
|
+
renderer.viewportWidth = 1;
|
|
62
|
+
renderer.viewportHeight = 1;
|
|
63
|
+
renderer.dpr = 1;
|
|
64
|
+
const pageId = currentPageId ?? graph.getPages()[0]?.id ?? graph.rootId;
|
|
65
|
+
return renderNodesToImage(ck, renderer, graph, pageId, nodeIds, {
|
|
66
|
+
scale: opts.scale ?? 1,
|
|
67
|
+
format: (opts.format ?? 'PNG')
|
|
68
|
+
});
|
|
69
|
+
};
|
|
47
70
|
return api;
|
|
48
71
|
}
|
|
49
72
|
function resolveAndCheckPath(filePath) {
|
|
@@ -65,6 +88,11 @@ export function createServer(version, options = {}) {
|
|
|
65
88
|
server.registerTool(def.name, { description: def.description, inputSchema: z.object(shape) }, async (args) => {
|
|
66
89
|
try {
|
|
67
90
|
const result = await def.execute(makeFigma(), args);
|
|
91
|
+
if (result && typeof result === 'object' && 'base64' in result && 'mimeType' in result) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'image', data: result.base64, mimeType: result.mimeType }]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
68
96
|
return ok(result);
|
|
69
97
|
}
|
|
70
98
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-pencil/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@hono/node-server": "^1.19.9",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
31
|
-
"@open-pencil/core": "
|
|
31
|
+
"@open-pencil/core": "^0.8.0",
|
|
32
32
|
"canvaskit-wasm": "^0.40.0",
|
|
33
33
|
"hono": "^4.11.4",
|
|
34
34
|
"zod": "^3.25.0"
|
package/src/server.ts
CHANGED
|
@@ -4,11 +4,21 @@ import { isAbsolute, relative, resolve } from 'node:path'
|
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import {
|
|
8
|
+
ALL_TOOLS,
|
|
9
|
+
FigmaAPI,
|
|
10
|
+
parseFigFile,
|
|
11
|
+
computeAllLayouts,
|
|
12
|
+
SceneGraph,
|
|
13
|
+
renderNodesToImage,
|
|
14
|
+
SkiaRenderer
|
|
15
|
+
} from '@open-pencil/core'
|
|
16
|
+
|
|
17
|
+
import type { ToolDef, ParamDef, ParamType, ExportFormat } from '@open-pencil/core'
|
|
18
|
+
import type { CanvasKit } from 'canvaskit-wasm'
|
|
19
|
+
|
|
20
|
+
type McpContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }
|
|
21
|
+
type McpResult = { content: McpContent[]; isError?: boolean }
|
|
12
22
|
export interface CreateServerOptions {
|
|
13
23
|
enableEval?: boolean
|
|
14
24
|
fileRoot?: string | null
|
|
@@ -44,6 +54,17 @@ function paramToZod(param: ParamDef): z.ZodTypeAny {
|
|
|
44
54
|
return param.required ? schema : schema.optional()
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
let ckInstance: CanvasKit | null = null
|
|
58
|
+
|
|
59
|
+
async function getCanvasKit(): Promise<CanvasKit> {
|
|
60
|
+
if (ckInstance) return ckInstance
|
|
61
|
+
const CanvasKitInit = (await import('canvaskit-wasm/full')).default
|
|
62
|
+
const ckPath = import.meta.resolve('canvaskit-wasm/full')
|
|
63
|
+
const binDir = new URL('.', ckPath).pathname
|
|
64
|
+
ckInstance = await CanvasKitInit({ locateFile: (file: string) => binDir + file })
|
|
65
|
+
return ckInstance
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
export function createServer(version: string, options: CreateServerOptions = {}): McpServer {
|
|
48
69
|
const server = new McpServer({ name: 'open-pencil', version })
|
|
49
70
|
const enableEval = options.enableEval ?? true
|
|
@@ -58,6 +79,19 @@ export function createServer(version: string, options: CreateServerOptions = {})
|
|
|
58
79
|
if (!graph) throw new Error('No document loaded. Use open_file or new_document first.')
|
|
59
80
|
const api = new FigmaAPI(graph)
|
|
60
81
|
if (currentPageId) api.currentPage = api.wrapNode(currentPageId)
|
|
82
|
+
api.exportImage = async (nodeIds, opts) => {
|
|
83
|
+
const ck = await getCanvasKit()
|
|
84
|
+
const surface = ck.MakeSurface(1, 1)!
|
|
85
|
+
const renderer = new SkiaRenderer(ck, surface)
|
|
86
|
+
renderer.viewportWidth = 1
|
|
87
|
+
renderer.viewportHeight = 1
|
|
88
|
+
renderer.dpr = 1
|
|
89
|
+
const pageId = currentPageId ?? graph!.getPages()[0]?.id ?? graph!.rootId
|
|
90
|
+
return renderNodesToImage(ck, renderer, graph!, pageId, nodeIds, {
|
|
91
|
+
scale: opts.scale ?? 1,
|
|
92
|
+
format: (opts.format ?? 'PNG') as ExportFormat
|
|
93
|
+
})
|
|
94
|
+
}
|
|
61
95
|
return api
|
|
62
96
|
}
|
|
63
97
|
|
|
@@ -80,7 +114,12 @@ export function createServer(version: string, options: CreateServerOptions = {})
|
|
|
80
114
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic schema from ToolDef params
|
|
81
115
|
server.registerTool(def.name, { description: def.description, inputSchema: z.object(shape) } as any, async (args: any) => {
|
|
82
116
|
try {
|
|
83
|
-
const result = await def.execute(makeFigma(), args
|
|
117
|
+
const result = await def.execute(makeFigma(), args)
|
|
118
|
+
if (result && typeof result === 'object' && 'base64' in result && 'mimeType' in result) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'image' as const, data: result.base64 as string, mimeType: result.mimeType as string }]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
84
123
|
return ok(result)
|
|
85
124
|
} catch (e) {
|
|
86
125
|
return fail(e)
|