mc8yp 2.3.3 → 2.5.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 +225 -307
- package/dist/cli.mjs +9376 -43086
- package/dist/{creds-NskvbfG3.mjs → creds-PUpnfVX2.mjs} +1 -1
- package/dist/{remove-CAB5quKV.mjs → remove-C6FCjW31.mjs} +6 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,186 +1,203 @@
|
|
|
1
|
-
# mc8yp
|
|
1
|
+
# mc8yp — Cumulocity API access for AI agents
|
|
2
2
|
|
|
3
3
|

|
|
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 just two code-mode tools instead of a huge fixed tool inventory:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **`query`** — inspect the OpenAPI specs available on the current tenant
|
|
10
|
+
- **`execute`** — call the live Cumulocity API
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
- **DTM API**
|
|
12
|
+
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 exposes them alongside the bundled ones. No code changes or rebuild required to support a new service.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
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
15
|
|
|
16
|
-
|
|
17
|
-
- `execute` — call the live Cumulocity API
|
|
18
|
-
|
|
19
|
-
The result is an MCP integration where agents can work across the broader Cumulocity platform, while operators still keep **fine-grained control** over what is actually allowed at runtime.
|
|
20
|
-
|
|
21
|
-
mc8yp is available in two modes:
|
|
22
|
-
|
|
23
|
-
- **Cumulocity microservice mode** for production use with [AI Agent Manager](https://cumulocity.com/docs/ai/aim-introduction/)
|
|
24
|
-
- **CLI mode** for local debugging, testing, and development
|
|
16
|
+
## How it works
|
|
25
17
|
|
|
26
|
-
|
|
18
|
+
1. mc8yp discovers every microservice installed on the tenant that declares an OpenAPI spec, and exposes those alongside the bundled Core (+ DTM) specs.
|
|
19
|
+
2. The agent uses `query` to inspect the available specs and pick an endpoint.
|
|
20
|
+
3. The agent uses `execute` to call the live Cumulocity API.
|
|
21
|
+
4. mc8yp enforces configured restrictions and allow rules before the request leaves the host.
|
|
27
22
|
|
|
28
|
-
###
|
|
23
|
+
### Live microservice API discovery
|
|
29
24
|
|
|
30
|
-
|
|
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 `serviceSpecs[contextPath]`. Results are cached per tenant for 30 minutes.
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
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.
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
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.
|
|
35
30
|
|
|
36
|
-
|
|
31
|
+
## Two ways to run it
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
- **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.
|
|
34
|
+
- **CLI mode** (local development) — run locally over stdio with an MCP client such as Claude Desktop. Credentials are stored in the OS keyring.
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
---
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
## Quick start — Microservice (recommended)
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
1. Download the latest release zip from [GitHub Releases](https://github.com/schplitt/mc8yp/releases).
|
|
41
|
+
2. Upload the `.zip` in Cumulocity **Application Management**.
|
|
42
|
+
3. Subscribe the application in your tenant.
|
|
43
|
+
4. Point your agent at:
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
- **sandboxed execution** and a tenant-host network boundary
|
|
50
|
-
- normal **Cumulocity permissions** from the authenticated user or service user
|
|
45
|
+
```txt
|
|
46
|
+
https://<tenant>.cumulocity.com/service/mc8yp-server/mcp
|
|
47
|
+
```
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
No extra credential setup is required — the microservice uses Cumulocity's deployment environment and request authentication.
|
|
53
50
|
|
|
54
|
-
-
|
|
55
|
-
- **non-destructive production agents**
|
|
56
|
-
- agents limited to **inventory**, **alarms**, or other selected API families
|
|
57
|
-
- agents allowed to write only to a small approved set of endpoints
|
|
51
|
+
The microservice manifest declares `exposeMcpServers`, so once the application is subscribed it auto-registers with AI Agent Manager as an MCP server at `/service/mc8yp-server/mcp` (with the user's authentication forwarded). No manual MCP server entry is needed in AI Agent Manager.
|
|
58
52
|
|
|
59
|
-
|
|
53
|
+
**Example: read-only production agent** (allow only safe GETs):
|
|
60
54
|
|
|
61
|
-
|
|
55
|
+
```txt
|
|
56
|
+
/mcp?allow=GET:/inventory/**&allow=GET:/alarm/**&allow=GET:/measurement/**
|
|
57
|
+
```
|
|
62
58
|
|
|
63
|
-
|
|
59
|
+
Or via headers:
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
```http
|
|
62
|
+
POST /mcp HTTP/1.1
|
|
63
|
+
mc8yp-allow: GET:/inventory/**
|
|
64
|
+
mc8yp-allow: GET:/alarm/**
|
|
65
|
+
mc8yp-allow: GET:/measurement/**
|
|
66
|
+
```
|
|
69
67
|
|
|
70
|
-
|
|
68
|
+
See [Access policy](#access-policy) for the full rule syntax.
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
---
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
## Quick start — Local CLI
|
|
75
73
|
|
|
76
|
-
|
|
74
|
+
### Platform support
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
| Platform | Supported | Notes |
|
|
77
|
+
| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
78
|
+
| macOS | ✅ native | Keychain is used for credentials |
|
|
79
|
+
| Linux | ✅ native | Secret Service (libsecret) is used for credentials |
|
|
80
|
+
| Windows | ❌ | Use [WSL 2](https://learn.microsoft.com/windows/wsl/) (see [WSL 2 one-time setup](#wsl-2-one-time-setup) below) or the microservice mode instead |
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
The sandboxed V8 runtime ([`@iso4/sandbox`](https://www.npmjs.com/package/@iso4/sandbox)) communicates with a Rust subprocess over Unix domain sockets, which is why native Windows is not supported.
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
> (`@iso4/sandbox`) communicates with a Rust subprocess over Unix domain sockets, which are not
|
|
87
|
-
> available on Windows. If you are on Windows, use
|
|
88
|
-
> [WSL 2](https://learn.microsoft.com/windows/wsl/) or connect to a
|
|
89
|
-
> [Cumulocity microservice deployment](#1-cumulocity-microservice-mode-recommended) instead.
|
|
84
|
+
### Install and run
|
|
90
85
|
|
|
91
|
-
|
|
86
|
+
```sh
|
|
87
|
+
# Run directly (recommended)
|
|
88
|
+
pnpm dlx mc8yp
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
- validating access-policy setups before deployment
|
|
96
|
-
- working with MCP clients such as Claude Desktop
|
|
90
|
+
# Pick a specific bundled core OpenAPI build for `query`
|
|
91
|
+
pnpm dlx mc8yp --spec 2025
|
|
97
92
|
|
|
98
|
-
|
|
93
|
+
# Or install globally
|
|
94
|
+
npm install -g mc8yp
|
|
95
|
+
mc8yp
|
|
96
|
+
```
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
### Add credentials
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
2. Upload the `.zip` in **Application Management**
|
|
104
|
-
3. Subscribe the application in your tenant
|
|
105
|
-
4. Connect your agent workflow to:
|
|
100
|
+
`mc8yp creds add` prompts for tenant URL, username, and a masked password, and writes them to the OS keyring.
|
|
106
101
|
|
|
107
|
-
```
|
|
108
|
-
|
|
102
|
+
```sh
|
|
103
|
+
pnpm dlx mc8yp creds add # add credentials (interactive, masked password)
|
|
104
|
+
pnpm dlx mc8yp creds list # list stored credentials
|
|
105
|
+
pnpm dlx mc8yp creds remove # remove stored credentials
|
|
109
106
|
```
|
|
110
107
|
|
|
111
|
-
|
|
108
|
+
On macOS and standard desktop Linux this works out of the box. On **WSL 2** the keyring stack is not wired up by default and needs a one-time bootstrap:
|
|
112
109
|
|
|
113
|
-
|
|
110
|
+
<details>
|
|
111
|
+
<summary><strong>WSL 2 one-time setup</strong> — required before <code>mc8yp creds add</code> works on WSL</summary>
|
|
114
112
|
|
|
115
|
-
|
|
113
|
+
A fresh WSL 2 distro has no Secret Service provider, no session D-Bus, and no `login` keyring collection, so `@napi-rs/keyring` (used by `mc8yp creds add`) has nothing to talk to. On a normal desktop Linux all of this is wired up automatically by the display manager and PAM; on WSL you have to do it once manually.
|
|
116
114
|
|
|
117
|
-
|
|
115
|
+
**1. Inside WSL, install the keyring stack:**
|
|
118
116
|
|
|
119
|
-
```
|
|
120
|
-
|
|
117
|
+
```sh
|
|
118
|
+
sudo apt install -y libsecret-tools dbus-x11
|
|
119
|
+
sudo apt install -y libpam-gnome-keyring
|
|
121
120
|
```
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
- `libsecret-tools` provides `secret-tool` and pulls in `libsecret` (the client library `@napi-rs/keyring` uses).
|
|
123
|
+
- `dbus-x11` provides `dbus-launch` so a session D-Bus can be started in a headless shell.
|
|
124
|
+
- `libpam-gnome-keyring` installs `gnome-keyring-daemon` (the actual Secret Service provider) and its PAM module.
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
mc8yp-allow: GET:/measurement/**
|
|
126
|
+
**2. Force the keyring database to initialize:**
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
secret-tool store --label="init" init init
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
A throwaway write so `gnome-keyring-daemon` creates its on-disk store.
|
|
133
|
+
|
|
134
|
+
**3. Wire up PAM so the keyring auto-unlocks at login:**
|
|
133
135
|
|
|
134
136
|
```sh
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
sudo bash -c 'cat >> /etc/pam.d/login <<EOF
|
|
138
|
+
auth optional pam_gnome_keyring.so
|
|
139
|
+
session optional pam_gnome_keyring.so auto_start
|
|
140
|
+
EOF'
|
|
141
|
+
```
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
pnpm dlx mc8yp --spec 2025
|
|
143
|
+
**4. From PowerShell, fully restart WSL so PAM picks up the new config:**
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
mc8yp
|
|
145
|
+
```powershell
|
|
146
|
+
wsl --shutdown
|
|
144
147
|
```
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
The interactive `mc8yp creds add` flow uses masked password input and stores credentials in your operating system's secure credential manager.
|
|
149
|
+
**5. Back in WSL, start a session D-Bus (WSL doesn't get one by default):**
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
-
|
|
151
|
+
```sh
|
|
152
|
+
echo $DBUS_SESSION_BUS_ADDRESS # should be empty
|
|
153
|
+
eval $(dbus-launch --sh-syntax)
|
|
154
|
+
```
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
**6. Create the `login` collection that libsecret writes into.** On a normal desktop this is created by the graphical login session; on WSL it does not exist and credential writes will fail without it:
|
|
155
157
|
|
|
156
158
|
```sh
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
gdbus call --session \
|
|
160
|
+
--dest org.freedesktop.secrets \
|
|
161
|
+
--object-path /org/freedesktop/secrets \
|
|
162
|
+
--method org.freedesktop.Secret.Service.OpenSession \
|
|
163
|
+
"plain" \
|
|
164
|
+
"<''>"
|
|
165
|
+
|
|
166
|
+
gdbus call --session \
|
|
167
|
+
--dest org.freedesktop.secrets \
|
|
168
|
+
--object-path /org/freedesktop/secrets \
|
|
169
|
+
--method org.freedesktop.Secret.Service.CreateCollection \
|
|
170
|
+
"{'org.freedesktop.Secret.Collection.Label': <'login'>}" \
|
|
171
|
+
""
|
|
172
|
+
```
|
|
159
173
|
|
|
160
|
-
|
|
161
|
-
pnpm dlx mc8yp creds list
|
|
174
|
+
**7. Trigger the keyring passphrase prompt once:**
|
|
162
175
|
|
|
163
|
-
|
|
164
|
-
|
|
176
|
+
```sh
|
|
177
|
+
secret-tool store --label="test" service myservice username myuser
|
|
165
178
|
```
|
|
166
179
|
|
|
167
|
-
|
|
180
|
+
This opens a prompt to set the keyring passphrase. You can leave it **empty** — the keyring will then auto-unlock without prompting later, which is what you want for headless WSL.
|
|
168
181
|
|
|
169
|
-
|
|
182
|
+
After this, `mc8yp creds add` will work.
|
|
170
183
|
|
|
171
|
-
|
|
184
|
+
</details>
|
|
172
185
|
|
|
173
|
-
|
|
174
|
-
2. Call `set-active-tenant` with one of the tenant URLs from `cli-status`. The selection is written to `~/.config/mc8yp/active-tenant.json` and re-applied automatically on every subsequent CLI start.
|
|
175
|
-
3. Call `query` and `execute` as needed. The query footer and execute marker keep the active tenant visible on every result.
|
|
186
|
+
### Activate a tenant
|
|
176
187
|
|
|
177
|
-
|
|
188
|
+
Adding credentials does **not** auto-activate a tenant. `execute` only runs against a live tenant once one has been selected, and the agent does that itself through MCP tools:
|
|
178
189
|
|
|
179
|
-
|
|
190
|
+
1. The agent calls `status` to see stored credentials, the current active tenant, and the specs currently visible to `query`.
|
|
191
|
+
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 `query` and `execute` as needed. Each result includes a footer or marker line showing which tenant it ran against.
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
To switch tenants, call `set-active-tenant` again. To stop targeting any tenant (browse bundled specs only), call it with `tenantUrl: null` — `query` keeps working, `execute` returns a missing-auth error so the agent cannot accidentally hit a tenant.
|
|
182
195
|
|
|
183
|
-
|
|
196
|
+
If the active tenant's credentials are removed via `mc8yp creds remove`, the next `status` call clears the active tenant automatically.
|
|
197
|
+
|
|
198
|
+
### Connect a local MCP client
|
|
199
|
+
|
|
200
|
+
For Claude Desktop or any stdio MCP client:
|
|
184
201
|
|
|
185
202
|
```json
|
|
186
203
|
{
|
|
@@ -194,7 +211,7 @@ For Claude Desktop or any MCP client, add:
|
|
|
194
211
|
}
|
|
195
212
|
```
|
|
196
213
|
|
|
197
|
-
|
|
214
|
+
With read-only access rules:
|
|
198
215
|
|
|
199
216
|
```json
|
|
200
217
|
{
|
|
@@ -205,67 +222,33 @@ Example with read-only access rules:
|
|
|
205
222
|
"args": [
|
|
206
223
|
"dlx",
|
|
207
224
|
"mc8yp",
|
|
208
|
-
"-a",
|
|
209
|
-
"GET:/
|
|
210
|
-
"-a",
|
|
211
|
-
"GET:/alarm/**",
|
|
212
|
-
"-a",
|
|
213
|
-
"GET:/measurement/**"
|
|
225
|
+
"-a", "GET:/inventory/**",
|
|
226
|
+
"-a", "GET:/alarm/**",
|
|
227
|
+
"-a", "GET:/measurement/**"
|
|
214
228
|
]
|
|
215
229
|
}
|
|
216
230
|
}
|
|
217
231
|
}
|
|
218
232
|
```
|
|
219
233
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
The `query` tool exposes the bundled OpenAPI snapshots included by this project:
|
|
223
|
-
|
|
224
|
-
- **Core** snapshots: `release`, `2026`, `2025`, and `2024`
|
|
225
|
-
- **DTM** snapshot: bundled alongside each supported core build
|
|
226
|
-
|
|
227
|
-
In CLI mode, use `--spec` or `-s` to choose which bundled **core** OpenAPI snapshot `query` exposes:
|
|
228
|
-
|
|
229
|
-
```sh
|
|
230
|
-
# Default: latest bundled release build
|
|
231
|
-
mc8yp
|
|
232
|
-
|
|
233
|
-
# Explicitly use the 2025 bundled build
|
|
234
|
-
mc8yp --spec 2025
|
|
235
|
-
|
|
236
|
-
# Short form
|
|
237
|
-
mc8yp -s 2024
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
This only changes the bundled OpenAPI data that `query` sees. The `execute` tool still calls the live Cumulocity API of the selected tenant or deployed service environment.
|
|
241
|
-
|
|
242
|
-
## Tools & Prompts
|
|
243
|
-
|
|
244
|
-
### Tools
|
|
245
|
-
|
|
246
|
-
| Tool | Description |
|
|
247
|
-
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
248
|
-
| `query` | Search and inspect the bundled and discovered OpenAPI specs by running a JavaScript function expression. The sandbox exposes `coreSpec` and `serviceSpecs` (microservice APIs keyed by `contextPath`). |
|
|
249
|
-
| `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. |
|
|
250
|
-
| `cli-status` | _(CLI mode only)_ Read the active tenant (or note that none is set) and the list of stored credentials from your system keyring. Auto-clears the active tenant if its credentials have been removed. Call this before `query` / `execute` so you know which tenant they will hit. |
|
|
251
|
-
| `set-active-tenant` | _(CLI mode only)_ Select the tenant `query` and `execute` operate against, persisted to `~/.config/mc8yp/active-tenant.json` across CLI restarts. Pass `tenantUrl: null` to clear the selection and fall back to browsing the bundled OpenAPI snapshots. |
|
|
252
|
-
|
|
253
|
-
Both code-mode tools run in a sandboxed V8 runtime ([@iso4/sandbox](https://github.com/schplitt/iso4)) hosted in a separate Rust subprocess.
|
|
234
|
+
---
|
|
254
235
|
|
|
255
|
-
|
|
256
|
-
- `execute` returns the successful function result in [Toon format](https://github.com/nicepkg/toon). If execution is blocked or fails, it returns a plain text message instead. In CLI mode every `execute` result is prefixed with an `Executed against tenant: <url>` marker line so the active tenant is always visible — the active tenant is global to a CLI session and can be flipped between calls by `set-active-tenant`.
|
|
236
|
+
## Tools and prompts
|
|
257
237
|
|
|
258
|
-
|
|
238
|
+
| Tool | Description |
|
|
239
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
240
|
+
| `query` | Inspect the bundled and discovered OpenAPI specs by running a JavaScript function expression in a sandbox. Exposes `coreSpec` and `serviceSpecs` (keyed by `contextPath`). Returns JSON text. |
|
|
241
|
+
| `execute` | Run an async JavaScript function expression that calls the live Cumulocity API via `cumulocity.request({ method, path, body?, headers? })`. Returns the function result in [Toon format](https://github.com/nicepkg/toon). |
|
|
242
|
+
| `status` | _(CLI only)_ Show the active tenant, stored credentials, and the specs currently visible to `query`. 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. |
|
|
243
|
+
| `set-active-tenant` | _(CLI only)_ Select the tenant `query` and `execute` operate against. Pass `tenantUrl: null` to clear. |
|
|
259
244
|
|
|
260
|
-
|
|
261
|
-
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
262
|
-
| `code-mode-guide` | Full reference for the `query` and `execute` tools, including available types, examples, and access-policy info for the current connection. |
|
|
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).
|
|
263
246
|
|
|
264
|
-
|
|
247
|
+
The **`code-mode-guide`** prompt contains the full reference for `query` and `execute`, including types, examples, and the active access policy for the current connection.
|
|
265
248
|
|
|
266
|
-
|
|
249
|
+
### `execute` input shape
|
|
267
250
|
|
|
268
|
-
|
|
251
|
+
`execute` expects an async function expression:
|
|
269
252
|
|
|
270
253
|
```js
|
|
271
254
|
async () => {
|
|
@@ -276,7 +259,7 @@ async () => {
|
|
|
276
259
|
}
|
|
277
260
|
```
|
|
278
261
|
|
|
279
|
-
You can
|
|
262
|
+
You can do intermediate work before returning:
|
|
280
263
|
|
|
281
264
|
```js
|
|
282
265
|
async () => {
|
|
@@ -285,63 +268,35 @@ async () => {
|
|
|
285
268
|
path: '/inventory/managedObjects?pageSize=20&withTotalPages=true',
|
|
286
269
|
})
|
|
287
270
|
|
|
288
|
-
return devices.managedObjects?.map((
|
|
271
|
+
return devices.managedObjects?.map((d) => ({ id: d.id, name: d.name }))
|
|
289
272
|
}
|
|
290
273
|
```
|
|
291
274
|
|
|
292
|
-
|
|
275
|
+
---
|
|
293
276
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- **Restrictions** — deny rules that block matching API operations
|
|
297
|
-
- **Allow rules** — allow-list rules that permit matching API operations and block everything else when at least one allow rule is configured
|
|
298
|
-
|
|
299
|
-
If both apply to the same operation, **restrictions take priority**.
|
|
300
|
-
|
|
301
|
-
This is what makes it possible to expose broad API capability while still keeping an agent in a **read-only** or otherwise **non-destructive** operating mode.
|
|
302
|
-
|
|
303
|
-
Example: allowing `/inventory/**` but restricting `/inventory/managedObjects` still blocks `/inventory/managedObjects`.
|
|
277
|
+
## Access policy
|
|
304
278
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
### Restrictions
|
|
308
|
-
|
|
309
|
-
Restrictions are deny rules that block specific API operations.
|
|
310
|
-
|
|
311
|
-
### Allow Rules
|
|
279
|
+
mc8yp supports two per-connection rule types:
|
|
312
280
|
|
|
313
|
-
|
|
281
|
+
- **Restrictions** — deny rules that block matching API operations.
|
|
282
|
+
- **Allow rules** — allow-list rules. When at least one allow rule is set, anything not matching is blocked.
|
|
314
283
|
|
|
315
|
-
|
|
284
|
+
If both apply to the same operation, **restrictions win**. This is how you expose broad API knowledge while still running an agent in a read-only or otherwise constrained mode.
|
|
316
285
|
|
|
317
|
-
|
|
286
|
+
### Rule format
|
|
318
287
|
|
|
319
288
|
```txt
|
|
320
289
|
<path-pattern>
|
|
321
290
|
<method>:<path-pattern>
|
|
322
291
|
```
|
|
323
292
|
|
|
324
|
-
-
|
|
325
|
-
-
|
|
326
|
-
-
|
|
327
|
-
-
|
|
328
|
-
- Method names are case-insensitive when parsed (`get:/inventory/**` becomes `GET:/inventory/**`)
|
|
293
|
+
- 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.
|
|
295
|
+
- Patterns must start with `/`. Query strings and fragments are not allowed in patterns.
|
|
296
|
+
- Wildcards: `*` matches within a single path segment; `**` matches zero or more whole segments and must be its own segment.
|
|
329
297
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
Patterns are matched against the request **pathname**.
|
|
333
|
-
|
|
334
|
-
- Query strings and fragments are **not allowed in rule patterns**
|
|
335
|
-
- Incoming request query strings are ignored for matching, so `/inventory/**` also matches requests such as `/inventory?pageSize=5`
|
|
336
|
-
- Patterns must start with `/`
|
|
337
|
-
- Matching is path-segment aware: `/` separates segments
|
|
338
|
-
|
|
339
|
-
Supported wildcards:
|
|
340
|
-
|
|
341
|
-
- `*` — wildcard **inside a single path segment**. It matches any characters except `/`
|
|
342
|
-
- `**` — recursive wildcard across **zero or more whole path segments**. `**` must be its own complete segment
|
|
343
|
-
|
|
344
|
-
### Path Pattern Examples
|
|
298
|
+
<details>
|
|
299
|
+
<summary><strong>Path pattern examples</strong></summary>
|
|
345
300
|
|
|
346
301
|
| Pattern | Matches | Does Not Match |
|
|
347
302
|
| --------------------- | ----------------------------------------------------------- | ------------------------------------------ |
|
|
@@ -353,160 +308,103 @@ Supported wildcards:
|
|
|
353
308
|
| `/inventory/*/child` | `/inventory/device-1/child`, `/inventory/x/child` | `/inventory/child`, `/inventory/a/b/child` |
|
|
354
309
|
| `/inventory/**/child` | `/inventory/child`, `/inventory/a/b/child` | `/inventory/a/b/sibling` |
|
|
355
310
|
|
|
356
|
-
|
|
311
|
+
Notes:
|
|
312
|
+
|
|
313
|
+
- `/inventory/**` already matches `/inventory` itself.
|
|
314
|
+
- `/i**` is invalid — `**` must be its own segment. Use `/i*/**` instead.
|
|
315
|
+
- Rule patterns may not contain `//`, `.`, `..`, query strings, or fragments.
|
|
316
|
+
|
|
317
|
+
</details>
|
|
318
|
+
|
|
319
|
+
<details>
|
|
320
|
+
<summary><strong>Common rule examples</strong></summary>
|
|
357
321
|
|
|
358
322
|
| Rule | Restriction Effect | Allow-list Effect |
|
|
359
323
|
| -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- |
|
|
360
|
-
| `/inventory/**` | Block all methods on `/inventory` and
|
|
361
|
-
| `DELETE:/inventory/**` | Block only DELETE on `/inventory` and
|
|
324
|
+
| `/inventory/**` | Block all methods on `/inventory` and below | Permit all methods on `/inventory` and below |
|
|
325
|
+
| `DELETE:/inventory/**` | Block only DELETE on `/inventory` and below | Permit only DELETE on `/inventory` and below |
|
|
362
326
|
| `/alarm/alarms` | Block all methods on the exact path `/alarm/alarms` | Permit all methods on the exact path `/alarm/alarms` |
|
|
363
327
|
| `GET:/measurement/measurements` | Block only GET on the exact path `/measurement/measurements` | Permit only GET on the exact path `/measurement/measurements` |
|
|
364
328
|
| `POST:/inventory/managedObjects` | Block creating new managed objects | Permit creating new managed objects |
|
|
365
|
-
| `/i*/**` | Block all routes whose first path segment starts with `i` | Permit all routes whose first path segment starts with `i` |
|
|
366
329
|
| `/user/**` | Block all user management paths | Permit all user management paths |
|
|
367
330
|
|
|
368
|
-
|
|
331
|
+
</details>
|
|
369
332
|
|
|
370
|
-
|
|
371
|
-
- `/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
|
|
372
|
-
- `*:/inventory/**` is allowed and means the same thing as `/inventory/**`
|
|
373
|
-
- 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
|
|
374
|
-
- Rule patterns may not contain empty segments (`//`), `.` or `..` segments, query strings, or fragments
|
|
333
|
+
### CLI usage
|
|
375
334
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
Pass restrictions and allow rules as CLI arguments.
|
|
379
|
-
|
|
380
|
-
Repeat `-r`, `--restrict`, or `--restriction` for deny rules:
|
|
335
|
+
Repeat `-r`, `--restrict`, or `--restriction` for deny rules; `-a`, `--allow`, or `--allowed` for allow rules:
|
|
381
336
|
|
|
382
337
|
```sh
|
|
383
|
-
# Block all inventory access
|
|
384
|
-
mc8yp -r "/inventory/**"
|
|
385
|
-
|
|
386
|
-
# Block deletes on inventory and all alarm access
|
|
338
|
+
# Block all inventory writes and all alarm access
|
|
387
339
|
mc8yp -r "DELETE:/inventory/**" -r "/alarm/**"
|
|
388
340
|
|
|
389
|
-
#
|
|
390
|
-
mc8yp --restrict "/user/**"
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
Repeat `-a`, `--allow`, or `--allowed` for allow rules:
|
|
394
|
-
|
|
395
|
-
```sh
|
|
396
|
-
# Only permit inventory access
|
|
397
|
-
mc8yp -a "/inventory/**"
|
|
398
|
-
|
|
399
|
-
# Permit GET inventory access and POST alarms
|
|
341
|
+
# Only permit GET inventory + POST alarms
|
|
400
342
|
mc8yp --allow "GET:/inventory/**" --allowed "POST:/alarm/**"
|
|
401
343
|
|
|
402
|
-
# Allow inventory broadly, but still block one path
|
|
344
|
+
# Allow inventory broadly, but still block one path
|
|
403
345
|
mc8yp -a "/inventory/**" -r "/inventory/managedObjects"
|
|
404
346
|
```
|
|
405
347
|
|
|
406
|
-
### Microservice
|
|
348
|
+
### Microservice usage (HTTP)
|
|
407
349
|
|
|
408
|
-
|
|
409
|
-
Pass allow rules as `allowed`, `allow`, or `a` query parameters.
|
|
410
|
-
You can also send project-scoped HTTP headers to avoid conflicts with well-known headers:
|
|
350
|
+
Use query parameters or project-scoped headers on the `/mcp` endpoint:
|
|
411
351
|
|
|
412
|
-
- `mc8yp-restriction`
|
|
413
|
-
- `
|
|
352
|
+
- Deny rules: `restriction`, `restrict`, or `r` query params, or `mc8yp-restriction` header.
|
|
353
|
+
- Allow rules: `allowed`, `allow`, or `a` query params, or `mc8yp-allow` header.
|
|
414
354
|
|
|
415
|
-
Both headers accept either repeated header instances or a comma-separated list
|
|
355
|
+
Both headers accept either repeated header instances or a comma-separated list. Query parameters and headers can be combined.
|
|
416
356
|
|
|
417
357
|
```txt
|
|
418
|
-
/mcp?
|
|
419
|
-
/mcp?r=/inventory/**&r=DELETE:/alarm/**
|
|
420
|
-
/mcp?allow=/inventory/**&allowed=POST:/alarm/**
|
|
358
|
+
/mcp?r=/inventory/**&r=DELETE:/alarm/**&allow=GET:/measurement/**
|
|
421
359
|
```
|
|
422
360
|
|
|
423
361
|
```http
|
|
424
362
|
POST /mcp HTTP/1.1
|
|
425
363
|
Authorization: Bearer <token>
|
|
426
364
|
mc8yp-restriction: /inventory/**
|
|
427
|
-
mc8yp-restriction: DELETE:/alarm/**
|
|
428
365
|
mc8yp-allow: GET:/measurement/**
|
|
429
366
|
```
|
|
430
367
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
1. **Query visibility**: The `query` tool exposes resolved OpenAPI specs through `coreSpec` and `serviceSpecs`. With an active tenant, services not installed on that tenant are dropped from the sandbox surface so the agent only sees what is actually reachable. In CLI mode with no active tenant, every bundled snapshot is exposed for reference browsing only — `execute` is unavailable in that state.
|
|
434
|
-
|
|
435
|
-
2. **Request enforcement**: The host-side bridge that backs `cumulocity.request` evaluates restrictions and allow rules before any HTTP request leaves the host. Matching deny rules block first. If any allow rules are configured, requests must also match at least one allow rule. Blocked requests never reach Cumulocity.
|
|
436
|
-
|
|
437
|
-
3. **Network boundary**: The sandbox itself has no `fetch` global. Sandbox code reaches the tenant only through the host-bridged `cumulocity.request` helper, which injects auth, evaluates restriction and allow rules, and issues the live HTTP call via [@iso4/fetch](https://www.npmjs.com/package/@iso4/fetch) (DNS-pinned, SSRF-hardened). Every other network egress is unavailable to the agent.
|
|
438
|
-
|
|
439
|
-
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.
|
|
440
|
-
|
|
441
|
-
## Build And Packaging
|
|
442
|
-
|
|
443
|
-
The repository bundles multiple OpenAPI specs for CLI use and builds one microservice server bundle per configured build version.
|
|
444
|
-
|
|
445
|
-
### Build Outputs
|
|
446
|
-
|
|
447
|
-
`pnpm build` produces:
|
|
368
|
+
When `execute` is blocked by connection policy, the tool returns explanatory text, no request is sent to Cumulocity, and retrying through the same connection will not help.
|
|
448
369
|
|
|
449
|
-
|
|
450
|
-
- Versioned server bundles in `.output/release/`, `.output/2026/`, `.output/2025/`, and `.output/2024/`
|
|
370
|
+
---
|
|
451
371
|
|
|
452
|
-
|
|
372
|
+
## OpenAPI coverage
|
|
453
373
|
|
|
454
|
-
|
|
374
|
+
What the agent sees through `query` comes from two layers:
|
|
455
375
|
|
|
456
|
-
|
|
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 `serviceSpecs[contextPath]`. This works for any service, not just the ones bundled here.
|
|
377
|
+
2. **Bundled snapshots** — shipped with the build so Core and DTM are always available even when discovery hasn't run yet:
|
|
378
|
+
- **Core** snapshots: `release`, `2026`, `2025`, `2024`
|
|
379
|
+
- **DTM** snapshot bundled alongside each supported core build
|
|
457
380
|
|
|
458
|
-
|
|
381
|
+
With an active tenant, services not installed on that tenant are dropped from the sandbox so the agent only sees what is actually reachable.
|
|
459
382
|
|
|
460
|
-
|
|
383
|
+
In CLI mode, pick which **core** snapshot `query` exposes:
|
|
461
384
|
|
|
462
385
|
```sh
|
|
463
|
-
|
|
386
|
+
mc8yp # default: latest bundled release
|
|
387
|
+
mc8yp --spec 2025 # use the 2025 snapshot
|
|
388
|
+
mc8yp -s 2024 # short form
|
|
464
389
|
```
|
|
465
390
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
- `mc8yp-core-release-dtm-v1.2.3.zip`
|
|
469
|
-
- `mc8yp-core-2026-dtm-v1.2.3.zip`
|
|
470
|
-
- `mc8yp-core-2025-dtm-v1.2.3.zip`
|
|
471
|
-
- `mc8yp-core-2024-dtm-v1.2.3.zip`
|
|
391
|
+
This only affects the bundled core view. `execute` always hits the live Cumulocity API of the selected tenant or deployed service environment.
|
|
472
392
|
|
|
473
|
-
|
|
393
|
+
---
|
|
474
394
|
|
|
475
395
|
## Development
|
|
476
396
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
- Node.js ≥24.0.0
|
|
480
|
-
- pnpm
|
|
481
|
-
|
|
482
|
-
### Setup
|
|
397
|
+
Requires Node.js ≥ 24 and pnpm.
|
|
483
398
|
|
|
484
399
|
```sh
|
|
485
400
|
pnpm install
|
|
486
|
-
pnpm
|
|
487
|
-
pnpm
|
|
488
|
-
pnpm
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
### Testing
|
|
492
|
-
|
|
493
|
-
```sh
|
|
494
|
-
# Run tests
|
|
495
|
-
pnpm test:run
|
|
496
|
-
|
|
497
|
-
# Run benchmarks
|
|
498
|
-
pnpm test:bench
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### Run Locally From Source
|
|
502
|
-
|
|
503
|
-
Build first, then point your MCP client at the compiled CLI:
|
|
504
|
-
|
|
505
|
-
```sh
|
|
506
|
-
pnpm build
|
|
401
|
+
pnpm test:run # tests
|
|
402
|
+
pnpm lint:fix # lint with autofix
|
|
403
|
+
pnpm typecheck # tsc --noEmit
|
|
404
|
+
pnpm build # CLI bundle in dist/, server bundles in .output/<version>/
|
|
507
405
|
```
|
|
508
406
|
|
|
509
|
-
|
|
407
|
+
Run locally from source by pointing your MCP client at the built CLI:
|
|
510
408
|
|
|
511
409
|
```json
|
|
512
410
|
{
|
|
@@ -520,6 +418,26 @@ Then add to your local MCP client configuration:
|
|
|
520
418
|
}
|
|
521
419
|
```
|
|
522
420
|
|
|
421
|
+
<details>
|
|
422
|
+
<summary><strong>Release packaging</strong></summary>
|
|
423
|
+
|
|
424
|
+
```sh
|
|
425
|
+
pnpm package:microservices
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Produces one Docker-based Cumulocity zip per bundled server variant in the repository root, e.g.:
|
|
429
|
+
|
|
430
|
+
- `mc8yp-core-release-dtm-v1.2.3.zip`
|
|
431
|
+
- `mc8yp-core-2026-dtm-v1.2.3.zip`
|
|
432
|
+
- `mc8yp-core-2025-dtm-v1.2.3.zip`
|
|
433
|
+
- `mc8yp-core-2024-dtm-v1.2.3.zip`
|
|
434
|
+
|
|
435
|
+
The packaging step writes a temporary generated Dockerfile under `.c8y/`, copies the selected versioned server bundle into `/app/server/`, and installs production dependencies inside a `linux/amd64` Docker image so the per-platform native binaries of `@iso4/sandbox` resolve correctly. The deployed HTTP transport is POST-only (`GET /mcp` returns `405`) because some reverse proxies and Cumulocity ingress layers do not keep a long-lived SSE channel stable enough for reliable MCP tool calls.
|
|
436
|
+
|
|
437
|
+
The build matrix is driven by [`openapi-builds.json`](openapi-builds.json). Core snapshots live under `openapi/core/`, DTM snapshots under `openapi/dtm/`.
|
|
438
|
+
|
|
439
|
+
</details>
|
|
440
|
+
|
|
523
441
|
## License
|
|
524
442
|
|
|
525
443
|
MIT
|