@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 @@
1
+ {"version":3,"file":"ping.cjs","names":["textResult"],"sources":["../../src/tools/ping.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerPingTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\"ping\", { description: \"Health check\" }, async () => {\n return textResult({ status: \"ok\", connected: !!ctx.getMl() });\n });\n}\n"],"mappings":";;AAIA,SAAgB,iBAAiB,QAAmB,KAAwB;AAC1E,QAAO,aAAa,QAAQ,EAAE,aAAa,gBAAgB,EAAE,YAAY;AACvE,SAAOA,cAAAA,WAAW;GAAE,QAAQ;GAAM,WAAW,CAAC,CAAC,IAAI,OAAO;GAAE,CAAC;GAC7D"}
@@ -0,0 +1,14 @@
1
+ import { textResult } from "./types.js";
2
+ //#region src/tools/ping.ts
3
+ function registerPingTool(server, ctx) {
4
+ server.registerTool("ping", { description: "Health check" }, async () => {
5
+ return textResult({
6
+ status: "ok",
7
+ connected: !!ctx.getMl()
8
+ });
9
+ });
10
+ }
11
+ //#endregion
12
+ export { registerPingTool };
13
+
14
+ //# sourceMappingURL=ping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ping.js","names":[],"sources":["../../src/tools/ping.ts"],"sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { ToolContext } from \"./types\";\nimport { textResult } from \"./types\";\n\nexport function registerPingTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\"ping\", { description: \"Health check\" }, async () => {\n return textResult({ status: \"ok\", connected: !!ctx.getMl() });\n });\n}\n"],"mappings":";;AAIA,SAAgB,iBAAiB,QAAmB,KAAwB;AAC1E,QAAO,aAAa,QAAQ,EAAE,aAAa,gBAAgB,EAAE,YAAY;AACvE,SAAO,WAAW;GAAE,QAAQ;GAAM,WAAW,CAAC,CAAC,IAAI,OAAO;GAAE,CAAC;GAC7D"}
@@ -0,0 +1,56 @@
1
+ const require_types = require("./types.cjs");
2
+ let _milaboratories_pl_middle_layer = require("@milaboratories/pl-middle-layer");
3
+ let zod = require("zod");
4
+ //#region src/tools/projects.ts
5
+ function registerProjectTools(server, ctx) {
6
+ server.registerTool("list_projects", { description: "List all projects with their IDs, labels, and status" }, async () => {
7
+ const ml = ctx.requireMl();
8
+ await ml.projectList.refreshState();
9
+ return require_types.textResult((await ml.projectList.awaitStableValue()).map((p) => ({
10
+ projectId: (0, _milaboratories_pl_middle_layer.resourceIdToString)(p.rid),
11
+ label: p.meta.label,
12
+ opened: p.opened,
13
+ created: p.created.toISOString(),
14
+ lastModified: p.lastModified.toISOString()
15
+ })));
16
+ });
17
+ server.registerTool("create_project", {
18
+ description: "Create a new project",
19
+ inputSchema: { label: zod.z.string().describe("Project name") }
20
+ }, async ({ label }) => {
21
+ const projectId = (0, _milaboratories_pl_middle_layer.resourceIdToString)(await ctx.requireMl().createProject({ label }));
22
+ await ctx.callbacks.onProjectCreated?.(projectId);
23
+ return require_types.textResult({ projectId });
24
+ });
25
+ server.registerTool("open_project", {
26
+ description: "Open a project for editing. Required before working with blocks.",
27
+ inputSchema: { projectId: zod.z.string().describe("Project ID from list_projects or create_project") }
28
+ }, async ({ projectId }) => {
29
+ const entry = await ctx.resolveProject(projectId);
30
+ await ctx.requireMl().openProject(entry.rid);
31
+ await ctx.callbacks.onProjectOpened?.(projectId);
32
+ return require_types.textResult({ ok: true });
33
+ });
34
+ server.registerTool("close_project", {
35
+ description: "Close an opened project, releasing its resources",
36
+ inputSchema: { projectId: zod.z.string().describe("Project ID") }
37
+ }, async ({ projectId }) => {
38
+ const entry = await ctx.resolveProject(projectId);
39
+ await ctx.requireMl().closeProject(entry.rid);
40
+ await ctx.callbacks.onProjectClosed?.(projectId);
41
+ return require_types.textResult({ ok: true });
42
+ });
43
+ server.registerTool("delete_project", {
44
+ description: "Delete a project permanently. The project must be closed first.",
45
+ inputSchema: { projectId: zod.z.string().describe("Project ID") }
46
+ }, async ({ projectId }) => {
47
+ const entry = await ctx.resolveProject(projectId);
48
+ await ctx.requireMl().deleteProject(entry.id);
49
+ await ctx.callbacks.onProjectDeleted?.(projectId);
50
+ return require_types.textResult({ ok: true });
51
+ });
52
+ }
53
+ //#endregion
54
+ exports.registerProjectTools = registerProjectTools;
55
+
56
+ //# sourceMappingURL=projects.cjs.map
@@ -0,0 +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"}
@@ -0,0 +1,56 @@
1
+ import { textResult } from "./types.js";
2
+ import { resourceIdToString } from "@milaboratories/pl-middle-layer";
3
+ import { z } from "zod";
4
+ //#region src/tools/projects.ts
5
+ function registerProjectTools(server, ctx) {
6
+ server.registerTool("list_projects", { description: "List all projects with their IDs, labels, and status" }, async () => {
7
+ const ml = ctx.requireMl();
8
+ await ml.projectList.refreshState();
9
+ return textResult((await ml.projectList.awaitStableValue()).map((p) => ({
10
+ projectId: resourceIdToString(p.rid),
11
+ label: p.meta.label,
12
+ opened: p.opened,
13
+ created: p.created.toISOString(),
14
+ lastModified: p.lastModified.toISOString()
15
+ })));
16
+ });
17
+ server.registerTool("create_project", {
18
+ description: "Create a new project",
19
+ inputSchema: { label: z.string().describe("Project name") }
20
+ }, async ({ label }) => {
21
+ const projectId = resourceIdToString(await ctx.requireMl().createProject({ label }));
22
+ await ctx.callbacks.onProjectCreated?.(projectId);
23
+ return textResult({ projectId });
24
+ });
25
+ server.registerTool("open_project", {
26
+ description: "Open a project for editing. Required before working with blocks.",
27
+ inputSchema: { projectId: z.string().describe("Project ID from list_projects or create_project") }
28
+ }, async ({ projectId }) => {
29
+ const entry = await ctx.resolveProject(projectId);
30
+ await ctx.requireMl().openProject(entry.rid);
31
+ await ctx.callbacks.onProjectOpened?.(projectId);
32
+ return textResult({ ok: true });
33
+ });
34
+ server.registerTool("close_project", {
35
+ description: "Close an opened project, releasing its resources",
36
+ inputSchema: { projectId: z.string().describe("Project ID") }
37
+ }, async ({ projectId }) => {
38
+ const entry = await ctx.resolveProject(projectId);
39
+ await ctx.requireMl().closeProject(entry.rid);
40
+ await ctx.callbacks.onProjectClosed?.(projectId);
41
+ return textResult({ ok: true });
42
+ });
43
+ server.registerTool("delete_project", {
44
+ description: "Delete a project permanently. The project must be closed first.",
45
+ inputSchema: { projectId: z.string().describe("Project ID") }
46
+ }, async ({ projectId }) => {
47
+ const entry = await ctx.resolveProject(projectId);
48
+ await ctx.requireMl().deleteProject(entry.id);
49
+ await ctx.callbacks.onProjectDeleted?.(projectId);
50
+ return textResult({ ok: true });
51
+ });
52
+ }
53
+ //#endregion
54
+ export { registerProjectTools };
55
+
56
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +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"}
@@ -0,0 +1,51 @@
1
+ let quickjs_emscripten = require("quickjs-emscripten");
2
+ //#region src/tools/sandbox.ts
3
+ /** Lazily initialized QuickJS runtime shared across evaluations. */
4
+ let sharedRuntime;
5
+ async function getRuntime() {
6
+ if (!sharedRuntime) {
7
+ sharedRuntime = (await (0, quickjs_emscripten.getQuickJS)()).newRuntime();
8
+ sharedRuntime.setMemoryLimit(1024 * 1024 * 16);
9
+ sharedRuntime.setMaxStackSize(1024 * 320);
10
+ }
11
+ return sharedRuntime;
12
+ }
13
+ /**
14
+ * Evaluate a JS expression in a QuickJS sandbox.
15
+ * Variables from `context` are injected as globals.
16
+ * Data is marshaled via JSON — no access to Node APIs, filesystem, or process.
17
+ */
18
+ async function safeEval(expression, context, timeout) {
19
+ const runtime = await getRuntime();
20
+ const deadline = Date.now() + timeout;
21
+ runtime.setInterruptHandler(() => Date.now() > deadline);
22
+ const vm = runtime.newContext();
23
+ try {
24
+ const contextJson = JSON.stringify(context);
25
+ const setup = `const __ctx = JSON.parse(${JSON.stringify(contextJson)});
26
+ ${Object.keys(context).map((k) => `const ${k} = __ctx[${JSON.stringify(k)}];`).join("\n")}`;
27
+ const setupResult = vm.evalCode(setup, "setup.js", { type: "global" });
28
+ if (setupResult.error) {
29
+ const err = vm.dump(setupResult.error);
30
+ setupResult.error.dispose();
31
+ throw new Error(`Context setup failed: ${err}`);
32
+ }
33
+ setupResult.value.dispose();
34
+ const result = vm.evalCode(`JSON.stringify((${expression}))`, "transform.js");
35
+ if (result.error) {
36
+ const err = vm.dump(result.error);
37
+ result.error.dispose();
38
+ throw new Error(String(err));
39
+ }
40
+ const json = vm.getString(result.value);
41
+ result.value.dispose();
42
+ return JSON.parse(json);
43
+ } finally {
44
+ runtime.setInterruptHandler(() => false);
45
+ vm.dispose();
46
+ }
47
+ }
48
+ //#endregion
49
+ exports.safeEval = safeEval;
50
+
51
+ //# sourceMappingURL=sandbox.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.cjs","names":[],"sources":["../../src/tools/sandbox.ts"],"sourcesContent":["import { getQuickJS, type QuickJSRuntime } from \"quickjs-emscripten\";\n\n/** Lazily initialized QuickJS runtime shared across evaluations. */\nlet sharedRuntime: QuickJSRuntime | undefined;\n\nasync function getRuntime(): Promise<QuickJSRuntime> {\n if (!sharedRuntime) {\n const quickjs = await getQuickJS();\n sharedRuntime = quickjs.newRuntime();\n sharedRuntime.setMemoryLimit(1024 * 1024 * 16); // 16 MB\n sharedRuntime.setMaxStackSize(1024 * 320);\n }\n return sharedRuntime;\n}\n\n/**\n * Evaluate a JS expression in a QuickJS sandbox.\n * Variables from `context` are injected as globals.\n * Data is marshaled via JSON — no access to Node APIs, filesystem, or process.\n */\nexport async function safeEval(\n expression: string,\n context: Record<string, unknown>,\n timeout: number,\n): Promise<unknown> {\n const runtime = await getRuntime();\n\n // Set interrupt handler for timeout\n const deadline = Date.now() + timeout;\n runtime.setInterruptHandler(() => Date.now() > deadline);\n\n const vm = runtime.newContext();\n try {\n // Inject context variables via JSON\n const contextJson = JSON.stringify(context);\n const setup = `const __ctx = JSON.parse(${JSON.stringify(contextJson)});\n${Object.keys(context)\n .map((k) => `const ${k} = __ctx[${JSON.stringify(k)}];`)\n .join(\"\\n\")}`;\n const setupResult = vm.evalCode(setup, \"setup.js\", { type: \"global\" });\n if (setupResult.error) {\n const err = vm.dump(setupResult.error);\n setupResult.error.dispose();\n throw new Error(`Context setup failed: ${err}`);\n }\n setupResult.value.dispose();\n\n // Evaluate the expression\n const result = vm.evalCode(`JSON.stringify((${expression}))`, \"transform.js\");\n if (result.error) {\n const err = vm.dump(result.error);\n result.error.dispose();\n throw new Error(String(err));\n }\n const json = vm.getString(result.value);\n result.value.dispose();\n return JSON.parse(json);\n } finally {\n runtime.setInterruptHandler(() => false);\n vm.dispose();\n }\n}\n"],"mappings":";;;AAGA,IAAI;AAEJ,eAAe,aAAsC;AACnD,KAAI,CAAC,eAAe;AAElB,mBADgB,OAAA,GAAA,mBAAA,aAAkB,EACV,YAAY;AACpC,gBAAc,eAAe,OAAO,OAAO,GAAG;AAC9C,gBAAc,gBAAgB,OAAO,IAAI;;AAE3C,QAAO;;;;;;;AAQT,eAAsB,SACpB,YACA,SACA,SACkB;CAClB,MAAM,UAAU,MAAM,YAAY;CAGlC,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SAAQ,0BAA0B,KAAK,KAAK,GAAG,SAAS;CAExD,MAAM,KAAK,QAAQ,YAAY;AAC/B,KAAI;EAEF,MAAM,cAAc,KAAK,UAAU,QAAQ;EAC3C,MAAM,QAAQ,4BAA4B,KAAK,UAAU,YAAY,CAAC;EACxE,OAAO,KAAK,QAAQ,CACnB,KAAK,MAAM,SAAS,EAAE,WAAW,KAAK,UAAU,EAAE,CAAC,IAAI,CACvD,KAAK,KAAK;EACT,MAAM,cAAc,GAAG,SAAS,OAAO,YAAY,EAAE,MAAM,UAAU,CAAC;AACtE,MAAI,YAAY,OAAO;GACrB,MAAM,MAAM,GAAG,KAAK,YAAY,MAAM;AACtC,eAAY,MAAM,SAAS;AAC3B,SAAM,IAAI,MAAM,yBAAyB,MAAM;;AAEjD,cAAY,MAAM,SAAS;EAG3B,MAAM,SAAS,GAAG,SAAS,mBAAmB,WAAW,KAAK,eAAe;AAC7E,MAAI,OAAO,OAAO;GAChB,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM;AACjC,UAAO,MAAM,SAAS;AACtB,SAAM,IAAI,MAAM,OAAO,IAAI,CAAC;;EAE9B,MAAM,OAAO,GAAG,UAAU,OAAO,MAAM;AACvC,SAAO,MAAM,SAAS;AACtB,SAAO,KAAK,MAAM,KAAK;WACf;AACR,UAAQ,0BAA0B,MAAM;AACxC,KAAG,SAAS"}
@@ -0,0 +1,51 @@
1
+ import { getQuickJS } from "quickjs-emscripten";
2
+ //#region src/tools/sandbox.ts
3
+ /** Lazily initialized QuickJS runtime shared across evaluations. */
4
+ let sharedRuntime;
5
+ async function getRuntime() {
6
+ if (!sharedRuntime) {
7
+ sharedRuntime = (await getQuickJS()).newRuntime();
8
+ sharedRuntime.setMemoryLimit(1024 * 1024 * 16);
9
+ sharedRuntime.setMaxStackSize(1024 * 320);
10
+ }
11
+ return sharedRuntime;
12
+ }
13
+ /**
14
+ * Evaluate a JS expression in a QuickJS sandbox.
15
+ * Variables from `context` are injected as globals.
16
+ * Data is marshaled via JSON — no access to Node APIs, filesystem, or process.
17
+ */
18
+ async function safeEval(expression, context, timeout) {
19
+ const runtime = await getRuntime();
20
+ const deadline = Date.now() + timeout;
21
+ runtime.setInterruptHandler(() => Date.now() > deadline);
22
+ const vm = runtime.newContext();
23
+ try {
24
+ const contextJson = JSON.stringify(context);
25
+ const setup = `const __ctx = JSON.parse(${JSON.stringify(contextJson)});
26
+ ${Object.keys(context).map((k) => `const ${k} = __ctx[${JSON.stringify(k)}];`).join("\n")}`;
27
+ const setupResult = vm.evalCode(setup, "setup.js", { type: "global" });
28
+ if (setupResult.error) {
29
+ const err = vm.dump(setupResult.error);
30
+ setupResult.error.dispose();
31
+ throw new Error(`Context setup failed: ${err}`);
32
+ }
33
+ setupResult.value.dispose();
34
+ const result = vm.evalCode(`JSON.stringify((${expression}))`, "transform.js");
35
+ if (result.error) {
36
+ const err = vm.dump(result.error);
37
+ result.error.dispose();
38
+ throw new Error(String(err));
39
+ }
40
+ const json = vm.getString(result.value);
41
+ result.value.dispose();
42
+ return JSON.parse(json);
43
+ } finally {
44
+ runtime.setInterruptHandler(() => false);
45
+ vm.dispose();
46
+ }
47
+ }
48
+ //#endregion
49
+ export { safeEval };
50
+
51
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","names":[],"sources":["../../src/tools/sandbox.ts"],"sourcesContent":["import { getQuickJS, type QuickJSRuntime } from \"quickjs-emscripten\";\n\n/** Lazily initialized QuickJS runtime shared across evaluations. */\nlet sharedRuntime: QuickJSRuntime | undefined;\n\nasync function getRuntime(): Promise<QuickJSRuntime> {\n if (!sharedRuntime) {\n const quickjs = await getQuickJS();\n sharedRuntime = quickjs.newRuntime();\n sharedRuntime.setMemoryLimit(1024 * 1024 * 16); // 16 MB\n sharedRuntime.setMaxStackSize(1024 * 320);\n }\n return sharedRuntime;\n}\n\n/**\n * Evaluate a JS expression in a QuickJS sandbox.\n * Variables from `context` are injected as globals.\n * Data is marshaled via JSON — no access to Node APIs, filesystem, or process.\n */\nexport async function safeEval(\n expression: string,\n context: Record<string, unknown>,\n timeout: number,\n): Promise<unknown> {\n const runtime = await getRuntime();\n\n // Set interrupt handler for timeout\n const deadline = Date.now() + timeout;\n runtime.setInterruptHandler(() => Date.now() > deadline);\n\n const vm = runtime.newContext();\n try {\n // Inject context variables via JSON\n const contextJson = JSON.stringify(context);\n const setup = `const __ctx = JSON.parse(${JSON.stringify(contextJson)});\n${Object.keys(context)\n .map((k) => `const ${k} = __ctx[${JSON.stringify(k)}];`)\n .join(\"\\n\")}`;\n const setupResult = vm.evalCode(setup, \"setup.js\", { type: \"global\" });\n if (setupResult.error) {\n const err = vm.dump(setupResult.error);\n setupResult.error.dispose();\n throw new Error(`Context setup failed: ${err}`);\n }\n setupResult.value.dispose();\n\n // Evaluate the expression\n const result = vm.evalCode(`JSON.stringify((${expression}))`, \"transform.js\");\n if (result.error) {\n const err = vm.dump(result.error);\n result.error.dispose();\n throw new Error(String(err));\n }\n const json = vm.getString(result.value);\n result.value.dispose();\n return JSON.parse(json);\n } finally {\n runtime.setInterruptHandler(() => false);\n vm.dispose();\n }\n}\n"],"mappings":";;;AAGA,IAAI;AAEJ,eAAe,aAAsC;AACnD,KAAI,CAAC,eAAe;AAElB,mBADgB,MAAM,YAAY,EACV,YAAY;AACpC,gBAAc,eAAe,OAAO,OAAO,GAAG;AAC9C,gBAAc,gBAAgB,OAAO,IAAI;;AAE3C,QAAO;;;;;;;AAQT,eAAsB,SACpB,YACA,SACA,SACkB;CAClB,MAAM,UAAU,MAAM,YAAY;CAGlC,MAAM,WAAW,KAAK,KAAK,GAAG;AAC9B,SAAQ,0BAA0B,KAAK,KAAK,GAAG,SAAS;CAExD,MAAM,KAAK,QAAQ,YAAY;AAC/B,KAAI;EAEF,MAAM,cAAc,KAAK,UAAU,QAAQ;EAC3C,MAAM,QAAQ,4BAA4B,KAAK,UAAU,YAAY,CAAC;EACxE,OAAO,KAAK,QAAQ,CACnB,KAAK,MAAM,SAAS,EAAE,WAAW,KAAK,UAAU,EAAE,CAAC,IAAI,CACvD,KAAK,KAAK;EACT,MAAM,cAAc,GAAG,SAAS,OAAO,YAAY,EAAE,MAAM,UAAU,CAAC;AACtE,MAAI,YAAY,OAAO;GACrB,MAAM,MAAM,GAAG,KAAK,YAAY,MAAM;AACtC,eAAY,MAAM,SAAS;AAC3B,SAAM,IAAI,MAAM,yBAAyB,MAAM;;AAEjD,cAAY,MAAM,SAAS;EAG3B,MAAM,SAAS,GAAG,SAAS,mBAAmB,WAAW,KAAK,eAAe;AAC7E,MAAI,OAAO,OAAO;GAChB,MAAM,MAAM,GAAG,KAAK,OAAO,MAAM;AACjC,UAAO,MAAM,SAAS;AACtB,SAAM,IAAI,MAAM,OAAO,IAAI,CAAC;;EAE9B,MAAM,OAAO,GAAG,UAAU,OAAO,MAAM;AACvC,SAAO,MAAM,SAAS;AACtB,SAAO,KAAK,MAAM,KAAK;WACf;AACR,UAAQ,0BAA0B,MAAM;AACxC,KAAG,SAAS"}
@@ -0,0 +1,35 @@
1
+ const require_types = require("./types.cjs");
2
+ let zod = require("zod");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ let node_path = require("node:path");
5
+ //#region src/tools/screenshot.ts
6
+ function registerScreenshotTool(server, ctx) {
7
+ server.registerTool("capture_screenshot", {
8
+ description: "Capture a screenshot of the current application window. Optionally save to a file.",
9
+ inputSchema: { savePath: zod.z.string().optional().describe("Absolute file path to save the screenshot as PNG. If omitted, returns the image inline only.") }
10
+ }, async ({ savePath }) => {
11
+ if (!ctx.callbacks.captureScreenshot) return require_types.errorResult("Screenshot capture 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");
12
+ const base64Png = await ctx.callbacks.captureScreenshot();
13
+ if (savePath) {
14
+ const absPath = (0, node_path.resolve)(savePath);
15
+ await (0, node_fs_promises.writeFile)(absPath, Buffer.from(base64Png, "base64"), { flag: "wx" });
16
+ return { content: [{
17
+ type: "image",
18
+ data: base64Png,
19
+ mimeType: "image/png"
20
+ }, {
21
+ type: "text",
22
+ text: `Screenshot saved to ${absPath}`
23
+ }] };
24
+ }
25
+ return { content: [{
26
+ type: "image",
27
+ data: base64Png,
28
+ mimeType: "image/png"
29
+ }] };
30
+ });
31
+ }
32
+ //#endregion
33
+ exports.registerScreenshotTool = registerScreenshotTool;
34
+
35
+ //# sourceMappingURL=screenshot.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.cjs","names":["z","errorResult"],"sources":["../../src/tools/screenshot.ts"],"sourcesContent":["import { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { errorResult } from \"./types\";\n\nexport function registerScreenshotTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"capture_screenshot\",\n {\n description:\n \"Capture a screenshot of the current application window. Optionally save to a file.\",\n inputSchema: {\n savePath: z\n .string()\n .optional()\n .describe(\n \"Absolute file path to save the screenshot as PNG. If omitted, returns the image inline only.\",\n ),\n },\n },\n async ({ savePath }: { savePath?: string }) => {\n if (!ctx.callbacks.captureScreenshot) {\n return errorResult(\n \"Screenshot capture 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 base64Png = await ctx.callbacks.captureScreenshot();\n\n if (savePath) {\n const absPath = resolve(savePath);\n await writeFile(absPath, Buffer.from(base64Png, \"base64\"), { flag: \"wx\" });\n return {\n content: [\n { type: \"image\" as const, data: base64Png, mimeType: \"image/png\" },\n { type: \"text\" as const, text: `Screenshot saved to ${absPath}` },\n ],\n };\n }\n\n return {\n content: [{ type: \"image\" as const, data: base64Png, mimeType: \"image/png\" }],\n };\n },\n );\n}\n"],"mappings":";;;;;AAOA,SAAgB,uBAAuB,QAAmB,KAAwB;AAChF,QAAO,aACL,sBACA;EACE,aACE;EACF,aAAa,EACX,UAAUA,IAAAA,EACP,QAAQ,CACR,UAAU,CACV,SACC,+FACD,EACJ;EACF,EACD,OAAO,EAAE,eAAsC;AAC7C,MAAI,CAAC,IAAI,UAAU,kBACjB,QAAOC,cAAAA,YACL,wCACA,sJACD;EAEH,MAAM,YAAY,MAAM,IAAI,UAAU,mBAAmB;AAEzD,MAAI,UAAU;GACZ,MAAM,WAAA,GAAA,UAAA,SAAkB,SAAS;AACjC,UAAA,GAAA,iBAAA,WAAgB,SAAS,OAAO,KAAK,WAAW,SAAS,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1E,UAAO,EACL,SAAS,CACP;IAAE,MAAM;IAAkB,MAAM;IAAW,UAAU;IAAa,EAClE;IAAE,MAAM;IAAiB,MAAM,uBAAuB;IAAW,CAClE,EACF;;AAGH,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAkB,MAAM;GAAW,UAAU;GAAa,CAAC,EAC9E;GAEJ"}
@@ -0,0 +1,35 @@
1
+ import { errorResult } from "./types.js";
2
+ import { z } from "zod";
3
+ import { writeFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ //#region src/tools/screenshot.ts
6
+ function registerScreenshotTool(server, ctx) {
7
+ server.registerTool("capture_screenshot", {
8
+ description: "Capture a screenshot of the current application window. Optionally save to a file.",
9
+ inputSchema: { savePath: z.string().optional().describe("Absolute file path to save the screenshot as PNG. If omitted, returns the image inline only.") }
10
+ }, async ({ savePath }) => {
11
+ if (!ctx.callbacks.captureScreenshot) return errorResult("Screenshot capture 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");
12
+ const base64Png = await ctx.callbacks.captureScreenshot();
13
+ if (savePath) {
14
+ const absPath = resolve(savePath);
15
+ await writeFile(absPath, Buffer.from(base64Png, "base64"), { flag: "wx" });
16
+ return { content: [{
17
+ type: "image",
18
+ data: base64Png,
19
+ mimeType: "image/png"
20
+ }, {
21
+ type: "text",
22
+ text: `Screenshot saved to ${absPath}`
23
+ }] };
24
+ }
25
+ return { content: [{
26
+ type: "image",
27
+ data: base64Png,
28
+ mimeType: "image/png"
29
+ }] };
30
+ });
31
+ }
32
+ //#endregion
33
+ export { registerScreenshotTool };
34
+
35
+ //# sourceMappingURL=screenshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.js","names":[],"sources":["../../src/tools/screenshot.ts"],"sourcesContent":["import { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport type { ToolContext } from \"./types\";\nimport { errorResult } from \"./types\";\n\nexport function registerScreenshotTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n \"capture_screenshot\",\n {\n description:\n \"Capture a screenshot of the current application window. Optionally save to a file.\",\n inputSchema: {\n savePath: z\n .string()\n .optional()\n .describe(\n \"Absolute file path to save the screenshot as PNG. If omitted, returns the image inline only.\",\n ),\n },\n },\n async ({ savePath }: { savePath?: string }) => {\n if (!ctx.callbacks.captureScreenshot) {\n return errorResult(\n \"Screenshot capture 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 base64Png = await ctx.callbacks.captureScreenshot();\n\n if (savePath) {\n const absPath = resolve(savePath);\n await writeFile(absPath, Buffer.from(base64Png, \"base64\"), { flag: \"wx\" });\n return {\n content: [\n { type: \"image\" as const, data: base64Png, mimeType: \"image/png\" },\n { type: \"text\" as const, text: `Screenshot saved to ${absPath}` },\n ],\n };\n }\n\n return {\n content: [{ type: \"image\" as const, data: base64Png, mimeType: \"image/png\" }],\n };\n },\n );\n}\n"],"mappings":";;;;;AAOA,SAAgB,uBAAuB,QAAmB,KAAwB;AAChF,QAAO,aACL,sBACA;EACE,aACE;EACF,aAAa,EACX,UAAU,EACP,QAAQ,CACR,UAAU,CACV,SACC,+FACD,EACJ;EACF,EACD,OAAO,EAAE,eAAsC;AAC7C,MAAI,CAAC,IAAI,UAAU,kBACjB,QAAO,YACL,wCACA,sJACD;EAEH,MAAM,YAAY,MAAM,IAAI,UAAU,mBAAmB;AAEzD,MAAI,UAAU;GACZ,MAAM,UAAU,QAAQ,SAAS;AACjC,SAAM,UAAU,SAAS,OAAO,KAAK,WAAW,SAAS,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1E,UAAO,EACL,SAAS,CACP;IAAE,MAAM;IAAkB,MAAM;IAAW,UAAU;IAAa,EAClE;IAAE,MAAM;IAAiB,MAAM,uBAAuB;IAAW,CAClE,EACF;;AAGH,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAkB,MAAM;GAAW,UAAU;GAAa,CAAC,EAC9E;GAEJ"}
@@ -0,0 +1,82 @@
1
+ //#region src/tools/tokens.ts
2
+ const NODE_LIMIT = 1e4;
3
+ const CHARS_PER_TOKEN = 4;
4
+ /**
5
+ * Estimate the number of LLM tokens needed to represent a value as JSON,
6
+ * without actually serializing it. Walks the object tree and sums up
7
+ * the JSON character lengths, then divides by ~4 chars/token.
8
+ *
9
+ * Returns token estimate, or a string like ">1234 (truncated at 10000 nodes)"
10
+ * if the object graph is too large to fully traverse.
11
+ */
12
+ function estimateTokens(value, nodeLimit = NODE_LIMIT) {
13
+ let nodes = 0;
14
+ let chars = 0;
15
+ /** Returns true if node limit exceeded. */
16
+ function walk(v) {
17
+ if (++nodes > nodeLimit) return true;
18
+ if (v === null || v === void 0) {
19
+ chars += 4;
20
+ return false;
21
+ }
22
+ switch (typeof v) {
23
+ case "string":
24
+ chars += v.length + 2;
25
+ return false;
26
+ case "number":
27
+ chars += String(v).length;
28
+ return false;
29
+ case "boolean":
30
+ chars += v ? 4 : 5;
31
+ return false;
32
+ case "bigint":
33
+ chars += String(v).length;
34
+ return false;
35
+ default: break;
36
+ }
37
+ if (v instanceof Uint8Array || ArrayBuffer.isView(v)) {
38
+ const arr = v;
39
+ chars += 2 + Math.max(0, arr.length - 1);
40
+ for (let i = 0; i < arr.length; i++) chars += String(arr[i]).length;
41
+ return false;
42
+ }
43
+ if (Array.isArray(v)) {
44
+ chars += 2;
45
+ if (v.length > 1) chars += v.length - 1;
46
+ for (const item of v) if (walk(item)) return true;
47
+ return false;
48
+ }
49
+ if (typeof v === "object") {
50
+ const entries = Object.entries(v);
51
+ chars += 2;
52
+ if (entries.length > 1) chars += entries.length - 1;
53
+ for (const [k, val] of entries) {
54
+ chars += k.length + 3;
55
+ if (walk(val)) return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ const overflow = walk(value);
61
+ const tokens = Math.ceil(chars / CHARS_PER_TOKEN);
62
+ return overflow ? `>${tokens} (truncated at ${nodeLimit} nodes)` : tokens;
63
+ }
64
+ /** Summarize block outputs as concise key/ok/hasValue/tokensEstimate entries. */
65
+ function summarizeOutputs(outputs) {
66
+ if (!outputs) return [];
67
+ return Object.entries(outputs).map(([key, out]) => {
68
+ const o = out;
69
+ const hasValue = o?.value != null;
70
+ const tokensEstimate = hasValue ? estimateTokens(o.value) : void 0;
71
+ return {
72
+ key,
73
+ ok: o?.ok ?? false,
74
+ hasValue,
75
+ tokensEstimate
76
+ };
77
+ });
78
+ }
79
+ //#endregion
80
+ exports.summarizeOutputs = summarizeOutputs;
81
+
82
+ //# sourceMappingURL=tokens.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.cjs","names":[],"sources":["../../src/tools/tokens.ts"],"sourcesContent":["const NODE_LIMIT = 10_000;\nconst CHARS_PER_TOKEN = 4;\n\n/**\n * Estimate the number of LLM tokens needed to represent a value as JSON,\n * without actually serializing it. Walks the object tree and sums up\n * the JSON character lengths, then divides by ~4 chars/token.\n *\n * Returns token estimate, or a string like \">1234 (truncated at 10000 nodes)\"\n * if the object graph is too large to fully traverse.\n */\nexport function estimateTokens(value: unknown, nodeLimit = NODE_LIMIT): number | string {\n let nodes = 0;\n let chars = 0;\n\n /** Returns true if node limit exceeded. */\n function walk(v: unknown): boolean {\n if (++nodes > nodeLimit) return true;\n if (v === null || v === undefined) {\n chars += 4; // \"null\"\n return false;\n }\n switch (typeof v) {\n case \"string\":\n chars += v.length + 2; // content + quotes\n return false;\n case \"number\":\n chars += String(v).length;\n return false;\n case \"boolean\":\n chars += v ? 4 : 5; // \"true\" or \"false\"\n return false;\n case \"bigint\":\n chars += String(v).length;\n return false;\n default:\n break;\n }\n if (v instanceof Uint8Array || ArrayBuffer.isView(v)) {\n // serialized as array of numbers\n const arr = v as Uint8Array;\n chars += 2 + Math.max(0, arr.length - 1); // [] + commas\n for (let i = 0; i < arr.length; i++) {\n chars += String(arr[i]).length;\n }\n return false;\n }\n if (Array.isArray(v)) {\n chars += 2; // []\n if (v.length > 1) chars += v.length - 1; // commas\n for (const item of v) {\n if (walk(item)) return true;\n }\n return false;\n }\n if (typeof v === \"object\") {\n const entries = Object.entries(v as Record<string, unknown>);\n chars += 2; // {}\n if (entries.length > 1) chars += entries.length - 1; // commas\n for (const [k, val] of entries) {\n chars += k.length + 3; // \"key\":\n if (walk(val)) return true;\n }\n }\n return false;\n }\n\n const overflow = walk(value);\n const tokens = Math.ceil(chars / CHARS_PER_TOKEN);\n return overflow ? `>${tokens} (truncated at ${nodeLimit} nodes)` : tokens;\n}\n\n/** Summarize block outputs as concise key/ok/hasValue/tokensEstimate entries. */\nexport function summarizeOutputs(\n outputs: Record<string, unknown> | undefined,\n): { key: string; ok: boolean; hasValue: boolean; tokensEstimate?: number | string }[] {\n if (!outputs) return [];\n return Object.entries(outputs).map(([key, out]) => {\n const o = out as { ok?: boolean; value?: unknown } | undefined;\n const hasValue = o?.value != null;\n const tokensEstimate = hasValue ? estimateTokens(o!.value) : undefined;\n return { key, ok: o?.ok ?? false, hasValue, tokensEstimate };\n });\n}\n"],"mappings":";AAAA,MAAM,aAAa;AACnB,MAAM,kBAAkB;;;;;;;;;AAUxB,SAAgB,eAAe,OAAgB,YAAY,YAA6B;CACtF,IAAI,QAAQ;CACZ,IAAI,QAAQ;;CAGZ,SAAS,KAAK,GAAqB;AACjC,MAAI,EAAE,QAAQ,UAAW,QAAO;AAChC,MAAI,MAAM,QAAQ,MAAM,KAAA,GAAW;AACjC,YAAS;AACT,UAAO;;AAET,UAAQ,OAAO,GAAf;GACE,KAAK;AACH,aAAS,EAAE,SAAS;AACpB,WAAO;GACT,KAAK;AACH,aAAS,OAAO,EAAE,CAAC;AACnB,WAAO;GACT,KAAK;AACH,aAAS,IAAI,IAAI;AACjB,WAAO;GACT,KAAK;AACH,aAAS,OAAO,EAAE,CAAC;AACnB,WAAO;GACT,QACE;;AAEJ,MAAI,aAAa,cAAc,YAAY,OAAO,EAAE,EAAE;GAEpD,MAAM,MAAM;AACZ,YAAS,IAAI,KAAK,IAAI,GAAG,IAAI,SAAS,EAAE;AACxC,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,UAAS,OAAO,IAAI,GAAG,CAAC;AAE1B,UAAO;;AAET,MAAI,MAAM,QAAQ,EAAE,EAAE;AACpB,YAAS;AACT,OAAI,EAAE,SAAS,EAAG,UAAS,EAAE,SAAS;AACtC,QAAK,MAAM,QAAQ,EACjB,KAAI,KAAK,KAAK,CAAE,QAAO;AAEzB,UAAO;;AAET,MAAI,OAAO,MAAM,UAAU;GACzB,MAAM,UAAU,OAAO,QAAQ,EAA6B;AAC5D,YAAS;AACT,OAAI,QAAQ,SAAS,EAAG,UAAS,QAAQ,SAAS;AAClD,QAAK,MAAM,CAAC,GAAG,QAAQ,SAAS;AAC9B,aAAS,EAAE,SAAS;AACpB,QAAI,KAAK,IAAI,CAAE,QAAO;;;AAG1B,SAAO;;CAGT,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,SAAS,KAAK,KAAK,QAAQ,gBAAgB;AACjD,QAAO,WAAW,IAAI,OAAO,iBAAiB,UAAU,WAAW;;;AAIrE,SAAgB,iBACd,SACqF;AACrF,KAAI,CAAC,QAAS,QAAO,EAAE;AACvB,QAAO,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,SAAS;EACjD,MAAM,IAAI;EACV,MAAM,WAAW,GAAG,SAAS;EAC7B,MAAM,iBAAiB,WAAW,eAAe,EAAG,MAAM,GAAG,KAAA;AAC7D,SAAO;GAAE;GAAK,IAAI,GAAG,MAAM;GAAO;GAAU;GAAgB;GAC5D"}
@@ -0,0 +1,82 @@
1
+ //#region src/tools/tokens.ts
2
+ const NODE_LIMIT = 1e4;
3
+ const CHARS_PER_TOKEN = 4;
4
+ /**
5
+ * Estimate the number of LLM tokens needed to represent a value as JSON,
6
+ * without actually serializing it. Walks the object tree and sums up
7
+ * the JSON character lengths, then divides by ~4 chars/token.
8
+ *
9
+ * Returns token estimate, or a string like ">1234 (truncated at 10000 nodes)"
10
+ * if the object graph is too large to fully traverse.
11
+ */
12
+ function estimateTokens(value, nodeLimit = NODE_LIMIT) {
13
+ let nodes = 0;
14
+ let chars = 0;
15
+ /** Returns true if node limit exceeded. */
16
+ function walk(v) {
17
+ if (++nodes > nodeLimit) return true;
18
+ if (v === null || v === void 0) {
19
+ chars += 4;
20
+ return false;
21
+ }
22
+ switch (typeof v) {
23
+ case "string":
24
+ chars += v.length + 2;
25
+ return false;
26
+ case "number":
27
+ chars += String(v).length;
28
+ return false;
29
+ case "boolean":
30
+ chars += v ? 4 : 5;
31
+ return false;
32
+ case "bigint":
33
+ chars += String(v).length;
34
+ return false;
35
+ default: break;
36
+ }
37
+ if (v instanceof Uint8Array || ArrayBuffer.isView(v)) {
38
+ const arr = v;
39
+ chars += 2 + Math.max(0, arr.length - 1);
40
+ for (let i = 0; i < arr.length; i++) chars += String(arr[i]).length;
41
+ return false;
42
+ }
43
+ if (Array.isArray(v)) {
44
+ chars += 2;
45
+ if (v.length > 1) chars += v.length - 1;
46
+ for (const item of v) if (walk(item)) return true;
47
+ return false;
48
+ }
49
+ if (typeof v === "object") {
50
+ const entries = Object.entries(v);
51
+ chars += 2;
52
+ if (entries.length > 1) chars += entries.length - 1;
53
+ for (const [k, val] of entries) {
54
+ chars += k.length + 3;
55
+ if (walk(val)) return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ const overflow = walk(value);
61
+ const tokens = Math.ceil(chars / CHARS_PER_TOKEN);
62
+ return overflow ? `>${tokens} (truncated at ${nodeLimit} nodes)` : tokens;
63
+ }
64
+ /** Summarize block outputs as concise key/ok/hasValue/tokensEstimate entries. */
65
+ function summarizeOutputs(outputs) {
66
+ if (!outputs) return [];
67
+ return Object.entries(outputs).map(([key, out]) => {
68
+ const o = out;
69
+ const hasValue = o?.value != null;
70
+ const tokensEstimate = hasValue ? estimateTokens(o.value) : void 0;
71
+ return {
72
+ key,
73
+ ok: o?.ok ?? false,
74
+ hasValue,
75
+ tokensEstimate
76
+ };
77
+ });
78
+ }
79
+ //#endregion
80
+ export { summarizeOutputs };
81
+
82
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","names":[],"sources":["../../src/tools/tokens.ts"],"sourcesContent":["const NODE_LIMIT = 10_000;\nconst CHARS_PER_TOKEN = 4;\n\n/**\n * Estimate the number of LLM tokens needed to represent a value as JSON,\n * without actually serializing it. Walks the object tree and sums up\n * the JSON character lengths, then divides by ~4 chars/token.\n *\n * Returns token estimate, or a string like \">1234 (truncated at 10000 nodes)\"\n * if the object graph is too large to fully traverse.\n */\nexport function estimateTokens(value: unknown, nodeLimit = NODE_LIMIT): number | string {\n let nodes = 0;\n let chars = 0;\n\n /** Returns true if node limit exceeded. */\n function walk(v: unknown): boolean {\n if (++nodes > nodeLimit) return true;\n if (v === null || v === undefined) {\n chars += 4; // \"null\"\n return false;\n }\n switch (typeof v) {\n case \"string\":\n chars += v.length + 2; // content + quotes\n return false;\n case \"number\":\n chars += String(v).length;\n return false;\n case \"boolean\":\n chars += v ? 4 : 5; // \"true\" or \"false\"\n return false;\n case \"bigint\":\n chars += String(v).length;\n return false;\n default:\n break;\n }\n if (v instanceof Uint8Array || ArrayBuffer.isView(v)) {\n // serialized as array of numbers\n const arr = v as Uint8Array;\n chars += 2 + Math.max(0, arr.length - 1); // [] + commas\n for (let i = 0; i < arr.length; i++) {\n chars += String(arr[i]).length;\n }\n return false;\n }\n if (Array.isArray(v)) {\n chars += 2; // []\n if (v.length > 1) chars += v.length - 1; // commas\n for (const item of v) {\n if (walk(item)) return true;\n }\n return false;\n }\n if (typeof v === \"object\") {\n const entries = Object.entries(v as Record<string, unknown>);\n chars += 2; // {}\n if (entries.length > 1) chars += entries.length - 1; // commas\n for (const [k, val] of entries) {\n chars += k.length + 3; // \"key\":\n if (walk(val)) return true;\n }\n }\n return false;\n }\n\n const overflow = walk(value);\n const tokens = Math.ceil(chars / CHARS_PER_TOKEN);\n return overflow ? `>${tokens} (truncated at ${nodeLimit} nodes)` : tokens;\n}\n\n/** Summarize block outputs as concise key/ok/hasValue/tokensEstimate entries. */\nexport function summarizeOutputs(\n outputs: Record<string, unknown> | undefined,\n): { key: string; ok: boolean; hasValue: boolean; tokensEstimate?: number | string }[] {\n if (!outputs) return [];\n return Object.entries(outputs).map(([key, out]) => {\n const o = out as { ok?: boolean; value?: unknown } | undefined;\n const hasValue = o?.value != null;\n const tokensEstimate = hasValue ? estimateTokens(o!.value) : undefined;\n return { key, ok: o?.ok ?? false, hasValue, tokensEstimate };\n });\n}\n"],"mappings":";AAAA,MAAM,aAAa;AACnB,MAAM,kBAAkB;;;;;;;;;AAUxB,SAAgB,eAAe,OAAgB,YAAY,YAA6B;CACtF,IAAI,QAAQ;CACZ,IAAI,QAAQ;;CAGZ,SAAS,KAAK,GAAqB;AACjC,MAAI,EAAE,QAAQ,UAAW,QAAO;AAChC,MAAI,MAAM,QAAQ,MAAM,KAAA,GAAW;AACjC,YAAS;AACT,UAAO;;AAET,UAAQ,OAAO,GAAf;GACE,KAAK;AACH,aAAS,EAAE,SAAS;AACpB,WAAO;GACT,KAAK;AACH,aAAS,OAAO,EAAE,CAAC;AACnB,WAAO;GACT,KAAK;AACH,aAAS,IAAI,IAAI;AACjB,WAAO;GACT,KAAK;AACH,aAAS,OAAO,EAAE,CAAC;AACnB,WAAO;GACT,QACE;;AAEJ,MAAI,aAAa,cAAc,YAAY,OAAO,EAAE,EAAE;GAEpD,MAAM,MAAM;AACZ,YAAS,IAAI,KAAK,IAAI,GAAG,IAAI,SAAS,EAAE;AACxC,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,UAAS,OAAO,IAAI,GAAG,CAAC;AAE1B,UAAO;;AAET,MAAI,MAAM,QAAQ,EAAE,EAAE;AACpB,YAAS;AACT,OAAI,EAAE,SAAS,EAAG,UAAS,EAAE,SAAS;AACtC,QAAK,MAAM,QAAQ,EACjB,KAAI,KAAK,KAAK,CAAE,QAAO;AAEzB,UAAO;;AAET,MAAI,OAAO,MAAM,UAAU;GACzB,MAAM,UAAU,OAAO,QAAQ,EAA6B;AAC5D,YAAS;AACT,OAAI,QAAQ,SAAS,EAAG,UAAS,QAAQ,SAAS;AAClD,QAAK,MAAM,CAAC,GAAG,QAAQ,SAAS;AAC9B,aAAS,EAAE,SAAS;AACpB,QAAI,KAAK,IAAI,CAAE,QAAO;;;AAG1B,SAAO;;CAGT,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,SAAS,KAAK,KAAK,QAAQ,gBAAgB;AACjD,QAAO,WAAW,IAAI,OAAO,iBAAiB,UAAU,WAAW;;;AAIrE,SAAgB,iBACd,SACqF;AACrF,KAAI,CAAC,QAAS,QAAO,EAAE;AACvB,QAAO,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,SAAS;EACjD,MAAM,IAAI;EACV,MAAM,WAAW,GAAG,SAAS;EAC7B,MAAM,iBAAiB,WAAW,eAAe,EAAG,MAAM,GAAG,KAAA;AAC7D,SAAO;GAAE;GAAK,IAAI,GAAG,MAAM;GAAO;GAAU;GAAgB;GAC5D"}
@@ -0,0 +1,22 @@
1
+ //#region src/tools/types.ts
2
+ function textResult(data) {
3
+ return { content: [{
4
+ type: "text",
5
+ text: JSON.stringify(data)
6
+ }] };
7
+ }
8
+ /** Return an MCP error result with an actionable hint for the AI agent. */
9
+ function errorResult(message, hint) {
10
+ return {
11
+ content: [{
12
+ type: "text",
13
+ text: hint ? `${message}\n\nHint: ${hint}` : message
14
+ }],
15
+ isError: true
16
+ };
17
+ }
18
+ //#endregion
19
+ exports.errorResult = errorResult;
20
+ exports.textResult = textResult;
21
+
22
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","names":[],"sources":["../../src/tools/types.ts"],"sourcesContent":["import type {\n AuthorMarker,\n MiddleLayer,\n Project,\n ProjectListEntry,\n} from \"@milaboratories/pl-middle-layer\";\nimport type { PlMcpServerCallbacks } from \"../server\";\n\nexport type { AuthorMarker };\n\nexport interface ToolContext {\n getMl: () => MiddleLayer | null;\n requireMl: () => MiddleLayer;\n resolveProject: (projectId: string) => Promise<ProjectListEntry>;\n getOpenedProject: (projectId: string) => Promise<Project>;\n callbacks: PlMcpServerCallbacks;\n /** Returns an AuthorMarker with auto-incrementing localVersion for this MCP session. */\n getAuthorMarker: () => AuthorMarker;\n}\n\nexport function textResult(data: unknown) {\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(data) }],\n };\n}\n\n/** Return an MCP error result with an actionable hint for the AI agent. */\nexport function errorResult(message: string, hint?: string) {\n const text = hint ? `${message}\\n\\nHint: ${hint}` : message;\n return {\n content: [{ type: \"text\" as const, text }],\n isError: true,\n };\n}\n"],"mappings":";AAoBA,SAAgB,WAAW,MAAe;AACxC,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC,EACjE;;;AAIH,SAAgB,YAAY,SAAiB,MAAe;AAE1D,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAiB,MAFxB,OAAO,GAAG,QAAQ,YAAY,SAAS;GAET,CAAC;EAC1C,SAAS;EACV"}
@@ -0,0 +1,21 @@
1
+ //#region src/tools/types.ts
2
+ function textResult(data) {
3
+ return { content: [{
4
+ type: "text",
5
+ text: JSON.stringify(data)
6
+ }] };
7
+ }
8
+ /** Return an MCP error result with an actionable hint for the AI agent. */
9
+ function errorResult(message, hint) {
10
+ return {
11
+ content: [{
12
+ type: "text",
13
+ text: hint ? `${message}\n\nHint: ${hint}` : message
14
+ }],
15
+ isError: true
16
+ };
17
+ }
18
+ //#endregion
19
+ export { errorResult, textResult };
20
+
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../src/tools/types.ts"],"sourcesContent":["import type {\n AuthorMarker,\n MiddleLayer,\n Project,\n ProjectListEntry,\n} from \"@milaboratories/pl-middle-layer\";\nimport type { PlMcpServerCallbacks } from \"../server\";\n\nexport type { AuthorMarker };\n\nexport interface ToolContext {\n getMl: () => MiddleLayer | null;\n requireMl: () => MiddleLayer;\n resolveProject: (projectId: string) => Promise<ProjectListEntry>;\n getOpenedProject: (projectId: string) => Promise<Project>;\n callbacks: PlMcpServerCallbacks;\n /** Returns an AuthorMarker with auto-incrementing localVersion for this MCP session. */\n getAuthorMarker: () => AuthorMarker;\n}\n\nexport function textResult(data: unknown) {\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(data) }],\n };\n}\n\n/** Return an MCP error result with an actionable hint for the AI agent. */\nexport function errorResult(message: string, hint?: string) {\n const text = hint ? `${message}\\n\\nHint: ${hint}` : message;\n return {\n content: [{ type: \"text\" as const, text }],\n isError: true,\n };\n}\n"],"mappings":";AAoBA,SAAgB,WAAW,MAAe;AACxC,QAAO,EACL,SAAS,CAAC;EAAE,MAAM;EAAiB,MAAM,KAAK,UAAU,KAAK;EAAE,CAAC,EACjE;;;AAIH,SAAgB,YAAY,SAAiB,MAAe;AAE1D,QAAO;EACL,SAAS,CAAC;GAAE,MAAM;GAAiB,MAFxB,OAAO,GAAG,QAAQ,YAAY,SAAS;GAET,CAAC;EAC1C,SAAS;EACV"}