mc8yp 2.0.1 → 2.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  ![License](https://img.shields.io/npm/l/mc8yp)
5
5
  ![Node Version](https://img.shields.io/node/v/mc8yp)
6
6
 
7
- A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gives AI agents full access to the Cumulocity IoT platform through code execution. Instead of exposing dozens of fixed tools, the server provides two code-mode tools — `query` and `execute` — that let the agent write JavaScript to inspect the core OpenAPI spec and call any API endpoint.
7
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gives AI agents full access to the Cumulocity IoT platform through code execution. Instead of exposing dozens of fixed tools, the server provides two code-mode tools — `query` and `execute` — that let the agent write JavaScript to inspect the bundled OpenAPI specs and call any API endpoint.
8
8
 
9
9
  **Two Deployment Modes:**
10
10
 
@@ -19,7 +19,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gi
19
19
  # Run directly (recommended)
20
20
  pnpm dlx mc8yp
21
21
 
22
- # Pick a specific core OpenAPI snapshot for query
22
+ # Pick a specific bundled OpenAPI build for query
23
23
  pnpm dlx mc8yp --spec 2025
24
24
 
25
25
  # Or install globally
@@ -28,7 +28,7 @@ mc8yp
28
28
  ```
29
29
 
30
30
  **Credential Storage:**
31
- Credentials are stored using your operating system's secure credential manager:
31
+ Credentials are stored using your operating system's secure credential manager. The interactive `mc8yp creds add` flow uses hidden password input so the secret is not echoed back in the terminal while you type it.
32
32
 
33
33
  - **macOS**: Keychain
34
34
  - **Windows**: Credential Vault
@@ -50,7 +50,7 @@ No additional credential configuration needed — the microservice uses Cumuloci
50
50
  ### Managing Credentials (CLI)
51
51
 
52
52
  ```sh
53
- # Add credentials (prompts for tenant URL, username, password)
53
+ # Add credentials (prompts for tenant URL, username, and a masked password)
54
54
  pnpm dlx mc8yp creds add
55
55
 
56
56
  # List stored credentials
@@ -60,24 +60,31 @@ pnpm dlx mc8yp creds list
60
60
  pnpm dlx mc8yp creds remove
61
61
  ```
62
62
 
63
- ### Selecting The Core OpenAPI Snapshot (CLI)
63
+ ### Selecting The Bundled OpenAPI Build (CLI)
64
64
 
65
- Use `--spec` or `-s` to choose which bundled core OpenAPI snapshot the `query` tool exposes.
65
+ Use `--spec` or `-s` to choose which bundled **core** OpenAPI snapshot the `query` tool exposes.
66
66
 
67
67
  Supported values are `release`, `2026`, `2025`, and `2024`.
68
68
 
69
+ This flag selects the bundled **core** API version only. The bundled **dtm** OpenAPI snapshot is currently fixed and is included alongside every supported core build.
70
+
71
+ Each bundled CLI build currently contains:
72
+
73
+ - the selected bundled **core** OpenAPI snapshot
74
+ - the bundled **dtm** OpenAPI snapshot
75
+
69
76
  ```sh
70
- # Default: latest bundled release snapshot
77
+ # Default: latest bundled release build
71
78
  mc8yp
72
79
 
73
- # Explicitly use the 2025 core OpenAPI snapshot
80
+ # Explicitly use the 2025 bundled build
74
81
  mc8yp --spec 2025
75
82
 
76
83
  # Short form
77
84
  mc8yp -s 2024
78
85
  ```
79
86
 
80
- This only affects the `query` tool's OpenAPI view. The `execute` tool still calls the live Cumulocity API of the selected tenant or deployed service environment.
87
+ This only affects the bundled OpenAPI data that `query` sees. The `execute` tool still calls the live Cumulocity API of the selected tenant or deployed service environment.
81
88
 
82
89
  ### Connecting to AI Agents
83
90
 
@@ -115,7 +122,7 @@ With restrictions (see [API Restrictions](#api-restrictions)):
115
122
 
116
123
  | Tool | Description |
117
124
  | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
118
- | `query` | Search and inspect the bundled Cumulocity core OpenAPI spec by running a JavaScript module. The spec is injected as a top-level `spec` binding. Export the result with `export default`. |
125
+ | `query` | Search and inspect the bundled OpenAPI specs by running a JavaScript function expression. The sandbox exposes `coreSpec`, `dtmSpec`, and `specsEnabled`, and it never hides bundled specs from the query surface. |
119
126
  | `execute` | Execute JavaScript against the live Cumulocity API. Provide an async JavaScript function expression. A top-level `cumulocity` binding provides `cumulocity.request({ method, path, body?, headers? })`. Return the final value from that function. |
120
127
  | `list-credentials` | _(CLI mode only)_ List stored credentials from your system keyring. |
121
128
 
@@ -154,39 +161,97 @@ async () => {
154
161
 
155
162
  ### Prompts
156
163
 
157
- | Prompt | Description |
158
- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
159
- | `code-mode-guide` | Full reference for the `query` and `execute` tools, including available types, examples, and restriction info for the current connection. |
164
+ | Prompt | Description |
165
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
166
+ | `code-mode-guide` | Full reference for the `query` and `execute` tools, including available types, examples, and access-policy info for the current connection. |
167
+
168
+ ## API Access Policy
169
+
170
+ mc8yp supports two per-connection rule types:
171
+
172
+ - **Restrictions** — deny rules that block matching API operations
173
+ - **Allow rules** — allow-list rules that permit matching API operations and block everything else when at least one allow rule is configured
174
+
175
+ If both apply to the same operation, **restrictions take priority**.
176
+
177
+ Example: allowing `/inventory/**` but restricting `/inventory/managedObjects` still blocks `/inventory/managedObjects`.
178
+
179
+ Both rule types use the same syntax.
180
+
181
+ ### Restrictions
160
182
 
161
- ## API Restrictions
183
+ Restrictions are deny rules that block specific API operations.
162
184
 
163
- Restrictions are deny rules that block specific API operations. They can be applied per-connection to limit what an AI agent can access.
185
+ ### Allow Rules
186
+
187
+ Allow rules are the inverse of restrictions. They define what is permitted. When one or more allow rules are configured, any operation that does not match at least one allow rule is blocked.
164
188
 
165
189
  ### Rule Format
166
190
 
191
+ A restriction or allow rule can be written in either of these forms:
192
+
193
+ ```txt
194
+ <path-pattern>
195
+ <method>:<path-pattern>
167
196
  ```
168
- [METHOD:]<path-pattern>
169
- ```
170
197
 
171
- - **Without a method prefix** — blocks all HTTP methods for matching paths
172
- - **With a method prefix** — blocks only that method (e.g. `GET:`, `DELETE:`, `POST:`)
173
- - **Path patterns** support `*` (single segment wildcard) and `**` (recursive wildcard)
174
- - Query strings and fragments are not allowed in patterns
198
+ - **Without a method prefix** — matches all HTTP methods for matching paths
199
+ - **With a method prefix** — matches only that method (for example `GET:`, `DELETE:`, `POST:`)
200
+ - **The `:` separator is only present when a method prefix is provided**
201
+ - **Supported methods** — `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, `PUT`, `TRACE`, or `*`
202
+ - Method names are case-insensitive when parsed (`get:/inventory/**` becomes `GET:/inventory/**`)
203
+
204
+ ### Path Pattern Syntax
205
+
206
+ Patterns are matched against the request **pathname**.
207
+
208
+ - Query strings and fragments are **not allowed in rule patterns**
209
+ - Incoming request query strings are ignored for matching, so `/inventory/**` also matches requests such as `/inventory?pageSize=5`
210
+ - Patterns must start with `/`
211
+ - Matching is path-segment aware: `/` separates segments
212
+
213
+ Supported wildcards:
214
+
215
+ - `*` — wildcard **inside a single path segment**. It matches any characters except `/`
216
+ - `**` — recursive wildcard across **zero or more whole path segments**. `**` must be its own complete segment
217
+
218
+ ### Path Pattern Examples
219
+
220
+ | Pattern | Matches | Does Not Match |
221
+ | --------------------- | ----------------------------------------------------------- | ------------------------------------------ |
222
+ | `/inventory` | `/inventory` | `/inventory/managedObjects` |
223
+ | `/inventory/**` | `/inventory`, `/inventory/managedObjects`, `/inventory/x/y` | `/alarm/alarms` |
224
+ | `/i*` | `/inventory`, `/identity`, `/i` | `/inventory/managedObjects` |
225
+ | `/i*/**` | `/inventory`, `/inventory/managedObjects`, `/identity/x` | `/alarm/alarms` |
226
+ | `/inventory/m*` | `/inventory/managedObjects`, `/inventory/measurements` | `/inventory/events`, `/inventory/m/x` |
227
+ | `/inventory/*/child` | `/inventory/device-1/child`, `/inventory/x/child` | `/inventory/child`, `/inventory/a/b/child` |
228
+ | `/inventory/**/child` | `/inventory/child`, `/inventory/a/b/child` | `/inventory/a/b/sibling` |
175
229
 
176
- ### Examples
230
+ ### Common Rule Examples
177
231
 
178
- | Rule | Effect |
179
- | -------------------------------- | --------------------------------------------------- |
180
- | `/inventory/**` | Block all methods on all inventory paths |
181
- | `DELETE:/inventory/**` | Block only DELETE on inventory paths |
182
- | `/alarm/alarms` | Block all methods on the exact path `/alarm/alarms` |
183
- | `GET:/measurement/measurements` | Block only GET on measurements |
184
- | `POST:/inventory/managedObjects` | Block creating new managed objects |
185
- | `/user/**` | Block all user management |
232
+ | Rule | Restriction Effect | Allow-list Effect |
233
+ | -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- |
234
+ | `/inventory/**` | Block all methods on `/inventory` and everything below it | Permit all methods on `/inventory` and everything below it |
235
+ | `DELETE:/inventory/**` | Block only DELETE on `/inventory` and everything below it | Permit only DELETE on `/inventory` and everything below it |
236
+ | `/alarm/alarms` | Block all methods on the exact path `/alarm/alarms` | Permit all methods on the exact path `/alarm/alarms` |
237
+ | `GET:/measurement/measurements` | Block only GET on the exact path `/measurement/measurements` | Permit only GET on the exact path `/measurement/measurements` |
238
+ | `POST:/inventory/managedObjects` | Block creating new managed objects | Permit creating new managed objects |
239
+ | `/i*/**` | Block all routes whose first path segment starts with `i` | Permit all routes whose first path segment starts with `i` |
240
+ | `/user/**` | Block all user management paths | Permit all user management paths |
241
+
242
+ ### Important Notes
243
+
244
+ - `/inventory/**` already matches `/inventory` itself, so you do **not** need both `/inventory` and `/inventory/**`
245
+ - `/i**` is **not valid** because `**` must be its own segment. Use `/i*/**` if you want to match a first segment starting with `i` and everything below it
246
+ - `*:/inventory/**` is allowed and means the same thing as `/inventory/**`
247
+ - Root paths across the bundled core and DTM specs are intentionally treated as disjoint, so path-based restriction and allow rules are enough for request enforcement
248
+ - Rule patterns may not contain empty segments (`//`), `.` or `..` segments, query strings, or fragments
186
249
 
187
250
  ### CLI Mode
188
251
 
189
- Pass restrictions as CLI arguments. Repeat `-r` / `--restriction` for multiple rules:
252
+ Pass restrictions and allow rules as CLI arguments.
253
+
254
+ Repeat `-r`, `--restrict`, or `--restriction` for deny rules:
190
255
 
191
256
  ```sh
192
257
  # Block all inventory access
@@ -195,31 +260,78 @@ mc8yp -r "/inventory/**"
195
260
  # Block deletes on inventory and all alarm access
196
261
  mc8yp -r "DELETE:/inventory/**" -r "/alarm/**"
197
262
 
198
- # Block everything under user management
199
- mc8yp --restriction "/user/**"
263
+ # Same thing using the long alias
264
+ mc8yp --restrict "/user/**"
265
+ ```
266
+
267
+ Repeat `-a`, `--allow`, or `--allowed` for allow rules:
268
+
269
+ ```sh
270
+ # Only permit inventory access
271
+ mc8yp -a "/inventory/**"
272
+
273
+ # Permit GET inventory access and POST alarms
274
+ mc8yp --allow "GET:/inventory/**" --allowed "POST:/alarm/**"
275
+
276
+ # Allow inventory broadly, but still block one path with a restriction
277
+ mc8yp -a "/inventory/**" -r "/inventory/managedObjects"
278
+ ```
279
+
280
+ To forbid one or more bundled OpenAPI parts for execute policy, repeat `--disable-openapi` (or `-d`) in CLI mode. `query` still sees all bundled specs and can inspect `specsEnabled` to understand which spec families remain enabled for execute policy:
281
+
282
+ ```sh
283
+ # Disable bundled DTM APIs for execute policy on this CLI connection
284
+ mc8yp -d dtm
285
+
286
+ # Disable multiple bundled specs if more are added in the future
287
+ mc8yp -d dtm -d core
200
288
  ```
201
289
 
202
290
  ### Microservice Mode (HTTP)
203
291
 
204
- Pass restrictions as `restriction` query parameters on the MCP endpoint URL:
292
+ Pass restrictions as `restriction`, `restrict`, or `r` query parameters on the MCP endpoint URL.
293
+ Pass allow rules as `allowed`, `allow`, or `a` query parameters.
294
+ To forbid bundled OpenAPI parts for execute policy, pass the `openapi-disabled` query parameter.
295
+ You can also send project-scoped HTTP headers to avoid conflicts with well-known headers:
296
+
297
+ - `mc8yp-restriction` for deny rules
298
+ - `mc8yp-allow` for allow-list rules
299
+ - `mc8yp-openapi-disabled` for bundled OpenAPI part disablement
205
300
 
301
+ Both headers accept either repeated header instances or a comma-separated list of values. Query parameters and headers can be combined on the same connection.
302
+
303
+ ```
304
+ /mcp?restriction=/inventory/**&restrict=DELETE:/alarm/**
305
+ /mcp?r=/inventory/**&r=DELETE:/alarm/**
306
+ /mcp?allow=/inventory/**&allowed=POST:/alarm/**
307
+ /mcp?openapi-disabled=dtm
308
+ /mcp?openapi-disabled=dtm&openapi-disabled=core
206
309
  ```
207
- /mcp?restriction=/inventory/**&restriction=DELETE:/alarm/**
310
+
311
+ ```http
312
+ POST /mcp HTTP/1.1
313
+ Authorization: Bearer <token>
314
+ mc8yp-restriction: /inventory/**
315
+ mc8yp-restriction: DELETE:/alarm/**
316
+ mc8yp-allow: GET:/measurement/**
317
+ mc8yp-openapi-disabled: dtm
208
318
  ```
209
319
 
210
- ### How Restrictions Work
320
+ ### How Access Policy Works
321
+
322
+ 1. **Query visibility**: The `query` tool exposes the raw bundled OpenAPI specs for the current MCP connection. Bundled specs are not hidden or rewritten, and the query sandbox exposes `specsEnabled` so the model can see which spec families are enabled for execute policy.
211
323
 
212
- 1. **OpenAPI spec annotation**: The `query` tool annotates blocked operations in the spec with `x-mc8yp-restricted` and related `x-mc8yp-*` metadata fields. The operations remain visible so the agent understands what exists, but they are clearly marked as blocked.
324
+ 2. **Sandbox request enforcement**: The `execute` tool checks restrictions and allow rules inside the generated sandbox request helper, where the actual HTTP method and normalized path are both available. Matching deny rules block first. If any allow rules are configured, requests must also match at least one allow rule.
213
325
 
214
- 2. **Sandbox request enforcement**: The `execute` tool checks restrictions inside the generated sandbox request helper, where the actual HTTP method and normalized path are both available. Matching requests are blocked before any `fetch` is attempted.
326
+ 3. **Bundled spec disablement**: When the connection disables bundled OpenAPI parts such as `dtm`, `query` still shows all bundled specs, while the server expands that selection into additional restrictions so `execute` stays path-and-method based.
215
327
 
216
- 3. **Network boundary**: The secure-exec permission layer independently restricts network access to the configured tenant host. Other network operations are denied.
328
+ 4. **Network boundary**: The secure-exec permission layer independently restricts network access to the configured tenant host. Other network operations are denied.
217
329
 
218
- When an `execute` request is blocked by MCP restrictions, the tool returns explanatory text stating that the operation was intentionally denied by MCP connection policy, no request was sent to Cumulocity, and retrying through the same connection will not help.
330
+ When an `execute` request is blocked by MCP connection policy, the tool returns explanatory text stating whether the operation was denied by a restriction or blocked because it is outside the configured allow list, no request was sent to Cumulocity, and retrying through the same connection will not help.
219
331
 
220
332
  ## Build And Packaging
221
333
 
222
- The repository bundles multiple core OpenAPI snapshots for CLI use and builds one microservice server bundle per snapshot version.
334
+ The repository bundles multiple OpenAPI specs for CLI use and builds one microservice server bundle per configured build version.
223
335
 
224
336
  ### Build Outputs
225
337
 
@@ -228,7 +340,7 @@ The repository bundles multiple core OpenAPI snapshots for CLI use and builds on
228
340
  - CLI bundle in `dist/`
229
341
  - Versioned server bundles in `.output/release/`, `.output/2026/`, `.output/2025/`, and `.output/2024/`
230
342
 
231
- The versions built are driven by [`openapi-versions.json`](openapi-versions.json). Each server bundle contains only its own `core-openapi/<version>.json` snapshot.
343
+ The build matrix is driven by [`openapi-builds.json`](openapi-builds.json). Core snapshots live under `openapi/core/`, DTM snapshots live under `openapi/dtm/`, and each server bundle contains the configured combination for that build.
232
344
 
233
345
  ### Release Packaging
234
346
 
@@ -244,10 +356,10 @@ pnpm package:microservices
244
356
 
245
357
  That command creates one zip per bundled server variant in the repository root, for example:
246
358
 
247
- - `mc8yp-release-v1.2.3.zip`
248
- - `mc8yp-2026-v1.2.3.zip`
249
- - `mc8yp-2025-v1.2.3.zip`
250
- - `mc8yp-2024-v1.2.3.zip`
359
+ - `mc8yp-core-release-dtm-v1.2.3.zip`
360
+ - `mc8yp-core-2026-dtm-v1.2.3.zip`
361
+ - `mc8yp-core-2025-dtm-v1.2.3.zip`
362
+ - `mc8yp-core-2024-dtm-v1.2.3.zip`
251
363
 
252
364
  The GitHub release workflow uses that packaging command when building tagged releases.
253
365
 
@@ -3,6 +3,7 @@ import { defineCommand } from "citty";
3
3
  import consola from "consola";
4
4
  import * as v from "valibot";
5
5
  import { exit } from "node:process";
6
+ import { cancel, isCancel, password } from "@clack/prompts";
6
7
  //#region src/cli/subcommands/subcommands/add.ts
7
8
  const command = defineCommand({
8
9
  meta: {
@@ -20,10 +21,17 @@ const command = defineCommand({
20
21
  type: "text",
21
22
  cancel: "reject"
22
23
  });
23
- const password = await consola.prompt("Password:", {
24
- type: "text",
25
- cancel: "reject"
24
+ const passwordPrompt = await password({
25
+ message: "Password:",
26
+ clearOnError: true,
27
+ validate: (value) => {
28
+ if (!value) return "Password is required.";
29
+ }
26
30
  });
31
+ if (isCancel(passwordPrompt)) {
32
+ cancel("Cancelled.");
33
+ exit();
34
+ }
27
35
  if ((await getStoredC8yAuth()).some((cred) => cred.tenantUrl === cleanTenantUrl(tenantUrl))) {
28
36
  if (!await consola.prompt("Credentials for this tenant already exist. Overwrite?", {
29
37
  type: "confirm",
@@ -36,7 +44,7 @@ const command = defineCommand({
36
44
  await setStoredC8yAuth({
37
45
  tenantUrl,
38
46
  user,
39
- password
47
+ password: passwordPrompt
40
48
  });
41
49
  consola.success("Credentials saved successfully!");
42
50
  exit();