Skip to content

mcp

Usage

The `mcp` command serves Terragrunt operations to AI agents over the Model Context Protocol, speaking the protocol on standard input and output.

It is typically not run by hand. An MCP client launches it, and the tools act on the units under the directory it was launched in.

Examples

No capabilities granted. Terragrunt parses configuration and refuses to start the commands or make the requests it otherwise would.

.mcp.json
{
"mcpServers": {
"terragrunt": {
"command": "terragrunt",
"args": ["mcp", "--working-dir", "/path/to/estate"],
"env": {
"TG_EXPERIMENT": "mcp-command"
}
}
}
}

Enough to plan. OpenTofu reaches state backends and module sources with the credentials in the server's environment.

.mcp.json
{
"mcpServers": {
"terragrunt": {
"command": "terragrunt",
"args": [
"mcp",
"--working-dir", "/path/to/estate",
"--allow=exec",
"--allow=http",
"--allow=env"
],
"env": {
"TG_EXPERIMENT": "mcp-command"
}
}
}
}

A throwaway environment the agent may change, with each run waiting on you to accept it. Point `--working-dir` at the environment, not the repository root. Tools cannot reach above it.

.mcp.json
{
"mcpServers": {
"terragrunt-throwaway": {
"command": "terragrunt",
"args": [
"mcp",
"--working-dir", "/path/to/estate/throwaway",
"--allow=exec",
"--allow=http",
"--allow=env",
"--dangerously-allow-apply"
],
"env": {
"TG_EXPERIMENT": "mcp-command"
}
}
}
}

Tools

Name What it does Needs
discover Lists the units and stacks under a directory None
render_config Evaluates one unit’s terragrunt.hcl and returns the resolved configuration exec to read dependency outputs
validate Reports syntax and evaluation errors in the configurations under a directory None
run_order Reports the order units run in, and what blocks each one None
get_outputs Reads the applied outputs of one unit exec, or http for s3, gcs, and azurerm state
plan Runs run --all plan across the units and returns the per-unit result exec
catalog Lists the components in the catalog the configuration names exec to clone a remote repository
scaffold Writes new configuration from a module, unit, or stack source http, or exec for git, to fetch a remote source
fmt Rewrites .hcl files into canonical format, as hcl fmt does None

A server started with no --allow flag can still write under its root: scaffold generates files from a local source, and fmt rewrites .hcl files.

Two more tools are served only under --dangerously-allow-apply. They change real infrastructure.

Name What it does Needs
apply Runs run --all apply across the units exec
destroy Runs run --all destroy across the units, dependents first exec

Neither acts when the agent calls it. Each first returns an elicitation (the protocol’s way for a server to ask the client’s user a question) naming the units it would run, and runs only once the person operating the client accepts. A client that cannot ask anyone is rejected.

--working-dir sets the server root, and defaults to the directory the server is launched in. Every tool takes a working_dir relative to the root. A value resolving outside it is refused, symlinks included, and a filter following dependencies or dependents cannot return a unit from outside it. working_dir is optional and defaults to the root, except on render_config and get_outputs, which act on a single unit and require it. Where a tool takes filter, it accepts filter queries and narrows that tool the way --filter narrows a run.

The root does not limit file access. file() reads anything the server’s user can read, and resolving a dependency downloads its source into a .terragrunt-cache.

The tools that run OpenTofu/Terraform use the binary the server was started with. Set it with --tf-path, as a program name found in PATH or as a filepath. It defaults to tofu, or to terraform when tofu is not installed. A unit’s terraform_binary and engine block are ignored.

scaffold writes new configuration the way scaffold does, without prompting. The agent passes the values the form would ask for, and anything it leaves out is written as a TODO placeholder.

Files go in output_folder, which defaults to working_dir, must resolve under the server root, and must be empty or not exist yet. A local source or template is read from wherever it is, outside the root included.

A template’s shell helpers and hooks never run, even under --allow=exec. The tool refuses a template that declares dependencies, which boilerplate would download past the server’s capabilities, or that uses template syntax in a file or directory name, which can render to a path outside output_folder. It also refuses a source holding a symlink that leads out of it.

Under --allow=docs, the server also relays the tools and resources of the Terragrunt docs MCP server, so an agent connected to terragrunt mcp can search the Terragrunt documentation without a second server configured. Each tool keeps its name on the docs server with a docs_ prefix, e.g. search_docs is served as docs_search_docs.

discover

Lists the units and stacks under a directory.

  • filter narrows what is returned.
  • dependencies adds each unit’s dependency edges, and whether an exclude block excluded it.
  • as is the command those exclude blocks are evaluated against: plan (the default), apply, or destroy.
  • hidden includes units in hidden directories.

Units and stacks come back as separate lists of paths, with unit_count.

render_config

Evaluates one unit’s terragrunt.hcl the way a run would, resolving locals, includes, functions, and dependency outputs.

  • format chooses how much comes back:
    • summary, the default, returns the unit’s source, its remote state backend, the IAM role it assumes, the names of its inputs, its dependencies, and its generate blocks.
    • full returns the whole resolved configuration.
  • render_metadata tags each value in a format=full result with where it was defined, so you can see which include a value came from.

Without --allow=exec, dependency outputs fall back to mock_outputs where the configuration declares them, and the call fails where it does not.

validate

Checks the configurations under a directory for syntax and evaluation errors. Each diagnostic gives a file, line, severity, summary, and detail. The result also counts errors and warnings, and sets valid.

  • unit validates a single unit or stack, relative to working_dir. Discovery is skipped, and filter must be empty.
  • filter narrows which units are validated.

run_order

Reports the order units run in, based on the DAG. Each unit lists the units that block it in blocked_by.

  • command is the command the order is for: plan (the default), apply, or destroy.

  • format selects the shape. edges, the default, returns the blocking relation as data. tree and dot return the graph as list prints it.

  • filter narrows the set of units.

The units array is sorted by path.

get_outputs

Reads one unit’s applied outputs, what a dependency block on that unit receives.

  • include_sensitive returns the values of outputs marked sensitive, which are otherwise replaced with (sensitive). The agent sets it itself, and nobody is asked.

Each output comes back with its value and whether it is sensitive. redacted lists the names whose values were replaced.

Under --allow=exec, Terragrunt runs OpenTofu/Terraform to read the outputs. Without it, Terragrunt reads the state itself, which needs --allow=http and works for the s3, gcs, and azurerm backends. Any other unit reports that it fetched nothing, and says why.

plan

Runs run --all plan across the units under a directory. Each unit comes back with its result, how long it took, and the cause when it failed. The result also counts what succeeded, failed, was excluded, and exited early.

  • filter narrows which units are planned.
  • parallelism caps how many units run at once, the way --parallelism does. By default there is no limit.
  • include_sensitive keeps each unit’s plan files and returns plan_json. The plan JSON holds every value in the plan in cleartext, sensitive ones included. Without it, Terragrunt deletes the plan files once it has counted their changes. The agent sets it itself, and nobody is asked.

Each unit result can have these fields:

Field What it holds Returned by
changes Counts of resources the plan would add, change, destroy, import, and forget, and of outputs that would change. A plan that changes only outputs counts them under outputs. plan
plan_json Path of the plan’s JSON representation, the document tofu show -json prints for the plan file. It contains every value in the plan, sensitive ones included. Only with include_sensitive. plan
stdout Path of a file with what OpenTofu/Terraform printed to stdout, color codes stripped, including the output of an init Terragrunt ran first. Sensitive values are redacted as the CLI redacts them. plan, apply, destroy
stdout_truncated true when the file keeps only the last 8 MiB of the unit’s stdout. plan, apply, destroy

A unit that printed nothing has no stdout, and a unit whose plan failed has no changes or plan_json.

The files go in a directory under the system temp directory, readable only by the server’s user, and are deleted when the server stops.

fmt rewrites the .hcl files under a directory into canonical format. It skips .terragrunt-cache, .terragrunt-stack, and .boilerplate directories, as hcl fmt does.

  • check reports the files that need formatting without changing any, as hcl fmt --check does.
  • diff adds a unified diff for each of those files.
  • file formats a single .hcl file, relative to working_dir. The directory is not walked, and filter must be empty.
  • filter narrows which files are formatted, with the path filters hcl fmt --filter takes, e.g. ./stage/** or !./modules/**. A filter that needs unit discovery, such as type=unit, is refused.

changed lists the files whose formatting differed. Without check, fmt rewrote them. A file that does not parse is left as it is and reported in parse_errors. formatted is true when changed and parse_errors are both empty, and file_count is the number of files examined.

fmt skips symlinks. A symlinked file is listed in skipped, and a symlinked directory is not walked. A file whose directory resolves outside the server root is refused. The check comes before the read, so a symlink that another process swaps in after the check, before the read, is followed.

apply and destroy

Run run --all apply and run --all destroy across the units under a directory, destroy with dependents first. Both answer in the shape plan does, with stdout and stdout_truncated and without changes or plan_json. An apply’s stdout has the apply summary and the unit’s outputs, sensitive values redacted.

  • filter narrows which units run.
  • parallelism works as it does for plan.
  • The approval prompt shows filter and parallelism as flags on the command it asks about.

Connecting a client

Clients read a .mcp.json like the examples above. Set TG_EXPERIMENT in its env, since there is no shell to export it from. Each client decides where the file lives and how much of this shape it reads, so check its own documentation:

Capabilities

The server denies every capability at startup, and grants each one separately:

Flag What it allows
--allow Any of exec, http, sops, env, or docs. Repeat it for each one.
--allow-cmd A command pattern a configuration may run
--dangerously-allow-apply The apply and destroy tools

docs lets the server relay the tools of the Terragrunt docs MCP server at https://mcp.docs.terragrunt.com/mcp, as Tools describes. It is a separate capability from http, although both grant network connectivity. If the docs server does not answer at startup, the server starts without its tools and logs a warning, and only a restart tries again. Every argument an agent passes to a docs_ tool leaves the machine, including any configuration it pastes in.

Wherever a denied capability stops a tool, the result includes a degraded list naming what it did instead, such as substituting mock_outputs for a dependency output.

exec

Lets the tools run the tofu, terraform, and git that Terragrunt starts for its own work: a plan, a source download, a git-range filter. It does not let a configuration run those programs itself. A program a configuration names, through run_cmd(), a hook, or --auth-provider-cmd, is refused like any other.

A refused program is reported in the call’s degraded list and the call continues, with run_cmd() yielding an empty string. apply and destroy fail the unit instead, so a hook written to block the run still blocks it.

To run a program a configuration names, allow it with an --allow-cmd pattern, or accept it when a tool asks. A pattern naming git matches the git on the server’s PATH, not a ./git in the tree.

discover, render_config, validate, run_order, and catalog ask. When a configuration names a refused program, the call returns an elicitation naming the programs, then runs again with the ones the person operating the client accepts. An acceptance covers those programs for that call only. get_outputs, plan, scaffold, apply, and destroy never ask.

http

Covers what Terragrunt itself fetches:

  • Downloading a unit’s terraform { source } when it is remote, such as a tfr:// registry module, an https archive, or an oci:// image. Without this, such a unit cannot be planned.

  • Fetching a stack’s sources during discovery and generation.

  • Reading remote state directly for get_outputs.

  • Cloud API calls Terragrunt makes itself, including assuming a role through STS and reading S3, GCS, or Azure Storage.

  • Provider downloads made by the provider cache server.

sops

Without it, a configuration calling sops_decrypt_file fails, so a unit whose inputs come from an encrypted file cannot be rendered. A refused decrypt fails the call. No value is substituted.

env

Without it, a tool call starts from an empty environment. A configuration’s get_env() returns its default, and every command the tools run, tofu included, sees none of the server’s variables. Terragrunt still finds tofu and git on the server’s own PATH.

Granting it passes every one of the server’s environment variables to configurations and the commands the tools run, as every other Terragrunt command does. That can include cloud credentials.

Without it, the server also points HOME at an empty directory, so nothing reads ~/.aws/credentials, ~/.gitconfig, or an age key file. A direct state read under http and a decrypt under sops both need env to find credentials in the environment or your home directory.

Logs

Logs, and any output from the commands the tools run, go to standard error, where MCP clients typically surface them as server logs. Standard output carries the protocol itself.

Flags

--allowoptional(list(string))

Grant a capability the server otherwise denies. Repeatable.

Turns on one capability the server otherwise denies. Repeat it for each one you want:

Terminal window
terragrunt mcp --allow exec --allow http

The capabilities, and what granting each one costs you, are documented with the mcp command.

Environment Variables:

  • TG_ALLOW
--allow-cmdoptional(list(string))

Allow MCP tools to run a command a configuration names, such as one a run_cmd or a hook calls, given as a pattern like 'jq **'. Repeatable. Requires --allow=exec.

--allow=exec runs tofu, terraform, and git, and refuses every other program a configuration names. This flag allows more, one pattern at a time:

Terminal window
terragrunt mcp --allow=exec --allow-cmd 'git rev-parse --show-toplevel' --allow-cmd 'jq **'

A pattern is split into words the way --auth-provider-cmd is, so quoting works as it does in a shell. Quote the whole pattern as well, so your own shell passes it through as one value.

  • The first word names the program and cannot contain glob syntax. A bare name, e.g. jq, matches a configuration that calls jq, or that calls the path where the server’s PATH finds jq. A path, e.g. ./scripts/version.sh, matches a configuration that calls the same path once . and .. are resolved on both sides, so scripts/version.sh matches too. A bare name does not match a file the tree ships, so git ** does not allow ./git.
  • Each later word matches one argument. Glob syntax applies within that argument, e.g. --profile=*.
  • A word that is ** matches any number of arguments, none included.
Pattern Allows Refuses
git rev-parse --show-toplevel git rev-parse --show-toplevel git rev-parse --show-toplevel --quiet
jq * jq . jq -r .version
jq ** jq, jq -r .version version.json yq .
aws ** --profile=* aws sts get-caller-identity --profile=dev aws sts get-caller-identity
./scripts/version.sh ** scripts/version.sh --short version.sh

An argument containing spaces stays one word, so sh -c 'echo hello' allows run_cmd("sh", "-c", "echo hello") and nothing else. A pattern that cannot be parsed, or that contains a shell operator such as ; or |, is refused at startup.

Finding out what to allow

You do not have to guess. Call a tool once and read the degraded list, which names every command that was refused:

"degraded": [
"stubbed \"jq -r .version\": --allow=exec runs Terragrunt's own commands, not programs a configuration names"
]

If your MCP client supports elicitation (the protocol’s way for a server to ask the client’s user a question), the server offers the refused programs for approval instead, and you can decide per call rather than up front. An approval covers the program as the configuration called it, by name or by path, with any arguments, for that call. This flag is the answer once you know which commands a tree needs and are tired of approving them.

Environment Variables:

  • TG_ALLOW_CMD
--dangerously-allow-applyoptional(bool)

Serve apply and destroy tools, which change and tear down real infrastructure. Each run is gated on a person accepting it in your MCP client. Requires --allow=exec.

Without this flag the server has no apply or destroy tool at all. The tools are not registered, so a client is never told they exist.

The flag requires --allow=exec, since applying is running OpenTofu/Terraform. Passing it alone is refused at startup.

What the tools do

Both take the same arguments as plan, a working_dir, a filter, and a parallelism, and return the same per-unit summary.

  • apply runs terragrunt run --all apply across the units under the working directory.
  • destroy runs terragrunt run --all destroy, in dependency order.

Runs are serialized with the other real-run tools, so concurrent calls queue rather than racing on the shared cache directories.

Approval

Neither tool acts when the agent calls it. The first call answers with an elicitation (the protocol’s way for a server to ask the client’s user a question) describing what the run would do, and the run starts only when the person operating the client accepts it. A decline or a dismissal ends the call, and calling again asks again.

An acceptance counts only for the arguments the elicitation asked about. One sent with different arguments, or with no elicitation from the server behind it, is refused, and nothing runs.

The description names the units the run would touch, worked out the way terragrunt find --as=apply and terragrunt find --as=destroy work them out, so the exclude blocks belonging to the action being approved are honored.

Approve running `terragrunt run --all apply`?
This APPLIES the following units (3):
- app
- db
- vpc
This names the units, not the changes: nothing was planned to build this list. Call the plan tool first to see what would change.
The apply runs with -auto-approve, so accepting here is the only confirmation.

A call that sets filter or parallelism shows them as flags on the first line, e.g. Approve running `terragrunt run --all apply --filter=./app --parallelism=2`?. An invalid value fails the call before anyone is asked.

Call the plan tool first when you want the changes in front of you before accepting.

A client with no way to put the question in front of a person is refused, and the run does not happen.

Narrowing what a call can touch

The server root already bounds every tool: a working_dir resolving outside the directory the server was launched in is refused. That is a coarse bound, and for these tools it is worth making it tight. Launch the server in the environment directory you are willing to have changed, not at the repository root:

.mcp.json
{
"mcpServers": {
"terragrunt-throwaway": {
"command": "terragrunt",
"args": ["mcp", "--working-dir", "/path/to/estate/throwaway", "--allow=exec", "--dangerously-allow-apply"],
"env": {
"TG_EXPERIMENT": "mcp-command"
}
}
}
}

Beyond that, scope the credentials the server process is given to what those units need. Terragrunt cannot bound what a spawned tofu does with the credentials it inherits, so the credentials are the real limit on blast radius.

Environment Variables:

  • TG_DANGEROUSLY_ALLOW_APPLY
--tf-pathoptional(string)

Path to the OpenTofu/Terraform binary. Default is tofu (on PATH).

Specifies the path to the OpenTofu/Terraform binary that Terragrunt should use. By default, Terragrunt will look for a binary named tofu in your system’s PATH.

This is useful when:

  • You have multiple versions of OpenTofu/Terraform installed.
  • The binary is not in your PATH.
  • You want to use a specific version for certain operations.
  • You want to switch between OpenTofu and Terraform binaries.

Example:

Terminal window
terragrunt run plan --tf-path=/usr/local/bin/tofu

Note that if you only have terraform installed, and it is available in your PATH, Terragrunt will automatically use that binary.

NOTE: This will override the terraform binary that is used by terragrunt in all instances, including dependency lookups. This setting will also override any terraform_binary configuration values specified in the terragrunt.hcl config for both the top level, and dependency lookups.

Environment Variables:

  • TG_TF_PATH