mc8yp 2.7.0 → 2.7.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.
package/README.md CHANGED
@@ -18,6 +18,7 @@ Operators stay in control through per-connection **restrictions** and **allow ru
18
18
 
19
19
  1. mc8yp discovers every microservice installed on the tenant that declares an OpenAPI spec, and derives typed method namespaces from those alongside the bundled Core (+ DTM) specs.
20
20
  2. Inside a `codemode` run, the agent finds the right method with `codemode.search`, inspects its exact input/output types with `codemode.describe`, and reads prose documentation (domain query languages, parameter syntax) with `docs.search`/`docs.read`.
21
+ `codemode.describe` works at three altitudes: no target lists the namespaces on this tenant, `describe("<namespace>")` lists every method in one namespace one line each — the callable target (`c8y.getAlarmCollectionResource`), `METHOD /path`, summary, no types — and `describe("<namespace>.<method>")` renders the full input/output types. The namespace listing is the survey step for when search keeps missing a domain's vocabulary; all ~250 core methods cost roughly 7k tokens, which is why full types stay method-level.
21
22
  3. In the same run, the agent calls the live Cumulocity API through the derived methods (`c8y.<method>({ ... })`). The namespaces are the complete surface — there is no raw-request escape hatch.
22
23
  4. mc8yp enforces configured restrictions and allow rules before any request leaves the host — blocked operations are also omitted from discovery entirely.
23
24
 
@@ -61,10 +62,46 @@ Behaviour worth knowing:
61
62
  - **The agent is told which namespaces are external.** `codemode.describe()` labels them `EXTERNAL MCP server at <url> — configured for this connection, NOT part of this tenant`, and the per-method describe repeats it. Whether a tenant namespace is backed by OpenAPI or MCP stays hidden (an implementation detail the agent cannot act on); tenant-vs-third-party is a data boundary it must be able to report on.
62
63
  - **Tool lists are fetched on first use and cached per MCP session**, then dropped 15 minutes after last use or immediately when the client closes its session cleanly. Rotating a token or changing a URL mid-session re-handshakes.
63
64
  - **A malformed entry fails the request** (HTTP 400 in server mode, a startup error in CLI) rather than silently leaving the agent without a namespace it was supposed to have. A well-formed but _unreachable_ server is reported in the `codemode.describe()` overview and retried on the next call.
64
- - **The tenant wins name collisions.** An external entry whose `name` matches a tenant namespace is skipped, so a header cannot shadow a real service.
65
+ - **The tenant wins name collisions.** An external entry whose `name` matches a tenant namespace is skipped, so a header cannot shadow a real service. That skip is silent to the caller — use `POST /resolve-mcp-servers` (below) to catch it while configuring rather than discovering it as a missing namespace at run time.
65
66
  - **Egress is unrestricted by design.** The URL is used as given — any host, no allowlist, no private-range filtering. The header is part of the connection's trusted configuration (the sandbox cannot set it), but the consequence is that whoever can set headers on the deployed microservice can make it issue requests to any address it can reach, including tenant-internal ones. Front the deployment accordingly.
66
67
  - Path-based restriction/allow rules do **not** apply to these namespaces, same as for tenant-discovered MCP tools (see [Access policy](#access-policy)).
67
68
 
69
+ #### Checking a configuration before you store it — `POST /resolve-mcp-servers`
70
+
71
+ Whoever builds that header — a tenant admin UI, a deployment script — cannot answer three things on its own: which namespace an entry gets, whether that namespace is free, and whether the server actually answers with its credentials. This route answers all three in one call, so no consumer has to reimplement mc8yp's namespace rules or its MCP handshake:
72
+
73
+ ```http
74
+ POST /service/mc8yp-server/resolve-mcp-servers
75
+ Content-Type: application/json
76
+
77
+ { "servers": [{ "name": "MaStR registry", "url": "https://mastr.example/mcp", "token": "…" }] }
78
+ ```
79
+
80
+ ```jsonc
81
+ {
82
+ "tenantUrl": "https://…cumulocity.com",
83
+ "tenantId": "t123",
84
+ "tenantNamespaces": ["c8y", "dtm", "knowledge_base_ms"], // null = the check could not run, see `warnings`
85
+ "warnings": [],
86
+ "servers": [
87
+ {
88
+ "name": "MaStR registry",
89
+ "namespace": "MaStR_registry", // derived — store THIS and send it in the header from now on
90
+ "status": "ok", // ok | invalid | namespace-taken | unreachable
91
+ "server": { "name": "mastr-mcp", "version": "1.0.0" },
92
+ "tools": [{ "name": "search_units", "description": "…" }]
93
+ }
94
+ ]
95
+ }
96
+ ```
97
+
98
+ - **`name` may be free-form here.** It is sanitized into the namespace exactly as a contextPath is (`MaStR registry` → `MaStR_registry`, the same rule that makes `knowledge-base-ms` into `knowledge_base_ms`). An identifier passes through unchanged.
99
+ - **`status` is the field to branch on**, and `namespace-taken` is the interesting one: reserved name, a namespace this tenant already holds, or a duplicate inside the same request. That is the collision that would otherwise be a silent skip at run time — `namespaceTakenBy` says who holds it. Tools are still reported for a taken entry, so renaming is the only fix needed.
100
+ - **The header contract does not change.** Derivation is a configure-time convenience: resolve once, store the `namespace` you got back, keep sending it verbatim. The agent-visible namespace stays stable even if the derivation is refined later.
101
+ - **Validation is the header's own**, so this route cannot green-light an entry the header would then 400.
102
+ - **Nothing is persisted, and nothing is cached.** mc8yp holds no external server configuration of its own, and the handshake bypasses the per-session tool-list cache so the answer always reflects the credentials as sent.
103
+ - Requires user auth (Authorization header or session cookie), like `POST /refresh-apis`.
104
+
68
105
  ## Two ways to run it
69
106
 
70
107
  - **Microservice mode** (recommended for production) — deploy inside Cumulocity IoT, expose `/mcp`, integrate with [AI Agent Manager](https://cumulocity.com/docs/ai/aim-introduction/). Auth comes from the request and the service user.
@@ -1,4 +1,5 @@
1
- import { a as parse, c as url, f as __commonJSMin, i as setStoredC8yAuth, l as consola, o as pipe, r as getStoredC8yAuth, s as string, t as cleanTenantUrl, u as defineCommand } from "./cli.mjs";
1
+ import { t as __commonJSMin } from "./rolldown-runtime-CMFfr-1z.mjs";
2
+ import { a as parse, c as url, i as setStoredC8yAuth, l as consola, o as pipe, r as getStoredC8yAuth, s as string, t as cleanTenantUrl, u as defineCommand } from "./cli.mjs";
2
3
  import process$1, { exit, stdin, stdout } from "node:process";
3
4
  import { styleText } from "node:util";
4
5
  import "node:path";
@@ -285,7 +286,7 @@ function wrapAnsi(string, columns, options) {
285
286
  return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join("\n");
286
287
  }
287
288
  //#endregion
288
- //#region node_modules/.pnpm/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
289
+ //#region node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
289
290
  var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
290
291
  const ESC = "\x1B";
291
292
  const CSI = `${ESC}[`;
@@ -339,7 +340,7 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
339
340
  };
340
341
  })))();
341
342
  const settings = {
342
- actions: new Set([
343
+ actions: /* @__PURE__ */ new Set([
343
344
  "up",
344
345
  "down",
345
346
  "left",
@@ -593,7 +594,7 @@ var V = class {
593
594
  }
594
595
  }
595
596
  };
596
- var o = class extends V {
597
+ let u$1 = class u extends V {
597
598
  _mask = "•";
598
599
  get cursor() {
599
600
  return this._cursor;
@@ -605,8 +606,8 @@ var o = class extends V {
605
606
  if (this.state === "submit" || this.state === "cancel") return this.masked;
606
607
  const t = this.userInput;
607
608
  if (this.cursor >= t.length) return `${this.masked}${styleText(["inverse", "hidden"], "_")}`;
608
- const s = this.masked, r = s.slice(0, this.cursor), e = s.slice(this.cursor);
609
- return `${r}${styleText("inverse", e[0])}${e.slice(1)}`;
609
+ const s = this.masked, r = s.slice(0, this.cursor), i = s.slice(this.cursor, this.cursor + 1), o = s.slice(this.cursor + 1);
610
+ return `${r}${styleText("inverse", i)}${o}`;
610
611
  }
611
612
  clear() {
612
613
  this._clearUserInput();
@@ -614,30 +615,43 @@ var o = class extends V {
614
615
  constructor({ mask: t, ...s }) {
615
616
  super(s), this._mask = t ?? "•", this.on("userInput", (r) => {
616
617
  this._setValue(r);
618
+ }), this.on("finalize", () => {
619
+ this.value === void 0 && (this.value = "");
617
620
  });
618
621
  }
619
622
  };
620
623
  //#endregion
621
- //#region node_modules/.pnpm/@clack+prompts@1.5.1/node_modules/@clack/prompts/dist/index.mjs
624
+ //#region node_modules/.pnpm/@clack+prompts@1.7.0/node_modules/@clack/prompts/dist/index.mjs
622
625
  function isUnicodeSupported() {
623
626
  if (process$1.platform !== "win32") return process$1.env.TERM !== "linux";
624
627
  return Boolean(process$1.env.CI) || Boolean(process$1.env.WT_SESSION) || Boolean(process$1.env.TERMINUS_SUBLIME) || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
625
628
  }
626
- const unicode = isUnicodeSupported(), unicodeOr = (e, o) => unicode ? e : o, S_STEP_ACTIVE = unicodeOr("◆", "*"), S_STEP_CANCEL = unicodeOr("■", "x"), S_STEP_ERROR = unicodeOr("▲", "x"), S_STEP_SUBMIT = unicodeOr("◇", "o"), S_BAR = unicodeOr("│", "|"), S_BAR_END = unicodeOr("└", "—"), S_PASSWORD_MASK = unicodeOr("▪", "•"), symbol = (e) => {
627
- switch (e) {
629
+ const unicode = isUnicodeSupported();
630
+ const unicodeOr = (o, e) => unicode ? o : e;
631
+ const S_STEP_ACTIVE = unicodeOr("◆", "*");
632
+ const S_STEP_CANCEL = unicodeOr("■", "x");
633
+ const S_STEP_ERROR = unicodeOr("▲", "x");
634
+ const S_STEP_SUBMIT = unicodeOr("◇", "o");
635
+ const S_BAR = unicodeOr("│", "|");
636
+ const S_BAR_END = unicodeOr("└", "—");
637
+ const S_PASSWORD_MASK = unicodeOr("▪", "•");
638
+ const symbol = (o) => {
639
+ switch (o) {
628
640
  case "initial":
629
641
  case "active": return styleText("cyan", S_STEP_ACTIVE);
630
642
  case "cancel": return styleText("red", S_STEP_CANCEL);
631
643
  case "error": return styleText("yellow", S_STEP_ERROR);
632
644
  case "submit": return styleText("green", S_STEP_SUBMIT);
633
645
  }
634
- }, cancel = (o = "", t) => {
646
+ };
647
+ `${styleText("dim", "↑/↓")}`, `${styleText("dim", "Space:")}`, `${styleText("dim", "Enter:")}`;
648
+ const cancel = (o = "", t) => {
635
649
  const i = t?.output ?? process.stdout, e = t?.withGuide ?? settings.withGuide ? `${styleText("gray", S_BAR_END)} ` : "";
636
650
  i.write(`${e}${styleText("red", o)}
637
651
 
638
652
  `);
639
653
  };
640
- const password = (r) => new o({
654
+ const password = (r) => new u$1({
641
655
  validate: r.validate,
642
656
  mask: r.mask ?? S_PASSWORD_MASK,
643
657
  signal: r.signal,
@@ -664,6 +678,7 @@ ${e ? styleText("cyan", S_BAR_END) : ""}
664
678
  }
665
679
  }
666
680
  }).prompt();
681
+ `${styleText("dim", "↑/↓")}`, `${styleText("dim", "Enter:")}`;
667
682
  `${styleText("gray", S_BAR)}`;
668
683
  //#endregion
669
684
  //#region src/cli/subcommands/subcommands/add.ts