mc8yp 2.6.0 → 2.6.2

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 (3) hide show
  1. package/README.md +5 -3
  2. package/dist/cli.mjs +19 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -276,11 +276,13 @@ There is deliberately no raw-request escape hatch — the typed namespaces are t
276
276
 
277
277
  Operations can be hidden from derivation and discovery by annotating them in the OpenAPI spec with the vendor extension `x-mc8yp-exclude: true` — with no escape hatch, exclusion is absolute for the sandbox.
278
278
 
279
- ### Sandbox surface (`sandbox`) — microservice mode only
279
+ ### Sandbox surface (`sandbox`) — microservice mode only, opt-in
280
280
 
281
- > **Experimental.** Available in deployed microservice mode; not exposed in the local CLI (agent harnesses there bring their own file I/O).
281
+ > **Experimental.** Available in deployed microservice mode only, and **disabled by default**; not exposed in the local CLI (agent harnesses there bring their own file I/O).
282
282
 
283
- In microservice mode, codemode has one more global — `sandbox` — an in-memory shell with a virtual filesystem for wrangling data you fetched from the API (`jq`, `awk`, `sed`, `grep`, `sort`, `uniq`, `cut`, `sqlite3`, …). It has **no network access and no host filesystem access** — it never reaches Cumulocity. Fetch with `c8y`/service namespaces, process in the sandbox, read the result back.
283
+ In microservice mode, codemode has one more optional global — `sandbox` — an in-memory shell with a virtual filesystem for wrangling data you fetched from the API (`jq`, `awk`, `sed`, `grep`, `sort`, `uniq`, `cut`, `sqlite3`, …). It has **no network access and no host filesystem access** — it never reaches Cumulocity. Fetch with `c8y`/service namespaces, process in the sandbox, read the result back.
284
+
285
+ **It is off unless you turn it on.** Enable it per connection with the `mc8yp-enable-sandbox` header or the `enableSandbox` query param (any of empty, `*`, or `true`). Without it, `sandbox` is absent — same as CLI mode.
284
286
 
285
287
  ```js
286
288
  async () => {
package/dist/cli.mjs CHANGED
@@ -1546,7 +1546,7 @@ const consola = createConsola();
1546
1546
  //#endregion
1547
1547
  //#region package.json
1548
1548
  var name = "mc8yp";
1549
- var version = "2.6.0";
1549
+ var version = "2.6.2";
1550
1550
  var description$1 = "Cumulocity IoT MCP Server - Model Context Protocol integration for IoT device management";
1551
1551
  //#endregion
1552
1552
  //#region \0virtual:core-openapi
@@ -58667,7 +58667,7 @@ function createCodeModeGuidePrompt() {
58667
58667
  const namespaceNames = resolvedSpecs ? buildNamespaces(resolvedSpecs, restrictions, allowRules).map((ns) => ns.name) : ["c8y"];
58668
58668
  const policyLines = [...restrictions.map((rule) => `- deny: \`${rule.source}\``), ...allowRules.map((rule) => `- allow: \`${rule.source}\``)];
58669
58669
  const restrictionSection = policyLines.length > 0 ? `\n## Current Connection Access Policy\n${policyLines.join("\n")}\n\nOperations blocked by these rules are omitted from discovery (search/describe) entirely, and any live request that matches a deny rule (or misses the allow list) fails before reaching the tenant.\n` : "";
58670
- const sandboxSection = c8yMcpServer.ctx.custom?.env === "server" ? `\n## Sandbox (scratch compute)
58670
+ const sandboxSection = c8yMcpServer.ctx.custom?.env === "server" && c8yMcpServer.ctx.custom?.enableSandbox ? `\n## Sandbox (scratch compute)
58671
58671
 
58672
58672
  \`sandbox\` is your workspace: a persistent in-memory filesystem + Unix shell, separate from the API. It is where you keep files and process data. When the user asks to save or write a file, write it here (\`sandbox.writeFile\`) — never dump a file into the chat instead. Use the shell for text/data processing that is awkward in plain JS — \`jq\`, \`awk\`, \`sed\`, \`grep\`, \`sort\`, \`uniq\`, \`cut\`, \`sqlite3\`, etc. via \`sandbox.exec\`. It has NO network access and NO host filesystem access; it never reaches Cumulocity. Fetch data with \`c8y\`/namespaces, write it into the sandbox, process it, read the result back.
58673
58673
 
@@ -77814,7 +77814,7 @@ function armIdleTimer(sessionId) {
77814
77814
  const session = sessions.get(sessionId);
77815
77815
  if (!session) return;
77816
77816
  if (session.timer) clearTimeout(session.timer);
77817
- session.timer = setTimeout(() => evictSandboxSession(sessionId), IDLE_TTL_MS);
77817
+ session.timer = setTimeout(() => evictSandboxSession(sessionId, "idle-timeout"), IDLE_TTL_MS);
77818
77818
  session.timer.unref?.();
77819
77819
  }
77820
77820
  function getSessionAdapter(sessionId) {
@@ -77833,22 +77833,24 @@ function resetSessionAdapter(sessionId) {
77833
77833
  armIdleTimer(sessionId);
77834
77834
  }
77835
77835
  /**
77836
- * Drop a session's sandbox and its timer. Exported for a future clean-close
77837
- * (DELETE) hook and used by the process-exit teardown.
77836
+ * Drop a session's sandbox and its timer. Called by the clean-close (DELETE)
77837
+ * hook, the idle timer, and the process-exit teardown.
77838
77838
  * @param sessionId - MCP session id.
77839
+ * @param reason - Why the sandbox is being dropped; included in the eviction log line.
77839
77840
  */
77840
- function evictSandboxSession(sessionId) {
77841
+ function evictSandboxSession(sessionId, reason) {
77841
77842
  const session = sessions.get(sessionId);
77842
77843
  if (!session) return;
77843
77844
  if (session.timer) clearTimeout(session.timer);
77844
77845
  session.adapter.dispose?.();
77845
77846
  sessions.delete(sessionId);
77847
+ consola.info(`[sandbox] evicted workspace for session ${sessionId} (reason: ${reason})`);
77846
77848
  }
77847
77849
  /**
77848
77850
  * Evict every session (process exit, test cleanup).
77849
77851
  */
77850
77852
  function disposeAllSandboxSessions() {
77851
- for (const sessionId of [...sessions.keys()]) evictSandboxSession(sessionId);
77853
+ for (const sessionId of [...sessions.keys()]) evictSandboxSession(sessionId, "shutdown");
77852
77854
  }
77853
77855
  /**
77854
77856
  * Build the `sandbox` host-module leaf for one codemode run: the full Flue
@@ -135160,7 +135162,7 @@ async function execute(functionCode) {
135160
135162
  live = createUnauthenticatedCalls(error instanceof Error ? error.message : String(error));
135161
135163
  }
135162
135164
  const sessionId = c8yMcpServer.ctx.sessionId;
135163
- const sandboxApi = c8yMcpServer.ctx.custom?.env === "server" && sessionId ? buildSandboxApi(sessionId) : void 0;
135165
+ const sandboxApi = c8yMcpServer.ctx.custom?.env === "server" && sessionId && c8yMcpServer.ctx.custom?.enableSandbox ? buildSandboxApi(sessionId) : void 0;
135164
135166
  const result = await (await getSandbox()).run({
135165
135167
  code: ENTRY_SOURCE,
135166
135168
  filename: EXECUTE_ENTRY_PATH,
@@ -135185,16 +135187,19 @@ function getSafetyPreface(env) {
135185
135187
  ].join("\n");
135186
135188
  }
135187
135189
  /**
135188
- * Purpose framing for the `sandbox` workspace. Server-only: deployed sessions
135189
- * always have it, so the always-read tool description can state it plainly. CLI
135190
- * has no sandbox. This is deliberately about PURPOSE (files/data processing
135191
- * live here), not procedure — the observed failure was the agent not realizing
135192
- * the sandbox is where files go and dumping output to chat.
135190
+ * Purpose framing for the `sandbox` workspace. Server-only, and disabled by
135191
+ * default — a connection must opt in (`mc8yp-enable-sandbox` header /
135192
+ * `enableSandbox` query param) before the `sandbox` global exists, so this
135193
+ * always-read tool description cannot assert it is present the way it can
135194
+ * for `codemode`/`docs`. CLI never has a sandbox regardless. This is
135195
+ * deliberately about PURPOSE (files/data processing live here), not
135196
+ * procedure — the observed failure was the agent not realizing the sandbox
135197
+ * is where files go and dumping output to chat.
135193
135198
  * @param env - execution environment.
135194
135199
  */
135195
135200
  function getSandboxNote(env) {
135196
135201
  if (env !== "server") return "";
135197
- return "\nYour workspace — `sandbox`: this session has a persistent in-memory filesystem + Unix shell, separate from the API. It is where you keep files and process data. When the user asks to save or write a file, or when you need to filter/transform/aggregate fetched data (jq, awk, grep, sort, sqlite), do it here — `sandbox.writeFile(path, text)`, `sandbox.readFile(path)`, `sandbox.exec(command)` — never dump a file into the chat instead. Files persist across codemode calls in this session. Full method list: `codemode.describe(\"sandbox\")`.\n";
135202
+ return "\nYour workspace — `sandbox`: if this connection has it enabled (check `typeof sandbox !== 'undefined'`), it is a persistent in-memory filesystem + Unix shell, separate from the API, for keeping files and processing data. When the user asks to save or write a file, or when you need to filter/transform/aggregate fetched data (jq, awk, grep, sort, sqlite), do it here — `sandbox.writeFile(path, text)`, `sandbox.readFile(path)`, `sandbox.exec(command)` — never dump a file into the chat instead. Files persist across codemode calls in this session. Full method list: `codemode.describe(\"sandbox\")`.\n";
135198
135203
  }
135199
135204
  function createCodemodeTool(env) {
135200
135205
  return defineTool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mc8yp",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "type": "module",
5
5
  "description": "Cumulocity IoT MCP Server - Model Context Protocol integration for IoT device management",
6
6
  "keywords": [