Skip to main content
Version: 12 - TBD

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

  1. Open the Cloudomation Engine UI and go to the user you want the agent to act as (see "Scoping access" below).
  2. In the user's detail page, find the API keys (MCP / programmatic access) section.
  3. 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:

  1. Create a dedicated user for the agent (for example claude-agent).
  2. Create a role with role_permission entries scoped to the specific projects, record types and operations the agent should be allowed to use (for example: READ/CREATE/UPDATE on FLOW and EXECUTION in a single project).
  3. Assign the role to the agent user.
  4. 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):

ToolDescription
run_flowStart an execution of a flow.
get_executionGet the status, message and output of an execution.
get_execution_logsGet the log entries of an execution.
wait_for_executionsBlock until one or more executions end, then return their details.
list_executionsList executions newest-first, filtered by flow and/or status.
list_record_typesList record types (FLOW, PROJECT, CONNECTOR, …).
list_subrecord_typesList subrecord types (OBJECT_TEMPLATE_ATTRIBUTE, RECORD_METADATA, …).
describe_record_typeJSON Schemas and field lists for a record type.
describe_subrecord_typeJSON Schemas and field lists for a subrecord type.
list_recordsList records of one type (uses that type's REST resource and filters).
list_subrecordsList subrecords of one type, scoped to a parent record.
get_recordGet one record by id or name; folds the record's recent comments into the result (see Record comments in get_record).
grep_recordSearch 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_linesRead a line-range slice of one record's body (token-lean paging of a big script/value/file).
search_recordsFull-text search across all indexed records — locate a record by any word it contains, ranked and permission-filtered.
get_subrecordGet one subrecord row by id.
create_recordCreate a record of any type.
update_recordPatch a record (replace whole fields by name).
patch_recordEdit 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_recordDelete a record (soft-delete to trash by default; permanently / recursive flags).
create_subrecordCreate a subrecord row (including RECORD_METADATA).
update_subrecordPatch a subrecord row.
delete_subrecordDelete a subrecord row.
list_pending_changesList writes staged with commit=false that are not yet committed to git.
commit_pending_changesCommit 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:

ParameterDescription
fieldsList of field names to return (each tool has a sensible default set).
orderOrder 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 / offsetPage 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, with fixed: true, a literal) pattern and get back only the matching lines with surrounding context, plus a total match count and a truncated flag. By default it searches the record type's primary body field (script for FLOW/WRAPPER/SCHEDULE, value for CUSTOM_OBJECT/SETTING/CONNECTOR/SCHEMA, content for FILE); pass field to target another column (e.g. on_create on an OBJECT_TEMPLATE). JSON fields are searched as their pretty-printed JSON. For a search across many records use list_records with filter_ (or search_records for full-text) instead.
  • read_record_lines — read a line_count slice of a body starting at start_line (1-indexed). Line numbers match the UI code view and grep_record, so a typical flow is grep to find the line, then read around it; page on with start_line = end_line + 1.
  • patch_record — edit a plain-text body (a flow script, file content, an object-template hook script) by sending only the changed hunks as an ordered edits array of {old_string, new_string} find/replace blocks, instead of resending the whole body with update_record. old_string must match the current content exactly and (unless replace_all is 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 JSON value fields are not supported — use update_record for 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 (default false) — 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 (default true) — also delete the record's child/dependent records. Set it to false to 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:

  1. Make the writes with commit: false — they are staged but not yet committed.
  2. Optionally inspect what is staged with list_pending_changes (filterable by project_id / bundle_id / record_type).
  3. Call commit_pending_changes with the container_type (project / bundle / workspace), its id, and a message. With no record_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 name as the MCP tool name in tools/list and tools/call
  • uses the record description as the MCP tool description
  • references a flow by flow_id (the implementation)
  • takes argument schemas from that flow's input_schema (MCP inputSchema)
  • returns the flow execution's output_value when the agent calls the tool
  • advertises that flow's output_schema as the MCP outputSchema — and returns the result as structuredContent — whenever the schema is an object schema (see Return contract)
  • exposes optional MCP readOnlyHint / destructiveHint annotations via read_only_hint and destructive_hint on 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:

  1. Cloudomation creates a flow execution with input_value set to the tool arguments. The execution is linked back to the tool via its mcp_user_tool_id field, so you can list all executions started by a tool.
  2. The MCP server waits until the execution finishes or reaches the tool's timeout_sec (default 300 seconds).
  3. On success (isError: false) the result contains the execution's output_value; when the flow declares an object output_schema the same value is also returned as MCP structuredContent (see Return contract).
  4. On failure or timeout, the result is an error (isError: true) that includes execution_id so the agent can inspect logs with get_execution / get_execution_logs or wait longer with wait_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 MCP outputSchema in tools/list, and a successful call returns the execution's (already schema-validated) output_value as MCP structuredContent alongside the text result. Agents that understand outputSchema can 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 returns output_value as 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.

Make a tool first-class

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.

License usage

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 INFO entry 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's input_value rather than copied into the log.
  • A failed call — the backing execution ends in error, or the wait exceeds timeout_sec — writes an ERROR entry on the tool's record, linked to the execution.
  • A call rejected before any execution is created also writes an ERROR entry:
    • 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

  1. Create a flow get-customer with an input_schema requiring customer_id and an output_schema describing the returned customer object.
  2. Create an MCP user tool named lookup_customer pointing at that flow, add a description, set Enabled.
  3. The agent sees lookup_customer in tools/list and can call it with {"customer_id": "C-42"} without knowing the underlying flow name.

Naming rules

  • The tool name must 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_customer or provision_vm.

RBAC

The agent user's API key needs at least:

  • READ on MCP_USER_TOOL (to list and invoke tools)
  • CREATE and READ on EXECUTION
  • READ on the referenced FLOW

Grant these via roles scoped to the relevant projects.

Real-world examples

User toolTypical argumentsFlow 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 URIContents
cloudomation://usage-guideHow to connect, authenticate and author flows.
cloudomation://developer-artifactsREST endpoints and layout for local editor typings and JSON Schemas.
cloudomation://connector-typesThe 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:

EndpointDescription
GET /api/latest/flow_api/typingsflow_api Python typings (?format=zip for archive)
GET /api/latest/schemas/exportJSON Schemas for export YAML files
GET /api/latest/schemas/connectorsJSON Schemas for connector values
GET /api/latest/developer-artifactsIndex 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.

KeyParent recordPurpose
agent_instructionsworkspaceFree-form guidance appended to MCP initialize
mcp_prompt:<slug>workspaceMCP 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 argument execution_id; pull status, logs, failure summary.
  • onboard-workspace — list projects, flows and connectors via list_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 instructionsRECORD_METADATA key agent_instructions on the workspace (delivered automatically in MCP initialize).
  • MCP promptsRECORD_METADATA keys mcp_prompt:<slug> (user-invoked workflows).
  • Record descriptions — a flow or project description may 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:

  1. Download flow_api/typings, schemas/export, and schemas/connectors (prefer ?format=zip).
  2. Extract into .cloudomation/ in the project root.
  3. Merge .vscode/settings.json with python.analysis.extraPaths and yaml.schemas from 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')
  • system is the gateway to the workspace.
  • this is the running execution.
  • inputs is a dict validated against the flow input schema.

See the flow_api reference for the full API.