mc8yp
mc8yp is an MCP server that gives AI agents typed access to the Cumulocity IoT API surface through a sandboxed JavaScript environment, with integrated API discovery, documentation search, and access control.
codemode — Run async JavaScript in a secure V8 sandbox to:
Discover APIs: Call
codemode.describe()for an overview of all available API namespacesSearch methods: Use
codemode.search(query)to fuzzy-search over method names, REST paths, and summariesInspect signatures: Use
codemode.describe('namespace.method')for typed input/output interfaces and parameter docsSearch prose docs: Use
docs.search(query)anddocs.read(id)for domain guides, query language references, and API conceptsCall live APIs: Invoke typed methods like
c8y.getManagedObjectCollectionResource({...})or any auto-discovered microservice namespaceAuto-discovered microservices: Any microservice on the tenant with an OpenAPI spec (or MCP server) is automatically exposed as a typed namespace
In-memory data sandbox (microservice mode, opt-in): Run shell tools (
jq,awk,sqlite3, etc.) against fetched data without network access
status (CLI only) — View stored credentials, the active tenant, and visible API namespaces; pass refresh: true to force re-discovery of microservice APIs.
set-active-tenant (CLI only) — Select or clear the active Cumulocity tenant for API calls; persists across restarts.
Access Control: Operators can configure deny and allow rules based on HTTP methods and path patterns; blocked operations are hidden from discovery entirely.
Deployment: Run as a CLI (stdio) for local development with OS keyring credential storage, or as a microservice inside Cumulocity IoT for production use.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mc8yplist all devices in inventory"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mc8yp — Cumulocity API access for AI agents
mc8yp is a Model Context Protocol (MCP) 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:
codemode— run an async JavaScript function in a sandbox where API discovery, documentation search, and typed live API calls are all available as globals
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).
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.
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.
How it works
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.
Inside a
codemoderun, the agent finds the right method withcodemode.search, inspects its exact input/output types withcodemode.describe, and reads prose documentation (domain query languages, parameter syntax) withdocs.search/docs.read.codemode.describeworks 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 — anddescribe("<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.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.mc8yp enforces configured restrictions and allow rules before any request leaves the host — blocked operations are also omitted from discovery entirely.
Live microservice API discovery
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.
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.
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).
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.
External MCP servers (per connection)
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:
mc8yp-mcp-server: {"name":"github","url":"https://api.githubcopilot.com/mcp/","token":"ghp_…"}
mc8yp-mcp-server: {"name":"linear","url":"https://mcp.linear.app/mcp"}Field | Required | Meaning |
| yes | The sandbox namespace ( |
| yes | Absolute |
| no | Sent as |
| no | Extra request headers, applied after |
| no | Shown in the |
The CLI takes the same JSON via a repeatable flag:
mc8yp-cli --mcp-server '{"name":"github","url":"https://api.githubcopilot.com/mcp/","token":"ghp_…"}'Behaviour worth knowing:
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.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).The agent is told which namespaces are external.
codemode.describe()labels themEXTERNAL 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.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.
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.The tenant wins name collisions. An external entry whose
namematches a tenant namespace is skipped, so a header cannot shadow a real service. That skip is silent to the caller — usePOST /resolve-mcp-servers(below) to catch it while configuring rather than discovering it as a missing namespace at run time.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.
Path-based restriction/allow rules do not apply to these namespaces, same as for tenant-discovered MCP tools (see Access policy).
Checking a configuration before you store it — POST /resolve-mcp-servers
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:
POST /service/mc8yp-server/resolve-mcp-servers
Content-Type: application/json
{ "servers": [{ "name": "MaStR registry", "url": "https://mastr.example/mcp", "token": "…" }] }{
"tenantUrl": "https://…cumulocity.com",
"tenantId": "t123",
"tenantNamespaces": ["c8y", "dtm", "knowledge_base_ms"], // null = the check could not run, see `warnings`
"warnings": [],
"servers": [
{
"name": "MaStR registry",
"namespace": "MaStR_registry", // derived — store THIS and send it in the header from now on
"status": "ok", // ok | invalid | namespace-taken | unreachable
"server": { "name": "mastr-mcp", "version": "1.0.0" },
"tools": [{ "name": "search_units", "description": "…" }]
}
]
}namemay be free-form here. It is sanitized into the namespace exactly as a contextPath is (MaStR registry→MaStR_registry, the same rule that makesknowledge-base-msintoknowledge_base_ms). An identifier passes through unchanged.statusis the field to branch on, andnamespace-takenis 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 —namespaceTakenBysays who holds it. Tools are still reported for a taken entry, so renaming is the only fix needed.The header contract does not change. Derivation is a configure-time convenience: resolve once, store the
namespaceyou got back, keep sending it verbatim. The agent-visible namespace stays stable even if the derivation is refined later.Validation is the header's own, so this route cannot green-light an entry the header would then 400.
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.
Requires user auth (Authorization header or session cookie), like
POST /refresh-apis.
Related MCP server: @restforge-dev/mcp-server
Two ways to run it
Microservice mode (recommended for production) — deploy inside Cumulocity IoT, expose
/mcp, integrate with AI Agent Manager. Auth comes from the request and the service user.CLI mode (local development) — run locally over stdio with an MCP client such as Claude Desktop. Credentials are stored in the OS keyring.
Quick start — Microservice (recommended)
Download the latest release zip from GitHub Releases.
Upload the
.zipin Cumulocity Application Management.Subscribe the application in your tenant.
Point your agent at:
https://<tenant>.cumulocity.com/service/mc8yp-server/mcp
No extra credential setup is required — the microservice uses Cumulocity's deployment environment and request authentication.
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.
Example: read-only production agent (allow only safe GETs):
/mcp?allow=GET:/inventory/**&allow=GET:/alarm/**&allow=GET:/measurement/**Or via headers:
POST /mcp HTTP/1.1
mc8yp-allow: GET:/inventory/**
mc8yp-allow: GET:/alarm/**
mc8yp-allow: GET:/measurement/**See Access policy for the full rule syntax.
Quick start — Local CLI
Platform support
Platform | Supported | Notes |
macOS | ✅ native | Keychain is used for credentials |
Linux | ✅ native | Secret Service (libsecret) is used for credentials |
Windows | ❌ | Use WSL 2 (see WSL 2 one-time setup below) or the microservice mode instead |
The sandboxed V8 runtime (@iso4/sandbox) communicates with a Rust subprocess over Unix domain sockets, which is why native Windows is not supported.
Install and run
# Run directly (recommended)
pnpm dlx mc8yp
# Pick a specific bundled core OpenAPI build for the codemode tool
pnpm dlx mc8yp --spec 2025
# Or install globally
npm install -g mc8yp
mc8ypAdd credentials
mc8yp creds add prompts for tenant URL, username, and a masked password, and writes them to the OS keyring.
pnpm dlx mc8yp creds add # add credentials (interactive, masked password)
pnpm dlx mc8yp creds list # list stored credentials
pnpm dlx mc8yp creds remove # remove stored credentialsOn 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:
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.
1. Inside WSL, install the keyring stack:
sudo apt install -y libsecret-tools dbus-x11
sudo apt install -y libpam-gnome-keyringlibsecret-toolsprovidessecret-tooland pulls inlibsecret(the client library@napi-rs/keyringuses).dbus-x11providesdbus-launchso a session D-Bus can be started in a headless shell.libpam-gnome-keyringinstallsgnome-keyring-daemon(the actual Secret Service provider) and its PAM module.
2. Force the keyring database to initialize:
secret-tool store --label="init" init initA throwaway write so gnome-keyring-daemon creates its on-disk store.
3. Wire up PAM so the keyring auto-unlocks at login:
sudo bash -c 'cat >> /etc/pam.d/login <<EOF
auth optional pam_gnome_keyring.so
session optional pam_gnome_keyring.so auto_start
EOF'4. From PowerShell, fully restart WSL so PAM picks up the new config:
wsl --shutdown5. Back in WSL, start a session D-Bus (WSL doesn't get one by default):
echo $DBUS_SESSION_BUS_ADDRESS # should be empty
eval $(dbus-launch --sh-syntax)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:
gdbus call --session \
--dest org.freedesktop.secrets \
--object-path /org/freedesktop/secrets \
--method org.freedesktop.Secret.Service.OpenSession \
"plain" \
"<''>"
gdbus call --session \
--dest org.freedesktop.secrets \
--object-path /org/freedesktop/secrets \
--method org.freedesktop.Secret.Service.CreateCollection \
"{'org.freedesktop.Secret.Collection.Label': <'login'>}" \
""7. Trigger the keyring passphrase prompt once:
secret-tool store --label="test" service myservice username myuserThis 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.
After this, mc8yp creds add will work.
Activate a tenant
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:
The agent calls
statusto see stored credentials, the current active tenant, and the API namespaces currently visible.The agent calls
set-active-tenantwith one of the tenant URLs. The selection is written to~/.config/mc8yp/active-tenant.jsonand reused across CLI restarts.The agent runs
codemodeas needed. Each result starts with a marker line showing which tenant it ran against.
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.
If the active tenant's credentials are removed via mc8yp creds remove, the next status call clears the active tenant automatically.
Connect a local MCP client
For Claude Desktop or any stdio MCP client:
{
"servers": {
"mc8yp": {
"type": "stdio",
"command": "pnpm",
"args": ["dlx", "mc8yp"]
}
}
}With read-only access rules:
{
"servers": {
"mc8yp": {
"type": "stdio",
"command": "pnpm",
"args": [
"dlx",
"mc8yp",
"-a", "GET:/inventory/**",
"-a", "GET:/alarm/**",
"-a", "GET:/measurement/**"
]
}
}
}Add to Claude Code
The quickest way to register mc8yp is the Claude Code CLI. Everything after -- is passed to the mc8yp subprocess, so access-policy flags go there:
# Local CLI (stdio) — default scope is this project only
claude mcp add mc8yp -- pnpm dlx mc8yp
# Make it available in every project (user scope)
claude mcp add -s user mc8yp -- pnpm dlx mc8yp
# Pin a bundled core spec and add read-only access rules
claude mcp add mc8yp -- pnpm dlx mc8yp --spec 2025 \
-a "GET:/inventory/**" -a "GET:/alarm/**" -a "GET:/measurement/**"For deployed microservice mode, add it as an HTTP server instead:
claude mcp add --transport http mc8yp \
https://<tenant>.cumulocity.com/service/mc8yp-server/mcp \
--header "Authorization: Bearer <token>"Manage the entry with claude mcp list, claude mcp get mc8yp, and claude mcp remove mc8yp.
Tools and prompts
Tool | Description |
| Run an async JavaScript function in a sandbox with discovery ( |
| (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 |
| (CLI only) Select the tenant |
The codemode tool runs in a sandboxed V8 runtime (@iso4/sandbox) 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, which injects auth, enforces the access policy, and parses responses before anything reaches the sandbox.
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.
The sandbox surface
async () => {
// 1. Find the method
const { results } = await codemode.search('managed objects')
// 2. Inspect its exact typed interface (input/output types, per-field docs)
const { content } = await codemode.describe(results[0].target)
// 3. When parameter syntax is unknown, search the prose documentation
const hits = await docs.search('inventory query language')
const grammar = await docs.read(hits[0].id)
// 4. Call it — path/query/header params and `body` share one flat object
const devices = await c8y.getManagedObjectCollectionResource({
query: '$filter=(type eq \'c8y_Device\')',
pageSize: 20,
})
return devices.managedObjects?.map((d) => ({ id: d.id, name: d.name }))
}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.
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.
Sandbox surface (sandbox) — microservice mode only, opt-in
Experimental. Available in deployed microservice mode only, and disabled by default; not exposed in the local CLI (agent harnesses there bring their own file I/O).
In microservice mode, codemode has one more optional 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.
It is off unless you turn it on. Enable it per connection with the mc8yp-enable-sandbox header or the enableSandbox query param (any of empty, *, or true). Without it, sandbox is absent — same as CLI mode.
async () => {
const alarms = await c8y.getAlarmCollectionResource({ pageSize: 2000 })
await sandbox.writeFile('/alarms.json', JSON.stringify(alarms.alarms ?? []))
const { stdout } = await sandbox.exec('jq "group_by(.severity) | map({severity: .[0].severity, count: length})" /alarms.json')
return JSON.parse(stdout)
}The surface mirrors Flue's SandboxApi: 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, core shell only) so the provider can be replaced later without changing agent-facing code.
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.)
Access policy
mc8yp supports two per-connection rule types:
Restrictions — deny rules that block matching API operations.
Allow rules — allow-list rules. When at least one allow rule is set, anything not matching is blocked.
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.
Rule format
<path-pattern>
<method>:<path-pattern>No method prefix → matches all HTTP methods.
With a method prefix → only that method. Supported:
DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,QUERY,TRACE, or*. Case-insensitive.Patterns must start with
/. Query strings and fragments are not allowed in patterns.Wildcards:
*matches within a single path segment;**matches zero or more whole segments and must be its own segment.
Pattern | Matches | Does Not Match |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Notes:
/inventory/**already matches/inventoryitself./i**is invalid —**must be its own segment. Use/i*/**instead.Rule patterns may not contain
//,.,.., query strings, or fragments.
Rule | Restriction Effect | Allow-list Effect |
| Block all methods on | Permit all methods on |
| Block only DELETE on | Permit only DELETE on |
| Block all methods on the exact path | Permit all methods on the exact path |
| Block only GET on the exact path | Permit only GET on the exact path |
| Block creating new managed objects | Permit creating new managed objects |
| Block all user management paths | Permit all user management paths |
CLI usage
Repeat -r, --restrict, or --restriction for deny rules; -a, --allow, or --allowed for allow rules:
# Block all inventory writes and all alarm access
mc8yp -r "DELETE:/inventory/**" -r "/alarm/**"
# Only permit GET inventory + POST alarms
mc8yp --allow "GET:/inventory/**" --allowed "POST:/alarm/**"
# Allow inventory broadly, but still block one path
mc8yp -a "/inventory/**" -r "/inventory/managedObjects"Microservice usage (HTTP)
Use query parameters or project-scoped headers on the /mcp endpoint:
Deny rules:
restriction,restrict, orrquery params, ormc8yp-restrictionheader.Allow rules:
allowed,allow, oraquery params, ormc8yp-allowheader.
Both headers accept either repeated header instances or a comma-separated list. Query parameters and headers can be combined.
/mcp?r=/inventory/**&r=DELETE:/alarm/**&allow=GET:/measurement/**POST /mcp HTTP/1.1
Authorization: Bearer <token>
mc8yp-restriction: /inventory/**
mc8yp-allow: GET:/measurement/**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.
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
noMcpopt-out to disable MCP wrapping for a connection if that matters for your deployment. The same gap applies to external MCP servers; there the lever is simply not configuring the server for that connection.
OpenAPI coverage
What the agent sees through codemode discovery comes from two layers:
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.Bundled snapshots — shipped with the build so Core and DTM are always available even when discovery hasn't run yet:
Core snapshots:
release,2026,2025,2024DTM snapshot bundled alongside each supported core build
With an active tenant, services not installed on that tenant get no namespace at all, so the agent only sees what is actually reachable.
In CLI mode, pick which core snapshot the c8y namespace derives from:
mc8yp # default: latest bundled release
mc8yp --spec 2025 # use the 2025 snapshot
mc8yp -s 2024 # short formThis only affects the bundled core view. Live calls always hit the Cumulocity API of the selected tenant or deployed service environment.
Development
Requires Node.js ≥ 24 and pnpm.
pnpm install
pnpm test:run # tests
pnpm lint:fix # lint with autofix
pnpm typecheck # tsc --noEmit
pnpm build # CLI bundle in dist/, server bundles in .output/<version>/Run locally from source by pointing your MCP client at the built CLI:
{
"servers": {
"local_mc8yp": {
"type": "stdio",
"command": "node",
"args": ["/path/to/your/project/dist/cli.mjs"]
}
}
}pnpm package:microservicesProduces one Docker-based Cumulocity zip per bundled server variant in the repository root, e.g.:
mc8yp-core-release-dtm-v1.2.3.zipmc8yp-core-2026-dtm-v1.2.3.zipmc8yp-core-2025-dtm-v1.2.3.zipmc8yp-core-2024-dtm-v1.2.3.zip
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.
The build matrix is driven by openapi-builds.json. Core snapshots live under openapi/core/, DTM snapshots under openapi/dtm/.
License
MIT
Available Tools
3 toolscodemodeCumulocity Code ModeA
Read first. Every result starts with a marker line: either Executed against tenant: <url> or a no-active-tenant notice. Verify it matches the tenant you intend to act on before reporting the result. The active tenant is global to this CLI session and can be flipped between calls by set-active-tenant. Without an active tenant, discovery (codemode/docs) works against bundled reference snapshots but live API calls fail with a missing-auth error — call status and set-active-tenant to connect.
A method visible in discovery may still return 404 when the service is not actually installed on the current tenant.
Run an async JavaScript function against the Cumulocity API. Discovery, documentation, and typed API calls all happen inside one function — find what you need and call it in the same run.
The expensive mistake is a hasty API call: an unparameterized request returns large payloads that flood your context. search and describe are cheap — spend calls there first.
Do NOT rely on prior knowledge of these APIs and do not assume anything: the available namespaces and their capabilities are tenant-specific and discovered at runtime. What you think you know about an API may be outdated or may be outclassed by a service on this tenant you have never seen. Verify through search, describe, and docs.
Available globals (do NOT declare these as function parameters):
declare const codemode: {
/** Ranked fuzzy search over API method names, REST paths, and summaries. Returns the top 20 by score. Pass several phrasings at once — results are unioned. */
search: (query: string | string[]) => Promise<{
results: Array<{ target: string, namespace: string, method: string, httpMethod?: string, apiPath?: string, summary?: string, score: number }>
total: number
truncated: boolean
}>
/**
* No target: overview of the namespaces on THIS tenant and their
* responsibilities — ALWAYS your first call.
* "namespace.method": the typed interface for ONE method — signature,
* input/output types with OpenAPI docs as JSDoc, related doc ids.
* Array of method targets (max 5): describe a shortlist in one call and
* COMPARE their input types before picking.
* "namespace": every method in it, ONE LINE each (name, path, summary) and
* NO types — for surveying a domain. The overview's method count is the
* cost; on a large namespace search first.
*/
describe: (target?: string | string[]) => Promise<
{ target: string, kind: 'overview' | 'namespace' | 'method', content: string }
| Array<{ target: string, kind: 'overview' | 'namespace' | 'method', content: string }>
>
}
declare const docs: {
/** Fuzzy full-text search over prose documentation topics: domain query languages, concepts, API-area guides. Per-method details live in codemode.describe instead. */
search: (query: string, opts?: { limit?: number, fuzzy?: number, prefix?: boolean, minScore?: number, maxTextLength?: number }) => Promise<Array<{
id: string, title: string, text: string, truncated: boolean, kind: 'topic' | 'overview', namespace: string, score: number
}>>
/**
* Full untruncated text of a documentation entry. Return it WHOLE — never
* slice or truncate doc text: the crucial capability (an operator, a
* function, a constraint) is often documented near the end, and a blind
* cut loses exactly the part you searched for. Long doc topics are the
* one output where length is justified.
*/
read: (id: string) => Promise<{ id: string, title: string, text: string, kind: string, namespace: string }>
}
// API namespaces: `c8y` (Cumulocity core — always present) plus one global
// per microservice available on the current tenant (e.g. `dtm`), plus any
// external MCP server configured for this connection — each with one typed
// method per operation. `codemode.describe()` lists what this connection
// actually has. If a method seems missing, search with different wording; if
// it truly does not exist, say so instead of improvising:
// await c8y.getManagedObjectCollectionResource({ pageSize: 5 })Workflow — ALWAYS in this order: describe() → search → describe(shortlist) → call.
codemode.describe()(no target) — ALWAYS start here. It lists the API namespaces on THIS tenant with their responsibilities. A domain service (asset management, data preparation, …) usually has a far better API for its domain — server-side hierarchy queries, bulk operations — than composing the generic core API. Decide which namespaces could own the problem's domain, and search with each of their vocabularies.codemode.search(["phrasing 1", "phrasing 2"])— find candidate methods (top 20 by score). Results usually contain several overlapping endpoints (single-item, collection, count, by-external-id, bulk variants) — read all summaries and shortlist every candidate that could satisfy the request in ONE call, don't grab the first hit. If all results come from one namespace, re-check the overview — another namespace may own the domain with a stronger API. If the expected method is missing, re-search with other domain words before concluding it does not exist — checktotaltoo.codemode.describe(["ns.methodA", "ns.methodB"])(max 5) — ALWAYS describe before calling, and describe the whole shortlist in one call. Compare the input types: prefer the method whose parameters push the work to the server — query/filter parameters (especially ones marked@format c8y:queryor documenting a query grammar), hierarchy/recursive selectors, bulk endpoints — over methods that would force per-item calls or client-side filtering. Describing five candidates costs almost nothing; one wrong or unfiltered API call costs more context than all your discovery combined. If search keeps missing the domain's vocabulary,codemode.describe("<ns>")lists every method in that namespace one line each (no types) — the method count from step 1 is what it costs.docs.search("...")/docs.read(id)— when a parameter references domain syntax you don't know, read the docs before guessing values. Read doc texts to the END — never.slice()them: the operator or function you need is often documented in the later sections, and a blind cut loses exactly the crucial part.Call the winner:
await c8y.someMethod({ ...params, body })(path/query/header parameters andbodyshare one flat input object). ONE well-parameterized call beats fetching broadly and filtering in your code, and beats chains of per-item calls. If you catch yourself looping over items to call an API for each one, go back to step 1 — a collection, query, or bulk endpoint usually exists.Return only the data needed to answer — never return raw unfiltered collections.
Discovery and API calls can be combined in a single run. Methods blocked by the connection access policy are omitted from discovery entirely; a blocked live request fails with an explanatory message — that is a connection-level access restriction, not a Cumulocity API failure, and retrying will not help.
Your code must evaluate to an async function. Return the final value you want; on success it is returned in Toon format.
Examples:
async () => {
const { results } = await codemode.search('alarms by severity')
const { content } = await codemode.describe(results[0].target)
return content
}async () => {
return await c8y.getAlarmCollectionResource({ pageSize: 10, severity: 'MAJOR' })
}async () => {
const hits = await docs.search('inventory query language syntax')
return hits.length > 0 ? (await docs.read(hits[0].id)).text : 'no docs found'
}| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | An async JavaScript function expression. The globals codemode, docs, c8y, and per-service namespaces are available automatically — do not declare them as parameters. Return the final result. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses critical behaviors not covered by annotations: every result starts with a marker line for tenant verification; active tenant is global to the session; live API calls fail without an active tenant; methods may return 404 if the service isn't installed; discovery is omitted for blocked methods; and blocked requests fail with explanatory messages. It also warns about context flooding from large payloads and instructs the agent to verify tenant before reporting.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but tightly structured: it opens with the most critical warning ('Read first'), then explains the globals with precise JSDoc-style comments, then gives a numbered workflow followed by examples. Every section earns its place—there's no filler. The front-loading of tenant verification and costly-call warnings is excellent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is exceptionally complete for a tool that executes arbitrary code. It covers tenant semantics, discovery workflow, failure modes, blocking policies, return conventions, and examples. Since there's no output schema, the description explains the return format ('Return the final value you want; on success it is returned in Toon format'). For a highly complex tool, this is near-exhaustive.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds significant meaning beyond the basic schema's `code` parameter. It explains that the code must be an async function expression, that globals are automatically available, and that the final value is returned. It also provides multiple examples. However, the schema itself fully covers the parameter (100% coverage), so the description's additional value is solid but not exhaustive.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool runs an async JavaScript function against the Cumulocity API and provides discovery, documentation, and typed API calls within one function. It names the specific global objects (codemode, docs, c8y) and explains the workflow, distinguishing from sibling tools (status, set-active-tenant) which manage connection state rather than execute code.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides an explicit, mandatory workflow: describe() → search → describe(shortlist) → call. It strongly emphasizes using discovery first to avoid expensive calls, warns against relying on prior knowledge, and gives detailed guidance on when to use docs.search/read and how to prefer server-side filtering. It also explicitly tells the agent to return only needed data and not raw unfiltered collections.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set-active-tenantSet Active TenantA
Set the Cumulocity tenant for this CLI session, or pass tenantUrl: null to clear the active tenant. The tenantUrl must match one returned by the status tool. The selection is persisted across sessions so you only need to call this once (or when switching tenants). Clearing falls back to bundled-only browsing — codemode discovery still works but live API calls are unavailable until a tenant is set again.
| Name | Required | Description | Default |
|---|---|---|---|
| tenantUrl | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full responsibility for behavioral disclosure. It clearly states persistence across sessions, the effect of clearing, fallback to bundled-only browsing, and unavailability of live API calls until a tenant is set again. This is excellent transparency for such a simple state-setting tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences with every sentence earning its place: core action, persistence/constraint, and fallback behavior. It is front-loaded and contains no filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter tool with no output schema and no annotations, the description is complete. It covers setting, clearing, valid input source, persistence, and downstream behavior, giving the agent enough context to select and use the tool correctly without additional lookups.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite the reported 0% schema description coverage, the tool description fully compensates by explaining that tenantUrl must match a value from the status tool and that null clears the active tenant. With only one parameter, this gives the agent everything needed to invoke the tool correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool sets or clears the active Cumulocity tenant, with explicit mention of the null value to clear. It uses a specific verb and resource, and distinguishes itself from status and codemode by connecting tenantUrl to status output and explaining fallback behavior.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage guidance: call once at session start or when switching tenants, pass null to clear, and understand that codemode discovery remains available but live API calls do not. It also tells the agent that the value must come from the status tool, providing clear context for when to use this tool vs alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
statusmc8yp StatusA
Show the current CLI status: stored tenant credentials, which tenant the codemode tool will hit, and the API namespaces visible right now. If no tenant is active, codemode discovery falls back to all bundled OpenAPI snapshots and live API calls are unavailable — set-active-tenant must be called first. This tool also self-heals: if the active tenant has lost its stored credentials it is automatically reset before the status is reported.
Pass refresh: true to force a fresh API spec discovery against the active tenant. Use this after subscribing or unsubscribing a microservice in the tenant — otherwise discovered specs stay cached for 30 minutes. If no tenant is active, refresh: true is a noop.
| Name | Required | Description | Default |
|---|---|---|---|
| refresh | No | When true, bust the API discovery cache for the active tenant and run a fresh discovery before reporting. Noop when no tenant is active. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, description fully carries burden. It discloses self-healing (resets if credentials lost), refresh behavior (busts 30-min cache, noop without tenant), and fallback behavior (bundled snapshots, live calls unavailable). Comprehensive and actionable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with main purpose first, followed by details. Each sentence adds value, though slightly verbose (e.g., explaining fallback in first paragraph could be tighter). Still effective.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema, but description explains what the tool shows (status info). Covers key behaviors, param usage, prerequisites, and edge cases. Missing explicit output format, but sufficient for a status tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single parameter. Description adds meaning by explaining the purpose of refresh and its effect (force fresh discovery, noop when no tenant), which goes beyond the schema's description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it shows CLI status including tenant credentials, active tenant, and API namespaces. It distinguishes from the sibling tool 'codemode' only implicitly by describing its own scope, but lacks explicit differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides when to use: checking status, and when to use refresh flag (after subscribing/unsubscribing microservices). Mentions prerequisite (set-active-tenant if no tenant active). However, does not explicitly state when not to use this tool versus alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
1 tool update
v2.7.2- Added
set-active-tenant
3 tool updates
v2.6.2- Added
codemode - Removed
execute - Removed
set-active-tenant
1 tool update
v2.5.2- Removed
query
5 tool updates
v2.5.1- Changed
execute3 fields changed- changed
Input schema / properties / code / descriptionPrevious value: -"An async JavaScript function expression. The top-level binding `cumulocity` is available automatically. Return the final result from that function. `await` is supported."New value: +"An async JavaScript function expression. The top-level binding `cumulocity` is available automatically. Return the final result. `await` is supported." - removed
Input schema / properties / tenantUrlRemoved value: -{ - "description": "The Cumulocity tenant URL against which the operation is executed.", - "examples": [ - "https://my-tenant.cumulocity.com", - "https://tenantName.acme.com" - ], - "format": "uri", - "type": "string" -} - changed
Input schema / requiredPrevious value: -[ - "code", - "tenantUrl" -]New value: +[ + "code" +]
- Removed
list-credentials - Changed
query1 field changed- changed
Input schema / properties / code / descriptionPrevious value: -"A zero-parameter JavaScript function expression. Do NOT declare `coreSpec`, `dtmSpec`, or `specsEnabled` as function parameters — they are already declared as top-level constants in the surrounding scope and are available in the function body automatically. Writing `(dtmSpec) => ...` would shadow the global binding with an undefined parameter and produce incorrect results. Return the final result from that function. Async functions are supported."New value: +"A zero-parameter JavaScript function expression. coreSpec and serviceSpecs are already declared as top-level constants — do not redeclare them as function parameters. Return the final result. Async functions are supported."
- Added
set-active-tenant - Added
status
3 tool updates
v2.2.3- First observed
execute - First observed
list-credentials - First observed
query
TDQS
Each tool has a clearly distinct purpose: codemode executes async JavaScript with API discovery, status reports the active tenant and CLI state, and set-active-tenant manages the session tenant. There is no functional overlap between them.
Tool names follow no consistent convention: 'codemode' is a single lowercase noun, 'status' is a noun, and 'set-active-tenant' is a hyphenated verb phrase. The mixed styles and lack of a predictable pattern make naming feel ad hoc.
Three tools is well-scoped for a CLI session manager: one core execution tool, one status/health tool, and one configuration tool. Each earns its place without bloat.
The tool surface fully covers the session lifecycle: setting and clearing the active tenant, checking status and refreshing discovery, and executing against the API. No obvious gaps remain for the stated purpose.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
An MCP server that provides an API to LLMs to manage their JumpCloud resources.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Related MCP Servers
- AlicenseBqualityDmaintenanceProduction-grade MCP server that gives AI agents safe access to your local dev environment: filesystem, databases, processes, and OpenAPI specs.15673MIT
- AlicenseAqualityCmaintenanceMCP server that exposes RESTForge capabilities to AI agents, enabling them to set up, configure, generate code, and manage RESTForge projects through natural language.2944MIT
- FlicenseNot gradedqualityDmaintenanceAn MCP server that gives AI agents full access to the Clever Cloud API through three tools: search, execute, and doc, using a code mode pattern to compose API commands.4-
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables AI agents to explore, search, and query API definitions from OpenAPI/Swagger JSON files.59MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/schplitt/mc8yp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server