@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
@@ -0,0 +1,117 @@
1
+ const require_types = require("./types.cjs");
2
+ let zod = require("zod");
3
+ //#region src/tools/ui-interaction.ts
4
+ function registerUIInteractionTools(server, ctx) {
5
+ server.registerTool("click", {
6
+ description: "Click at coordinates (x, y) in the application window. Use capture_screenshot to find element positions.",
7
+ inputSchema: {
8
+ x: zod.z.number().describe("X coordinate"),
9
+ y: zod.z.number().describe("Y coordinate"),
10
+ doubleClick: zod.z.boolean().optional().describe("Double click")
11
+ }
12
+ }, async ({ x, y, doubleClick }) => {
13
+ if (!ctx.callbacks.sendInputEvent) return require_types.errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
14
+ const clickCount = doubleClick ? 2 : 1;
15
+ await ctx.callbacks.sendInputEvent({
16
+ type: "mouseDown",
17
+ x,
18
+ y,
19
+ button: "left",
20
+ clickCount
21
+ });
22
+ await ctx.callbacks.sendInputEvent({
23
+ type: "mouseUp",
24
+ x,
25
+ y,
26
+ button: "left",
27
+ clickCount
28
+ });
29
+ return require_types.textResult({ ok: true });
30
+ });
31
+ server.registerTool("type_text", {
32
+ description: "Type text into the currently focused element",
33
+ inputSchema: { text: zod.z.string().describe("Text to type") }
34
+ }, async ({ text }) => {
35
+ if (!ctx.callbacks.sendInputEvent) return require_types.errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
36
+ for (const char of text) {
37
+ await ctx.callbacks.sendInputEvent({
38
+ type: "keyDown",
39
+ keyCode: char
40
+ });
41
+ await ctx.callbacks.sendInputEvent({
42
+ type: "char",
43
+ keyCode: char
44
+ });
45
+ await ctx.callbacks.sendInputEvent({
46
+ type: "keyUp",
47
+ keyCode: char
48
+ });
49
+ }
50
+ return require_types.textResult({ ok: true });
51
+ });
52
+ server.registerTool("press_key", {
53
+ description: "Press a keyboard key (Enter, Tab, Escape, Backspace, ArrowDown, ArrowUp, etc.)",
54
+ inputSchema: {
55
+ key: zod.z.string().describe("Key name (e.g. 'Enter', 'Tab', 'Escape', 'Backspace', 'ArrowDown')"),
56
+ modifiers: zod.z.array(zod.z.enum([
57
+ "shift",
58
+ "control",
59
+ "alt",
60
+ "meta"
61
+ ])).optional().describe("Modifier keys to hold")
62
+ }
63
+ }, async ({ key, modifiers }) => {
64
+ if (!ctx.callbacks.sendInputEvent) return require_types.errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
65
+ await ctx.callbacks.sendInputEvent({
66
+ type: "keyDown",
67
+ keyCode: key,
68
+ ...modifiers && {
69
+ shift: modifiers.includes("shift"),
70
+ control: modifiers.includes("control"),
71
+ alt: modifiers.includes("alt"),
72
+ meta: modifiers.includes("meta")
73
+ }
74
+ });
75
+ await ctx.callbacks.sendInputEvent({
76
+ type: "keyUp",
77
+ keyCode: key,
78
+ ...modifiers && {
79
+ shift: modifiers.includes("shift"),
80
+ control: modifiers.includes("control"),
81
+ alt: modifiers.includes("alt"),
82
+ meta: modifiers.includes("meta")
83
+ }
84
+ });
85
+ return require_types.textResult({ ok: true });
86
+ });
87
+ server.registerTool("scroll", {
88
+ description: "Scroll the page at a given position",
89
+ inputSchema: {
90
+ x: zod.z.number().describe("X coordinate to scroll at"),
91
+ y: zod.z.number().describe("Y coordinate to scroll at"),
92
+ deltaX: zod.z.number().optional().default(0).describe("Horizontal scroll amount"),
93
+ deltaY: zod.z.number().describe("Vertical scroll amount (negative = up, positive = down)")
94
+ }
95
+ }, async ({ x, y, deltaX, deltaY }) => {
96
+ if (!ctx.callbacks.sendInputEvent) return require_types.errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
97
+ await ctx.callbacks.sendInputEvent({
98
+ type: "mouseWheel",
99
+ x,
100
+ y,
101
+ deltaX: deltaX ?? 0,
102
+ deltaY
103
+ });
104
+ return require_types.textResult({ ok: true });
105
+ });
106
+ server.registerTool("execute_js", {
107
+ description: "Execute JavaScript in the renderer process and return the result. Useful for querying DOM, reading text, or complex interactions.",
108
+ inputSchema: { code: zod.z.string().describe("JavaScript code to execute") }
109
+ }, async ({ code }) => {
110
+ if (!ctx.callbacks.executeJavaScript) return require_types.errorResult("JS execution is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
111
+ return require_types.textResult(await ctx.callbacks.executeJavaScript(code));
112
+ });
113
+ }
114
+ //#endregion
115
+ exports.registerUIInteractionTools = registerUIInteractionTools;
116
+
117
+ //# sourceMappingURL=ui-interaction.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-interaction.cjs","names":["z","errorResult","textResult"],"sources":["../../src/tools/ui-interaction.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { errorResult, textResult } from \"./types\";\n\nexport function registerUIInteractionTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"click\",\n {\n description:\n \"Click at coordinates (x, y) in the application window. Use capture_screenshot to find element positions.\",\n inputSchema: {\n x: z.number().describe(\"X coordinate\"),\n y: z.number().describe(\"Y coordinate\"),\n doubleClick: z.boolean().optional().describe(\"Double click\"),\n },\n },\n async ({ x, y, doubleClick }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n const clickCount = doubleClick ? 2 : 1;\n await ctx.callbacks.sendInputEvent({\n type: \"mouseDown\",\n x,\n y,\n button: \"left\",\n clickCount,\n });\n await ctx.callbacks.sendInputEvent({ type: \"mouseUp\", x, y, button: \"left\", clickCount });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"type_text\",\n {\n description: \"Type text into the currently focused element\",\n inputSchema: {\n text: z.string().describe(\"Text to type\"),\n },\n },\n async ({ text }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n for (const char of text) {\n await ctx.callbacks.sendInputEvent({ type: \"keyDown\", keyCode: char });\n await ctx.callbacks.sendInputEvent({ type: \"char\", keyCode: char });\n await ctx.callbacks.sendInputEvent({ type: \"keyUp\", keyCode: char });\n }\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"press_key\",\n {\n description: \"Press a keyboard key (Enter, Tab, Escape, Backspace, ArrowDown, ArrowUp, etc.)\",\n inputSchema: {\n key: z\n .string()\n .describe(\"Key name (e.g. 'Enter', 'Tab', 'Escape', 'Backspace', 'ArrowDown')\"),\n modifiers: z\n .array(z.enum([\"shift\", \"control\", \"alt\", \"meta\"]))\n .optional()\n .describe(\"Modifier keys to hold\"),\n },\n },\n async ({ key, modifiers }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n await ctx.callbacks.sendInputEvent({\n type: \"keyDown\",\n keyCode: key,\n ...(modifiers && {\n shift: modifiers.includes(\"shift\"),\n control: modifiers.includes(\"control\"),\n alt: modifiers.includes(\"alt\"),\n meta: modifiers.includes(\"meta\"),\n }),\n });\n await ctx.callbacks.sendInputEvent({\n type: \"keyUp\",\n keyCode: key,\n ...(modifiers && {\n shift: modifiers.includes(\"shift\"),\n control: modifiers.includes(\"control\"),\n alt: modifiers.includes(\"alt\"),\n meta: modifiers.includes(\"meta\"),\n }),\n });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"scroll\",\n {\n description: \"Scroll the page at a given position\",\n inputSchema: {\n x: z.number().describe(\"X coordinate to scroll at\"),\n y: z.number().describe(\"Y coordinate to scroll at\"),\n deltaX: z.number().optional().default(0).describe(\"Horizontal scroll amount\"),\n deltaY: z.number().describe(\"Vertical scroll amount (negative = up, positive = down)\"),\n },\n },\n async ({ x, y, deltaX, deltaY }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n await ctx.callbacks.sendInputEvent({\n type: \"mouseWheel\",\n x,\n y,\n deltaX: deltaX ?? 0,\n deltaY,\n });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"execute_js\",\n {\n description:\n \"Execute JavaScript in the renderer process and return the result. Useful for querying DOM, reading text, or complex interactions.\",\n inputSchema: {\n code: z.string().describe(\"JavaScript code to execute\"),\n },\n },\n async ({ code }) => {\n if (!ctx.callbacks.executeJavaScript) {\n return errorResult(\n \"JS execution is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n const result = await ctx.callbacks.executeJavaScript(code);\n return textResult(result);\n },\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ import { errorResult, textResult } from "./types.js";
2
+ import { z } from "zod";
3
+ //#region src/tools/ui-interaction.ts
4
+ function registerUIInteractionTools(server, ctx) {
5
+ server.registerTool("click", {
6
+ description: "Click at coordinates (x, y) in the application window. Use capture_screenshot to find element positions.",
7
+ inputSchema: {
8
+ x: z.number().describe("X coordinate"),
9
+ y: z.number().describe("Y coordinate"),
10
+ doubleClick: z.boolean().optional().describe("Double click")
11
+ }
12
+ }, async ({ x, y, doubleClick }) => {
13
+ if (!ctx.callbacks.sendInputEvent) return errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
14
+ const clickCount = doubleClick ? 2 : 1;
15
+ await ctx.callbacks.sendInputEvent({
16
+ type: "mouseDown",
17
+ x,
18
+ y,
19
+ button: "left",
20
+ clickCount
21
+ });
22
+ await ctx.callbacks.sendInputEvent({
23
+ type: "mouseUp",
24
+ x,
25
+ y,
26
+ button: "left",
27
+ clickCount
28
+ });
29
+ return textResult({ ok: true });
30
+ });
31
+ server.registerTool("type_text", {
32
+ description: "Type text into the currently focused element",
33
+ inputSchema: { text: z.string().describe("Text to type") }
34
+ }, async ({ text }) => {
35
+ if (!ctx.callbacks.sendInputEvent) return errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
36
+ for (const char of text) {
37
+ await ctx.callbacks.sendInputEvent({
38
+ type: "keyDown",
39
+ keyCode: char
40
+ });
41
+ await ctx.callbacks.sendInputEvent({
42
+ type: "char",
43
+ keyCode: char
44
+ });
45
+ await ctx.callbacks.sendInputEvent({
46
+ type: "keyUp",
47
+ keyCode: char
48
+ });
49
+ }
50
+ return textResult({ ok: true });
51
+ });
52
+ server.registerTool("press_key", {
53
+ description: "Press a keyboard key (Enter, Tab, Escape, Backspace, ArrowDown, ArrowUp, etc.)",
54
+ inputSchema: {
55
+ key: z.string().describe("Key name (e.g. 'Enter', 'Tab', 'Escape', 'Backspace', 'ArrowDown')"),
56
+ modifiers: z.array(z.enum([
57
+ "shift",
58
+ "control",
59
+ "alt",
60
+ "meta"
61
+ ])).optional().describe("Modifier keys to hold")
62
+ }
63
+ }, async ({ key, modifiers }) => {
64
+ if (!ctx.callbacks.sendInputEvent) return errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
65
+ await ctx.callbacks.sendInputEvent({
66
+ type: "keyDown",
67
+ keyCode: key,
68
+ ...modifiers && {
69
+ shift: modifiers.includes("shift"),
70
+ control: modifiers.includes("control"),
71
+ alt: modifiers.includes("alt"),
72
+ meta: modifiers.includes("meta")
73
+ }
74
+ });
75
+ await ctx.callbacks.sendInputEvent({
76
+ type: "keyUp",
77
+ keyCode: key,
78
+ ...modifiers && {
79
+ shift: modifiers.includes("shift"),
80
+ control: modifiers.includes("control"),
81
+ alt: modifiers.includes("alt"),
82
+ meta: modifiers.includes("meta")
83
+ }
84
+ });
85
+ return textResult({ ok: true });
86
+ });
87
+ server.registerTool("scroll", {
88
+ description: "Scroll the page at a given position",
89
+ inputSchema: {
90
+ x: z.number().describe("X coordinate to scroll at"),
91
+ y: z.number().describe("Y coordinate to scroll at"),
92
+ deltaX: z.number().optional().default(0).describe("Horizontal scroll amount"),
93
+ deltaY: z.number().describe("Vertical scroll amount (negative = up, positive = down)")
94
+ }
95
+ }, async ({ x, y, deltaX, deltaY }) => {
96
+ if (!ctx.callbacks.sendInputEvent) return errorResult("UI interaction is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
97
+ await ctx.callbacks.sendInputEvent({
98
+ type: "mouseWheel",
99
+ x,
100
+ y,
101
+ deltaX: deltaX ?? 0,
102
+ deltaY
103
+ });
104
+ return textResult({ ok: true });
105
+ });
106
+ server.registerTool("execute_js", {
107
+ description: "Execute JavaScript in the renderer process and return the result. Useful for querying DOM, reading text, or complex interactions.",
108
+ inputSchema: { code: z.string().describe("JavaScript code to execute") }
109
+ }, async ({ code }) => {
110
+ if (!ctx.callbacks.executeJavaScript) return errorResult("JS execution is not available.", "Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log");
111
+ return textResult(await ctx.callbacks.executeJavaScript(code));
112
+ });
113
+ }
114
+ //#endregion
115
+ export { registerUIInteractionTools };
116
+
117
+ //# sourceMappingURL=ui-interaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-interaction.js","names":[],"sources":["../../src/tools/ui-interaction.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { errorResult, textResult } from \"./types\";\n\nexport function registerUIInteractionTools(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"click\",\n {\n description:\n \"Click at coordinates (x, y) in the application window. Use capture_screenshot to find element positions.\",\n inputSchema: {\n x: z.number().describe(\"X coordinate\"),\n y: z.number().describe(\"Y coordinate\"),\n doubleClick: z.boolean().optional().describe(\"Double click\"),\n },\n },\n async ({ x, y, doubleClick }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n const clickCount = doubleClick ? 2 : 1;\n await ctx.callbacks.sendInputEvent({\n type: \"mouseDown\",\n x,\n y,\n button: \"left\",\n clickCount,\n });\n await ctx.callbacks.sendInputEvent({ type: \"mouseUp\", x, y, button: \"left\", clickCount });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"type_text\",\n {\n description: \"Type text into the currently focused element\",\n inputSchema: {\n text: z.string().describe(\"Text to type\"),\n },\n },\n async ({ text }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n for (const char of text) {\n await ctx.callbacks.sendInputEvent({ type: \"keyDown\", keyCode: char });\n await ctx.callbacks.sendInputEvent({ type: \"char\", keyCode: char });\n await ctx.callbacks.sendInputEvent({ type: \"keyUp\", keyCode: char });\n }\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"press_key\",\n {\n description: \"Press a keyboard key (Enter, Tab, Escape, Backspace, ArrowDown, ArrowUp, etc.)\",\n inputSchema: {\n key: z\n .string()\n .describe(\"Key name (e.g. 'Enter', 'Tab', 'Escape', 'Backspace', 'ArrowDown')\"),\n modifiers: z\n .array(z.enum([\"shift\", \"control\", \"alt\", \"meta\"]))\n .optional()\n .describe(\"Modifier keys to hold\"),\n },\n },\n async ({ key, modifiers }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n await ctx.callbacks.sendInputEvent({\n type: \"keyDown\",\n keyCode: key,\n ...(modifiers && {\n shift: modifiers.includes(\"shift\"),\n control: modifiers.includes(\"control\"),\n alt: modifiers.includes(\"alt\"),\n meta: modifiers.includes(\"meta\"),\n }),\n });\n await ctx.callbacks.sendInputEvent({\n type: \"keyUp\",\n keyCode: key,\n ...(modifiers && {\n shift: modifiers.includes(\"shift\"),\n control: modifiers.includes(\"control\"),\n alt: modifiers.includes(\"alt\"),\n meta: modifiers.includes(\"meta\"),\n }),\n });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"scroll\",\n {\n description: \"Scroll the page at a given position\",\n inputSchema: {\n x: z.number().describe(\"X coordinate to scroll at\"),\n y: z.number().describe(\"Y coordinate to scroll at\"),\n deltaX: z.number().optional().default(0).describe(\"Horizontal scroll amount\"),\n deltaY: z.number().describe(\"Vertical scroll amount (negative = up, positive = down)\"),\n },\n },\n async ({ x, y, deltaX, deltaY }) => {\n if (!ctx.callbacks.sendInputEvent) {\n return errorResult(\n \"UI interaction is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n await ctx.callbacks.sendInputEvent({\n type: \"mouseWheel\",\n x,\n y,\n deltaX: deltaX ?? 0,\n deltaY,\n });\n return textResult({ ok: true });\n },\n );\n\n server.registerTool(\n \"execute_js\",\n {\n description:\n \"Execute JavaScript in the renderer process and return the result. Useful for querying DOM, reading text, or complex interactions.\",\n inputSchema: {\n code: z.string().describe(\"JavaScript code to execute\"),\n },\n },\n async ({ code }) => {\n if (!ctx.callbacks.executeJavaScript) {\n return errorResult(\n \"JS execution is not available.\",\n \"Make sure the MCP server is running inside Platforma Desktop and MCP connected properly. If everything is fine check Electron logs with get_app_log\",\n );\n }\n const result = await ctx.callbacks.executeJavaScript(code);\n return textResult(result);\n },\n );\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@milaboratories/pl-mcp-server",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for Platforma Desktop",
5
+ "keywords": [],
6
+ "license": "UNLICENSED",
7
+ "files": [
8
+ "dist/**/*",
9
+ "src/**/*"
10
+ ],
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.27.1",
23
+ "quickjs-emscripten": "0.31.0",
24
+ "zod": "~3.25.76"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "~24.5.2",
28
+ "@vitest/coverage-istanbul": "^4.1.3",
29
+ "typescript": "~5.9.3",
30
+ "vitest": "^4.1.3",
31
+ "@milaboratories/build-configs": "2.0.0",
32
+ "@milaboratories/pl-middle-layer": "1.55.5",
33
+ "@milaboratories/pl-model-common": "1.31.1",
34
+ "@milaboratories/ts-configs": "1.2.3",
35
+ "@platforma-sdk/model": "1.63.1",
36
+ "@milaboratories/ts-builder": "1.3.1"
37
+ },
38
+ "peerDependencies": {
39
+ "@milaboratories/pl-middle-layer": ">=1.48.0",
40
+ "@platforma-sdk/model": ">=1.60.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=22.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "ts-builder build --target node",
47
+ "watch": "ts-builder build --target node --watch",
48
+ "check": "ts-builder check --target node",
49
+ "formatter:check": "ts-builder formatter --check",
50
+ "linter:check": "ts-builder linter --check",
51
+ "types:check": "ts-builder type-check --target node",
52
+ "test": "vitest run",
53
+ "do-pack": "rm -f *.tgz && pnpm pack && mv *.tgz package.tgz",
54
+ "fmt": "ts-builder format"
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { PlMcpServer } from "./server";
2
+ export type {
3
+ PlMcpServerOptions,
4
+ PlMcpServerCallbacks,
5
+ ServerConnection,
6
+ McpSecret,
7
+ } from "./server";
package/src/server.ts ADDED
@@ -0,0 +1,271 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
4
+ import { randomUUID } from "node:crypto";
5
+ import { type MiddleLayer, resourceIdToString } from "@milaboratories/pl-middle-layer";
6
+ import type { Branded } from "@milaboratories/pl-model-common";
7
+ import type { ToolContext } from "./tools/types";
8
+ import { registerPingTool } from "./tools/ping";
9
+ import { registerConnectionTools } from "./tools/connection";
10
+ import { registerProjectTools } from "./tools/projects";
11
+ import { registerBlockTools } from "./tools/blocks";
12
+ import { registerBlockStateTools } from "./tools/block-state";
13
+ import { registerAwaitTools } from "./tools/await";
14
+ import { registerLogTools } from "./tools/logs";
15
+ import { registerDataQueryTools } from "./tools/data-query";
16
+ import { registerScreenshotTool } from "./tools/screenshot";
17
+ import { registerUIInteractionTools } from "./tools/ui-interaction";
18
+
19
+ export interface PlMcpServerCallbacks {
20
+ onProjectCreated?: (projectId: string) => void | Promise<void>;
21
+ onProjectOpened?: (projectId: string) => void | Promise<void>;
22
+ onProjectClosed?: (projectId: string) => void | Promise<void>;
23
+ onProjectDeleted?: (projectId: string) => void | Promise<void>;
24
+ /** Capture the current application window as a PNG screenshot. Returns base64-encoded PNG. */
25
+ captureScreenshot?: () => Promise<string>;
26
+ /** Send an input event to the application window. */
27
+ sendInputEvent?: (event: unknown) => Promise<void>;
28
+ /** Execute JavaScript in the renderer and return the result. */
29
+ executeJavaScript?: (code: string) => Promise<unknown>;
30
+ /** List available blocks from all configured registries. */
31
+ listAvailableBlocks?: (query?: string) => Promise<unknown[]>;
32
+ /** Navigate the desktop UI to show a specific block. */
33
+ selectBlock?: (projectId: string, blockId: string) => Promise<void>;
34
+ /** Read recent lines from the application log. */
35
+ readAppLog?: (lines: number, search?: string) => Promise<string>;
36
+ /** List saved server connections. */
37
+ listConnections?: () => Promise<ServerConnection[]>;
38
+ /** Connect to a server. */
39
+ connectToServer?: (
40
+ addr: string,
41
+ login: string,
42
+ password?: string,
43
+ ) => Promise<{ status: string; message: string }>;
44
+ /** Get current connection status. */
45
+ getConnectionStatus?: () => Promise<{
46
+ connected: boolean;
47
+ type?: string;
48
+ addr?: string;
49
+ login?: string;
50
+ }>;
51
+ /** Disconnect from current server. */
52
+ disconnect?: () => Promise<void>;
53
+ /** Get detailed info about a specific block package. */
54
+ getBlockInfo?: (
55
+ registryUrl: string,
56
+ organization: string,
57
+ name: string,
58
+ version: string,
59
+ ) => Promise<unknown>;
60
+ }
61
+
62
+ export interface ServerConnection {
63
+ addr: string;
64
+ login: string;
65
+ coreVersion?: string;
66
+ lastConnected?: string;
67
+ }
68
+
69
+ /** Branded type for the MCP server URL secret path segment. */
70
+ export type McpSecret = Branded<string, "McpSecret">;
71
+
72
+ export interface PlMcpServerOptions {
73
+ /** MiddleLayer instance providing access to projects, blocks, etc. Optional — server can start without it. */
74
+ middleLayer?: MiddleLayer;
75
+ /** Port to listen on. */
76
+ port: number;
77
+ /** Secret path segment for URL security. */
78
+ secret: McpSecret;
79
+ /** Optional callbacks for project lifecycle events (e.g. to sync UI state). */
80
+ callbacks?: PlMcpServerCallbacks;
81
+ }
82
+
83
+ export class PlMcpServer {
84
+ private ml: MiddleLayer | null;
85
+ private port: number;
86
+ private readonly secret: McpSecret;
87
+ private readonly callbacks: PlMcpServerCallbacks;
88
+ private httpServer: Server | undefined;
89
+ private readonly transports = new Map<string, StreamableHTTPServerTransport>();
90
+
91
+ constructor(options: PlMcpServerOptions) {
92
+ this.ml = options.middleLayer ?? null;
93
+ this.port = options.port;
94
+ this.secret = options.secret;
95
+ this.callbacks = options.callbacks ?? {};
96
+ }
97
+
98
+ /** Set or update the MiddleLayer instance (e.g. after connecting to a server). */
99
+ setMiddleLayer(ml: MiddleLayer | null | undefined) {
100
+ this.ml = ml ?? null;
101
+ }
102
+
103
+ get url(): string {
104
+ return `http://127.0.0.1:${this.port}/${this.secret}/mcp`;
105
+ }
106
+
107
+ async start(): Promise<void> {
108
+ if (this.httpServer) {
109
+ throw new Error("MCP server is already running");
110
+ }
111
+
112
+ const expectedPath = `/${this.secret}/mcp`;
113
+
114
+ this.httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
115
+ try {
116
+ // Origin validation — only allow localhost
117
+ if (req.headers.origin !== undefined) {
118
+ try {
119
+ const origin = new URL(req.headers.origin);
120
+ if (origin.hostname !== "localhost" && origin.hostname !== "127.0.0.1") {
121
+ res.writeHead(403, { "Content-Type": "application/json" });
122
+ res.end(JSON.stringify({ error: "Forbidden" }));
123
+ return;
124
+ }
125
+ } catch {
126
+ res.writeHead(403, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify({ error: "Forbidden" }));
128
+ return;
129
+ }
130
+ }
131
+
132
+ // Secret path check
133
+ if (req.url !== expectedPath) {
134
+ res.writeHead(404, { "Content-Type": "application/json" });
135
+ res.end(JSON.stringify({ error: "Not Found" }));
136
+ return;
137
+ }
138
+
139
+ // Route to existing session transport
140
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
141
+ if (sessionId && this.transports.has(sessionId)) {
142
+ await this.transports.get(sessionId)!.handleRequest(req, res);
143
+ return;
144
+ }
145
+
146
+ // New session — create transport and connect MCP server
147
+ const transport = new StreamableHTTPServerTransport({
148
+ sessionIdGenerator: () => randomUUID(),
149
+ });
150
+ transport.onclose = () => {
151
+ const sid = transport.sessionId;
152
+ if (sid) this.transports.delete(sid);
153
+ };
154
+
155
+ const server = this.createMcpServer();
156
+ await server.connect(transport);
157
+ await transport.handleRequest(req, res);
158
+
159
+ const sid = transport.sessionId;
160
+ if (sid) this.transports.set(sid, transport);
161
+ } catch {
162
+ if (!res.headersSent) {
163
+ res.writeHead(500, { "Content-Type": "application/json" });
164
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
165
+ }
166
+ }
167
+ });
168
+
169
+ const maxRetries = 10;
170
+ const requestHandler = this.httpServer.listeners("request")[0] as (
171
+ req: IncomingMessage,
172
+ res: ServerResponse,
173
+ ) => void;
174
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
175
+ const server = this.httpServer;
176
+ try {
177
+ await new Promise<void>((resolve, reject) => {
178
+ server.listen(this.port, "127.0.0.1", () => resolve());
179
+ server.once("error", reject);
180
+ });
181
+ // Read back the actual port (important when port is 0)
182
+ const addr = server.address();
183
+ if (addr && typeof addr === "object") {
184
+ this.port = addr.port;
185
+ }
186
+ return;
187
+ } catch (err: unknown) {
188
+ if (
189
+ err instanceof Error &&
190
+ "code" in err &&
191
+ err.code === "EADDRINUSE" &&
192
+ attempt < maxRetries - 1
193
+ ) {
194
+ server.removeAllListeners();
195
+ this.httpServer = createServer(requestHandler);
196
+ this.port++;
197
+ continue;
198
+ }
199
+ throw err;
200
+ }
201
+ }
202
+ }
203
+
204
+ async stop(): Promise<void> {
205
+ for (const transport of this.transports.values()) {
206
+ await transport.close();
207
+ }
208
+ this.transports.clear();
209
+
210
+ const server = this.httpServer;
211
+ if (server) {
212
+ await new Promise<void>((resolve, reject) => {
213
+ server.close((err) => (err ? reject(err) : resolve()));
214
+ });
215
+ this.httpServer = undefined;
216
+ }
217
+ }
218
+
219
+ private createMcpServer(): McpServer {
220
+ const sessionId = randomUUID().slice(0, 8);
221
+ const server = new McpServer({ name: "pl", version: "0.1.0" }, { capabilities: { tools: {} } });
222
+ this.registerTools(server, sessionId);
223
+ return server;
224
+ }
225
+
226
+ private registerTools(server: McpServer, sessionId: string): void {
227
+ const authorId = `mcp-${sessionId}`;
228
+ let localVersion = 0;
229
+ const ctx: ToolContext = {
230
+ getMl: () => this.ml,
231
+ requireMl: () => this.requireMl(),
232
+ resolveProject: (id) => this.resolveProject(id),
233
+ getOpenedProject: (id) => this.getOpenedProject(id),
234
+ callbacks: this.callbacks,
235
+ getAuthorMarker: () => ({ authorId, localVersion: ++localVersion }),
236
+ };
237
+ registerPingTool(server, ctx);
238
+ registerConnectionTools(server, ctx);
239
+ registerProjectTools(server, ctx);
240
+ registerBlockTools(server, ctx);
241
+ registerBlockStateTools(server, ctx);
242
+ registerAwaitTools(server, ctx);
243
+ registerLogTools(server, ctx);
244
+ registerDataQueryTools(server, ctx);
245
+ registerScreenshotTool(server, ctx);
246
+ registerUIInteractionTools(server, ctx);
247
+ }
248
+
249
+ /** Throws if MiddleLayer is not available (not connected to a server). */
250
+ private requireMl(): MiddleLayer {
251
+ if (!this.ml) throw new Error("Not connected to a server. Use connect_to_server first.");
252
+ return this.ml;
253
+ }
254
+
255
+ /** Resolves a project from the list by its projectId (resourceIdToString format). */
256
+ private async resolveProject(projectId: string) {
257
+ const ml = this.requireMl();
258
+ await ml.projectList.refreshState();
259
+ const projects = await ml.projectList.awaitStableValue();
260
+ const entry = projects.find((p) => resourceIdToString(p.rid) === projectId);
261
+ if (!entry) throw new Error(`Project ${projectId} not found`);
262
+ return entry;
263
+ }
264
+
265
+ /** Gets an opened project by projectId. Resolves via project list → rid → getOpenedProject. */
266
+ private async getOpenedProject(projectId: string) {
267
+ const ml = this.requireMl();
268
+ const entry = await this.resolveProject(projectId);
269
+ return ml.getOpenedProject(entry.rid);
270
+ }
271
+ }