@milaboratories/pl-mcp-server 9.0.0 → 10.0.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.cjs CHANGED
@@ -12,7 +12,6 @@ let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk
12
12
  let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
13
13
  let node_http = require("node:http");
14
14
  let node_crypto = require("node:crypto");
15
- let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
16
15
  //#region src/server.ts
17
16
  var PlMcpServer = class {
18
17
  ml;
@@ -150,19 +149,19 @@ var PlMcpServer = class {
150
149
  if (!this.ml) throw new Error("Not connected to a server. Use connect_to_server first.");
151
150
  return this.ml;
152
151
  }
153
- /** Resolves a project from the list by its projectId (resourceIdToString format). */
152
+ /** Resolves a project from the list by its projectId. */
154
153
  async resolveProject(projectId) {
155
154
  const ml = this.requireMl();
156
155
  await ml.projectList.refreshState();
157
- const entry = (await ml.projectList.awaitStableValue()).find((p) => (0, _milaboratories_pl_middle_layer.resourceIdToString)(p.rid) === projectId);
156
+ const entry = (await ml.projectList.awaitStableValue()).find((p) => p.id === projectId);
158
157
  if (!entry) throw new Error(`Project ${projectId} not found`);
159
158
  return entry;
160
159
  }
161
- /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
160
+ /** Gets an opened project by projectId. */
162
161
  async getOpenedProject(projectId) {
163
162
  const ml = this.requireMl();
164
163
  const entry = await this.resolveProject(projectId);
165
- return ml.getOpenedProject(entry.rid);
164
+ return ml.getOpenedProject(entry.id);
166
165
  }
167
166
  };
168
167
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":["StreamableHTTPServerTransport","McpServer"],"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { type MiddleLayer, resourceIdToString } from \"@milaboratories/pl-middle-layer\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport type { ToolContext } from \"./tools/types\";\nimport { registerPingTool } from \"./tools/ping\";\nimport { registerConnectionTools } from \"./tools/connection\";\nimport { registerProjectTools } from \"./tools/projects\";\nimport { registerBlockTools } from \"./tools/blocks\";\nimport { registerBlockStateTools } from \"./tools/block-state\";\nimport { registerAwaitTools } from \"./tools/await\";\nimport { registerLogTools } from \"./tools/logs\";\nimport { registerDataQueryTools } from \"./tools/data-query\";\nimport { registerScreenshotTool } from \"./tools/screenshot\";\nimport { registerUIInteractionTools } from \"./tools/ui-interaction\";\n\nexport interface PlMcpServerCallbacks {\n onProjectCreated?: (projectId: string) => void | Promise<void>;\n onProjectOpened?: (projectId: string) => void | Promise<void>;\n onProjectClosed?: (projectId: string) => void | Promise<void>;\n onProjectDeleted?: (projectId: string) => void | Promise<void>;\n /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */\n captureScreenshot?: () => Promise<string>;\n /** Send an input event to the application window. */\n sendInputEvent?: (event: unknown) => Promise<void>;\n /**\n * Execute JavaScript in a renderer and return the result.\n * With no `target`, runs in the topmost webContents (usually the main app).\n * With `target`, runs in the specified block's webview (where `window.platforma` is\n * exposed) — the block must already be loaded (e.g. via `select_block`).\n */\n executeJavaScript?: (\n code: string,\n target?: { projectId: string; blockId: string },\n ) => Promise<unknown>;\n /** List available blocks from all configured registries. */\n listAvailableBlocks?: (query?: string) => Promise<unknown[]>;\n /** Navigate the desktop UI to show a specific block. */\n selectBlock?: (projectId: string, blockId: string) => Promise<void>;\n /** Read recent lines from the application log. */\n readAppLog?: (lines: number, search?: string) => Promise<string>;\n /** List saved server connections. */\n listConnections?: () => Promise<ServerConnection[]>;\n /** Connect to a server. */\n connectToServer?: (\n addr: string,\n login: string,\n password?: string,\n ) => Promise<{ status: string; message: string }>;\n /** Get current connection status. */\n getConnectionStatus?: () => Promise<{\n connected: boolean;\n type?: string;\n addr?: string;\n login?: string;\n }>;\n /** Disconnect from current server. */\n disconnect?: () => Promise<void>;\n /** Get detailed info about a specific block package. */\n getBlockInfo?: (\n registryUrl: string,\n organization: string,\n name: string,\n version: string,\n ) => Promise<unknown>;\n}\n\nexport interface ServerConnection {\n addr: string;\n login: string;\n coreVersion?: string;\n lastConnected?: string;\n}\n\n/** Branded type for the MCP server URL secret path segment. */\nexport type McpSecret = Branded<string, \"McpSecret\">;\n\nexport interface PlMcpServerOptions {\n /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */\n middleLayer?: MiddleLayer;\n /** Port to listen on. */\n port: number;\n /** Secret path segment for URL security. */\n secret: McpSecret;\n /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */\n callbacks?: PlMcpServerCallbacks;\n}\n\nexport class PlMcpServer {\n private ml: MiddleLayer | null;\n private port: number;\n private readonly secret: McpSecret;\n private readonly callbacks: PlMcpServerCallbacks;\n private httpServer: Server | undefined;\n private readonly transports = new Map<string, StreamableHTTPServerTransport>();\n\n constructor(options: PlMcpServerOptions) {\n this.ml = options.middleLayer ?? null;\n this.port = options.port;\n this.secret = options.secret;\n this.callbacks = options.callbacks ?? {};\n }\n\n /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */\n setMiddleLayer(ml: MiddleLayer | null | undefined) {\n this.ml = ml ?? null;\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;\n }\n\n async start(): Promise<void> {\n if (this.httpServer) {\n throw new Error(\"MCP server is already running\");\n }\n\n const expectedPath = `/${this.secret}/mcp`;\n\n this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n // Origin validation — only allow localhost\n if (req.headers.origin !== undefined) {\n try {\n const origin = new URL(req.headers.origin);\n if (origin.hostname !== \"localhost\" && origin.hostname !== \"127.0.0.1\") {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n } catch {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n }\n\n // Secret path check\n if (req.url !== expectedPath) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not Found\" }));\n return;\n }\n\n // Route to existing session transport\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (sessionId && this.transports.has(sessionId)) {\n await this.transports.get(sessionId)!.handleRequest(req, res);\n return;\n }\n\n // New session — create transport and connect MCP server\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n transport.onclose = () => {\n const sid = transport.sessionId;\n if (sid) this.transports.delete(sid);\n };\n\n const server = this.createMcpServer();\n await server.connect(transport);\n await transport.handleRequest(req, res);\n\n const sid = transport.sessionId;\n if (sid) this.transports.set(sid, transport);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n }\n }\n });\n\n const maxRetries = 10;\n const requestHandler = this.httpServer.listeners(\"request\")[0] as (\n req: IncomingMessage,\n res: ServerResponse,\n ) => void;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const server = this.httpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.listen(this.port, \"127.0.0.1\", () => resolve());\n server.once(\"error\", reject);\n });\n // Read back the actual port (important when port is 0)\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n this.port = addr.port;\n }\n return;\n } catch (err: unknown) {\n if (\n err instanceof Error &&\n \"code\" in err &&\n err.code === \"EADDRINUSE\" &&\n attempt < maxRetries - 1\n ) {\n server.removeAllListeners();\n this.httpServer = createServer(requestHandler);\n this.port++;\n continue;\n }\n throw err;\n }\n }\n }\n\n async stop(): Promise<void> {\n for (const transport of this.transports.values()) {\n await transport.close();\n }\n this.transports.clear();\n\n const server = this.httpServer;\n if (server) {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n this.httpServer = undefined;\n }\n }\n\n private createMcpServer(): McpServer {\n const sessionId = randomUUID().slice(0, 8);\n const server = new McpServer({ name: \"pl\", version: \"0.1.0\" }, { capabilities: { tools: {} } });\n this.registerTools(server, sessionId);\n return server;\n }\n\n private registerTools(server: McpServer, sessionId: string): void {\n const authorId = `mcp-${sessionId}`;\n let localVersion = 0;\n const ctx: ToolContext = {\n getMl: () => this.ml,\n requireMl: () => this.requireMl(),\n resolveProject: (id) => this.resolveProject(id),\n getOpenedProject: (id) => this.getOpenedProject(id),\n callbacks: this.callbacks,\n getAuthorMarker: () => ({ authorId, localVersion: ++localVersion }),\n };\n registerPingTool(server, ctx);\n registerConnectionTools(server, ctx);\n registerProjectTools(server, ctx);\n registerBlockTools(server, ctx);\n registerBlockStateTools(server, ctx);\n registerAwaitTools(server, ctx);\n registerLogTools(server, ctx);\n registerDataQueryTools(server, ctx);\n registerScreenshotTool(server, ctx);\n registerUIInteractionTools(server, ctx);\n }\n\n /** Throws if MiddleLayer is not available (not connected to a server). */\n private requireMl(): MiddleLayer {\n if (!this.ml) throw new Error(\"Not connected to a server. Use connect_to_server first.\");\n return this.ml;\n }\n\n /** Resolves a project from the list by its projectId (resourceIdToString format). */\n private async resolveProject(projectId: string) {\n const ml = this.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n const entry = projects.find((p) => resourceIdToString(p.rid) === projectId);\n if (!entry) throw new Error(`Project ${projectId} not found`);\n return entry;\n }\n\n /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */\n private async getOpenedProject(projectId: string) {\n const ml = this.requireMl();\n const entry = await this.resolveProject(projectId);\n return ml.getOpenedProject(entry.rid);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.cjs","names":["StreamableHTTPServerTransport","McpServer"],"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport type { MiddleLayer } from \"@milaboratories/pl-middle-layer\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport type { ToolContext } from \"./tools/types\";\nimport { registerPingTool } from \"./tools/ping\";\nimport { registerConnectionTools } from \"./tools/connection\";\nimport { registerProjectTools } from \"./tools/projects\";\nimport { registerBlockTools } from \"./tools/blocks\";\nimport { registerBlockStateTools } from \"./tools/block-state\";\nimport { registerAwaitTools } from \"./tools/await\";\nimport { registerLogTools } from \"./tools/logs\";\nimport { registerDataQueryTools } from \"./tools/data-query\";\nimport { registerScreenshotTool } from \"./tools/screenshot\";\nimport { registerUIInteractionTools } from \"./tools/ui-interaction\";\n\nexport interface PlMcpServerCallbacks {\n onProjectCreated?: (projectId: string) => void | Promise<void>;\n onProjectOpened?: (projectId: string) => void | Promise<void>;\n onProjectClosed?: (projectId: string) => void | Promise<void>;\n onProjectDeleted?: (projectId: string) => void | Promise<void>;\n /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */\n captureScreenshot?: () => Promise<string>;\n /** Send an input event to the application window. */\n sendInputEvent?: (event: unknown) => Promise<void>;\n /**\n * Execute JavaScript in a renderer and return the result.\n * With no `target`, runs in the topmost webContents (usually the main app).\n * With `target`, runs in the specified block's webview (where `window.platforma` is\n * exposed) — the block must already be loaded (e.g. via `select_block`).\n */\n executeJavaScript?: (\n code: string,\n target?: { projectId: string; blockId: string },\n ) => Promise<unknown>;\n /** List available blocks from all configured registries. */\n listAvailableBlocks?: (query?: string) => Promise<unknown[]>;\n /** Navigate the desktop UI to show a specific block. */\n selectBlock?: (projectId: string, blockId: string) => Promise<void>;\n /** Read recent lines from the application log. */\n readAppLog?: (lines: number, search?: string) => Promise<string>;\n /** List saved server connections. */\n listConnections?: () => Promise<ServerConnection[]>;\n /** Connect to a server. */\n connectToServer?: (\n addr: string,\n login: string,\n password?: string,\n ) => Promise<{ status: string; message: string }>;\n /** Get current connection status. */\n getConnectionStatus?: () => Promise<{\n connected: boolean;\n type?: string;\n addr?: string;\n login?: string;\n }>;\n /** Disconnect from current server. */\n disconnect?: () => Promise<void>;\n /** Get detailed info about a specific block package. */\n getBlockInfo?: (\n registryUrl: string,\n organization: string,\n name: string,\n version: string,\n ) => Promise<unknown>;\n}\n\nexport interface ServerConnection {\n addr: string;\n login: string;\n coreVersion?: string;\n lastConnected?: string;\n}\n\n/** Branded type for the MCP server URL secret path segment. */\nexport type McpSecret = Branded<string, \"McpSecret\">;\n\nexport interface PlMcpServerOptions {\n /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */\n middleLayer?: MiddleLayer;\n /** Port to listen on. */\n port: number;\n /** Secret path segment for URL security. */\n secret: McpSecret;\n /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */\n callbacks?: PlMcpServerCallbacks;\n}\n\nexport class PlMcpServer {\n private ml: MiddleLayer | null;\n private port: number;\n private readonly secret: McpSecret;\n private readonly callbacks: PlMcpServerCallbacks;\n private httpServer: Server | undefined;\n private readonly transports = new Map<string, StreamableHTTPServerTransport>();\n\n constructor(options: PlMcpServerOptions) {\n this.ml = options.middleLayer ?? null;\n this.port = options.port;\n this.secret = options.secret;\n this.callbacks = options.callbacks ?? {};\n }\n\n /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */\n setMiddleLayer(ml: MiddleLayer | null | undefined) {\n this.ml = ml ?? null;\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;\n }\n\n async start(): Promise<void> {\n if (this.httpServer) {\n throw new Error(\"MCP server is already running\");\n }\n\n const expectedPath = `/${this.secret}/mcp`;\n\n this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n // Origin validation — only allow localhost\n if (req.headers.origin !== undefined) {\n try {\n const origin = new URL(req.headers.origin);\n if (origin.hostname !== \"localhost\" && origin.hostname !== \"127.0.0.1\") {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n } catch {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n }\n\n // Secret path check\n if (req.url !== expectedPath) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not Found\" }));\n return;\n }\n\n // Route to existing session transport\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (sessionId && this.transports.has(sessionId)) {\n await this.transports.get(sessionId)!.handleRequest(req, res);\n return;\n }\n\n // New session — create transport and connect MCP server\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n transport.onclose = () => {\n const sid = transport.sessionId;\n if (sid) this.transports.delete(sid);\n };\n\n const server = this.createMcpServer();\n await server.connect(transport);\n await transport.handleRequest(req, res);\n\n const sid = transport.sessionId;\n if (sid) this.transports.set(sid, transport);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n }\n }\n });\n\n const maxRetries = 10;\n const requestHandler = this.httpServer.listeners(\"request\")[0] as (\n req: IncomingMessage,\n res: ServerResponse,\n ) => void;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const server = this.httpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.listen(this.port, \"127.0.0.1\", () => resolve());\n server.once(\"error\", reject);\n });\n // Read back the actual port (important when port is 0)\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n this.port = addr.port;\n }\n return;\n } catch (err: unknown) {\n if (\n err instanceof Error &&\n \"code\" in err &&\n err.code === \"EADDRINUSE\" &&\n attempt < maxRetries - 1\n ) {\n server.removeAllListeners();\n this.httpServer = createServer(requestHandler);\n this.port++;\n continue;\n }\n throw err;\n }\n }\n }\n\n async stop(): Promise<void> {\n for (const transport of this.transports.values()) {\n await transport.close();\n }\n this.transports.clear();\n\n const server = this.httpServer;\n if (server) {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n this.httpServer = undefined;\n }\n }\n\n private createMcpServer(): McpServer {\n const sessionId = randomUUID().slice(0, 8);\n const server = new McpServer({ name: \"pl\", version: \"0.1.0\" }, { capabilities: { tools: {} } });\n this.registerTools(server, sessionId);\n return server;\n }\n\n private registerTools(server: McpServer, sessionId: string): void {\n const authorId = `mcp-${sessionId}`;\n let localVersion = 0;\n const ctx: ToolContext = {\n getMl: () => this.ml,\n requireMl: () => this.requireMl(),\n resolveProject: (id) => this.resolveProject(id),\n getOpenedProject: (id) => this.getOpenedProject(id),\n callbacks: this.callbacks,\n getAuthorMarker: () => ({ authorId, localVersion: ++localVersion }),\n };\n registerPingTool(server, ctx);\n registerConnectionTools(server, ctx);\n registerProjectTools(server, ctx);\n registerBlockTools(server, ctx);\n registerBlockStateTools(server, ctx);\n registerAwaitTools(server, ctx);\n registerLogTools(server, ctx);\n registerDataQueryTools(server, ctx);\n registerScreenshotTool(server, ctx);\n registerUIInteractionTools(server, ctx);\n }\n\n /** Throws if MiddleLayer is not available (not connected to a server). */\n private requireMl(): MiddleLayer {\n if (!this.ml) throw new Error(\"Not connected to a server. Use connect_to_server first.\");\n return this.ml;\n }\n\n /** Resolves a project from the list by its projectId. */\n private async resolveProject(projectId: string) {\n const ml = this.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n const entry = projects.find((p) => p.id === projectId);\n if (!entry) throw new Error(`Project ${projectId} not found`);\n return entry;\n }\n\n /** Gets an opened project by projectId. */\n private async getOpenedProject(projectId: string) {\n const ml = this.requireMl();\n const entry = await this.resolveProject(projectId);\n return ml.getOpenedProject(entry.id);\n }\n}\n"],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -81,9 +81,9 @@ declare class PlMcpServer {
81
81
  private registerTools;
82
82
  /** Throws if MiddleLayer is not available (not connected to a server). */
83
83
  private requireMl;
84
- /** Resolves a project from the list by its projectId (resourceIdToString format). */
84
+ /** Resolves a project from the list by its projectId. */
85
85
  private resolveProject;
86
- /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
86
+ /** Gets an opened project by projectId. */
87
87
  private getOpenedProject;
88
88
  }
89
89
  //#endregion
package/dist/server.js CHANGED
@@ -12,7 +12,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
13
  import { createServer } from "node:http";
14
14
  import { randomUUID } from "node:crypto";
15
- import { resourceIdToString } from "@milaboratories/pl-middle-layer";
16
15
  //#region src/server.ts
17
16
  var PlMcpServer = class {
18
17
  ml;
@@ -150,19 +149,19 @@ var PlMcpServer = class {
150
149
  if (!this.ml) throw new Error("Not connected to a server. Use connect_to_server first.");
151
150
  return this.ml;
152
151
  }
153
- /** Resolves a project from the list by its projectId (resourceIdToString format). */
152
+ /** Resolves a project from the list by its projectId. */
154
153
  async resolveProject(projectId) {
155
154
  const ml = this.requireMl();
156
155
  await ml.projectList.refreshState();
157
- const entry = (await ml.projectList.awaitStableValue()).find((p) => resourceIdToString(p.rid) === projectId);
156
+ const entry = (await ml.projectList.awaitStableValue()).find((p) => p.id === projectId);
158
157
  if (!entry) throw new Error(`Project ${projectId} not found`);
159
158
  return entry;
160
159
  }
161
- /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
160
+ /** Gets an opened project by projectId. */
162
161
  async getOpenedProject(projectId) {
163
162
  const ml = this.requireMl();
164
163
  const entry = await this.resolveProject(projectId);
165
- return ml.getOpenedProject(entry.rid);
164
+ return ml.getOpenedProject(entry.id);
166
165
  }
167
166
  };
168
167
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { type MiddleLayer, resourceIdToString } from \"@milaboratories/pl-middle-layer\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport type { ToolContext } from \"./tools/types\";\nimport { registerPingTool } from \"./tools/ping\";\nimport { registerConnectionTools } from \"./tools/connection\";\nimport { registerProjectTools } from \"./tools/projects\";\nimport { registerBlockTools } from \"./tools/blocks\";\nimport { registerBlockStateTools } from \"./tools/block-state\";\nimport { registerAwaitTools } from \"./tools/await\";\nimport { registerLogTools } from \"./tools/logs\";\nimport { registerDataQueryTools } from \"./tools/data-query\";\nimport { registerScreenshotTool } from \"./tools/screenshot\";\nimport { registerUIInteractionTools } from \"./tools/ui-interaction\";\n\nexport interface PlMcpServerCallbacks {\n onProjectCreated?: (projectId: string) => void | Promise<void>;\n onProjectOpened?: (projectId: string) => void | Promise<void>;\n onProjectClosed?: (projectId: string) => void | Promise<void>;\n onProjectDeleted?: (projectId: string) => void | Promise<void>;\n /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */\n captureScreenshot?: () => Promise<string>;\n /** Send an input event to the application window. */\n sendInputEvent?: (event: unknown) => Promise<void>;\n /**\n * Execute JavaScript in a renderer and return the result.\n * With no `target`, runs in the topmost webContents (usually the main app).\n * With `target`, runs in the specified block's webview (where `window.platforma` is\n * exposed) — the block must already be loaded (e.g. via `select_block`).\n */\n executeJavaScript?: (\n code: string,\n target?: { projectId: string; blockId: string },\n ) => Promise<unknown>;\n /** List available blocks from all configured registries. */\n listAvailableBlocks?: (query?: string) => Promise<unknown[]>;\n /** Navigate the desktop UI to show a specific block. */\n selectBlock?: (projectId: string, blockId: string) => Promise<void>;\n /** Read recent lines from the application log. */\n readAppLog?: (lines: number, search?: string) => Promise<string>;\n /** List saved server connections. */\n listConnections?: () => Promise<ServerConnection[]>;\n /** Connect to a server. */\n connectToServer?: (\n addr: string,\n login: string,\n password?: string,\n ) => Promise<{ status: string; message: string }>;\n /** Get current connection status. */\n getConnectionStatus?: () => Promise<{\n connected: boolean;\n type?: string;\n addr?: string;\n login?: string;\n }>;\n /** Disconnect from current server. */\n disconnect?: () => Promise<void>;\n /** Get detailed info about a specific block package. */\n getBlockInfo?: (\n registryUrl: string,\n organization: string,\n name: string,\n version: string,\n ) => Promise<unknown>;\n}\n\nexport interface ServerConnection {\n addr: string;\n login: string;\n coreVersion?: string;\n lastConnected?: string;\n}\n\n/** Branded type for the MCP server URL secret path segment. */\nexport type McpSecret = Branded<string, \"McpSecret\">;\n\nexport interface PlMcpServerOptions {\n /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */\n middleLayer?: MiddleLayer;\n /** Port to listen on. */\n port: number;\n /** Secret path segment for URL security. */\n secret: McpSecret;\n /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */\n callbacks?: PlMcpServerCallbacks;\n}\n\nexport class PlMcpServer {\n private ml: MiddleLayer | null;\n private port: number;\n private readonly secret: McpSecret;\n private readonly callbacks: PlMcpServerCallbacks;\n private httpServer: Server | undefined;\n private readonly transports = new Map<string, StreamableHTTPServerTransport>();\n\n constructor(options: PlMcpServerOptions) {\n this.ml = options.middleLayer ?? null;\n this.port = options.port;\n this.secret = options.secret;\n this.callbacks = options.callbacks ?? {};\n }\n\n /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */\n setMiddleLayer(ml: MiddleLayer | null | undefined) {\n this.ml = ml ?? null;\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;\n }\n\n async start(): Promise<void> {\n if (this.httpServer) {\n throw new Error(\"MCP server is already running\");\n }\n\n const expectedPath = `/${this.secret}/mcp`;\n\n this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n // Origin validation — only allow localhost\n if (req.headers.origin !== undefined) {\n try {\n const origin = new URL(req.headers.origin);\n if (origin.hostname !== \"localhost\" && origin.hostname !== \"127.0.0.1\") {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n } catch {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n }\n\n // Secret path check\n if (req.url !== expectedPath) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not Found\" }));\n return;\n }\n\n // Route to existing session transport\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (sessionId && this.transports.has(sessionId)) {\n await this.transports.get(sessionId)!.handleRequest(req, res);\n return;\n }\n\n // New session — create transport and connect MCP server\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n transport.onclose = () => {\n const sid = transport.sessionId;\n if (sid) this.transports.delete(sid);\n };\n\n const server = this.createMcpServer();\n await server.connect(transport);\n await transport.handleRequest(req, res);\n\n const sid = transport.sessionId;\n if (sid) this.transports.set(sid, transport);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n }\n }\n });\n\n const maxRetries = 10;\n const requestHandler = this.httpServer.listeners(\"request\")[0] as (\n req: IncomingMessage,\n res: ServerResponse,\n ) => void;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const server = this.httpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.listen(this.port, \"127.0.0.1\", () => resolve());\n server.once(\"error\", reject);\n });\n // Read back the actual port (important when port is 0)\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n this.port = addr.port;\n }\n return;\n } catch (err: unknown) {\n if (\n err instanceof Error &&\n \"code\" in err &&\n err.code === \"EADDRINUSE\" &&\n attempt < maxRetries - 1\n ) {\n server.removeAllListeners();\n this.httpServer = createServer(requestHandler);\n this.port++;\n continue;\n }\n throw err;\n }\n }\n }\n\n async stop(): Promise<void> {\n for (const transport of this.transports.values()) {\n await transport.close();\n }\n this.transports.clear();\n\n const server = this.httpServer;\n if (server) {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n this.httpServer = undefined;\n }\n }\n\n private createMcpServer(): McpServer {\n const sessionId = randomUUID().slice(0, 8);\n const server = new McpServer({ name: \"pl\", version: \"0.1.0\" }, { capabilities: { tools: {} } });\n this.registerTools(server, sessionId);\n return server;\n }\n\n private registerTools(server: McpServer, sessionId: string): void {\n const authorId = `mcp-${sessionId}`;\n let localVersion = 0;\n const ctx: ToolContext = {\n getMl: () => this.ml,\n requireMl: () => this.requireMl(),\n resolveProject: (id) => this.resolveProject(id),\n getOpenedProject: (id) => this.getOpenedProject(id),\n callbacks: this.callbacks,\n getAuthorMarker: () => ({ authorId, localVersion: ++localVersion }),\n };\n registerPingTool(server, ctx);\n registerConnectionTools(server, ctx);\n registerProjectTools(server, ctx);\n registerBlockTools(server, ctx);\n registerBlockStateTools(server, ctx);\n registerAwaitTools(server, ctx);\n registerLogTools(server, ctx);\n registerDataQueryTools(server, ctx);\n registerScreenshotTool(server, ctx);\n registerUIInteractionTools(server, ctx);\n }\n\n /** Throws if MiddleLayer is not available (not connected to a server). */\n private requireMl(): MiddleLayer {\n if (!this.ml) throw new Error(\"Not connected to a server. Use connect_to_server first.\");\n return this.ml;\n }\n\n /** Resolves a project from the list by its projectId (resourceIdToString format). */\n private async resolveProject(projectId: string) {\n const ml = this.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n const entry = projects.find((p) => resourceIdToString(p.rid) === projectId);\n if (!entry) throw new Error(`Project ${projectId} not found`);\n return entry;\n }\n\n /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */\n private async getOpenedProject(projectId: string) {\n const ml = this.requireMl();\n const entry = await this.resolveProject(projectId);\n return ml.getOpenedProject(entry.rid);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport type { MiddleLayer } from \"@milaboratories/pl-middle-layer\";\nimport type { Branded } from \"@milaboratories/pl-model-common\";\nimport type { ToolContext } from \"./tools/types\";\nimport { registerPingTool } from \"./tools/ping\";\nimport { registerConnectionTools } from \"./tools/connection\";\nimport { registerProjectTools } from \"./tools/projects\";\nimport { registerBlockTools } from \"./tools/blocks\";\nimport { registerBlockStateTools } from \"./tools/block-state\";\nimport { registerAwaitTools } from \"./tools/await\";\nimport { registerLogTools } from \"./tools/logs\";\nimport { registerDataQueryTools } from \"./tools/data-query\";\nimport { registerScreenshotTool } from \"./tools/screenshot\";\nimport { registerUIInteractionTools } from \"./tools/ui-interaction\";\n\nexport interface PlMcpServerCallbacks {\n onProjectCreated?: (projectId: string) => void | Promise<void>;\n onProjectOpened?: (projectId: string) => void | Promise<void>;\n onProjectClosed?: (projectId: string) => void | Promise<void>;\n onProjectDeleted?: (projectId: string) => void | Promise<void>;\n /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */\n captureScreenshot?: () => Promise<string>;\n /** Send an input event to the application window. */\n sendInputEvent?: (event: unknown) => Promise<void>;\n /**\n * Execute JavaScript in a renderer and return the result.\n * With no `target`, runs in the topmost webContents (usually the main app).\n * With `target`, runs in the specified block's webview (where `window.platforma` is\n * exposed) — the block must already be loaded (e.g. via `select_block`).\n */\n executeJavaScript?: (\n code: string,\n target?: { projectId: string; blockId: string },\n ) => Promise<unknown>;\n /** List available blocks from all configured registries. */\n listAvailableBlocks?: (query?: string) => Promise<unknown[]>;\n /** Navigate the desktop UI to show a specific block. */\n selectBlock?: (projectId: string, blockId: string) => Promise<void>;\n /** Read recent lines from the application log. */\n readAppLog?: (lines: number, search?: string) => Promise<string>;\n /** List saved server connections. */\n listConnections?: () => Promise<ServerConnection[]>;\n /** Connect to a server. */\n connectToServer?: (\n addr: string,\n login: string,\n password?: string,\n ) => Promise<{ status: string; message: string }>;\n /** Get current connection status. */\n getConnectionStatus?: () => Promise<{\n connected: boolean;\n type?: string;\n addr?: string;\n login?: string;\n }>;\n /** Disconnect from current server. */\n disconnect?: () => Promise<void>;\n /** Get detailed info about a specific block package. */\n getBlockInfo?: (\n registryUrl: string,\n organization: string,\n name: string,\n version: string,\n ) => Promise<unknown>;\n}\n\nexport interface ServerConnection {\n addr: string;\n login: string;\n coreVersion?: string;\n lastConnected?: string;\n}\n\n/** Branded type for the MCP server URL secret path segment. */\nexport type McpSecret = Branded<string, \"McpSecret\">;\n\nexport interface PlMcpServerOptions {\n /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */\n middleLayer?: MiddleLayer;\n /** Port to listen on. */\n port: number;\n /** Secret path segment for URL security. */\n secret: McpSecret;\n /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */\n callbacks?: PlMcpServerCallbacks;\n}\n\nexport class PlMcpServer {\n private ml: MiddleLayer | null;\n private port: number;\n private readonly secret: McpSecret;\n private readonly callbacks: PlMcpServerCallbacks;\n private httpServer: Server | undefined;\n private readonly transports = new Map<string, StreamableHTTPServerTransport>();\n\n constructor(options: PlMcpServerOptions) {\n this.ml = options.middleLayer ?? null;\n this.port = options.port;\n this.secret = options.secret;\n this.callbacks = options.callbacks ?? {};\n }\n\n /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */\n setMiddleLayer(ml: MiddleLayer | null | undefined) {\n this.ml = ml ?? null;\n }\n\n get url(): string {\n return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;\n }\n\n async start(): Promise<void> {\n if (this.httpServer) {\n throw new Error(\"MCP server is already running\");\n }\n\n const expectedPath = `/${this.secret}/mcp`;\n\n this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n // Origin validation — only allow localhost\n if (req.headers.origin !== undefined) {\n try {\n const origin = new URL(req.headers.origin);\n if (origin.hostname !== \"localhost\" && origin.hostname !== \"127.0.0.1\") {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n } catch {\n res.writeHead(403, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Forbidden\" }));\n return;\n }\n }\n\n // Secret path check\n if (req.url !== expectedPath) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not Found\" }));\n return;\n }\n\n // Route to existing session transport\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (sessionId && this.transports.has(sessionId)) {\n await this.transports.get(sessionId)!.handleRequest(req, res);\n return;\n }\n\n // New session — create transport and connect MCP server\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n });\n transport.onclose = () => {\n const sid = transport.sessionId;\n if (sid) this.transports.delete(sid);\n };\n\n const server = this.createMcpServer();\n await server.connect(transport);\n await transport.handleRequest(req, res);\n\n const sid = transport.sessionId;\n if (sid) this.transports.set(sid, transport);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n }\n }\n });\n\n const maxRetries = 10;\n const requestHandler = this.httpServer.listeners(\"request\")[0] as (\n req: IncomingMessage,\n res: ServerResponse,\n ) => void;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n const server = this.httpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.listen(this.port, \"127.0.0.1\", () => resolve());\n server.once(\"error\", reject);\n });\n // Read back the actual port (important when port is 0)\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n this.port = addr.port;\n }\n return;\n } catch (err: unknown) {\n if (\n err instanceof Error &&\n \"code\" in err &&\n err.code === \"EADDRINUSE\" &&\n attempt < maxRetries - 1\n ) {\n server.removeAllListeners();\n this.httpServer = createServer(requestHandler);\n this.port++;\n continue;\n }\n throw err;\n }\n }\n }\n\n async stop(): Promise<void> {\n for (const transport of this.transports.values()) {\n await transport.close();\n }\n this.transports.clear();\n\n const server = this.httpServer;\n if (server) {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n });\n this.httpServer = undefined;\n }\n }\n\n private createMcpServer(): McpServer {\n const sessionId = randomUUID().slice(0, 8);\n const server = new McpServer({ name: \"pl\", version: \"0.1.0\" }, { capabilities: { tools: {} } });\n this.registerTools(server, sessionId);\n return server;\n }\n\n private registerTools(server: McpServer, sessionId: string): void {\n const authorId = `mcp-${sessionId}`;\n let localVersion = 0;\n const ctx: ToolContext = {\n getMl: () => this.ml,\n requireMl: () => this.requireMl(),\n resolveProject: (id) => this.resolveProject(id),\n getOpenedProject: (id) => this.getOpenedProject(id),\n callbacks: this.callbacks,\n getAuthorMarker: () => ({ authorId, localVersion: ++localVersion }),\n };\n registerPingTool(server, ctx);\n registerConnectionTools(server, ctx);\n registerProjectTools(server, ctx);\n registerBlockTools(server, ctx);\n registerBlockStateTools(server, ctx);\n registerAwaitTools(server, ctx);\n registerLogTools(server, ctx);\n registerDataQueryTools(server, ctx);\n registerScreenshotTool(server, ctx);\n registerUIInteractionTools(server, ctx);\n }\n\n /** Throws if MiddleLayer is not available (not connected to a server). */\n private requireMl(): MiddleLayer {\n if (!this.ml) throw new Error(\"Not connected to a server. Use connect_to_server first.\");\n return this.ml;\n }\n\n /** Resolves a project from the list by its projectId. */\n private async resolveProject(projectId: string) {\n const ml = this.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n const entry = projects.find((p) => p.id === projectId);\n if (!entry) throw new Error(`Project ${projectId} not found`);\n return entry;\n }\n\n /** Gets an opened project by projectId. */\n private async getOpenedProject(projectId: string) {\n const ml = this.requireMl();\n const entry = await this.resolveProject(projectId);\n return ml.getOpenedProject(entry.id);\n }\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  const require_types = require("./types.cjs");
2
2
  const require_tokens = require("./tokens.cjs");
3
3
  const require_sandbox = require("./sandbox.cjs");
4
- let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
5
4
  let zod = require("zod");
6
5
  let _platforma_sdk_model = require("@platforma-sdk/model");
6
+ let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
7
7
  //#region src/tools/await.ts
8
8
  function registerAwaitTools(server, ctx) {
9
9
  server.registerTool("await_block_done", {
@@ -1,9 +1,9 @@
1
1
  import { errorResult, textResult } from "./types.js";
2
2
  import { summarizeOutputs } from "./tokens.js";
3
3
  import { safeEval } from "./sandbox.js";
4
- import { isTimeoutError } from "@milaboratories/pl-middle-layer";
5
4
  import { z } from "zod";
6
5
  import { deriveDataFromStorage } from "@platforma-sdk/model";
6
+ import { isTimeoutError } from "@milaboratories/pl-middle-layer";
7
7
  //#region src/tools/await.ts
8
8
  function registerAwaitTools(server, ctx) {
9
9
  server.registerTool("await_block_done", {
@@ -1,5 +1,4 @@
1
1
  const require_types = require("./types.cjs");
2
- let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
3
2
  let zod = require("zod");
4
3
  //#region src/tools/projects.ts
5
4
  function registerProjectTools(server, ctx) {
@@ -7,7 +6,7 @@ function registerProjectTools(server, ctx) {
7
6
  const ml = ctx.requireMl();
8
7
  await ml.projectList.refreshState();
9
8
  return require_types.textResult((await ml.projectList.awaitStableValue()).map((p) => ({
10
- projectId: (0, _milaboratories_pl_middle_layer.resourceIdToString)(p.rid),
9
+ projectId: p.id,
11
10
  label: p.meta.label,
12
11
  opened: p.opened,
13
12
  created: p.created.toISOString(),
@@ -18,7 +17,7 @@ function registerProjectTools(server, ctx) {
18
17
  description: "Create a new project",
19
18
  inputSchema: { label: zod.z.string().describe("Project name") }
20
19
  }, async ({ label }) => {
21
- const projectId = (0, _milaboratories_pl_middle_layer.resourceIdToString)(await ctx.requireMl().createProject({ label }));
20
+ const projectId = await ctx.requireMl().createProject({ label });
22
21
  await ctx.callbacks.onProjectCreated?.(projectId);
23
22
  return require_types.textResult({ projectId });
24
23
  });
@@ -27,7 +26,7 @@ function registerProjectTools(server, ctx) {
27
26
  inputSchema: { projectId: zod.z.string().describe("Project ID from list_projects or create_project") }
28
27
  }, async ({ projectId }) => {
29
28
  const entry = await ctx.resolveProject(projectId);
30
- await ctx.requireMl().openProject(entry.rid);
29
+ await ctx.requireMl().openProject(entry.id);
31
30
  await ctx.callbacks.onProjectOpened?.(projectId);
32
31
  return require_types.textResult({ ok: true });
33
32
  });
@@ -36,7 +35,7 @@ function registerProjectTools(server, ctx) {
36
35
  inputSchema: { projectId: zod.z.string().describe("Project ID") }
37
36
  }, async ({ projectId }) => {
38
37
  const entry = await ctx.resolveProject(projectId);
39
- await ctx.requireMl().closeProject(entry.rid);
38
+ await ctx.requireMl().closeProject(entry.id);
40
39
  await ctx.callbacks.onProjectClosed?.(projectId);
41
40
  return require_types.textResult({ ok: true });
42
41
  });
@@ -1 +1 @@
1
- {"version":3,"file":"projects.cjs","names":["textResult","z"],"sources":["../../src/tools/projects.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { resourceIdToString } from \"@milaboratories/pl-middle-layer\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerProjectTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"list_projects\",\n { description: \"List all projects with their IDs, labels, and status\" },\n async () => {\n const ml = ctx.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n return textResult(\n projects.map((p) => ({\n projectId: resourceIdToString(p.rid),\n label: p.meta.label,\n opened: p.opened,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n },\n );\n\n server.registerTool(\n \"create_project\",\n {\n description: \"Create a new project\",\n inputSchema: { label: z.string().describe(\"Project name\") },\n },\n async ({ label }) => {\n const rid = await ctx.requireMl().createProject({ label });\n const projectId = resourceIdToString(rid);\n await ctx.callbacks.onProjectCreated?.(projectId);\n return textResult({ projectId });\n },\n );\n\n server.registerTool(\n \"open_project\",\n {\n description: \"Open a project for editing. Required before working with blocks.\",\n inputSchema: {\n projectId: z.string().describe(\"Project ID from list_projects or create_project\"),\n },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().openProject(entry.rid);\n await ctx.callbacks.onProjectOpened?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"close_project\",\n {\n description: \"Close an opened project, releasing its resources\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().closeProject(entry.rid);\n await ctx.callbacks.onProjectClosed?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"delete_project\",\n {\n description: \"Delete a project permanently. The project must be closed first.\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().deleteProject(entry.id);\n await ctx.callbacks.onProjectDeleted?.(projectId);\n return textResult({ ok: true });\n },\n );\n}\n"],"mappings":";;;;AAMA,SAAgB,qBAAqB,QAAmB,KAAwB;AAC9E,QAAO,aACL,iBACA,EAAE,aAAa,wDAAwD,EACvE,YAAY;EACV,MAAM,KAAK,IAAI,WAAW;AAC1B,QAAM,GAAG,YAAY,cAAc;AAEnC,SAAOA,cAAAA,YADU,MAAM,GAAG,YAAY,kBAAkB,EAE7C,KAAK,OAAO;GACnB,YAAA,GAAA,gCAAA,oBAA8B,EAAE,IAAI;GACpC,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE;GACV,SAAS,EAAE,QAAQ,aAAa;GAChC,cAAc,EAAE,aAAa,aAAa;GAC3C,EAAE,CACJ;GAEJ;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,OAAOC,IAAAA,EAAE,QAAQ,CAAC,SAAS,eAAe,EAAE;EAC5D,EACD,OAAO,EAAE,YAAY;EAEnB,MAAM,aAAA,GAAA,gCAAA,oBADM,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,CACjB;AACzC,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAOD,cAAAA,WAAW,EAAE,WAAW,CAAC;GAEnC;AAED,QAAO,aACL,gBACA;EACE,aAAa;EACb,aAAa,EACX,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,kDAAkD,EAClF;EACF,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,YAAY,MAAM,IAAI;AAC5C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,iBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,aAAa,MAAM,IAAI;AAC7C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,cAAc,MAAM,GAAG;AAC7C,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC"}
1
+ {"version":3,"file":"projects.cjs","names":["textResult","z"],"sources":["../../src/tools/projects.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerProjectTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"list_projects\",\n { description: \"List all projects with their IDs, labels, and status\" },\n async () => {\n const ml = ctx.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n return textResult(\n projects.map((p) => ({\n projectId: p.id,\n label: p.meta.label,\n opened: p.opened,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n },\n );\n\n server.registerTool(\n \"create_project\",\n {\n description: \"Create a new project\",\n inputSchema: { label: z.string().describe(\"Project name\") },\n },\n async ({ label }) => {\n const projectId = await ctx.requireMl().createProject({ label });\n await ctx.callbacks.onProjectCreated?.(projectId);\n return textResult({ projectId });\n },\n );\n\n server.registerTool(\n \"open_project\",\n {\n description: \"Open a project for editing. Required before working with blocks.\",\n inputSchema: {\n projectId: z.string().describe(\"Project ID from list_projects or create_project\"),\n },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().openProject(entry.id);\n await ctx.callbacks.onProjectOpened?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"close_project\",\n {\n description: \"Close an opened project, releasing its resources\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().closeProject(entry.id);\n await ctx.callbacks.onProjectClosed?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"delete_project\",\n {\n description: \"Delete a project permanently. The project must be closed first.\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().deleteProject(entry.id);\n await ctx.callbacks.onProjectDeleted?.(projectId);\n return textResult({ ok: true });\n },\n );\n}\n"],"mappings":";;;AAKA,SAAgB,qBAAqB,QAAmB,KAAwB;AAC9E,QAAO,aACL,iBACA,EAAE,aAAa,wDAAwD,EACvE,YAAY;EACV,MAAM,KAAK,IAAI,WAAW;AAC1B,QAAM,GAAG,YAAY,cAAc;AAEnC,SAAOA,cAAAA,YADU,MAAM,GAAG,YAAY,kBAAkB,EAE7C,KAAK,OAAO;GACnB,WAAW,EAAE;GACb,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE;GACV,SAAS,EAAE,QAAQ,aAAa;GAChC,cAAc,EAAE,aAAa,aAAa;GAC3C,EAAE,CACJ;GAEJ;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,OAAOC,IAAAA,EAAE,QAAQ,CAAC,SAAS,eAAe,EAAE;EAC5D,EACD,OAAO,EAAE,YAAY;EACnB,MAAM,YAAY,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC;AAChE,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAOD,cAAAA,WAAW,EAAE,WAAW,CAAC;GAEnC;AAED,QAAO,aACL,gBACA;EACE,aAAa;EACb,aAAa,EACX,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,kDAAkD,EAClF;EACF,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,YAAY,MAAM,GAAG;AAC3C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,iBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,aAAa,MAAM,GAAG;AAC5C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAWC,IAAAA,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,cAAc,MAAM,GAAG;AAC7C,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAOD,cAAAA,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC"}
@@ -1,5 +1,4 @@
1
1
  import { textResult } from "./types.js";
2
- import { resourceIdToString } from "@milaboratories/pl-middle-layer";
3
2
  import { z } from "zod";
4
3
  //#region src/tools/projects.ts
5
4
  function registerProjectTools(server, ctx) {
@@ -7,7 +6,7 @@ function registerProjectTools(server, ctx) {
7
6
  const ml = ctx.requireMl();
8
7
  await ml.projectList.refreshState();
9
8
  return textResult((await ml.projectList.awaitStableValue()).map((p) => ({
10
- projectId: resourceIdToString(p.rid),
9
+ projectId: p.id,
11
10
  label: p.meta.label,
12
11
  opened: p.opened,
13
12
  created: p.created.toISOString(),
@@ -18,7 +17,7 @@ function registerProjectTools(server, ctx) {
18
17
  description: "Create a new project",
19
18
  inputSchema: { label: z.string().describe("Project name") }
20
19
  }, async ({ label }) => {
21
- const projectId = resourceIdToString(await ctx.requireMl().createProject({ label }));
20
+ const projectId = await ctx.requireMl().createProject({ label });
22
21
  await ctx.callbacks.onProjectCreated?.(projectId);
23
22
  return textResult({ projectId });
24
23
  });
@@ -27,7 +26,7 @@ function registerProjectTools(server, ctx) {
27
26
  inputSchema: { projectId: z.string().describe("Project ID from list_projects or create_project") }
28
27
  }, async ({ projectId }) => {
29
28
  const entry = await ctx.resolveProject(projectId);
30
- await ctx.requireMl().openProject(entry.rid);
29
+ await ctx.requireMl().openProject(entry.id);
31
30
  await ctx.callbacks.onProjectOpened?.(projectId);
32
31
  return textResult({ ok: true });
33
32
  });
@@ -36,7 +35,7 @@ function registerProjectTools(server, ctx) {
36
35
  inputSchema: { projectId: z.string().describe("Project ID") }
37
36
  }, async ({ projectId }) => {
38
37
  const entry = await ctx.resolveProject(projectId);
39
- await ctx.requireMl().closeProject(entry.rid);
38
+ await ctx.requireMl().closeProject(entry.id);
40
39
  await ctx.callbacks.onProjectClosed?.(projectId);
41
40
  return textResult({ ok: true });
42
41
  });
@@ -1 +1 @@
1
- {"version":3,"file":"projects.js","names":[],"sources":["../../src/tools/projects.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { resourceIdToString } from \"@milaboratories/pl-middle-layer\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerProjectTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"list_projects\",\n { description: \"List all projects with their IDs, labels, and status\" },\n async () => {\n const ml = ctx.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n return textResult(\n projects.map((p) => ({\n projectId: resourceIdToString(p.rid),\n label: p.meta.label,\n opened: p.opened,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n },\n );\n\n server.registerTool(\n \"create_project\",\n {\n description: \"Create a new project\",\n inputSchema: { label: z.string().describe(\"Project name\") },\n },\n async ({ label }) => {\n const rid = await ctx.requireMl().createProject({ label });\n const projectId = resourceIdToString(rid);\n await ctx.callbacks.onProjectCreated?.(projectId);\n return textResult({ projectId });\n },\n );\n\n server.registerTool(\n \"open_project\",\n {\n description: \"Open a project for editing. Required before working with blocks.\",\n inputSchema: {\n projectId: z.string().describe(\"Project ID from list_projects or create_project\"),\n },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().openProject(entry.rid);\n await ctx.callbacks.onProjectOpened?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"close_project\",\n {\n description: \"Close an opened project, releasing its resources\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().closeProject(entry.rid);\n await ctx.callbacks.onProjectClosed?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"delete_project\",\n {\n description: \"Delete a project permanently. The project must be closed first.\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().deleteProject(entry.id);\n await ctx.callbacks.onProjectDeleted?.(projectId);\n return textResult({ ok: true });\n },\n );\n}\n"],"mappings":";;;;AAMA,SAAgB,qBAAqB,QAAmB,KAAwB;AAC9E,QAAO,aACL,iBACA,EAAE,aAAa,wDAAwD,EACvE,YAAY;EACV,MAAM,KAAK,IAAI,WAAW;AAC1B,QAAM,GAAG,YAAY,cAAc;AAEnC,SAAO,YADU,MAAM,GAAG,YAAY,kBAAkB,EAE7C,KAAK,OAAO;GACnB,WAAW,mBAAmB,EAAE,IAAI;GACpC,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE;GACV,SAAS,EAAE,QAAQ,aAAa;GAChC,cAAc,EAAE,aAAa,aAAa;GAC3C,EAAE,CACJ;GAEJ;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,SAAS,eAAe,EAAE;EAC5D,EACD,OAAO,EAAE,YAAY;EAEnB,MAAM,YAAY,mBADN,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,CACjB;AACzC,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAO,WAAW,EAAE,WAAW,CAAC;GAEnC;AAED,QAAO,aACL,gBACA;EACE,aAAa;EACb,aAAa,EACX,WAAW,EAAE,QAAQ,CAAC,SAAS,kDAAkD,EAClF;EACF,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,YAAY,MAAM,IAAI;AAC5C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,iBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAW,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,aAAa,MAAM,IAAI;AAC7C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAW,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,cAAc,MAAM,GAAG;AAC7C,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC"}
1
+ {"version":3,"file":"projects.js","names":[],"sources":["../../src/tools/projects.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerProjectTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"list_projects\",\n { description: \"List all projects with their IDs, labels, and status\" },\n async () => {\n const ml = ctx.requireMl();\n await ml.projectList.refreshState();\n const projects = await ml.projectList.awaitStableValue();\n return textResult(\n projects.map((p) => ({\n projectId: p.id,\n label: p.meta.label,\n opened: p.opened,\n created: p.created.toISOString(),\n lastModified: p.lastModified.toISOString(),\n })),\n );\n },\n );\n\n server.registerTool(\n \"create_project\",\n {\n description: \"Create a new project\",\n inputSchema: { label: z.string().describe(\"Project name\") },\n },\n async ({ label }) => {\n const projectId = await ctx.requireMl().createProject({ label });\n await ctx.callbacks.onProjectCreated?.(projectId);\n return textResult({ projectId });\n },\n );\n\n server.registerTool(\n \"open_project\",\n {\n description: \"Open a project for editing. Required before working with blocks.\",\n inputSchema: {\n projectId: z.string().describe(\"Project ID from list_projects or create_project\"),\n },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().openProject(entry.id);\n await ctx.callbacks.onProjectOpened?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"close_project\",\n {\n description: \"Close an opened project, releasing its resources\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().closeProject(entry.id);\n await ctx.callbacks.onProjectClosed?.(projectId);\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"delete_project\",\n {\n description: \"Delete a project permanently. The project must be closed first.\",\n inputSchema: { projectId: z.string().describe(\"Project ID\") },\n },\n async ({ projectId }) => {\n const entry = await ctx.resolveProject(projectId);\n await ctx.requireMl().deleteProject(entry.id);\n await ctx.callbacks.onProjectDeleted?.(projectId);\n return textResult({ ok: true });\n },\n );\n}\n"],"mappings":";;;AAKA,SAAgB,qBAAqB,QAAmB,KAAwB;AAC9E,QAAO,aACL,iBACA,EAAE,aAAa,wDAAwD,EACvE,YAAY;EACV,MAAM,KAAK,IAAI,WAAW;AAC1B,QAAM,GAAG,YAAY,cAAc;AAEnC,SAAO,YADU,MAAM,GAAG,YAAY,kBAAkB,EAE7C,KAAK,OAAO;GACnB,WAAW,EAAE;GACb,OAAO,EAAE,KAAK;GACd,QAAQ,EAAE;GACV,SAAS,EAAE,QAAQ,aAAa;GAChC,cAAc,EAAE,aAAa,aAAa;GAC3C,EAAE,CACJ;GAEJ;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,SAAS,eAAe,EAAE;EAC5D,EACD,OAAO,EAAE,YAAY;EACnB,MAAM,YAAY,MAAM,IAAI,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC;AAChE,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAO,WAAW,EAAE,WAAW,CAAC;GAEnC;AAED,QAAO,aACL,gBACA;EACE,aAAa;EACb,aAAa,EACX,WAAW,EAAE,QAAQ,CAAC,SAAS,kDAAkD,EAClF;EACF,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,YAAY,MAAM,GAAG;AAC3C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,iBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAW,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,aAAa,MAAM,GAAG;AAC5C,QAAM,IAAI,UAAU,kBAAkB,UAAU;AAChD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC;AAED,QAAO,aACL,kBACA;EACE,aAAa;EACb,aAAa,EAAE,WAAW,EAAE,QAAQ,CAAC,SAAS,aAAa,EAAE;EAC9D,EACD,OAAO,EAAE,gBAAgB;EACvB,MAAM,QAAQ,MAAM,IAAI,eAAe,UAAU;AACjD,QAAM,IAAI,WAAW,CAAC,cAAc,MAAM,GAAG;AAC7C,QAAM,IAAI,UAAU,mBAAmB,UAAU;AACjD,SAAO,WAAW,EAAE,IAAI,MAAM,CAAC;GAElC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-mcp-server",
3
- "version": "9.0.0",
3
+ "version": "10.0.0",
4
4
  "description": "MCP server for Platforma Desktop",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -28,17 +28,17 @@
28
28
  "@vitest/coverage-istanbul": "^4.1.3",
29
29
  "typescript": "~5.9.3",
30
30
  "vitest": "^4.1.3",
31
+ "@milaboratories/pl-middle-layer": "1.59.0",
31
32
  "@milaboratories/build-configs": "2.0.0",
32
- "@milaboratories/pl-errors": "1.3.13",
33
+ "@milaboratories/pl-model-common": "1.40.0",
34
+ "@milaboratories/pl-errors": "1.4.0",
33
35
  "@milaboratories/ts-configs": "1.2.3",
34
- "@milaboratories/pl-model-common": "1.39.0",
35
- "@platforma-sdk/model": "1.73.0",
36
- "@milaboratories/pl-middle-layer": "1.58.1",
37
- "@milaboratories/ts-builder": "1.3.2"
36
+ "@platforma-sdk/model": "1.74.0",
37
+ "@milaboratories/ts-builder": "1.4.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@milaboratories/pl-middle-layer": ">=1.58.1",
41
- "@platforma-sdk/model": ">=1.73.0"
40
+ "@milaboratories/pl-middle-layer": ">=1.59.0",
41
+ "@platforma-sdk/model": ">=1.74.0"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=22.0.0"
package/src/server.ts CHANGED
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
3
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
4
4
  import { randomUUID } from "node:crypto";
5
- import { type MiddleLayer, resourceIdToString } from "@milaboratories/pl-middle-layer";
5
+ import type { MiddleLayer } from "@milaboratories/pl-middle-layer";
6
6
  import type { Branded } from "@milaboratories/pl-model-common";
7
7
  import type { ToolContext } from "./tools/types";
8
8
  import { registerPingTool } from "./tools/ping";
@@ -260,20 +260,20 @@ export class PlMcpServer {
260
260
  return this.ml;
261
261
  }
262
262
 
263
- /** Resolves a project from the list by its projectId (resourceIdToString format). */
263
+ /** Resolves a project from the list by its projectId. */
264
264
  private async resolveProject(projectId: string) {
265
265
  const ml = this.requireMl();
266
266
  await ml.projectList.refreshState();
267
267
  const projects = await ml.projectList.awaitStableValue();
268
- const entry = projects.find((p) => resourceIdToString(p.rid) === projectId);
268
+ const entry = projects.find((p) => p.id === projectId);
269
269
  if (!entry) throw new Error(`Project ${projectId} not found`);
270
270
  return entry;
271
271
  }
272
272
 
273
- /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
273
+ /** Gets an opened project by projectId. */
274
274
  private async getOpenedProject(projectId: string) {
275
275
  const ml = this.requireMl();
276
276
  const entry = await this.resolveProject(projectId);
277
- return ml.getOpenedProject(entry.rid);
277
+ return ml.getOpenedProject(entry.id);
278
278
  }
279
279
  }
@@ -1,5 +1,4 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { resourceIdToString } from "@milaboratories/pl-middle-layer";
3
2
  import { z } from "zod";
4
3
  import type { ToolContext } from "./types";
5
4
  import { textResult } from "./types";
@@ -14,7 +13,7 @@ export function registerProjectTools(server: McpServer, ctx: ToolContext): void
14
13
  const projects = await ml.projectList.awaitStableValue();
15
14
  return textResult(
16
15
  projects.map((p) => ({
17
- projectId: resourceIdToString(p.rid),
16
+ projectId: p.id,
18
17
  label: p.meta.label,
19
18
  opened: p.opened,
20
19
  created: p.created.toISOString(),
@@ -31,8 +30,7 @@ export function registerProjectTools(server: McpServer, ctx: ToolContext): void
31
30
  inputSchema: { label: z.string().describe("Project name") },
32
31
  },
33
32
  async ({ label }) => {
34
- const rid = await ctx.requireMl().createProject({ label });
35
- const projectId = resourceIdToString(rid);
33
+ const projectId = await ctx.requireMl().createProject({ label });
36
34
  await ctx.callbacks.onProjectCreated?.(projectId);
37
35
  return textResult({ projectId });
38
36
  },
@@ -48,7 +46,7 @@ export function registerProjectTools(server: McpServer, ctx: ToolContext): void
48
46
  },
49
47
  async ({ projectId }) => {
50
48
  const entry = await ctx.resolveProject(projectId);
51
- await ctx.requireMl().openProject(entry.rid);
49
+ await ctx.requireMl().openProject(entry.id);
52
50
  await ctx.callbacks.onProjectOpened?.(projectId);
53
51
  return textResult({ ok: true });
54
52
  },
@@ -62,7 +60,7 @@ export function registerProjectTools(server: McpServer, ctx: ToolContext): void
62
60
  },
63
61
  async ({ projectId }) => {
64
62
  const entry = await ctx.resolveProject(projectId);
65
- await ctx.requireMl().closeProject(entry.rid);
63
+ await ctx.requireMl().closeProject(entry.id);
66
64
  await ctx.callbacks.onProjectClosed?.(projectId);
67
65
  return textResult({ ok: true });
68
66
  },