glasspane-mcp 1.8.2 → 1.10.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.
@@ -26,7 +26,7 @@ export declare const MCP_PROTOCOL_VERSION: "2025-06-18";
26
26
  export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
27
27
  export declare const SERVER_INFO: {
28
28
  readonly name: "glasspane-mcp";
29
- readonly version: "1.8.2";
29
+ readonly version: "1.10.0";
30
30
  };
31
31
  export declare const PARSE_ERROR = -32700;
32
32
  export declare const INVALID_REQUEST = -32600;
@@ -2557,7 +2557,7 @@ function mapProjectError(error) {
2557
2557
  }
2558
2558
  function projectErrorRemedy(code) {
2559
2559
  if (code === GP_E_PROJECT_LIMIT) {
2560
- return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot by removing an entry from that file itself (read it first with \`python3 -m json.tool <that path>\`; this surface offers no delete, so this is a repair of the file the message names, the same route a damaged registry takes) and retry. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
2560
+ return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot with a removal this product does implement: \`glasspaned --project-remove <projectId>\` removes exactly one registration, and \`glasspaned --project-prune\` removes only the engine's own test residue \u2014 both take \`--dry-run\` to preview and print JSON, and an unknown id exits 3. A running daemon keeps its own copy of the table, so the removal only sticks after it is restarted (the reply's requiresDaemonRestart says whether one is pending; \`launchctl kickstart -k gui/$(id -u)/com.glasspane.daemon\`). Do NOT hand-edit the projects file: the next daemon save overwrites it. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
2561
2561
  }
2562
2562
  if (code === GP_E_NOT_FOUND) {
2563
2563
  return "check the projectId; use gp_project_list to view available projects";
@@ -2865,22 +2865,31 @@ function createHttpGateway(options) {
2865
2865
  server.listen(port, host);
2866
2866
  const close = () => new Promise((resolve, reject) => {
2867
2867
  client.close?.();
2868
+ const reaper = setInterval(() => closeIdleKeepAliveConnections(server), IDLE_REAP_MS);
2869
+ reaper.unref();
2870
+ const deadline = setTimeout(() => {
2871
+ report(
2872
+ `shutdown is giving up on a connection that never went idle after ${CALLER_VISIBLE_CEILING_MS}ms; any engine work it carried may still be running \u2014 do not re-issue it`
2873
+ );
2874
+ server.closeAllConnections?.();
2875
+ }, CALLER_VISIBLE_CEILING_MS);
2876
+ deadline.unref();
2868
2877
  server.close((error) => {
2878
+ clearInterval(reaper);
2879
+ clearTimeout(deadline);
2869
2880
  if (error) {
2870
2881
  reject(error);
2871
2882
  } else {
2872
2883
  resolve();
2873
2884
  }
2874
2885
  });
2875
- forceCloseConnections(server);
2886
+ closeIdleKeepAliveConnections(server);
2876
2887
  });
2877
2888
  return { server, close };
2878
2889
  }
2879
- function forceCloseConnections(server) {
2880
- const withCloseAll = server;
2881
- if (typeof withCloseAll.closeAllConnections === "function") {
2882
- withCloseAll.closeAllConnections();
2883
- }
2890
+ var IDLE_REAP_MS = 200;
2891
+ function closeIdleKeepAliveConnections(server) {
2892
+ server.closeIdleConnections?.();
2884
2893
  }
2885
2894
  function isLoopbackHost(value) {
2886
2895
  const trimmed = value.trim().toLowerCase();
@@ -3466,7 +3475,8 @@ function readBody(req, limit) {
3466
3475
  req.on("data", (chunk) => {
3467
3476
  size += chunk.length;
3468
3477
  if (size > limit) {
3469
- req.destroy();
3478
+ chunks.length = 0;
3479
+ req.pause();
3470
3480
  reject(new HTTPPayloadTooLargeError(limit));
3471
3481
  return;
3472
3482
  }
@@ -3511,6 +3521,12 @@ function parseArgs(argv) {
3511
3521
  i += 1;
3512
3522
  } else if (arg.startsWith("--port=")) {
3513
3523
  options.port = parsePort(arg.slice("--port=".length), "--port");
3524
+ } else {
3525
+ throw new Error(
3526
+ `unknown argument: ${arg}
3527
+ ${USAGE}
3528
+ This entrypoint accepts only --help/-h, --socket-path/-s <path> and --port/-p <n>. A flag spelled wrong here is not an error the daemon will ever report.`
3529
+ );
3514
3530
  }
3515
3531
  }
3516
3532
  return options;
@@ -2547,7 +2547,7 @@ function mapProjectError(error) {
2547
2547
  }
2548
2548
  function projectErrorRemedy(code) {
2549
2549
  if (code === GP_E_PROJECT_LIMIT) {
2550
- return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot by removing an entry from that file itself (read it first with \`python3 -m json.tool <that path>\`; this surface offers no delete, so this is a repair of the file the message names, the same route a damaged registry takes) and retry. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
2550
+ return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot with a removal this product does implement: \`glasspaned --project-remove <projectId>\` removes exactly one registration, and \`glasspaned --project-prune\` removes only the engine's own test residue \u2014 both take \`--dry-run\` to preview and print JSON, and an unknown id exits 3. A running daemon keeps its own copy of the table, so the removal only sticks after it is restarted (the reply's requiresDaemonRestart says whether one is pending; \`launchctl kickstart -k gui/$(id -u)/com.glasspane.daemon\`). Do NOT hand-edit the projects file: the next daemon save overwrites it. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
2551
2551
  }
2552
2552
  if (code === GP_E_NOT_FOUND) {
2553
2553
  return "check the projectId; use gp_project_list to view available projects";
@@ -2855,22 +2855,31 @@ function createHttpGateway(options) {
2855
2855
  server.listen(port, host);
2856
2856
  const close = () => new Promise((resolve, reject) => {
2857
2857
  client.close?.();
2858
+ const reaper = setInterval(() => closeIdleKeepAliveConnections(server), IDLE_REAP_MS);
2859
+ reaper.unref();
2860
+ const deadline = setTimeout(() => {
2861
+ report(
2862
+ `shutdown is giving up on a connection that never went idle after ${CALLER_VISIBLE_CEILING_MS}ms; any engine work it carried may still be running \u2014 do not re-issue it`
2863
+ );
2864
+ server.closeAllConnections?.();
2865
+ }, CALLER_VISIBLE_CEILING_MS);
2866
+ deadline.unref();
2858
2867
  server.close((error) => {
2868
+ clearInterval(reaper);
2869
+ clearTimeout(deadline);
2859
2870
  if (error) {
2860
2871
  reject(error);
2861
2872
  } else {
2862
2873
  resolve();
2863
2874
  }
2864
2875
  });
2865
- forceCloseConnections(server);
2876
+ closeIdleKeepAliveConnections(server);
2866
2877
  });
2867
2878
  return { server, close };
2868
2879
  }
2869
- function forceCloseConnections(server) {
2870
- const withCloseAll = server;
2871
- if (typeof withCloseAll.closeAllConnections === "function") {
2872
- withCloseAll.closeAllConnections();
2873
- }
2880
+ var IDLE_REAP_MS = 200;
2881
+ function closeIdleKeepAliveConnections(server) {
2882
+ server.closeIdleConnections?.();
2874
2883
  }
2875
2884
  function isLoopbackHost(value) {
2876
2885
  const trimmed = value.trim().toLowerCase();
@@ -3456,7 +3465,8 @@ function readBody(req, limit) {
3456
3465
  req.on("data", (chunk) => {
3457
3466
  size += chunk.length;
3458
3467
  if (size > limit) {
3459
- req.destroy();
3468
+ chunks.length = 0;
3469
+ req.pause();
3460
3470
  reject(new HTTPPayloadTooLargeError(limit));
3461
3471
  return;
3462
3472
  }
package/dist/index.js CHANGED
@@ -3384,7 +3384,7 @@ function mapProjectError(error) {
3384
3384
  }
3385
3385
  function projectErrorRemedy(code) {
3386
3386
  if (code === GP_E_PROJECT_LIMIT) {
3387
- return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot by removing an entry from that file itself (read it first with \`python3 -m json.tool <that path>\`; this surface offers no delete, so this is a repair of the file the message names, the same route a damaged registry takes) and retry. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
3387
+ return `the limit (${MAX_PROJECTS}) counts the entries stored in the projects file named above, so a new registration cannot be made to fit by changing an existing one: gp_project_set with an existing projectId patches that entry and leaves the count where it is. See what is stored with gp_project_list, then free a slot with a removal this product does implement: \`glasspaned --project-remove <projectId>\` removes exactly one registration, and \`glasspaned --project-prune\` removes only the engine's own test residue \u2014 both take \`--dry-run\` to preview and print JSON, and an unknown id exits 3. A running daemon keeps its own copy of the table, so the removal only sticks after it is restarted (the reply's requiresDaemonRestart says whether one is pending; \`launchctl kickstart -k gui/$(id -u)/com.glasspane.daemon\`). Do NOT hand-edit the projects file: the next daemon save overwrites it. To read the same set without this shell: glasspaned --list-projects prints the file the service loads by default`;
3388
3388
  }
3389
3389
  if (code === GP_E_NOT_FOUND) {
3390
3390
  return "check the projectId; use gp_project_list to view available projects";
@@ -3665,7 +3665,7 @@ var SUPPORTED_PROTOCOL_VERSIONS = [
3665
3665
  "2025-03-26",
3666
3666
  MCP_PROTOCOL_VERSION
3667
3667
  ];
3668
- var SERVER_INFO = { name: "glasspane-mcp", version: "1.8.2" };
3668
+ var SERVER_INFO = { name: "glasspane-mcp", version: "1.10.0" };
3669
3669
  var PARSE_ERROR = -32700;
3670
3670
  var INVALID_REQUEST = -32600;
3671
3671
  var METHOD_NOT_FOUND = -32601;
@@ -3784,7 +3784,7 @@ var McpServer = class {
3784
3784
  if (!spec) {
3785
3785
  return this.error(INVALID_PARAMS, `Unknown tool: ${name}`, id);
3786
3786
  }
3787
- const outcome = await executeTool(spec, params.arguments, this.deps.engine, this.session);
3787
+ const outcome = await executeTool(spec, params.arguments ?? {}, this.deps.engine, this.session);
3788
3788
  return this.result(id, { content: outcome.content, isError: outcome.isError });
3789
3789
  }
3790
3790
  asParams(rawParams) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glasspane-mcp",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "description": "GlassPane MCP stdio shell: JSON-RPC 2.0 over stdio bridging an AI agent to the glasspane engine daemon",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",