mc8yp 2.5.1 → 2.6.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 +67 -42
- package/dist/cli.mjs +4026 -774
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -4,28 +4,31 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
mc8yp is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gives AI agents access to the **full Cumulocity API surface** through
|
|
7
|
+
mc8yp is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gives AI agents access to the **full Cumulocity API surface** through a single code-mode tool instead of a huge fixed tool inventory:
|
|
8
8
|
|
|
9
|
-
- **`
|
|
10
|
-
- **`execute`** — call the live Cumulocity API
|
|
9
|
+
- **`codemode`** — run an async JavaScript function in a sandbox where API discovery, documentation search, and typed live API calls are all available as globals
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
Inside the sandbox the agent sees one **typed namespace per API**: `c8y` for the Cumulocity core REST surface plus one namespace per microservice available on the tenant (e.g. `dtm`), each with one method per operation derived from the OpenAPI specs — `c8y.getAlarmCollectionResource({ pageSize: 10 })` instead of hand-built REST calls. Discovery happens in-sandbox through `codemode.search`/`codemode.describe` (ranked method search + on-demand TypeScript interfaces) and `docs.search`/`docs.read` (fuzzy full-text search over the specs' prose documentation, e.g. query-language grammars).
|
|
12
|
+
|
|
13
|
+
The agent sees not only the bundled **Core** and **DTM** specs, but **any microservice installed on the tenant** that declares an OpenAPI spec in its manifest — mc8yp discovers those live and derives namespaces for them alongside the bundled ones. Services that expose an **MCP server** (`exposeMcpServers` in their manifest) are wrapped as MCP namespaces instead — one typed method per MCP tool, with MCP preferred over the OpenAPI spec when a service declares both. No code changes or rebuild required to support a new service.
|
|
13
14
|
|
|
14
15
|
Operators stay in control through per-connection **restrictions** and **allow rules**, so the same broad capability can be deployed as a read-only agent, a non-destructive production agent, or anything in between.
|
|
15
16
|
|
|
16
17
|
## How it works
|
|
17
18
|
|
|
18
|
-
1. mc8yp discovers every microservice installed on the tenant that declares an OpenAPI spec, and
|
|
19
|
-
2.
|
|
20
|
-
3.
|
|
21
|
-
4. mc8yp enforces configured restrictions and allow rules before
|
|
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
|
+
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
|
+
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
|
+
4. mc8yp enforces configured restrictions and allow rules before any request leaves the host — blocked operations are also omitted from discovery entirely.
|
|
22
23
|
|
|
23
24
|
### Live microservice API discovery
|
|
24
25
|
|
|
25
|
-
When a tenant is active, mc8yp asks Cumulocity which applications the tenant is subscribed to, reads the `openApiSpec` declaration from each application manifest, fetches the spec, prefixes its paths with the service's `contextPath`, and exposes it to the sandbox as
|
|
26
|
+
When a tenant is active, mc8yp asks Cumulocity which applications the tenant is subscribed to, reads the `openApiSpec` declaration from each application manifest, fetches the spec, prefixes its paths with the service's `contextPath`, and exposes it to the sandbox as a typed namespace named after the contextPath. Results are cached per tenant for 30 minutes.
|
|
26
27
|
|
|
27
28
|
The practical effect: **any Cumulocity microservice that ships an OpenAPI spec is automatically usable by the agent**, whether it is one of the bundled snapshots, a Cumulocity-provided service, or a custom microservice built in-house. The bundled specs are just guaranteed offline coverage; the discovery layer fills in everything else.
|
|
28
29
|
|
|
30
|
+
The same discovery run also picks up MCP servers declared via `exposeMcpServers` (type `http`) and fetches their tool lists. A service exposing both an MCP server and an OpenAPI spec is wrapped as an MCP namespace; per connection you can opt services back to their spec with the `mc8yp-no-mcp` header / `noMcp` query param (server mode) or `--no-mcp` (CLI) — pass `*` for all services or a comma-separated contextPath list. Wrapped MCP tools run with the end user's credentials; elicitation and sampling are NOT forwarded (mc8yp advertises no such capabilities, so compliant servers use their fallbacks).
|
|
31
|
+
|
|
29
32
|
When a new service is subscribed mid-session, CLI agents can call the `status` tool with `refresh: true` to bust the cache without waiting for the 30-minute window. Server mode does not yet expose an in-protocol refresh trigger — use the `POST /refresh-apis` HTTP route from ops/CI scripts that sit outside the MCP protocol.
|
|
30
33
|
|
|
31
34
|
## Two ways to run it
|
|
@@ -87,7 +90,7 @@ The sandboxed V8 runtime ([`@iso4/sandbox`](https://www.npmjs.com/package/@iso4/
|
|
|
87
90
|
# Run directly (recommended)
|
|
88
91
|
pnpm dlx mc8yp
|
|
89
92
|
|
|
90
|
-
# Pick a specific bundled core OpenAPI build for
|
|
93
|
+
# Pick a specific bundled core OpenAPI build for the codemode tool
|
|
91
94
|
pnpm dlx mc8yp --spec 2025
|
|
92
95
|
|
|
93
96
|
# Or install globally
|
|
@@ -185,13 +188,13 @@ After this, `mc8yp creds add` will work.
|
|
|
185
188
|
|
|
186
189
|
### Activate a tenant
|
|
187
190
|
|
|
188
|
-
Adding credentials does **not** auto-activate a tenant.
|
|
191
|
+
Adding credentials does **not** auto-activate a tenant. Live API calls only run against a tenant once one has been selected, and the agent does that itself through MCP tools:
|
|
189
192
|
|
|
190
|
-
1. The agent calls `status` to see stored credentials, the current active tenant, and the
|
|
193
|
+
1. The agent calls `status` to see stored credentials, the current active tenant, and the API namespaces currently visible.
|
|
191
194
|
2. The agent calls `set-active-tenant` with one of the tenant URLs. The selection is written to `~/.config/mc8yp/active-tenant.json` and reused across CLI restarts.
|
|
192
|
-
3. The agent runs `
|
|
195
|
+
3. The agent runs `codemode` as needed. Each result starts with a marker line showing which tenant it ran against.
|
|
193
196
|
|
|
194
|
-
To switch tenants, call `set-active-tenant` again. To stop targeting any tenant (browse bundled specs only), call it with `tenantUrl: null` — `
|
|
197
|
+
To switch tenants, call `set-active-tenant` again. To stop targeting any tenant (browse bundled specs only), call it with `tenantUrl: null` — discovery (`codemode.search`/`describe`, `docs`) keeps working against the bundled reference snapshots, while live API calls return a missing-auth error so the agent cannot accidentally hit a tenant.
|
|
195
198
|
|
|
196
199
|
If the active tenant's credentials are removed via `mc8yp creds remove`, the next `status` call clears the active tenant automatically.
|
|
197
200
|
|
|
@@ -235,43 +238,63 @@ With read-only access rules:
|
|
|
235
238
|
|
|
236
239
|
## Tools and prompts
|
|
237
240
|
|
|
238
|
-
| Tool | Description
|
|
239
|
-
| ------------------- |
|
|
240
|
-
| `
|
|
241
|
-
| `
|
|
242
|
-
| `
|
|
243
|
-
| `set-active-tenant` | _(CLI only)_ Select the tenant `query` and `execute` operate against. Pass `tenantUrl: null` to clear. |
|
|
244
|
-
|
|
245
|
-
Both code-mode tools run in a sandboxed V8 runtime ([`@iso4/sandbox`](https://github.com/schplitt/iso4)) hosted in a separate Rust subprocess. The sandbox has no `fetch` global — the only path to the tenant is the host-bridged `cumulocity.request` helper, which is DNS-pinned and SSRF-hardened via [`@iso4/fetch`](https://www.npmjs.com/package/@iso4/fetch).
|
|
241
|
+
| Tool | Description |
|
|
242
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
243
|
+
| `codemode` | Run an async JavaScript function in a sandbox with discovery (`codemode.search`/`describe`), documentation (`docs.search`/`read`), and typed API namespaces (`c8y`, per-service globals) available. Returns the function result in [Toon format](https://github.com/nicepkg/toon). |
|
|
244
|
+
| `status` | _(CLI only)_ Show the active tenant, stored credentials, and the API namespaces currently visible. Auto-clears the active tenant if its credentials are gone. Pass `refresh: true` to bust the 30-minute discovery cache and re-run discovery for the active tenant — useful right after (un)subscribing a microservice. Noop when no tenant is active. |
|
|
245
|
+
| `set-active-tenant` | _(CLI only)_ Select the tenant `codemode` operates against. Pass `tenantUrl: null` to clear. |
|
|
246
246
|
|
|
247
|
-
The
|
|
247
|
+
The codemode tool runs in a sandboxed V8 runtime ([`@iso4/sandbox`](https://github.com/schplitt/iso4)) hosted in a separate Rust subprocess. The sandbox has no `fetch` global — every live call is dispatched host-side through a hardened request funnel built on [`@iso4/fetch`](https://www.npmjs.com/package/@iso4/fetch), which injects auth, enforces the access policy, and parses responses before anything reaches the sandbox.
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
The **`code-mode-guide`** prompt contains the full reference for the codemode tool, including types, examples, and the active access policy for the current connection.
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
### The sandbox surface
|
|
252
252
|
|
|
253
253
|
```js
|
|
254
254
|
async () => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
// 1. Find the method
|
|
256
|
+
const { results } = await codemode.search('managed objects')
|
|
257
|
+
|
|
258
|
+
// 2. Inspect its exact typed interface (input/output types, per-field docs)
|
|
259
|
+
const { content } = await codemode.describe(results[0].target)
|
|
260
|
+
|
|
261
|
+
// 3. When parameter syntax is unknown, search the prose documentation
|
|
262
|
+
const hits = await docs.search('inventory query language')
|
|
263
|
+
const grammar = await docs.read(hits[0].id)
|
|
264
|
+
|
|
265
|
+
// 4. Call it — path/query/header params and `body` share one flat object
|
|
266
|
+
const devices = await c8y.getManagedObjectCollectionResource({
|
|
267
|
+
query: '$filter=(type eq \'c8y_Device\')',
|
|
268
|
+
pageSize: 20,
|
|
258
269
|
})
|
|
270
|
+
|
|
271
|
+
return devices.managedObjects?.map((d) => ({ id: d.id, name: d.name }))
|
|
259
272
|
}
|
|
260
273
|
```
|
|
261
274
|
|
|
262
|
-
|
|
275
|
+
There is deliberately no raw-request escape hatch — the typed namespaces are the complete surface. Whether a namespace wraps an OpenAPI spec or an MCP server is invisible to the agent; the backing protocol is an operator concern.
|
|
276
|
+
|
|
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
|
+
|
|
279
|
+
### Sandbox surface (`sandbox`) — microservice mode only
|
|
280
|
+
|
|
281
|
+
> **Experimental.** Available in deployed microservice mode; not exposed in the local CLI (agent harnesses there bring their own file I/O).
|
|
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.
|
|
263
284
|
|
|
264
285
|
```js
|
|
265
286
|
async () => {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return devices.managedObjects?.map((d) => ({ id: d.id, name: d.name }))
|
|
287
|
+
const alarms = await c8y.getAlarmCollectionResource({ pageSize: 2000 })
|
|
288
|
+
await sandbox.writeFile('/alarms.json', JSON.stringify(alarms.alarms ?? []))
|
|
289
|
+
const { stdout } = await sandbox.exec('jq "group_by(.severity) | map({severity: .[0].severity, count: length})" /alarms.json')
|
|
290
|
+
return JSON.parse(stdout)
|
|
272
291
|
}
|
|
273
292
|
```
|
|
274
293
|
|
|
294
|
+
The surface mirrors [Flue's `SandboxApi`](https://flueframework.com/docs/api/sandbox-api/): `readFile`, `readFileBuffer`, `writeFile`, `stat`, `readdir`, `exists`, `mkdir`, `rm`, `exec`, plus an mc8yp-specific `clear()` that wipes the filesystem. It is backed by a swappable adapter (currently [`just-bash`](https://github.com/vercel-labs/just-bash), core shell only) so the provider can be replaced later without changing agent-facing code.
|
|
295
|
+
|
|
296
|
+
**Lifecycle:** one in-memory sandbox per MCP session, so files persist across `codemode` calls within a session. It is evicted from memory 15 minutes after its last use (or immediately via `sandbox.clear()`). Nothing is ever written to disk, and sessions never share state. (Cross-call persistence relies on your MCP client maintaining the `mcp-session-id`, which standard clients do.)
|
|
297
|
+
|
|
275
298
|
---
|
|
276
299
|
|
|
277
300
|
## Access policy
|
|
@@ -291,7 +314,7 @@ If both apply to the same operation, **restrictions win**. This is how you expos
|
|
|
291
314
|
```
|
|
292
315
|
|
|
293
316
|
- No method prefix → matches all HTTP methods.
|
|
294
|
-
- With a method prefix → only that method. Supported: `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, `PUT`, `TRACE`, or `*`. Case-insensitive.
|
|
317
|
+
- With a method prefix → only that method. Supported: `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, `PUT`, `QUERY`, `TRACE`, or `*`. Case-insensitive.
|
|
295
318
|
- Patterns must start with `/`. Query strings and fragments are not allowed in patterns.
|
|
296
319
|
- Wildcards: `*` matches within a single path segment; `**` matches zero or more whole segments and must be its own segment.
|
|
297
320
|
|
|
@@ -365,22 +388,24 @@ mc8yp-restriction: /inventory/**
|
|
|
365
388
|
mc8yp-allow: GET:/measurement/**
|
|
366
389
|
```
|
|
367
390
|
|
|
368
|
-
When
|
|
391
|
+
When a live call is blocked by connection policy, the codemode run returns explanatory text, no request is sent to Cumulocity, and retrying through the same connection will not help. Blocked operations are additionally omitted from `codemode.search`/`describe` and the docs index, so the agent never plans around a method it cannot call.
|
|
392
|
+
|
|
393
|
+
> **Note:** path-based restriction/allow rules apply to OpenAPI-derived namespaces only. Wrapped MCP tools have no METHOD:path identity and are not covered by these rules — use the `noMcp` opt-out to disable MCP wrapping for a connection if that matters for your deployment.
|
|
369
394
|
|
|
370
395
|
---
|
|
371
396
|
|
|
372
397
|
## OpenAPI coverage
|
|
373
398
|
|
|
374
|
-
What the agent sees through
|
|
399
|
+
What the agent sees through codemode discovery comes from two layers:
|
|
375
400
|
|
|
376
|
-
1. **Live-discovered specs** — every microservice subscribed on the active tenant whose manifest declares an `openApiSpec`. Discovered at runtime, cached for 30 minutes per tenant, exposed as
|
|
401
|
+
1. **Live-discovered specs** — every microservice subscribed on the active tenant whose manifest declares an `openApiSpec`. Discovered at runtime, cached for 30 minutes per tenant, exposed as a typed namespace named after the contextPath. This works for any service, not just the ones bundled here.
|
|
377
402
|
2. **Bundled snapshots** — shipped with the build so Core and DTM are always available even when discovery hasn't run yet:
|
|
378
403
|
- **Core** snapshots: `release`, `2026`, `2025`, `2024`
|
|
379
404
|
- **DTM** snapshot bundled alongside each supported core build
|
|
380
405
|
|
|
381
|
-
With an active tenant, services not installed on that tenant
|
|
406
|
+
With an active tenant, services not installed on that tenant get no namespace at all, so the agent only sees what is actually reachable.
|
|
382
407
|
|
|
383
|
-
In CLI mode, pick which **core** snapshot `
|
|
408
|
+
In CLI mode, pick which **core** snapshot the `c8y` namespace derives from:
|
|
384
409
|
|
|
385
410
|
```sh
|
|
386
411
|
mc8yp # default: latest bundled release
|
|
@@ -388,7 +413,7 @@ mc8yp --spec 2025 # use the 2025 snapshot
|
|
|
388
413
|
mc8yp -s 2024 # short form
|
|
389
414
|
```
|
|
390
415
|
|
|
391
|
-
This only affects the bundled core view.
|
|
416
|
+
This only affects the bundled core view. Live calls always hit the Cumulocity API of the selected tenant or deployed service environment.
|
|
392
417
|
|
|
393
418
|
---
|
|
394
419
|
|