@open-pencil/mcp 0.8.0 → 0.11.1

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 CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "@open-pencil/mcp",
3
- "version": "0.8.0",
3
+ "version": "0.11.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "main": "./src/server.ts",
6
+ "main": "./dist/server.js",
7
7
  "bin": {
8
- "openpencil-mcp": "./dist/index.js",
9
- "openpencil-mcp-http": "./dist/http.js"
8
+ "openpencil-mcp": "./dist/index.js"
10
9
  },
11
10
  "files": [
12
- "src",
13
11
  "dist"
14
12
  ],
15
13
  "scripts": {
@@ -28,12 +26,13 @@
28
26
  "dependencies": {
29
27
  "@hono/node-server": "^1.19.9",
30
28
  "@modelcontextprotocol/sdk": "^1.25.2",
31
- "@open-pencil/core": "^0.8.0",
32
- "canvaskit-wasm": "^0.40.0",
29
+ "@open-pencil/core": "^0.11.1",
33
30
  "hono": "^4.11.4",
34
- "zod": "^3.25.0"
31
+ "zod": "^4.3.6",
32
+ "ws": "^8.19.0"
35
33
  },
36
34
  "devDependencies": {
37
- "@types/node": "^22.0.0"
35
+ "@types/node": "^22.0.0",
36
+ "@types/ws": "^8.18.1"
38
37
  }
39
38
  }
package/dist/http.js DELETED
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env node
2
- import { randomUUID } from 'node:crypto';
3
- import { readFile } from 'node:fs/promises';
4
- import { resolve } from 'node:path';
5
- import { Hono } from 'hono';
6
- import { cors } from 'hono/cors';
7
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
8
- import { createServer } from './server.js';
9
- const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8'));
10
- const port = parseInt(process.env.PORT ?? '3100', 10);
11
- const host = process.env.HOST ?? '127.0.0.1';
12
- const authToken = process.env.OPENPENCIL_MCP_AUTH_TOKEN?.trim() || null;
13
- const corsOrigin = process.env.OPENPENCIL_MCP_CORS_ORIGIN?.trim() || null;
14
- const fileRoot = resolve(process.env.OPENPENCIL_MCP_ROOT ?? process.cwd());
15
- const sessions = new Map();
16
- async function getOrCreateSession(sessionId) {
17
- if (sessionId && sessions.has(sessionId)) {
18
- return sessions.get(sessionId);
19
- }
20
- const id = sessionId ?? randomUUID();
21
- const server = createServer(pkg.version, {
22
- enableEval: false,
23
- fileRoot
24
- });
25
- const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => id });
26
- await server.connect(transport);
27
- sessions.set(id, { server, transport });
28
- return { server, transport };
29
- }
30
- const app = new Hono();
31
- if (corsOrigin) {
32
- app.use('*', cors({
33
- origin: corsOrigin,
34
- allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
35
- allowHeaders: [
36
- 'Content-Type',
37
- 'Authorization',
38
- 'x-mcp-token',
39
- 'mcp-session-id',
40
- 'Last-Event-ID',
41
- 'mcp-protocol-version'
42
- ],
43
- exposeHeaders: ['mcp-session-id', 'mcp-protocol-version']
44
- }));
45
- }
46
- app.get('/health', (c) => c.json({
47
- status: 'ok',
48
- version: pkg.version,
49
- authRequired: Boolean(authToken),
50
- evalEnabled: false,
51
- fileRoot
52
- }));
53
- app.all('/mcp', async (c) => {
54
- if (authToken) {
55
- const authHeader = c.req.header('authorization');
56
- const token = authHeader?.startsWith('Bearer ')
57
- ? authHeader.slice('Bearer '.length)
58
- : c.req.header('x-mcp-token');
59
- if (token !== authToken) {
60
- return c.json({ error: 'Unauthorized' }, 401);
61
- }
62
- }
63
- const sessionId = c.req.header('mcp-session-id') ?? undefined;
64
- const { transport } = await getOrCreateSession(sessionId);
65
- return transport.handleRequest(c.req.raw);
66
- });
67
- const isBun = typeof globalThis.Bun !== 'undefined';
68
- if (isBun) {
69
- Bun.serve({ fetch: app.fetch, port, hostname: host });
70
- }
71
- else {
72
- const { serve } = await import('@hono/node-server');
73
- serve({ fetch: app.fetch, port, hostname: host });
74
- }
75
- console.log(`OpenPencil MCP server v${pkg.version}`);
76
- console.log(` Health: http://${host}:${port}/health`);
77
- console.log(` MCP: http://${host}:${port}/mcp`);
78
- console.log(` Auth: ${authToken ? 'required (OPENPENCIL_MCP_AUTH_TOKEN)' : 'disabled'}`);
79
- console.log(` CORS: ${corsOrigin ?? 'disabled'}`);
80
- console.log(` Eval: disabled`);
81
- console.log(` Root: ${fileRoot}`);
package/dist/index.js DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { createServer } from './server.js';
5
- const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8'));
6
- const server = createServer(pkg.version);
7
- const transport = new StdioServerTransport();
8
- await server.connect(transport);
package/dist/server.js DELETED
@@ -1,158 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises';
2
- import { isAbsolute, relative, resolve } from 'node:path';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { z } from 'zod';
5
- import { ALL_TOOLS, FigmaAPI, parseFigFile, computeAllLayouts, SceneGraph, renderNodesToImage, SkiaRenderer } from '@open-pencil/core';
6
- function ok(data) {
7
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
8
- }
9
- function fail(e) {
10
- const msg = e instanceof Error ? e.message : String(e);
11
- return { content: [{ type: 'text', text: JSON.stringify({ error: msg }) }], isError: true };
12
- }
13
- function paramToZod(param) {
14
- const typeMap = {
15
- string: () => param.enum
16
- ? z.enum(param.enum).describe(param.description)
17
- : z.string().describe(param.description),
18
- number: () => {
19
- let s = z.number();
20
- if (param.min !== undefined)
21
- s = s.min(param.min);
22
- if (param.max !== undefined)
23
- s = s.max(param.max);
24
- return s.describe(param.description);
25
- },
26
- boolean: () => z.boolean().describe(param.description),
27
- color: () => z.string().describe(param.description),
28
- 'string[]': () => z.array(z.string()).min(1).describe(param.description)
29
- };
30
- const schema = typeMap[param.type]();
31
- return param.required ? schema : schema.optional();
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
- }
43
- export function createServer(version, options = {}) {
44
- const server = new McpServer({ name: 'open-pencil', version });
45
- const enableEval = options.enableEval ?? true;
46
- const fileRoot = options.fileRoot === null || options.fileRoot === undefined
47
- ? null
48
- : resolve(options.fileRoot);
49
- let graph = null;
50
- let currentPageId = null;
51
- function makeFigma() {
52
- if (!graph)
53
- throw new Error('No document loaded. Use open_file or new_document first.');
54
- const api = new FigmaAPI(graph);
55
- if (currentPageId)
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
- };
70
- return api;
71
- }
72
- function resolveAndCheckPath(filePath) {
73
- const resolved = resolve(filePath);
74
- if (!fileRoot)
75
- return resolved;
76
- const rel = relative(fileRoot, resolved);
77
- if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) {
78
- return resolved;
79
- }
80
- throw new Error(`Path "${filePath}" is outside allowed root "${fileRoot}"`);
81
- }
82
- function registerTool(def) {
83
- const shape = {};
84
- for (const [key, param] of Object.entries(def.params)) {
85
- shape[key] = paramToZod(param);
86
- }
87
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic schema from ToolDef params
88
- server.registerTool(def.name, { description: def.description, inputSchema: z.object(shape) }, async (args) => {
89
- try {
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
- }
96
- return ok(result);
97
- }
98
- catch (e) {
99
- return fail(e);
100
- }
101
- });
102
- }
103
- const register = server.registerTool.bind(server);
104
- register('open_file', {
105
- description: 'Open a .fig file for editing. Must be called before using other tools.',
106
- inputSchema: z.object({ path: z.string().describe('Absolute path to a .fig file') })
107
- }, async ({ path: filePath }) => {
108
- try {
109
- const path = resolveAndCheckPath(filePath);
110
- const buf = await readFile(path);
111
- graph = await parseFigFile(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
112
- computeAllLayouts(graph);
113
- const pages = graph.getPages();
114
- currentPageId = pages[0]?.id ?? null;
115
- return ok({ pages: pages.map((p) => ({ id: p.id, name: p.name })), currentPage: pages[0]?.name });
116
- }
117
- catch (e) {
118
- return fail(e);
119
- }
120
- });
121
- register('save_file', {
122
- description: 'Save the current document to a .fig file.',
123
- inputSchema: z.object({ path: z.string().describe('Absolute path to save the .fig file') })
124
- }, async ({ path: filePath }) => {
125
- try {
126
- if (!graph)
127
- throw new Error('No document loaded');
128
- const { exportFigFile } = await import('@open-pencil/core');
129
- const path = resolveAndCheckPath(filePath);
130
- const data = await exportFigFile(graph);
131
- await writeFile(path, new Uint8Array(data));
132
- return ok({ saved: path, bytes: data.byteLength });
133
- }
134
- catch (e) {
135
- return fail(e);
136
- }
137
- });
138
- register('new_document', {
139
- description: 'Create a new empty document with a blank page.',
140
- inputSchema: z.object({})
141
- }, async () => {
142
- try {
143
- graph = new SceneGraph();
144
- const pages = graph.getPages();
145
- currentPageId = pages[0]?.id ?? null;
146
- return ok({ page: pages[0]?.name, id: currentPageId });
147
- }
148
- catch (e) {
149
- return fail(e);
150
- }
151
- });
152
- for (const tool of ALL_TOOLS) {
153
- if (!enableEval && tool.name === 'eval')
154
- continue;
155
- registerTool(tool);
156
- }
157
- return server;
158
- }
package/src/http.ts DELETED
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
- import { randomUUID } from 'node:crypto'
3
- import { readFile } from 'node:fs/promises'
4
- import { resolve } from 'node:path'
5
-
6
- import { Hono } from 'hono'
7
- import { cors } from 'hono/cors'
8
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'
9
-
10
- import { createServer } from './server.js'
11
-
12
- const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8'))
13
- const port = parseInt(process.env.PORT ?? '3100', 10)
14
- const host = process.env.HOST ?? '127.0.0.1'
15
- const authToken = process.env.OPENPENCIL_MCP_AUTH_TOKEN?.trim() || null
16
- const corsOrigin = process.env.OPENPENCIL_MCP_CORS_ORIGIN?.trim() || null
17
- const fileRoot = resolve(process.env.OPENPENCIL_MCP_ROOT ?? process.cwd())
18
-
19
- const sessions = new Map<string, { server: ReturnType<typeof createServer>; transport: WebStandardStreamableHTTPServerTransport }>()
20
-
21
- async function getOrCreateSession(sessionId?: string) {
22
- if (sessionId && sessions.has(sessionId)) {
23
- return sessions.get(sessionId)!
24
- }
25
-
26
- const id = sessionId ?? randomUUID()
27
- const server = createServer(pkg.version, {
28
- enableEval: false,
29
- fileRoot
30
- })
31
- const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => id })
32
- await server.connect(transport)
33
- sessions.set(id, { server, transport })
34
- return { server, transport }
35
- }
36
-
37
- const app = new Hono()
38
-
39
- if (corsOrigin) {
40
- app.use('*', cors({
41
- origin: corsOrigin,
42
- allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
43
- allowHeaders: [
44
- 'Content-Type',
45
- 'Authorization',
46
- 'x-mcp-token',
47
- 'mcp-session-id',
48
- 'Last-Event-ID',
49
- 'mcp-protocol-version'
50
- ],
51
- exposeHeaders: ['mcp-session-id', 'mcp-protocol-version']
52
- }))
53
- }
54
-
55
- app.get('/health', (c) =>
56
- c.json({
57
- status: 'ok',
58
- version: pkg.version,
59
- authRequired: Boolean(authToken),
60
- evalEnabled: false,
61
- fileRoot
62
- })
63
- )
64
-
65
- app.all('/mcp', async (c) => {
66
- if (authToken) {
67
- const authHeader = c.req.header('authorization')
68
- const token =
69
- authHeader?.startsWith('Bearer ')
70
- ? authHeader.slice('Bearer '.length)
71
- : c.req.header('x-mcp-token')
72
- if (token !== authToken) {
73
- return c.json({ error: 'Unauthorized' }, 401)
74
- }
75
- }
76
-
77
- const sessionId = c.req.header('mcp-session-id') ?? undefined
78
- const { transport } = await getOrCreateSession(sessionId)
79
- return transport.handleRequest(c.req.raw)
80
- })
81
-
82
- const isBun = typeof globalThis.Bun !== 'undefined'
83
-
84
- if (isBun) {
85
- Bun.serve({ fetch: app.fetch, port, hostname: host })
86
- } else {
87
- const { serve } = await import('@hono/node-server')
88
- serve({ fetch: app.fetch, port, hostname: host })
89
- }
90
-
91
- console.log(`OpenPencil MCP server v${pkg.version}`)
92
- console.log(` Health: http://${host}:${port}/health`)
93
- console.log(` MCP: http://${host}:${port}/mcp`)
94
- console.log(` Auth: ${authToken ? 'required (OPENPENCIL_MCP_AUTH_TOKEN)' : 'disabled'}`)
95
- console.log(` CORS: ${corsOrigin ?? 'disabled'}`)
96
- console.log(` Eval: disabled`)
97
- console.log(` Root: ${fileRoot}`)
package/src/index.ts DELETED
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises'
3
-
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
5
-
6
- import { createServer } from './server.js'
7
-
8
- const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf-8'))
9
- const server = createServer(pkg.version)
10
-
11
- const transport = new StdioServerTransport()
12
- await server.connect(transport)
package/src/server.ts DELETED
@@ -1,196 +0,0 @@
1
- import { readFile, writeFile } from 'node:fs/promises'
2
- import { isAbsolute, relative, resolve } from 'node:path'
3
-
4
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
5
- import { z } from 'zod'
6
-
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 }
22
- export interface CreateServerOptions {
23
- enableEval?: boolean
24
- fileRoot?: string | null
25
- }
26
-
27
- function ok(data: unknown): McpResult {
28
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
29
- }
30
-
31
- function fail(e: unknown): McpResult {
32
- const msg = e instanceof Error ? e.message : String(e)
33
- return { content: [{ type: 'text', text: JSON.stringify({ error: msg }) }], isError: true }
34
- }
35
-
36
- function paramToZod(param: ParamDef): z.ZodTypeAny {
37
- const typeMap: Record<ParamType, () => z.ZodTypeAny> = {
38
- string: () =>
39
- param.enum
40
- ? z.enum(param.enum as [string, ...string[]]).describe(param.description)
41
- : z.string().describe(param.description),
42
- number: () => {
43
- let s = z.number()
44
- if (param.min !== undefined) s = s.min(param.min)
45
- if (param.max !== undefined) s = s.max(param.max)
46
- return s.describe(param.description)
47
- },
48
- boolean: () => z.boolean().describe(param.description),
49
- color: () => z.string().describe(param.description),
50
- 'string[]': () => z.array(z.string()).min(1).describe(param.description)
51
- }
52
-
53
- const schema = typeMap[param.type]()
54
- return param.required ? schema : schema.optional()
55
- }
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
-
68
- export function createServer(version: string, options: CreateServerOptions = {}): McpServer {
69
- const server = new McpServer({ name: 'open-pencil', version })
70
- const enableEval = options.enableEval ?? true
71
- const fileRoot = options.fileRoot === null || options.fileRoot === undefined
72
- ? null
73
- : resolve(options.fileRoot)
74
-
75
- let graph: SceneGraph | null = null
76
- let currentPageId: string | null = null
77
-
78
- function makeFigma(): FigmaAPI {
79
- if (!graph) throw new Error('No document loaded. Use open_file or new_document first.')
80
- const api = new FigmaAPI(graph)
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
- }
95
- return api
96
- }
97
-
98
- function resolveAndCheckPath(filePath: string): string {
99
- const resolved = resolve(filePath)
100
- if (!fileRoot) return resolved
101
- const rel = relative(fileRoot, resolved)
102
- if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) {
103
- return resolved
104
- }
105
- throw new Error(`Path "${filePath}" is outside allowed root "${fileRoot}"`)
106
- }
107
-
108
- function registerTool(def: ToolDef) {
109
- const shape: Record<string, z.ZodTypeAny> = {}
110
- for (const [key, param] of Object.entries(def.params)) {
111
- shape[key] = paramToZod(param)
112
- }
113
-
114
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- dynamic schema from ToolDef params
115
- server.registerTool(def.name, { description: def.description, inputSchema: z.object(shape) } as any, async (args: any) => {
116
- try {
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
- }
123
- return ok(result)
124
- } catch (e) {
125
- return fail(e)
126
- }
127
- })
128
- }
129
-
130
- const register = server.registerTool.bind(server) as (...args: unknown[]) => void
131
- register(
132
- 'open_file',
133
- {
134
- description: 'Open a .fig file for editing. Must be called before using other tools.',
135
- inputSchema: z.object({ path: z.string().describe('Absolute path to a .fig file') })
136
- },
137
- async ({ path: filePath }: { path: string }) => {
138
- try {
139
- const path = resolveAndCheckPath(filePath)
140
- const buf = await readFile(path)
141
- graph = await parseFigFile(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength))
142
- computeAllLayouts(graph)
143
- const pages = graph.getPages()
144
- currentPageId = pages[0]?.id ?? null
145
- return ok({ pages: pages.map((p) => ({ id: p.id, name: p.name })), currentPage: pages[0]?.name })
146
- } catch (e) {
147
- return fail(e)
148
- }
149
- }
150
- )
151
-
152
- register(
153
- 'save_file',
154
- {
155
- description: 'Save the current document to a .fig file.',
156
- inputSchema: z.object({ path: z.string().describe('Absolute path to save the .fig file') })
157
- },
158
- async ({ path: filePath }: { path: string }) => {
159
- try {
160
- if (!graph) throw new Error('No document loaded')
161
- const { exportFigFile } = await import('@open-pencil/core')
162
- const path = resolveAndCheckPath(filePath)
163
- const data = await exportFigFile(graph)
164
- await writeFile(path, new Uint8Array(data))
165
- return ok({ saved: path, bytes: data.byteLength })
166
- } catch (e) {
167
- return fail(e)
168
- }
169
- }
170
- )
171
-
172
- register(
173
- 'new_document',
174
- {
175
- description: 'Create a new empty document with a blank page.',
176
- inputSchema: z.object({})
177
- },
178
- async () => {
179
- try {
180
- graph = new SceneGraph()
181
- const pages = graph.getPages()
182
- currentPageId = pages[0]?.id ?? null
183
- return ok({ page: pages[0]?.name, id: currentPageId })
184
- } catch (e) {
185
- return fail(e)
186
- }
187
- }
188
- )
189
-
190
- for (const tool of ALL_TOOLS) {
191
- if (!enableEval && tool.name === 'eval') continue
192
- registerTool(tool)
193
- }
194
-
195
- return server
196
- }