MCP tools reference
Loom MCP exposes 12 tools. Validation, compile, and run operations call shared Loom libraries in-process; the read tools operate on workspace artifacts. All tools return typed JSON.
All tools accept an optional workspace_root compatibility field. When present, it must exactly match the server's canonical startup directory. Requests cannot add roots or use relative or symlink aliases. Every content path is repo-relative and resolved beneath that root before open; workflow/log path rejections use WF_TRUSTED_PATH at the offending field. Declared artifact tools use the inspection errors documented below. See MCP overview for startup and process-isolation guidance.
Quick reference
| Tool | Purpose | Key inputs |
|---|---|---|
loom_version | Print CLI version | — |
loom_validate | Validate a workflow | workflow_path, source_format, variable_context, gitlab_context |
loom_compile | Compile or plan a workflow | workflow_path, source_format, variable_context, gitlab_context |
loom_run_local | Execute a workflow locally | workflow_path, source_format, variable_context, gitlab_context, mount_mode, parallel |
loom_start_run | Start a server-owned local run | Same run inputs |
loom_run_status | Observe owned or recorded outcome | run_id |
loom_cancel_run | Cancel this server’s owned run | run_id |
loom_list_artifacts | List declared members | run_id, job_id, max_members |
loom_read_artifact | Read original member bytes | run_id, job_id, member_path, encoding, max_bytes |
loom_list_runs | Discover saved runs | — |
loom_read_run | Read recorded state | run_id |
loom_read_events | Read recorded events | run_id, max_lines, tail |
Workflow inputs
Validate, compile, and run accept source_format: loom (the default) or
gitlab. Existing requests keep their native workflow behavior. Native input
defaults to .loom/workflow.yml; GitLab input defaults to .gitlab-ci.yml.
Set workflow_path to select another repo-relative file. Selection and local
includes stay beneath the startup root, including when the server starts in a
subdirectory of a Git repository.
The validate, compile, run_local and start_run tools accept optional
configuration_inputs, a plain typed JSON object bound per document before
composition. Explicit top-level null, duplicate keys at any depth, malformed
values and unknown input names fail. An empty object is valid. Null members use
the declaration's default/required behavior; numbers retain their original
precision through registered protocol validation. This field is separate from
variable_context and gitlab_context. See the
configuration input contract.
All three tools accept optional requested_jobs and requested_instances, each
a nonempty string array. Jobs resolve source aliases first, then exact final IDs;
instances resolve only exact final IDs. Both include ordinary prerequisite
closure. Omit both for full-workflow behavior. Empty arrays, JSON null, empty
or whitespace-padded IDs, and nonstring values are rejected. Commas are literal;
duplicates form one sorted set. The shared planner includes all ordering and
artifact prerequisites without bypassing rules. Unknown or unavailable selectors
fail before snapshot materialization or provider work.
Selected responses expose optional job_selection with requested_jobs, optional requested_instances,
effective_jobs and a stable scope identity. Native Graph IR and GitLab plans
also retain that scope. Validation receipts persist it. Runs retain one protected
job selection event from the final snapshot, or a terminal preflight when no
snapshot is created. A selected result verifies this scope, not the entire
workflow. Native validation compiles a graph for explicit selectors or integer fanout;
other unscoped sources retain schema-only validation.
Native requests may supply variable_context
to validate, compile, run locally or start a server-owned run. Omitted/null means
no context; an empty variables map is supplied. Mixed contexts and wrong-format
contexts fail before execution.
GitLab requests may supply gitlab_context, using the
versioned pipeline context:
version, variables, optional changes, and optional runner_tags. Without
it, planning uses an empty variable map and no runner tags. Context input
requires source_format: "gitlab".
{
"tool": "loom_compile",
"source_format": "gitlab",
"gitlab_context": {
"version": "loom.gitlab-pipeline-context.v1",
"variables": { "CI_COMMIT_BRANCH": "main" },
"changes": { "base": "main", "head": "HEAD" },
"runner_tags": ["local"]
}
}
For changes, omitting paths resolves tracked Git changes from the given
comparison; paths: [] explicitly means no changes. Git path whitespace is
preserved. Invalid format or context input fails before execution.
loom_version
Print the Loom CLI version string. Use to verify the toolchain is available.
Equivalent Loom action: loom version
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspace_root | string | no | Optional exact canonical startup directory; other values are rejected. |
Output
| Field | Type | Description |
|---|---|---|
version | string | CLI version, e.g. loom v0.0.0-alpha.1 |
Example
// Request
{ "tool": "loom_version" }
// Response
{ "version": "loom v0.0.0-alpha.1" }
loom_validate
Validate a native workflow or plan a GitLab workflow without executing it. Returns a receipt with validation results.
Equivalent Loom action: loom check
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspace_root | string | no | Optional exact canonical startup directory; other values are rejected. |
Also accepts the workflow inputs: workflow_path,
source_format, variable_context, gitlab_context, configuration_inputs, requested_jobs, and requested_instances.
Output
| Field | Type | Description |
|---|---|---|
status | string | "success", "failed", or GitLab "skipped" |
exit_code | number | Process exit code |
receipt_path | string | Repo-relative path to the validation receipt |
receipt_json | object | Parsed receipt contents |
job_selection | object | Optional requested/effective job scope and identity |
Example
// Request
{ "tool": "loom_validate" }
// Response
{
"status": "success",
"exit_code": 0,
"receipt_path": ".loom/.runtime/receipts/loom-check-1772050684042246000.json",
"receipt_json": { "..." }
}
See Receipts contract for the receipt schema.
GitLab receipts identify the selected source and include gitlab_plan with
its state, selections, diagnostics, requirements, and resolved changes.
loom_compile
Compile a native workflow into Graph IR or return a GitLab plan without executing it.
Equivalent Loom action: loom compile
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
workspace_root | string | no | Optional exact canonical startup directory; other values are rejected. |
workflow_path | string | no | Repo-relative workflow YAML path. Defaults to .loom/workflow.yml. |
source_format | string | no | loom (default) or gitlab; GitLab defaults to .gitlab-ci.yml. |
configuration_inputs | object | no | File-scoped typed configuration values; omitted means none. |
variable_context | object | no | Native caller scalar context; see its versioned contract. |
gitlab_context | object | no | Versioned GitLab pipeline context and runner tags. |
requested_jobs | string[] | no | Nonempty source jobs or exact final IDs plus prerequisite closure; omit for the full workflow. |
requested_instances | string[] | no | Exact final IDs with prerequisite closure, bypassing source aliases. |
Output
| Field | Type | Description |
|---|---|---|
graph_ir_json | object | The compiled Graph IR; absent on compilation failure |
warnings | object[] | Compiler warnings (each: code, path, message); omitted when empty |
diagnostics | object[] | Ordered public compiler findings; optional, omitted when no findings exist |
exit_code | number | 0 on success; 1 on ordinary compilation failure |
gitlab_plan | object | Complete GitLab plan; omitted for native workflows. |
job_selection | object | Optional requested/effective job scope and identity |
Each diagnostic has code, pointer, and message. Its optional location has
format, path, line, and column, preserving the public compiler source
location when supplied. A missing location is omitted. Findings do not include
raw source snippets or wrapper error text. Errors without public findings return
exit_code: 1 without diagnostics.
Trusted-path findings continue to return a transport error, including a trusted-path finding later in a wrapped error's list. They do not return a structured compilation-failure payload. Native selection retains the existing format hint for GitLab-shaped input.
For GitLab input, gitlab_plan preserves the shared loom.gitlab-plan.v1
contract: state, selection_identity, selections, requirements,
diagnostics, resolved_changes, and graph when executable. The states
source_invalid and non_executable return exit code 1; executable and
workflow_skipped return 0. A skipped plan has no graph. An executable plan
may have an empty graph when all jobs were excluded.
Example
// Request
{ "tool": "loom_compile" }
// Response
{
"graph_ir_json": { "..." },
"warnings": [],
"exit_code": 0
}
For an invalid native workflow such as version: v2 with stages: [test], the
ordinary failure response includes the public finding:
{
"diagnostics": [
{
"code": "WF_SCHEMA_V1",
"pointer": "/version",
"message": "set version to the exact string \"v1\""
}
],
"exit_code": 1
}
loom_run_local
Execute the workflow through the shared local-run service.
| Parameter | Type | Description |
|---|---|---|
workflow_path | string | Repo-relative workflow YAML; default .loom/workflow.yml. |
mount_mode | string | ephemeral_volume (default) or bind_mount. |
parallel | integer | Optional positive maximum concurrent jobs. |
source_format | string | loom (default) or gitlab; GitLab defaults to .gitlab-ci.yml. |
configuration_inputs | object | File-scoped typed configuration values; explicit null is invalid. |
variable_context | object | Native caller scalar context; omitted/null means absent. |
gitlab_context | object | Versioned GitLab pipeline context and runner tags. |
privileged_service_aliases | string[] | Explicit GitLab privileged service aliases authorized for this run. |
requested_jobs | string[] | Nonempty source jobs or exact final IDs plus prerequisite closure; omit for the full workflow. |
requested_instances | string[] | Exact final IDs with prerequisite closure, bypassing source aliases. |
The response contains status, exit_code, optional message and
workflow_rule, plus run_id, run_directory, state_path and events_path
when the run directory was created. Paths are relative to the startup root.
Selected runs also return optional job_selection identifying their requested
and effective scope with a stable identity.
A failed workflow still returns its recorded paths and failed status.
The shared service checks rooted source before snapshot preparation. Invalid,
blocked, skipped and unsupported GitLab cache-diff outcomes retain their run
identity and planning evidence without materializing a snapshot. Accepted source
is compiled again from the isolated snapshot before execution.
GitLab runs also return gitlab_plan from the actual run, including its
selections, diagnostics, requirements, resolved changes, and executable graph.
Service authorization applies only to the named aliases in this invocation;
validation and compile requests grant no privileged service consent.
{ "tool": "loom_run_local", "parallel": 2 }
loom_start_run
Explicitly start a server-owned local run with the same inputs and normalization
as loom_run_local. The start returns exact run_id,
run_directory, state_path and events_path as soon as recording begins,
before workspace preparation and execution finish. Concurrent starts are
independent attempts even with identical inputs. Only this explicit start detaches
from its request after handoff; loom_run_local remains synchronous and bound to
request cancellation. Cancellation before handoff cancels and joins pending work.
The response has state (active, terminal or unknown), owned and
cancel_requested. Active responses omit status and exit_code; zero is never
used to imply unfinished success. Terminal responses include status,
exit_code (including zero), optional protected message, workflow_rule and
known job_selection. A semantic outcome before recording returns terminal
information without inventing a run ID; an operational startup failure is a tool
error. Paths are relative to the startup root.
{ "tool": "loom_start_run", "workflow_path": ".loom/workflow.yml" }
loom_run_status
Supply required exact run_id and optional exact workspace_root. For an attempt
owned by this server, the response uses the same fields as loom_start_run and
reports its active or compact terminal outcome. Ownership is limited to this
server process and startup root; another server cannot establish its liveness.
For an unowned ID, the tool reads only that run's existing state.jsonl through
the retained startup root. A recorded terminal workflow state returns
state: terminal, owned: false, status and its recorded exit_code.
Historical pending/running state returns state: unknown, owned: false and
recorded_status, without a terminal exit code. A valid missing ID is unknown.
Malformed complete records, identity mismatches and unsafe paths fail the call;
an incomplete final append follows the existing state-reader rules. This does
not create a durable daemon, new receipt or persisted liveness signal.
{ "tool": "loom_run_status", "run_id": "<run_id>" }
loom_cancel_run
Supply required exact run_id and optional exact workspace_root. Cancellation
is authorized only for an attempt owned by this server. For an active attempt,
the response acknowledges cancel_requested: true; poll loom_run_status for
the eventual terminal outcome and inspect its saved states/events. Repeated
cancellation is safe, including after completion. Foreign or historical IDs are
tool errors and cannot authorize cancellation.
The server cancels and joins all pending/active owned work during shutdown before closing its retained startup-root reader. Owned terminal views last only for the server lifetime; recorded evidence remains readable after restart.
{ "tool": "loom_cancel_run", "run_id": "<run_id>" }
loom_list_runs
Discover saved run IDs without executing a workflow. The only input is the
optional exact workspace_root binding described above. This reads state files
through the retained startup root and returns { "runs": [] } when none exist.
Each summary contains run_id and, when recorded, workflow_id, status,
exit_code, first_workflow_timestamp and last_workflow_timestamp. Exit zero
is retained; unavailable values are omitted. First/last timestamps refer to
complete workflow records in append order. Rows sort by first recorded timestamp
descending, run ID ascending for ties, with unavailable starts last. Empty or
no-workflow state files still list the ID. Pending/running is last recorded
state, not process liveness.
A malformed complete record, missing/unreadable/nonregular candidate state file or escaping path fails the call without partial structured output. Incomplete final appends follow the normal state reader rules. Non-directory entries, symlink aliases and invalid run-ID directories are ignored. No index is written; there is no automatic latest selection or listing filter/limit.
{ "tool": "loom_list_runs" }
Choose a returned ID explicitly, call loom_read_run to find its failed command,
then call loom_read_events with that job/step/command scope. The same saved files
remain readable after a server restart. See loom runs
for terminal discovery.
loom_read_run
Read all state records from .loom/.runtime/runs/<run_id>/state.jsonl.
The required run_id is a directory name discovered with loom_list_runs or
returned by loom_run_local.
The response contains run_id, state_path and ordered states. Each state
records a workflow, job, step or command transition, its context and status.
Optional exact job_id, step_id and command_id filters select one execution
scope. All supplied filters must match; omitted fields select every state.
{ "tool": "loom_read_run", "run_id": "<run_id>" }
loom_read_events
Read event records from .loom/.runtime/runs/<run_id>/events.jsonl.
| Parameter | Type | Description |
|---|---|---|
run_id | string | Required run directory name. |
all | boolean | Return every matching record; excludes tail and nonzero max_lines. |
max_lines | integer | Maximum returned records; default 200. |
tail | boolean | Return the last matching records, preserving file order. |
job_id | string | Optional exact job ID filter. |
step_id | string | Optional exact step ID filter. |
command_id | string | Optional exact command ID filter. |
level | string | Optional exact log level filter. |
stream | string | Optional exact stream filter, such as stderr. |
The response contains run_id, events_path, ordered events,
total_events (the number matching the filters before limiting),
returned_events, and complete. Use all: true to read every matching record
in one request. Omit tail and max_lines for that request. Explicit positive
max_lines values remain uncapped; negative values are rejected.
complete means every complete matching record available during this read was
returned. A live run can append more records afterward.
Messages preserve their string contents, including blank lines, carriage returns,
and unterminated output. Filters select records rather than splitting messages.
All supplied filters must match and apply before the record limit or tail.
{ "tool": "loom_read_events", "run_id": "<run_id>", "all": true }
{
"tool": "loom_read_events",
"run_id": "<run_id>",
"tail": true,
"max_lines": 30,
"job_id": "build",
"stream": "stderr"
}
Both readers ignore an incomplete final JSON record left by an interrupted append. A malformed complete line fails the read. Run names cannot contain path separators; readers reject escaping symlinks and nonregular files. The JSONL files are the recorded source; no manifests or derived views select other files.
See Runtime logs for the record fields and
loom logs for terminal inspection.
loom_list_artifacts
List a published job archive without extraction. Required run_id and job_id
are exact identities; the job ID must already be canonical. Optional
max_members defaults to 200 (maximum 1,000). workspace_root has the same exact
startup-root contract as every tool.
The typed result contains run_id, job_id, status (published or empty),
archive (path, format, size_bytes), sorted members (path, size_bytes),
total_members, returned_members and complete. empty means a validated
empty archive. Missing publication cannot prove skipped, empty or undeclared
outputs. Missing run/job errors describe only the artifact namespace.
loom_read_artifact
Required inputs are exact run_id, canonical job_id, canonical member_path
from the listing, and explicit encoding: text or base64. Optional max_bytes
defaults to 65,536 (maximum 1,048,576). The typed result contains run_id, job_id,
archive, member, encoding, body, original returned_bytes, total_bytes
and truncated. Text validates the entire selected member as UTF-8 and preserves
an original prefix ending at a rune boundary. Base64 returns an original byte
prefix. Artifacts are not redacted.
Both artifact tools validate the entire archive before publishing any result,
using the shared reader behind loom artifacts.
They only read .loom/.runtime/artifacts/<run_id>/jobs/<job-id>/artifacts.tar.gz,
never a log-derived or arbitrary path. Linux/macOS descriptor-relative no-follow
opens and substitution rechecks enforce containment; they do not authenticate
bytes, continuously freeze the namespace or guarantee hostile-mount safety.
Other platforms return unsupported_platform.
Per-call limits are 256 MiB compressed, 1 GiB decoded including metadata,
10,000 regular members, 4,096 UTF-8 bytes per member path, and a private 2 MiB
budget per internal tar header traversal. Unsafe paths, symlinks, special/link
members, duplicate/conflicting names, sparse metadata and malformed/truncated
archives fail without partial results. InspectionError codes are
invalid_request, path_rejected, run_not_found, job_not_found,
publication_absent, publication_empty, member_not_found, invalid_archive,
resource_limit, invalid_encoding and unsupported_platform; messages include
the affected field. Cancellation remains a context error.
Native selection example
loom_compile and loom_validate apply the same compile selection as the CLI:
{
"workflow_path": ".loom/conformance/compile-selection/native-host.yml",
"source_format": "loom",
"variable_context": {
"version": "loom.variable-context.v1",
"variables": { "MODE": "blue", "EMPTY": "" }
}
}
The compiled graph selects blue, common, raw-phase, scalar-logic and
consumer, with safe compile_selection decisions. loom_run_local accepts
the same source/context and executes retained nodes subject to their runtime
rules. For GitLab use source_format: "gitlab" and gitlab_context; its if
lookup preserves raw strings independently of execution variable expansion.
Public remote source effects
Compile, validate and run tools resolve public HTTP(S) include sources through
the shared compiler. Requests can perform network reads; cache: true or a
valid per-include duration also writes original source bytes under
.loom/.runtime/source-cache/v1/. Omitted/cleared cache controls bypass disk
reuse. There is no request field granting internal network access or ambient
credentials. Request/run cancellation reaches retrieval and retry delays.
Graph IR, GitLab plans and check receipts optionally expose sources: an
aggregate identity and selected requested/final address hashes, query-free
origins, raw content digests and byte counts. This evidence contains no bodies
or URL queries. Configuration input identities remain separate. Runs retain a
protected source closure event in their existing events file, with the same
bytes pinned from preflight through authoritative snapshot compilation.
See the include contract for literal
URLs, integrity, cache forms and anonymous nesting.