MCP server for LLM agents
Cloudomation ships a built-in MCP (Model Context Protocol) server. It lets your own LLM agents (Claude, Cursor, OpenAI agents, and any other MCP-compatible client) use Cloudomation as one tool among others to create, run and monitor automation content.
The MCP server is served by your workspace, so there is nothing extra to deploy.
Endpoint
https://<your-workspace>.cloudomation.com/api/latest/mcp
The server uses the MCP Streamable HTTP transport. All interaction happens
as request/response over POST.
Authentication
Agents authenticate with an API key on every request, sent either as a bearer token or via a dedicated header:
Authorization: Bearer cldm_<prefix>_<secret>
or
x-cloudomation-api-key: cldm_<prefix>_<secret>
The same API key also works against the full REST API and the GraphQL API.
Creating an API key
- Open the Cloudomation Engine UI and go to the user you want the agent to act as (see "Scoping access" below).
- In the user's detail page, find the API keys (MCP / programmatic access) section.
- Click Create API key, give it a descriptive name, and copy the key. The key is shown only once.
API keys can also be minted via the REST API:
curl -X POST \
-H "x-cloudomation-session-id: <your-session-id>" \
-H "Content-Type: application/json" \
-d '{"name": "Claude agent", "user_id": "<agent-user-id>"}' \
https://<your-workspace>.cloudomation.com/api/latest/api_key
The response contains the plaintext token. Revoke a key with
DELETE /api/latest/api_key/{id} or from the UI.
Scoping access (RBAC)
An API key is bound to a Cloudomation user identity and inherits exactly that user's permissions. To give an agent fine-grained access:
- Create a dedicated user for the agent (for example
claude-agent). - Create a role with
role_permissionentries scoped to the specific projects, record types and operations the agent should be allowed to use (for example:READ/CREATE/UPDATEonFLOWandEXECUTIONin a single project). - Assign the role to the agent user.
- Mint an API key for the agent user.
Because every MCP tool call runs through the same permission checks as the REST API, the agent can only ever see and change what the bound user is allowed to. If a tool call fails with a permission error, the bound user is missing a role.
An execution started with run_flow runs as its own identity, which by default
inherits the roles the calling user holds with the propagate flag set. A role
assigned to the user without propagate is not inherited by the executions it
starts. To control this per call, pass the roles argument to run_flow: a list
of {name, propagate} objects assigns exactly those roles to the new execution,
each with its own propagate flag, and an empty list starts the execution with no
roles. This lets an agent grant an execution a role the calling user holds only
non-propagating, or deliberately drop privileges for a run.
Connecting a client
Most MCP clients accept a remote server URL plus custom headers. For example, a generic configuration looks like:
{
"mcpServers": {
"cloudomation": {
"url": "https://<your-workspace>.cloudomation.com/api/latest/mcp",
"headers": {
"Authorization": "Bearer cldm_<prefix>_<secret>"
}
}
}
}
Refer to your client's documentation for the exact configuration format. MCP
prompts defined in your workspace appear in the client's prompt picker
(slash commands) once the server supports the prompts capability.
Available tools
The server exposes the following tools (discoverable via MCP tools/list):
| Tool | Description |
|---|---|
run_flow | Start an execution of a flow. |
get_execution | Get the status, message and output of an execution. |
get_execution_logs | Get the log entries of an execution. |
wait_for_executions | Block until one or more executions end, then return their details. |
list_executions | List executions newest-first, filtered by flow and/or status. |
list_record_types | List record types (FLOW, PROJECT, CONNECTOR, …). |
list_subrecord_types | List subrecord types (OBJECT_TEMPLATE_ATTRIBUTE, RECORD_METADATA, …). |
describe_record_type | JSON Schemas and field lists for a record type. |
describe_subrecord_type | JSON Schemas and field lists for a subrecord type. |
list_records | List records of one type (uses that type's REST resource and filters). |
list_subrecords | List subrecords of one type, scoped to a parent record. |
get_record | Get one record by id or name; folds the record's recent comments into the result (see Record comments in get_record). |
grep_record | Search one record's large text/JSON body for a pattern; returns only the matching lines with context — token-lean vs. fetching the whole body with get_record. |
read_record_lines | Read a line-range slice of one record's body (token-lean paging of a big script/value/file). |
search_records | Full-text search across all indexed records — locate a record by any word it contains, ranked and permission-filtered. |
get_subrecord | Get one subrecord row by id. |
create_record | Create a record of any type. |
update_record | Patch a record (replace whole fields by name). |
patch_record | Edit one record's large plain-text body (a flow script, file content, hook script) with find/replace edit blocks — token-lean and stale-write-guarded. |
delete_record | Delete a record (soft-delete to trash by default; permanently / recursive flags). |
create_subrecord | Create a subrecord row (including RECORD_METADATA). |
update_subrecord | Patch a subrecord row. |
delete_subrecord | Delete a subrecord row. |
list_pending_changes | List writes staged with commit=false that are not yet committed to git. |
commit_pending_changes | Commit previously staged (commit=false) writes to git as one commit. |
Most workspace content is accessed through the generic record/subrecord tools.
Dedicated execution tools (run_flow, list_executions, get_execution,
get_execution_logs, wait_for_executions) remain for the common run-and-monitor workflow.
Read cloudomation://usage-guide for examples (listing projects/flows/connectors
via list_records, metadata via RECORD_METADATA subrecords, type-specific
filter_ fields).
Restricting the built-in tool surface
An installation can ship a locked-down MCP surface by disabling individual
built-in tools with the MCP_DISABLED_TOOLS
workspace configuration option
— a list of built-in tool names. A disabled tool is neither advertised in
tools/list nor callable via tools/call (it is treated as if it did not
exist). Typical uses:
- Read-only surface — disable the write/content-maintenance tools
(
create_record,update_record,patch_record,delete_record,create_subrecord,update_subrecord,delete_subrecord,commit_pending_changes,run_flow) so agents can only read. - Curated surface — disable all built-in tools and expose only a chosen set of user tools.
The default (empty list) enables every built-in tool. Unknown names in the list are ignored. This setting affects only built-in tools; user tools are governed by RBAC and are unaffected.
Listing parameters
list_records, list_subrecords, and list_executions share optional listing
parameters:
| Parameter | Description |
|---|---|
fields | List of field names to return (each tool has a sensible default set). |
order | Order by a field; - descending, + ascending (e.g. -created_at). Defaults to -modified_at, or -created_at for executions. |
filter_ | Extra filter on type-specific columns, AND-combined with convenience filters. A {"field","op","value"} comparison or an {"and":[...]} / {"or":[...]} of such. Ops: eq, neq, like, notlike, lt, gt, lte, gte, set, unset, in, notin. |
limit / offset | Page through large result sets. |
To check the status of a flow's runs, use list_executions with flow_id or
flow_name (optionally status): it returns status, message and timestamps
directly and is ordered newest-first, so the latest run is the first result.
get_execution_logs and list_pending_changes accept the same limit /
offset / order paging parameters, so you can page long log or pending-change
lists (get_execution_logs defaults to +created_at, oldest first).
Listing custom objects by template
A custom object carries a second type layer on top of the CUSTOM_OBJECT
record type: its object template. To list only the custom objects of one
template, pass object_template_name to list_records:
{
"record_type": "CUSTOM_OBJECT",
"object_template_name": "support-ticket",
"filter_": {"field": "value.status", "op": "eq", "value": "open"}
}
object_template_name is only valid for record_type="CUSTOM_OBJECT"; it
AND-combines with name and filter_.
Attribute projection (custom objects)
A fields entry of the form value.<attr> returns just that attribute of a
custom object's JSON value instead of the whole value blob — a token-lean way
to read only the fields you need. It works on both get_record and
list_records and combines with plain fields:
{
"record_type": "CUSTOM_OBJECT",
"object_template_name": "support-ticket",
"fields": ["id", "name", "value.status", "value.priority"]
}
Record comments in get_record
Comments (RECORD_COMMENT subrecords) are a threaded discussion trail attached
to any record — the place humans leave feedback, questions, and re-open notes.
Because they are a separate subrecord type, they do not appear in a plain record
fetch, so an agent could read, act on, or close a record without ever seeing that
a person had commented on it.
To prevent that, get_record folds a record's most recent comments into its
result under a comments key: an array of {id, author, created_at, body}
(oldest first), with assignee, parent_comment_id, and resolved_at /
resolved_by included only when set. author is resolved to a display name.
The list is bounded to the 20 most recent comments; when older ones exist,
comments_truncated: true and a comments_note point you to
list_subrecords(subrecord_type="RECORD_COMMENT", parent_record_id="<id>") for
the full thread. A record with no comments has no comments key at all, so the
lookup adds no output for the common case.
Pass include_comments: false to skip the comment lookup when you do not need
the discussion (for example, a bulk read where feedback is irrelevant).
Token-lean body access
The generic read tools return a record's whole body. For large bodies (a long
flow script, a big custom-object value, a file content) three tools let
an agent read and edit only the part it needs, spending far fewer tokens:
grep_record— search one record's body for a regular-expression (or, withfixed: true, a literal)patternand get back only the matching lines with surroundingcontext, plus a total match count and atruncatedflag. By default it searches the record type's primary body field (scriptforFLOW/WRAPPER/SCHEDULE,valueforCUSTOM_OBJECT/SETTING/CONNECTOR/SCHEMA,contentforFILE); passfieldto target another column (e.g.on_createon anOBJECT_TEMPLATE). JSON fields are searched as their pretty-printed JSON. For a search across many records uselist_recordswithfilter_(orsearch_recordsfor full-text) instead.read_record_lines— read aline_countslice of a body starting atstart_line(1-indexed). Line numbers match the UI code view andgrep_record, so a typical flow is grep to find the line, then read around it; page on withstart_line = end_line + 1.patch_record— edit a plain-text body (a flowscript,filecontent, an object-template hook script) by sending only the changed hunks as an orderededitsarray of{old_string, new_string}find/replace blocks, instead of resending the whole body withupdate_record.old_stringmust match the current content exactly and (unlessreplace_allis true) uniquely; if it does not, the patch is refused rather than applied — a built-in stale-write guard against clobbering a concurrent change. Edits apply in order and atomically (all or none). Structured JSONvaluefields are not supported — useupdate_recordfor those.
Deleting records
delete_record soft-deletes by default: the record is moved to the trash
and can be restored. Two flags change that:
permanently(defaultfalse) — hard-delete, bypassing the trash. This is irreversible. RBAC and referential rules still apply, and tables that cannot be trashed are always deleted permanently regardless of the flag.recursive(defaulttrue) — also delete the record's child/dependent records. Set it tofalseto delete only the record itself; the delete then fails if the record still has children that block it.
Batching writes into one commit
In a git-synced project or bundle, every write is committed to git. The write
tools (create_record, update_record, patch_record, delete_record and
their subrecord equivalents) commit immediately by default. To group several
related writes into a single, well-described commit, pass commit: false on
each write and then finalize them together:
- Make the writes with
commit: false— they are staged but not yet committed. - Optionally inspect what is staged with
list_pending_changes(filterable byproject_id/bundle_id/record_type). - Call
commit_pending_changeswith thecontainer_type(project/bundle/workspace), itsid, and amessage. With norecord_names/git_log_ids, it commits everything currently pending for that container.
A workspace may enforce immediate commits, in which case commit is unavailable
and every write commits on its own.
Set the commit message on a single write
For a single write you do not need the staging dance: include a
commit_message in the payload of create_record / update_record /
delete_record (and their subrecord equivalents). The immediate per-record
commit then uses that message — one call, instead of a commit: false write
followed by commit_pending_changes. Omitting commit_message keeps the
autogenerated change note.
{
"record_type": "FLOW",
"id": "my-flow",
"payload": {
"description": "tune retry backoff",
"commit_message": "flow(my-flow): tune retry backoff"
}
}
commit_message is honoured on create, update, delete, move and rename. It is
ignored when the write does not commit (commit: false); use
commit_pending_changes's message for the batched case above.
Provenance: created_via
Records created through the MCP surface are tagged with a created_via=mcp
RECORD_METADATA row (best-effort; it never fails the create). This lets you
tell agent-created records apart from those made in the UI or via other API
clients — filter on the metadata key/data with list_subrecords or the
record_metadata filters.
User tools
Built-in MCP tools let an agent maintain and monitor workspace content. User
tools add a second class: domain-specific capabilities you define for agents
(for example lookup_customer, provision_vm, run_monthly_report).
Each user tool is an MCP_USER_TOOL record that:
- exposes the record
nameas the MCP tool name intools/listandtools/call - uses the record
descriptionas the MCP tool description - references a flow by
flow_id(the implementation) - takes argument schemas from that flow's
input_schema(MCPinputSchema) - returns the flow execution's
output_valuewhen the agent calls the tool - advertises that flow's
output_schemaas the MCPoutputSchema— and returns the result asstructuredContent— whenever the schema is an object schema (see Return contract) - exposes optional MCP
readOnlyHint/destructiveHintannotations viaread_only_hintanddestructive_hinton the record (defaults: read-only off, destructive on)
Configure user tools in the Engine UI under Create → Configuration → MCP user
tool, or via the REST/GraphQL API and MCP record tools
(list_records(record_type="MCP_USER_TOOL"), create_record, …).
How invocation works
When an agent calls a user tool:
- Cloudomation creates a flow execution with
input_valueset to the tool arguments. The execution is linked back to the tool via itsmcp_user_tool_idfield, so you can list all executions started by a tool. - The MCP server waits until the execution finishes or reaches the tool's
timeout_sec(default 300 seconds). - On success (
isError: false) the result contains the execution'soutput_value; when the flow declares an objectoutput_schemathe same value is also returned as MCPstructuredContent(see Return contract). - On failure or timeout, the result is an error (
isError: true) that includesexecution_idso the agent can inspect logs withget_execution/get_execution_logsor wait longer withwait_for_executions.
The MCP user tool detail screen in the Engine UI shows the tool's latest
executions and a link to the execution live monitor filtered to that tool
(filtering executions by mcp_user_tool_id).
Return contract
What the agent receives back depends on whether the backing flow declares an
object output_schema:
- Flow has an object
output_schema(type: object) — the tool advertises that schema as its MCPoutputSchemaintools/list, and a successful call returns the execution's (already schema-validated)output_valueas MCPstructuredContentalongside the text result. Agents that understandoutputSchemacan consume the structured result directly. - No
output_schema, or a non-object one — nothing is advertised and the tool keeps its raw passthrough: the call returnsoutput_valueas text only. This is the backwards-compatible default.
A successful call returns isError: false; a failed or timed-out call
returns isError: true with the error message and execution_id.
Declare both an object input_schema and an object output_schema
on the backing flow. If either is missing the tool still works (raw passthrough),
but the server logs a warning — a flow without an input_schema accepts
unstructured arguments, and one without an output_schema cannot advertise
outputSchema. Both schemas make the tool self-describing to agents.
Each user tool call consumes one connection from your connection allowance, the same way an incoming productive webhook call does. The connection is counted when the execution is created; if the allowance is exhausted the call fails.
Define input_schema and output_schema on the flow, not on the tool
record. Changing the flow updates the MCP contract on the next tools/list.
Set read_only_hint when the flow only reads data (for example a lookup or
status check). Leave destructive_hint enabled (default) when the flow may
delete data, call external systems with irreversible effects, or otherwise do
something an agent should confirm first. MCP clients use these hints for
auto-approval and confirmation dialogs; they do not restrict what the flow can do.
Audit logging
Every user-tool invocation leaves a record_log audit entry, so the
workspace keeps a durable trail of who called which tool, when, and with what
outcome. Each entry is authored by the calling identity (the user behind the
API key).
- A successful dispatch writes an
INFOentry on the tool's own record, linked to the backing execution. The entry records only the argument keys — the argument values may contain secrets, so they are read from the linked execution'sinput_valuerather than copied into the log. - A failed call — the backing execution ends in error, or the wait exceeds
timeout_sec— writes anERRORentry on the tool's record, linked to the execution. - A call rejected before any execution is created also writes an
ERRORentry:- an unknown or disabled tool name is logged against the workspace record (there is no tool record to attach it to);
- a tool with no flow configured, or a call whose arguments are not a JSON object, is logged against the tool's record.
Read a tool's entries on its detail screen, or query record_log for the tool
record or the workspace record. Because rejected probes are logged as well, the
trail doubles as a security signal: it captures attempts to call tool names that
do not exist or are disabled.
Example
- Create a flow
get-customerwith aninput_schemarequiringcustomer_idand anoutput_schemadescribing the returned customer object. - Create an MCP user tool named
lookup_customerpointing at that flow, add a description, set Enabled. - The agent sees
lookup_customerintools/listand can call it with{"customer_id": "C-42"}without knowing the underlying flow name.
Naming rules
- The tool
namemust be unique across the workspace (same rule as any record name). - Names matching a built-in MCP tool (for example
run_flow,list_records) are rejected when saving. Built-in tools always take precedence at runtime. - Prefer descriptive names such as
lookup_customerorprovision_vm.
RBAC
The agent user's API key needs at least:
READonMCP_USER_TOOL(to list and invoke tools)CREATEandREADonEXECUTIONREADon the referencedFLOW
Grant these via roles scoped to the relevant projects.
Real-world examples
| User tool | Typical arguments | Flow implements |
|---|---|---|
reset_user_password | { "username": "alice" } | AD/LDAP reset and notification |
check_server_health | { "hostname": "app-01" } | Monitoring connectors, status summary |
provision_environment | { "template": "small-linux", "ttl_hours": 24 } | DevStack or internal provisioning |
export_sales_summary | { "from_date", "to_date", "region" } | Warehouse query and report output |
integrationtest_status | { "limit": 5 } | Wraps an existing test orchestrator flow |
Resources (discovery and typings)
The server exposes MCP resources so an agent can learn how to use
Cloudomation and author correct flows:
| Resource URI | Contents |
|---|---|
cloudomation://usage-guide | How to connect, authenticate and author flows. |
cloudomation://developer-artifacts | REST endpoints and layout for local editor typings and JSON Schemas. |
cloudomation://connector-types | The available connector types and a description of each. |
connector-type://<TYPE> | The description and input/output JSON schemas of one connector type (e.g. connector-type://REST). |
flow_api://<module> | The flow_api typings (one per module), with full docstrings. |
The flow_api typings are the authoritative type definitions for the system
and this objects and all resource classes. An agent should read them before
writing or editing a flow script. They are also available over REST:
| Endpoint | Description |
|---|---|
GET /api/latest/flow_api/typings | flow_api Python typings (?format=zip for archive) |
GET /api/latest/schemas/export | JSON Schemas for export YAML files |
GET /api/latest/schemas/connectors | JSON Schemas for connector values |
GET /api/latest/developer-artifacts | Index of all developer artifact endpoints |
See Local editor setup for manual
download and editor configuration. Agents should follow the Local editor setup
(agent-driven) section in cloudomation://usage-guide when the user edits
flows or Cloudomation YAML locally.
Metadata and MCP prompts
Key/value metadata on records (including workspace agent instructions and MCP
prompt templates) is stored as RECORD_METADATA subrecords. Use
list_subrecords(subrecord_type="RECORD_METADATA", parent_record_id="<record-id>")
and the subrecord CRUD tools.
The workspace record id is included in the MCP server's initialize
instructions and in the cloudomation://usage-guide resource (section "This
workspace"). Use that id as parent_record_id for workspace-level metadata.
| Key | Parent record | Purpose |
|---|---|---|
agent_instructions | workspace | Free-form guidance appended to MCP initialize |
mcp_prompt:<slug> | workspace | MCP prompt template (slug: [a-z][a-z0-9_-]*) |
Example: set workspace instructions with create_subrecord(subrecord_type="RECORD_METADATA", payload={"record_id": "<workspace-id>", "key": "agent_instructions", "data": "..."}).
MCP prompts (customer workflows)
The server exposes MCP prompts so users can start repeatable agent tasks from
their client (slash-command style). There are no built-in prompts — you define
your own on the workspace as RECORD_METADATA subrecords with keys mcp_prompt:<slug>.
Example prompt data object:
{
"title": "Check integration test status",
"description": "Summarize the latest run-integrationtest executions.",
"arguments": [
{
"name": "flow_name",
"description": "Orchestrator flow name",
"required": false
}
],
"template": "Check integration test status on this workspace.\n\n1. list_executions(flow_name=\"{{flow_name}}\", limit=10)\n2. Summarize running/failed runs and link to failing child executions.\n\nDefault flow_name: run-integrationtest"
}
Use template (one user message) or a messages array (role + text per item).
Placeholders {{argument_name}} are filled when the user runs the prompt.
Example templates worth creating (not shipped by Cloudomation):
integrationtest-status— monitor a test orchestrator flow (as above).investigate-execution— required argumentexecution_id; pull status, logs, failure summary.onboard-workspace— list projects, flows and connectors vialist_records; produce a short workspace map.
Create and update prompts with create_subrecord / update_subrecord on
RECORD_METADATA; remove with delete_subrecord. The MCP client lists them via
prompts/list.
Instructions for agents
You can give agents additional guidance in three ways:
- Workspace-level instructions —
RECORD_METADATAkeyagent_instructionson the workspace (delivered automatically in MCPinitialize). - MCP prompts —
RECORD_METADATAkeysmcp_prompt:<slug>(user-invoked workflows). - Record descriptions — a flow or project
descriptionmay contain agent-directed notes, but may also be ordinary documentation; agents treat clearly operational guidance as advisory only.
Instructions never widen what the API key is allowed to do; RBAC always applies.
Local editor setup (agents)
When the user edits flows or Cloudomation YAML locally, agents should read
cloudomation://developer-artifacts and follow the Local editor setup
(agent-driven) section in cloudomation://usage-guide to:
- Download
flow_api/typings,schemas/export, andschemas/connectors(prefer?format=zip). - Extract into
.cloudomation/in the project root. - Merge
.vscode/settings.jsonwithpython.analysis.extraPathsandyaml.schemasfrom the export manifest.
Tell the user to re-run setup after workspace upgrades.
For manual setup without an agent, see Local editor setup.
Flow authoring contract
A flow is a Python module that defines a handler function:
import flow_api
def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
# automation logic
return this.success('done')
systemis the gateway to the workspace.thisis the running execution.inputsis a dict validated against the flow input schema.
See the flow_api reference for the full API.