@milaboratories/pl-mcp-server 0.2.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.
Files changed (78) hide show
  1. package/dist/index.cjs +3 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +2 -0
  4. package/dist/server.cjs +171 -0
  5. package/dist/server.cjs.map +1 -0
  6. package/dist/server.d.ts +83 -0
  7. package/dist/server.d.ts.map +1 -0
  8. package/dist/server.js +171 -0
  9. package/dist/server.js.map +1 -0
  10. package/dist/tools/await.cjs +89 -0
  11. package/dist/tools/await.cjs.map +1 -0
  12. package/dist/tools/await.js +89 -0
  13. package/dist/tools/await.js.map +1 -0
  14. package/dist/tools/block-state.cjs +71 -0
  15. package/dist/tools/block-state.cjs.map +1 -0
  16. package/dist/tools/block-state.js +71 -0
  17. package/dist/tools/block-state.js.map +1 -0
  18. package/dist/tools/blocks.cjs +123 -0
  19. package/dist/tools/blocks.cjs.map +1 -0
  20. package/dist/tools/blocks.js +123 -0
  21. package/dist/tools/blocks.js.map +1 -0
  22. package/dist/tools/connection.cjs +33 -0
  23. package/dist/tools/connection.cjs.map +1 -0
  24. package/dist/tools/connection.js +33 -0
  25. package/dist/tools/connection.js.map +1 -0
  26. package/dist/tools/data-query.cjs +186 -0
  27. package/dist/tools/data-query.cjs.map +1 -0
  28. package/dist/tools/data-query.js +186 -0
  29. package/dist/tools/data-query.js.map +1 -0
  30. package/dist/tools/logs.cjs +57 -0
  31. package/dist/tools/logs.cjs.map +1 -0
  32. package/dist/tools/logs.js +57 -0
  33. package/dist/tools/logs.js.map +1 -0
  34. package/dist/tools/ping.cjs +14 -0
  35. package/dist/tools/ping.cjs.map +1 -0
  36. package/dist/tools/ping.js +14 -0
  37. package/dist/tools/ping.js.map +1 -0
  38. package/dist/tools/projects.cjs +56 -0
  39. package/dist/tools/projects.cjs.map +1 -0
  40. package/dist/tools/projects.js +56 -0
  41. package/dist/tools/projects.js.map +1 -0
  42. package/dist/tools/sandbox.cjs +51 -0
  43. package/dist/tools/sandbox.cjs.map +1 -0
  44. package/dist/tools/sandbox.js +51 -0
  45. package/dist/tools/sandbox.js.map +1 -0
  46. package/dist/tools/screenshot.cjs +35 -0
  47. package/dist/tools/screenshot.cjs.map +1 -0
  48. package/dist/tools/screenshot.js +35 -0
  49. package/dist/tools/screenshot.js.map +1 -0
  50. package/dist/tools/tokens.cjs +82 -0
  51. package/dist/tools/tokens.cjs.map +1 -0
  52. package/dist/tools/tokens.js +82 -0
  53. package/dist/tools/tokens.js.map +1 -0
  54. package/dist/tools/types.cjs +22 -0
  55. package/dist/tools/types.cjs.map +1 -0
  56. package/dist/tools/types.js +21 -0
  57. package/dist/tools/types.js.map +1 -0
  58. package/dist/tools/ui-interaction.cjs +117 -0
  59. package/dist/tools/ui-interaction.cjs.map +1 -0
  60. package/dist/tools/ui-interaction.js +117 -0
  61. package/dist/tools/ui-interaction.js.map +1 -0
  62. package/package.json +56 -0
  63. package/src/index.ts +7 -0
  64. package/src/server.ts +271 -0
  65. package/src/tools/await.ts +151 -0
  66. package/src/tools/block-state.ts +115 -0
  67. package/src/tools/blocks.ts +222 -0
  68. package/src/tools/connection.ts +63 -0
  69. package/src/tools/data-query.ts +308 -0
  70. package/src/tools/logs.ts +97 -0
  71. package/src/tools/ping.ts +9 -0
  72. package/src/tools/projects.ts +84 -0
  73. package/src/tools/sandbox.ts +62 -0
  74. package/src/tools/screenshot.ts +48 -0
  75. package/src/tools/tokens.test.ts +239 -0
  76. package/src/tools/tokens.ts +84 -0
  77. package/src/tools/types.ts +34 -0
  78. package/src/tools/ui-interaction.ts +156 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_server = require("./server.cjs");
3
+ exports.PlMcpServer = require_server.PlMcpServer;
@@ -0,0 +1,2 @@
1
+ import { McpSecret, PlMcpServer, PlMcpServerCallbacks, PlMcpServerOptions, ServerConnection } from "./server.js";
2
+ export { type McpSecret, PlMcpServer, type PlMcpServerCallbacks, type PlMcpServerOptions, type ServerConnection };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { PlMcpServer } from "./server.js";
2
+ export { PlMcpServer };
@@ -0,0 +1,171 @@
1
+ const require_ping = require("./tools/ping.cjs");
2
+ const require_connection = require("./tools/connection.cjs");
3
+ const require_projects = require("./tools/projects.cjs");
4
+ const require_blocks = require("./tools/blocks.cjs");
5
+ const require_block_state = require("./tools/block-state.cjs");
6
+ const require_await = require("./tools/await.cjs");
7
+ const require_logs = require("./tools/logs.cjs");
8
+ const require_data_query = require("./tools/data-query.cjs");
9
+ const require_screenshot = require("./tools/screenshot.cjs");
10
+ const require_ui_interaction = require("./tools/ui-interaction.cjs");
11
+ let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
12
+ let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
13
+ let node_http = require("node:http");
14
+ let node_crypto = require("node:crypto");
15
+ let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
16
+ //#region src/server.ts
17
+ var PlMcpServer = class {
18
+ ml;
19
+ port;
20
+ secret;
21
+ callbacks;
22
+ httpServer;
23
+ transports = /* @__PURE__ */ new Map();
24
+ constructor(options) {
25
+ this.ml = options.middleLayer ?? null;
26
+ this.port = options.port;
27
+ this.secret = options.secret;
28
+ this.callbacks = options.callbacks ?? {};
29
+ }
30
+ /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */
31
+ setMiddleLayer(ml) {
32
+ this.ml = ml ?? null;
33
+ }
34
+ get url() {
35
+ return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;
36
+ }
37
+ async start() {
38
+ if (this.httpServer) throw new Error("MCP server is already running");
39
+ const expectedPath = `/${this.secret}/mcp`;
40
+ this.httpServer = (0, node_http.createServer)(async (req, res) => {
41
+ try {
42
+ if (req.headers.origin !== void 0) try {
43
+ const origin = new URL(req.headers.origin);
44
+ if (origin.hostname !== "localhost" && origin.hostname !== "127.0.0.1") {
45
+ res.writeHead(403, { "Content-Type": "application/json" });
46
+ res.end(JSON.stringify({ error: "Forbidden" }));
47
+ return;
48
+ }
49
+ } catch {
50
+ res.writeHead(403, { "Content-Type": "application/json" });
51
+ res.end(JSON.stringify({ error: "Forbidden" }));
52
+ return;
53
+ }
54
+ if (req.url !== expectedPath) {
55
+ res.writeHead(404, { "Content-Type": "application/json" });
56
+ res.end(JSON.stringify({ error: "Not Found" }));
57
+ return;
58
+ }
59
+ const sessionId = req.headers["mcp-session-id"];
60
+ if (sessionId && this.transports.has(sessionId)) {
61
+ await this.transports.get(sessionId).handleRequest(req, res);
62
+ return;
63
+ }
64
+ const transport = new _modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({ sessionIdGenerator: () => (0, node_crypto.randomUUID)() });
65
+ transport.onclose = () => {
66
+ const sid = transport.sessionId;
67
+ if (sid) this.transports.delete(sid);
68
+ };
69
+ await this.createMcpServer().connect(transport);
70
+ await transport.handleRequest(req, res);
71
+ const sid = transport.sessionId;
72
+ if (sid) this.transports.set(sid, transport);
73
+ } catch {
74
+ if (!res.headersSent) {
75
+ res.writeHead(500, { "Content-Type": "application/json" });
76
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
77
+ }
78
+ }
79
+ });
80
+ const maxRetries = 10;
81
+ const requestHandler = this.httpServer.listeners("request")[0];
82
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
83
+ const server = this.httpServer;
84
+ try {
85
+ await new Promise((resolve, reject) => {
86
+ server.listen(this.port, "127.0.0.1", () => resolve());
87
+ server.once("error", reject);
88
+ });
89
+ const addr = server.address();
90
+ if (addr && typeof addr === "object") this.port = addr.port;
91
+ return;
92
+ } catch (err) {
93
+ if (err instanceof Error && "code" in err && err.code === "EADDRINUSE" && attempt < maxRetries - 1) {
94
+ server.removeAllListeners();
95
+ this.httpServer = (0, node_http.createServer)(requestHandler);
96
+ this.port++;
97
+ continue;
98
+ }
99
+ throw err;
100
+ }
101
+ }
102
+ }
103
+ async stop() {
104
+ for (const transport of this.transports.values()) await transport.close();
105
+ this.transports.clear();
106
+ const server = this.httpServer;
107
+ if (server) {
108
+ await new Promise((resolve, reject) => {
109
+ server.close((err) => err ? reject(err) : resolve());
110
+ });
111
+ this.httpServer = void 0;
112
+ }
113
+ }
114
+ createMcpServer() {
115
+ const sessionId = (0, node_crypto.randomUUID)().slice(0, 8);
116
+ const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
117
+ name: "pl",
118
+ version: "0.1.0"
119
+ }, { capabilities: { tools: {} } });
120
+ this.registerTools(server, sessionId);
121
+ return server;
122
+ }
123
+ registerTools(server, sessionId) {
124
+ const authorId = `mcp-${sessionId}`;
125
+ let localVersion = 0;
126
+ const ctx = {
127
+ getMl: () => this.ml,
128
+ requireMl: () => this.requireMl(),
129
+ resolveProject: (id) => this.resolveProject(id),
130
+ getOpenedProject: (id) => this.getOpenedProject(id),
131
+ callbacks: this.callbacks,
132
+ getAuthorMarker: () => ({
133
+ authorId,
134
+ localVersion: ++localVersion
135
+ })
136
+ };
137
+ require_ping.registerPingTool(server, ctx);
138
+ require_connection.registerConnectionTools(server, ctx);
139
+ require_projects.registerProjectTools(server, ctx);
140
+ require_blocks.registerBlockTools(server, ctx);
141
+ require_block_state.registerBlockStateTools(server, ctx);
142
+ require_await.registerAwaitTools(server, ctx);
143
+ require_logs.registerLogTools(server, ctx);
144
+ require_data_query.registerDataQueryTools(server, ctx);
145
+ require_screenshot.registerScreenshotTool(server, ctx);
146
+ require_ui_interaction.registerUIInteractionTools(server, ctx);
147
+ }
148
+ /** Throws if MiddleLayer is not available (not connected to a server). */
149
+ requireMl() {
150
+ if (!this.ml) throw new Error("Not connected to a server. Use connect_to_server first.");
151
+ return this.ml;
152
+ }
153
+ /** Resolves a project from the list by its projectId (resourceIdToString format). */
154
+ async resolveProject(projectId) {
155
+ const ml = this.requireMl();
156
+ await ml.projectList.refreshState();
157
+ const entry = (await ml.projectList.awaitStableValue()).find((p) => (0, _milaboratories_pl_middle_layer.resourceIdToString)(p.rid) === projectId);
158
+ if (!entry) throw new Error(`Project ${projectId} not found`);
159
+ return entry;
160
+ }
161
+ /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
162
+ async getOpenedProject(projectId) {
163
+ const ml = this.requireMl();
164
+ const entry = await this.resolveProject(projectId);
165
+ return ml.getOpenedProject(entry.rid);
166
+ }
167
+ };
168
+ //#endregion
169
+ exports.PlMcpServer = PlMcpServer;
170
+
171
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +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 /** Execute JavaScript in the renderer and return the result. */\n executeJavaScript?: (code: string) => 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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"}
@@ -0,0 +1,83 @@
1
+ import { MiddleLayer } from "@milaboratories/pl-middle-layer";
2
+ import { Branded } from "@milaboratories/pl-model-common";
3
+
4
+ //#region src/server.d.ts
5
+ interface PlMcpServerCallbacks {
6
+ onProjectCreated?: (projectId: string) => void | Promise<void>;
7
+ onProjectOpened?: (projectId: string) => void | Promise<void>;
8
+ onProjectClosed?: (projectId: string) => void | Promise<void>;
9
+ onProjectDeleted?: (projectId: string) => void | Promise<void>;
10
+ /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */
11
+ captureScreenshot?: () => Promise<string>;
12
+ /** Send an input event to the application window. */
13
+ sendInputEvent?: (event: unknown) => Promise<void>;
14
+ /** Execute JavaScript in the renderer and return the result. */
15
+ executeJavaScript?: (code: string) => Promise<unknown>;
16
+ /** List available blocks from all configured registries. */
17
+ listAvailableBlocks?: (query?: string) => Promise<unknown[]>;
18
+ /** Navigate the desktop UI to show a specific block. */
19
+ selectBlock?: (projectId: string, blockId: string) => Promise<void>;
20
+ /** Read recent lines from the application log. */
21
+ readAppLog?: (lines: number, search?: string) => Promise<string>;
22
+ /** List saved server connections. */
23
+ listConnections?: () => Promise<ServerConnection[]>;
24
+ /** Connect to a server. */
25
+ connectToServer?: (addr: string, login: string, password?: string) => Promise<{
26
+ status: string;
27
+ message: string;
28
+ }>;
29
+ /** Get current connection status. */
30
+ getConnectionStatus?: () => Promise<{
31
+ connected: boolean;
32
+ type?: string;
33
+ addr?: string;
34
+ login?: string;
35
+ }>;
36
+ /** Disconnect from current server. */
37
+ disconnect?: () => Promise<void>;
38
+ /** Get detailed info about a specific block package. */
39
+ getBlockInfo?: (registryUrl: string, organization: string, name: string, version: string) => Promise<unknown>;
40
+ }
41
+ interface ServerConnection {
42
+ addr: string;
43
+ login: string;
44
+ coreVersion?: string;
45
+ lastConnected?: string;
46
+ }
47
+ /** Branded type for the MCP server URL secret path segment. */
48
+ type McpSecret = Branded<string, "McpSecret">;
49
+ interface PlMcpServerOptions {
50
+ /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */
51
+ middleLayer?: MiddleLayer;
52
+ /** Port to listen on. */
53
+ port: number;
54
+ /** Secret path segment for URL security. */
55
+ secret: McpSecret;
56
+ /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */
57
+ callbacks?: PlMcpServerCallbacks;
58
+ }
59
+ declare class PlMcpServer {
60
+ private ml;
61
+ private port;
62
+ private readonly secret;
63
+ private readonly callbacks;
64
+ private httpServer;
65
+ private readonly transports;
66
+ constructor(options: PlMcpServerOptions);
67
+ /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */
68
+ setMiddleLayer(ml: MiddleLayer | null | undefined): void;
69
+ get url(): string;
70
+ start(): Promise<void>;
71
+ stop(): Promise<void>;
72
+ private createMcpServer;
73
+ private registerTools;
74
+ /** Throws if MiddleLayer is not available (not connected to a server). */
75
+ private requireMl;
76
+ /** Resolves a project from the list by its projectId (resourceIdToString format). */
77
+ private resolveProject;
78
+ /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
79
+ private getOpenedProject;
80
+ }
81
+ //#endregion
82
+ export { McpSecret, PlMcpServer, PlMcpServerCallbacks, PlMcpServerOptions, ServerConnection };
83
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;UAkBiB,oBAAA;EACf,gBAAA,IAAoB,SAAA,oBAA6B,OAAA;EACjD,eAAA,IAAmB,SAAA,oBAA6B,OAAA;EAChD,eAAA,IAAmB,SAAA,oBAA6B,OAAA;EAChD,gBAAA,IAAoB,SAAA,oBAA6B,OAAA;EAHA;EAKjD,iBAAA,SAA0B,OAAA;EAHsB;EAKhD,cAAA,IAAkB,KAAA,cAAmB,OAAA;EAFX;EAI1B,iBAAA,IAAqB,IAAA,aAAiB,OAAA;EAAA;EAEtC,mBAAA,IAAuB,KAAA,cAAmB,OAAA;EAEY;EAAtD,WAAA,IAAe,SAAA,UAAmB,OAAA,aAAoB,OAAA;EAItB;EAFhC,UAAA,IAAc,KAAA,UAAe,MAAA,cAAoB,OAAA;EAQ5C;EANL,eAAA,SAAwB,OAAA,CAAQ,gBAAA;EAeb;EAbnB,eAAA,IACE,IAAA,UACA,KAAA,UACA,QAAA,cACG,OAAA;IAAU,MAAA;IAAgB,OAAA;EAAA;EAvBX;EAyBpB,mBAAA,SAA4B,OAAA;IAC1B,SAAA;IACA,IAAA;IACA,IAAA;IACA,KAAA;EAAA;EA3B8C;EA8BhD,UAAA,SAAmB,OAAA;EA7BC;EA+BpB,YAAA,IACE,WAAA,UACA,YAAA,UACA,IAAA,UACA,OAAA,aACG,OAAA;AAAA;AAAA,UAGU,gBAAA;EACf,IAAA;EACA,KAAA;EACA,WAAA;EACA,aAAA;AAAA;;KAIU,SAAA,GAAY,OAAA;AAAA,UAEP,kBAAA;EAzC2B;EA2C1C,WAAA,GAAc,WAAA;EAzCC;EA2Cf,IAAA;EA3CsD;EA6CtD,MAAA,EAAQ,SAAA;EA3CM;EA6Cd,SAAA,GAAY,oBAAA;AAAA;AAAA,cAGD,WAAA;EAAA,QACH,EAAA;EAAA,QACA,IAAA;EAAA,iBACS,MAAA;EAAA,iBACA,SAAA;EAAA,QACT,UAAA;EAAA,iBACS,UAAA;cAEL,OAAA,EAAS,kBAAA;EAhDN;EAwDf,cAAA,CAAe,EAAA,EAAI,WAAA;EAAA,IAIf,GAAA,CAAA;EAIE,KAAA,CAAA,GAAS,OAAA;EAiGT,IAAA,CAAA,GAAQ,OAAA;EAAA,QAeN,eAAA;EAAA,QAOA,aAAA;EAjLN;EAAA,QAyMM,SAAA;EAtMW;EAAA,QA4ML,cAAA;EAzMZ;EAAA,QAmNY,gBAAA;AAAA"}
package/dist/server.js ADDED
@@ -0,0 +1,171 @@
1
+ import { registerPingTool } from "./tools/ping.js";
2
+ import { registerConnectionTools } from "./tools/connection.js";
3
+ import { registerProjectTools } from "./tools/projects.js";
4
+ import { registerBlockTools } from "./tools/blocks.js";
5
+ import { registerBlockStateTools } from "./tools/block-state.js";
6
+ import { registerAwaitTools } from "./tools/await.js";
7
+ import { registerLogTools } from "./tools/logs.js";
8
+ import { registerDataQueryTools } from "./tools/data-query.js";
9
+ import { registerScreenshotTool } from "./tools/screenshot.js";
10
+ import { registerUIInteractionTools } from "./tools/ui-interaction.js";
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
+ import { createServer } from "node:http";
14
+ import { randomUUID } from "node:crypto";
15
+ import { resourceIdToString } from "@milaboratories/pl-middle-layer";
16
+ //#region src/server.ts
17
+ var PlMcpServer = class {
18
+ ml;
19
+ port;
20
+ secret;
21
+ callbacks;
22
+ httpServer;
23
+ transports = /* @__PURE__ */ new Map();
24
+ constructor(options) {
25
+ this.ml = options.middleLayer ?? null;
26
+ this.port = options.port;
27
+ this.secret = options.secret;
28
+ this.callbacks = options.callbacks ?? {};
29
+ }
30
+ /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */
31
+ setMiddleLayer(ml) {
32
+ this.ml = ml ?? null;
33
+ }
34
+ get url() {
35
+ return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;
36
+ }
37
+ async start() {
38
+ if (this.httpServer) throw new Error("MCP server is already running");
39
+ const expectedPath = `/${this.secret}/mcp`;
40
+ this.httpServer = createServer(async (req, res) => {
41
+ try {
42
+ if (req.headers.origin !== void 0) try {
43
+ const origin = new URL(req.headers.origin);
44
+ if (origin.hostname !== "localhost" && origin.hostname !== "127.0.0.1") {
45
+ res.writeHead(403, { "Content-Type": "application/json" });
46
+ res.end(JSON.stringify({ error: "Forbidden" }));
47
+ return;
48
+ }
49
+ } catch {
50
+ res.writeHead(403, { "Content-Type": "application/json" });
51
+ res.end(JSON.stringify({ error: "Forbidden" }));
52
+ return;
53
+ }
54
+ if (req.url !== expectedPath) {
55
+ res.writeHead(404, { "Content-Type": "application/json" });
56
+ res.end(JSON.stringify({ error: "Not Found" }));
57
+ return;
58
+ }
59
+ const sessionId = req.headers["mcp-session-id"];
60
+ if (sessionId && this.transports.has(sessionId)) {
61
+ await this.transports.get(sessionId).handleRequest(req, res);
62
+ return;
63
+ }
64
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
65
+ transport.onclose = () => {
66
+ const sid = transport.sessionId;
67
+ if (sid) this.transports.delete(sid);
68
+ };
69
+ await this.createMcpServer().connect(transport);
70
+ await transport.handleRequest(req, res);
71
+ const sid = transport.sessionId;
72
+ if (sid) this.transports.set(sid, transport);
73
+ } catch {
74
+ if (!res.headersSent) {
75
+ res.writeHead(500, { "Content-Type": "application/json" });
76
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
77
+ }
78
+ }
79
+ });
80
+ const maxRetries = 10;
81
+ const requestHandler = this.httpServer.listeners("request")[0];
82
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
83
+ const server = this.httpServer;
84
+ try {
85
+ await new Promise((resolve, reject) => {
86
+ server.listen(this.port, "127.0.0.1", () => resolve());
87
+ server.once("error", reject);
88
+ });
89
+ const addr = server.address();
90
+ if (addr && typeof addr === "object") this.port = addr.port;
91
+ return;
92
+ } catch (err) {
93
+ if (err instanceof Error && "code" in err && err.code === "EADDRINUSE" && attempt < maxRetries - 1) {
94
+ server.removeAllListeners();
95
+ this.httpServer = createServer(requestHandler);
96
+ this.port++;
97
+ continue;
98
+ }
99
+ throw err;
100
+ }
101
+ }
102
+ }
103
+ async stop() {
104
+ for (const transport of this.transports.values()) await transport.close();
105
+ this.transports.clear();
106
+ const server = this.httpServer;
107
+ if (server) {
108
+ await new Promise((resolve, reject) => {
109
+ server.close((err) => err ? reject(err) : resolve());
110
+ });
111
+ this.httpServer = void 0;
112
+ }
113
+ }
114
+ createMcpServer() {
115
+ const sessionId = randomUUID().slice(0, 8);
116
+ const server = new McpServer({
117
+ name: "pl",
118
+ version: "0.1.0"
119
+ }, { capabilities: { tools: {} } });
120
+ this.registerTools(server, sessionId);
121
+ return server;
122
+ }
123
+ registerTools(server, sessionId) {
124
+ const authorId = `mcp-${sessionId}`;
125
+ let localVersion = 0;
126
+ const ctx = {
127
+ getMl: () => this.ml,
128
+ requireMl: () => this.requireMl(),
129
+ resolveProject: (id) => this.resolveProject(id),
130
+ getOpenedProject: (id) => this.getOpenedProject(id),
131
+ callbacks: this.callbacks,
132
+ getAuthorMarker: () => ({
133
+ authorId,
134
+ localVersion: ++localVersion
135
+ })
136
+ };
137
+ registerPingTool(server, ctx);
138
+ registerConnectionTools(server, ctx);
139
+ registerProjectTools(server, ctx);
140
+ registerBlockTools(server, ctx);
141
+ registerBlockStateTools(server, ctx);
142
+ registerAwaitTools(server, ctx);
143
+ registerLogTools(server, ctx);
144
+ registerDataQueryTools(server, ctx);
145
+ registerScreenshotTool(server, ctx);
146
+ registerUIInteractionTools(server, ctx);
147
+ }
148
+ /** Throws if MiddleLayer is not available (not connected to a server). */
149
+ requireMl() {
150
+ if (!this.ml) throw new Error("Not connected to a server. Use connect_to_server first.");
151
+ return this.ml;
152
+ }
153
+ /** Resolves a project from the list by its projectId (resourceIdToString format). */
154
+ async resolveProject(projectId) {
155
+ const ml = this.requireMl();
156
+ await ml.projectList.refreshState();
157
+ const entry = (await ml.projectList.awaitStableValue()).find((p) => resourceIdToString(p.rid) === projectId);
158
+ if (!entry) throw new Error(`Project ${projectId} not found`);
159
+ return entry;
160
+ }
161
+ /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
162
+ async getOpenedProject(projectId) {
163
+ const ml = this.requireMl();
164
+ const entry = await this.resolveProject(projectId);
165
+ return ml.getOpenedProject(entry.rid);
166
+ }
167
+ };
168
+ //#endregion
169
+ export { PlMcpServer };
170
+
171
+ //# sourceMappingURL=server.js.map
@@ -0,0 +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 /** Execute JavaScript in the renderer and return the result. */\n executeJavaScript?: (code: string) => 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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"}
@@ -0,0 +1,89 @@
1
+ const require_types = require("./types.cjs");
2
+ const require_tokens = require("./tokens.cjs");
3
+ const require_sandbox = require("./sandbox.cjs");
4
+ let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
5
+ let zod = require("zod");
6
+ let _platforma_sdk_model = require("@platforma-sdk/model");
7
+ //#region src/tools/await.ts
8
+ function registerAwaitTools(server, ctx) {
9
+ server.registerTool("await_block_done", {
10
+ description: "Wait for a block to finish computation and outputs to stabilize. Returns block status, data, and concise output summary with token estimates. Use `transform` to extract specific data server-side on completion.",
11
+ inputSchema: {
12
+ projectId: zod.z.string().describe("Project ID"),
13
+ blockId: zod.z.string().describe("Block ID to wait for"),
14
+ timeout: zod.z.number().optional().default(12e4).describe("Timeout in ms (default 120000)"),
15
+ transform: zod.z.string().optional().describe("JS expression evaluated server-side when block completes. Available variables: `data` (block args), `outputs` (raw outputs), `block` (status info). Omit for default concise summary."),
16
+ transformTimeout: zod.z.number().optional().default(5e3).describe("Timeout in ms for transform evaluation (default 5000).")
17
+ }
18
+ }, async ({ projectId, blockId, timeout, transform, transformTimeout }) => {
19
+ const project = await ctx.getOpenedProject(projectId);
20
+ const deadline = Date.now() + timeout;
21
+ while (Date.now() < deadline) {
22
+ const overview = await project.overview.getValue();
23
+ if (!overview) continue;
24
+ const block = overview.blocks.find((b) => b.id === blockId);
25
+ if (!block) return require_types.errorResult(`Block ${blockId} not found in project ${projectId}.`, "Use get_project_overview to list all block IDs in this project.");
26
+ if (block.calculationStatus === "Limbo") return require_types.errorResult("Block entered Limbo state (upstream failed or was stopped).", "Check upstream blocks with get_project_overview. Fix or re-run the failed upstream, then retry.");
27
+ if (block.calculationStatus === "NotCalculated") return require_types.errorResult("Block has not been started.", "Use run_block to start it first, then call await_block_done.");
28
+ if (block.calculationStatus === "Done") {
29
+ const remaining = Math.max(deadline - Date.now(), 1e3);
30
+ let state;
31
+ try {
32
+ state = await project.getBlockState(blockId).awaitStableValue(AbortSignal.timeout(remaining));
33
+ } catch (e) {
34
+ if ((0, _milaboratories_pl_middle_layer.isTimeoutError)(e)) return require_types.textResult({
35
+ timedOut: true,
36
+ status: "Done",
37
+ note: "Computation done but outputs did not stabilize in time. Retry with a longer timeout."
38
+ });
39
+ return require_types.errorResult(`Failed to get block state: ${e instanceof Error ? e.message : String(e)}`);
40
+ }
41
+ const data = (0, _platforma_sdk_model.deriveDataFromStorage)(state.blockStorage);
42
+ const blockInfo = {
43
+ id: block.id,
44
+ title: block.title ?? block.label,
45
+ calculationStatus: block.calculationStatus,
46
+ canRun: block.canRun,
47
+ stale: block.stale,
48
+ outputErrors: block.outputErrors
49
+ };
50
+ if (transform) try {
51
+ return require_types.textResult({
52
+ status: "Done",
53
+ block: blockInfo,
54
+ result: await require_sandbox.safeEval(transform, {
55
+ data,
56
+ outputs: state.outputs,
57
+ block: blockInfo
58
+ }, transformTimeout)
59
+ });
60
+ } catch (e) {
61
+ return require_types.errorResult(`Transform failed: ${e instanceof Error ? e.message : String(e)}`, "Check your JS expression syntax. Available variables: data, outputs, block.");
62
+ }
63
+ return require_types.textResult({
64
+ status: "Done",
65
+ block: blockInfo,
66
+ data,
67
+ outputs: require_tokens.summarizeOutputs(state.outputs)
68
+ });
69
+ }
70
+ const pollStart = Date.now();
71
+ try {
72
+ const result = await project.overview.getFullValue();
73
+ await project.overview.awaitChange(AbortSignal.timeout(5e3), result.uTag);
74
+ } catch {}
75
+ const elapsed = Date.now() - pollStart;
76
+ if (elapsed < 500) await new Promise((r) => setTimeout(r, 500 - elapsed));
77
+ }
78
+ const block = (await project.overview.getValue())?.blocks.find((b) => b.id === blockId);
79
+ return require_types.textResult({
80
+ timedOut: true,
81
+ status: block?.calculationStatus ?? "Unknown",
82
+ hint: "The block is still running. Call await_block_done again with a longer timeout."
83
+ });
84
+ });
85
+ }
86
+ //#endregion
87
+ exports.registerAwaitTools = registerAwaitTools;
88
+
89
+ //# sourceMappingURL=await.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"await.cjs","names":["z","errorResult","textResult","safeEval","summarizeOutputs"],"sources":["../../src/tools/await.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { isTimeoutError } from \"@milaboratories/pl-middle-layer\";\nimport { deriveDataFromStorage } from \"@platforma-sdk/model\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { summarizeOutputs } from \"./tokens\";\nimport { safeEval } from \"./sandbox\";\nimport { errorResult, textResult } from \"./types\";\n\nexport function registerAwaitTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"await_block_done\",\n {\n description:\n \"Wait for a block to finish computation and outputs to stabilize. \" +\n \"Returns block status, data, and concise output summary with token estimates. \" +\n \"Use `transform` to extract specific data server-side on completion.\",\n inputSchema: {\n projectId: z.string().describe(\"Project ID\"),\n blockId: z.string().describe(\"Block ID to wait for\"),\n timeout: z.number().optional().default(120000).describe(\"Timeout in ms (default 120000)\"),\n transform: z\n .string()\n .optional()\n .describe(\n \"JS expression evaluated server-side when block completes. \" +\n \"Available variables: `data` (block args), `outputs` (raw outputs), `block` (status info). \" +\n \"Omit for default concise summary.\",\n ),\n transformTimeout: z\n .number()\n .optional()\n .default(5000)\n .describe(\"Timeout in ms for transform evaluation (default 5000).\"),\n },\n },\n async ({ projectId, blockId, timeout, transform, transformTimeout }) => {\n const project = await ctx.getOpenedProject(projectId);\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const overview = await project.overview.getValue();\n if (!overview) continue;\n const block = overview.blocks.find((b) => b.id === blockId);\n if (!block)\n return errorResult(\n `Block ${blockId} not found in project ${projectId}.`,\n \"Use get_project_overview to list all block IDs in this project.\",\n );\n\n // Terminal error states — return immediately\n if (block.calculationStatus === \"Limbo\") {\n return errorResult(\n \"Block entered Limbo state (upstream failed or was stopped).\",\n \"Check upstream blocks with get_project_overview. Fix or re-run the failed upstream, then retry.\",\n );\n }\n\n if (block.calculationStatus === \"NotCalculated\") {\n return errorResult(\n \"Block has not been started.\",\n \"Use run_block to start it first, then call await_block_done.\",\n );\n }\n\n if (block.calculationStatus === \"Done\") {\n // Await stable block state with remaining time budget\n const remaining = Math.max(deadline - Date.now(), 1000);\n let state;\n try {\n state = await project\n .getBlockState(blockId)\n .awaitStableValue(AbortSignal.timeout(remaining));\n } catch (e: unknown) {\n if (isTimeoutError(e)) {\n return textResult({\n timedOut: true,\n status: \"Done\",\n note: \"Computation done but outputs did not stabilize in time. Retry with a longer timeout.\",\n });\n }\n return errorResult(\n `Failed to get block state: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n\n const data = deriveDataFromStorage(state.blockStorage);\n\n const blockInfo = {\n id: block.id,\n title: block.title ?? block.label,\n calculationStatus: block.calculationStatus,\n canRun: block.canRun,\n stale: block.stale,\n outputErrors: block.outputErrors,\n };\n\n if (transform) {\n try {\n const result = await safeEval(\n transform,\n {\n data,\n outputs: state.outputs,\n block: blockInfo,\n },\n transformTimeout,\n );\n return textResult({ status: \"Done\", block: blockInfo, result });\n } catch (e: unknown) {\n return errorResult(\n `Transform failed: ${e instanceof Error ? e.message : String(e)}`,\n \"Check your JS expression syntax. Available variables: data, outputs, block.\",\n );\n }\n }\n\n return textResult({\n status: \"Done\",\n block: blockInfo,\n data,\n outputs: summarizeOutputs(state.outputs as Record<string, unknown> | undefined),\n });\n }\n\n // Still running — wait up to 5s for overview to change, then re-poll.\n // Minimum 500ms delay to avoid busy-looping if awaitChange resolves immediately.\n const pollStart = Date.now();\n try {\n const result = await project.overview.getFullValue();\n await project.overview.awaitChange(AbortSignal.timeout(5000), result.uTag);\n } catch {\n // timeout or abort — just re-poll\n }\n const elapsed = Date.now() - pollStart;\n if (elapsed < 500) {\n await new Promise((r) => setTimeout(r, 500 - elapsed));\n }\n }\n\n // Timed out while running\n const overview = await project.overview.getValue();\n const block = overview?.blocks.find((b) => b.id === blockId);\n return textResult({\n timedOut: true,\n status: block?.calculationStatus ?? \"Unknown\",\n hint: \"The block is still running. Call await_block_done again with a longer timeout.\",\n });\n },\n );\n}\n"],"mappings":";;;;;;;AASA,SAAgB,mBAAmB,QAAmB,KAAwB;AAC5E,QAAO,aACL,oBACA;EACE,aACE;EAGF,aAAa;GACX,WAAWA,IAAAA,EAAE,QAAQ,CAAC,SAAS,aAAa;GAC5C,SAASA,IAAAA,EAAE,QAAQ,CAAC,SAAS,uBAAuB;GACpD,SAASA,IAAAA,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ,KAAO,CAAC,SAAS,iCAAiC;GACzF,WAAWA,IAAAA,EACR,QAAQ,CACR,UAAU,CACV,SACC,wLAGD;GACH,kBAAkBA,IAAAA,EACf,QAAQ,CACR,UAAU,CACV,QAAQ,IAAK,CACb,SAAS,yDAAyD;GACtE;EACF,EACD,OAAO,EAAE,WAAW,SAAS,SAAS,WAAW,uBAAuB;EACtE,MAAM,UAAU,MAAM,IAAI,iBAAiB,UAAU;EACrD,MAAM,WAAW,KAAK,KAAK,GAAG;AAE9B,SAAO,KAAK,KAAK,GAAG,UAAU;GAC5B,MAAM,WAAW,MAAM,QAAQ,SAAS,UAAU;AAClD,OAAI,CAAC,SAAU;GACf,MAAM,QAAQ,SAAS,OAAO,MAAM,MAAM,EAAE,OAAO,QAAQ;AAC3D,OAAI,CAAC,MACH,QAAOC,cAAAA,YACL,SAAS,QAAQ,wBAAwB,UAAU,IACnD,kEACD;AAGH,OAAI,MAAM,sBAAsB,QAC9B,QAAOA,cAAAA,YACL,+DACA,kGACD;AAGH,OAAI,MAAM,sBAAsB,gBAC9B,QAAOA,cAAAA,YACL,+BACA,+DACD;AAGH,OAAI,MAAM,sBAAsB,QAAQ;IAEtC,MAAM,YAAY,KAAK,IAAI,WAAW,KAAK,KAAK,EAAE,IAAK;IACvD,IAAI;AACJ,QAAI;AACF,aAAQ,MAAM,QACX,cAAc,QAAQ,CACtB,iBAAiB,YAAY,QAAQ,UAAU,CAAC;aAC5C,GAAY;AACnB,UAAA,GAAA,gCAAA,gBAAmB,EAAE,CACnB,QAAOC,cAAAA,WAAW;MAChB,UAAU;MACV,QAAQ;MACR,MAAM;MACP,CAAC;AAEJ,YAAOD,cAAAA,YACL,8BAA8B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,GACzE;;IAGH,MAAM,QAAA,GAAA,qBAAA,uBAA6B,MAAM,aAAa;IAEtD,MAAM,YAAY;KAChB,IAAI,MAAM;KACV,OAAO,MAAM,SAAS,MAAM;KAC5B,mBAAmB,MAAM;KACzB,QAAQ,MAAM;KACd,OAAO,MAAM;KACb,cAAc,MAAM;KACrB;AAED,QAAI,UACF,KAAI;AAUF,YAAOC,cAAAA,WAAW;MAAE,QAAQ;MAAQ,OAAO;MAAW,QATvC,MAAMC,gBAAAA,SACnB,WACA;OACE;OACA,SAAS,MAAM;OACf,OAAO;OACR,EACD,iBACD;MAC6D,CAAC;aACxD,GAAY;AACnB,YAAOF,cAAAA,YACL,qBAAqB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,IAC/D,8EACD;;AAIL,WAAOC,cAAAA,WAAW;KAChB,QAAQ;KACR,OAAO;KACP;KACA,SAASE,eAAAA,iBAAiB,MAAM,QAA+C;KAChF,CAAC;;GAKJ,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAI;IACF,MAAM,SAAS,MAAM,QAAQ,SAAS,cAAc;AACpD,UAAM,QAAQ,SAAS,YAAY,YAAY,QAAQ,IAAK,EAAE,OAAO,KAAK;WACpE;GAGR,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,OAAI,UAAU,IACZ,OAAM,IAAI,SAAS,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC;;EAM1D,MAAM,SADW,MAAM,QAAQ,SAAS,UAAU,GAC1B,OAAO,MAAM,MAAM,EAAE,OAAO,QAAQ;AAC5D,SAAOF,cAAAA,WAAW;GAChB,UAAU;GACV,QAAQ,OAAO,qBAAqB;GACpC,MAAM;GACP,CAAC;GAEL"}