mc8yp 2.6.2 → 2.7.1
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 +97 -1
- package/dist/cli.mjs +1449 -425
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,76 @@ The same discovery run also picks up MCP servers declared via `exposeMcpServers`
|
|
|
31
31
|
|
|
32
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.
|
|
33
33
|
|
|
34
|
+
### External MCP servers (per connection)
|
|
35
|
+
|
|
36
|
+
Besides the MCP servers a tenant exposes, a connection can bring its own. Pass one `mc8yp-mcp-server` header per server (or a JSON array in a single header); each becomes a codemode namespace for that connection only:
|
|
37
|
+
|
|
38
|
+
```http
|
|
39
|
+
mc8yp-mcp-server: {"name":"github","url":"https://api.githubcopilot.com/mcp/","token":"ghp_…"}
|
|
40
|
+
mc8yp-mcp-server: {"name":"linear","url":"https://mcp.linear.app/mcp"}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Field | Required | Meaning |
|
|
44
|
+
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `name` | yes | The sandbox namespace (`github.<tool>`). Must be a valid JS identifier and must not collide with a reserved namespace (`codemode`, `docs`, `sandbox`, `c8y`, `cumulocity`). |
|
|
46
|
+
| `url` | yes | Absolute `http`/`https` MCP endpoint, used as given. |
|
|
47
|
+
| `token` | no | Sent as `Authorization: Bearer <token>`. |
|
|
48
|
+
| `headers` | no | Extra request headers, applied after `token` so an explicit `authorization` entry replaces the bearer shorthand. |
|
|
49
|
+
| `description` | no | Shown in the `codemode.describe()` overview. Defaults to the server's own `instructions`. |
|
|
50
|
+
|
|
51
|
+
The CLI takes the same JSON via a repeatable flag:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
mc8yp-cli --mcp-server '{"name":"github","url":"https://api.githubcopilot.com/mcp/","token":"ghp_…"}'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Behaviour worth knowing:
|
|
58
|
+
|
|
59
|
+
- **Config is header/flag-only.** There is deliberately no `?mcpServer=` query parameter even though the other connection options have one — the value can carry a bearer token, and query strings end up in access logs and proxy traces.
|
|
60
|
+
- **Tenant credentials are never forwarded.** An external server only ever receives the credentials in its own entry, and these namespaces work even when no tenant is active (CLI before `set-active-tenant`).
|
|
61
|
+
- **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
|
+
- **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
|
+
- **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. 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
|
+
- **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
|
+
- 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
|
+
#### Checking a configuration before you store it — `POST /resolve-mcp-servers`
|
|
69
|
+
|
|
70
|
+
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:
|
|
71
|
+
|
|
72
|
+
```http
|
|
73
|
+
POST /service/mc8yp-server/resolve-mcp-servers
|
|
74
|
+
Content-Type: application/json
|
|
75
|
+
|
|
76
|
+
{ "servers": [{ "name": "MaStR registry", "url": "https://mastr.example/mcp", "token": "…" }] }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```jsonc
|
|
80
|
+
{
|
|
81
|
+
"tenantUrl": "https://…cumulocity.com",
|
|
82
|
+
"tenantId": "t123",
|
|
83
|
+
"tenantNamespaces": ["c8y", "dtm", "knowledge_base_ms"], // null = the check could not run, see `warnings`
|
|
84
|
+
"warnings": [],
|
|
85
|
+
"servers": [
|
|
86
|
+
{
|
|
87
|
+
"name": "MaStR registry",
|
|
88
|
+
"namespace": "MaStR_registry", // derived — store THIS and send it in the header from now on
|
|
89
|
+
"status": "ok", // ok | invalid | namespace-taken | unreachable
|
|
90
|
+
"server": { "name": "mastr-mcp", "version": "1.0.0" },
|
|
91
|
+
"tools": [{ "name": "search_units", "description": "…" }]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- **`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.
|
|
98
|
+
- **`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.
|
|
99
|
+
- **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.
|
|
100
|
+
- **Validation is the header's own**, so this route cannot green-light an entry the header would then 400.
|
|
101
|
+
- **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.
|
|
102
|
+
- Requires user auth (Authorization header or session cookie), like `POST /refresh-apis`.
|
|
103
|
+
|
|
34
104
|
## Two ways to run it
|
|
35
105
|
|
|
36
106
|
- **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.
|
|
@@ -234,6 +304,32 @@ With read-only access rules:
|
|
|
234
304
|
}
|
|
235
305
|
```
|
|
236
306
|
|
|
307
|
+
### Add to Claude Code
|
|
308
|
+
|
|
309
|
+
The quickest way to register mc8yp is the [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI. Everything after `--` is passed to the mc8yp subprocess, so access-policy flags go there:
|
|
310
|
+
|
|
311
|
+
```sh
|
|
312
|
+
# Local CLI (stdio) — default scope is this project only
|
|
313
|
+
claude mcp add mc8yp -- pnpm dlx mc8yp
|
|
314
|
+
|
|
315
|
+
# Make it available in every project (user scope)
|
|
316
|
+
claude mcp add -s user mc8yp -- pnpm dlx mc8yp
|
|
317
|
+
|
|
318
|
+
# Pin a bundled core spec and add read-only access rules
|
|
319
|
+
claude mcp add mc8yp -- pnpm dlx mc8yp --spec 2025 \
|
|
320
|
+
-a "GET:/inventory/**" -a "GET:/alarm/**" -a "GET:/measurement/**"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
For deployed **microservice mode**, add it as an HTTP server instead:
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
claude mcp add --transport http mc8yp \
|
|
327
|
+
https://<tenant>.cumulocity.com/service/mc8yp-server/mcp \
|
|
328
|
+
--header "Authorization: Bearer <token>"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Manage the entry with `claude mcp list`, `claude mcp get mc8yp`, and `claude mcp remove mc8yp`.
|
|
332
|
+
|
|
237
333
|
---
|
|
238
334
|
|
|
239
335
|
## Tools and prompts
|
|
@@ -392,7 +488,7 @@ mc8yp-allow: GET:/measurement/**
|
|
|
392
488
|
|
|
393
489
|
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.
|
|
394
490
|
|
|
395
|
-
> **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.
|
|
491
|
+
> **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. The same gap applies to [external MCP servers](#external-mcp-servers-per-connection); there the lever is simply not configuring the server for that connection.
|
|
396
492
|
|
|
397
493
|
---
|
|
398
494
|
|