# operation-mcp

Exposes the DevOps (Ops Portal) operation API as a stdio MCP server, so an agent
handling an alert can request an operation instead of asking a human to click
through Ops Portal.

**It does not bypass approval.** The server only calls the `/operation/*`
endpoints; it never touches the approval API. Submitting creates an instance
that enters whatever approval chain the DevOps server has configured for that
operation, and nothing runs until a human approves it. In the prod catalog every
operation except `ImpersonateTenant` has approval nodes.

## Running it

```jsonc
{
  "mcpServers": {
    "devops-operation": {
      "command": "operation-mcp",
      "args": ["serve", "--enable-write"],
      "env": { "DEVOPS_API_KEY": "<key issued to the agent>" }
    }
  }
}
```

Use a credential dedicated to the agent. The DevOps server records the caller's
operator type and the API key owner on every instance, so a separate key is what
makes agent-submitted operations distinguishable from human ones in the audit
log. One of `DEVOPS_ACCESS_TOKEN`, `DEVOPS_API_TOKEN` or `DEVOPS_API_KEY` is
required.

`operation-mcp tools [--schemas]` prints the tools a client would see, which is
the quickest way to check credentials, endpoints and generated schemas.

## Tools

Read-only, always registered:

| Tool | Purpose |
| --- | --- |
| `list_operations` | The operation catalog, with `submittable` marking the allowlist |
| `describe_operation` | Params, actions and the approval chain of one operation |
| `preview_operation` | Server-side pre-checks and their advisories, creating nothing |
| `get_operation_instance` | One instance with its phase, actions and Ops Portal link |
| `list_operation_instances` | Instances filtered by operation, phase, applicant or target |

Registered only with `--enable-write`:

| Tool | Purpose |
| --- | --- |
| `submit_<operation>` | One generated tool per allowlisted operation, params as typed arguments |
| `submit_operation` | Generic submit by name, for allowlisted operations |
| `cancel_operation_instance` | Withdraw an instance that has not finished |

`reason` is required on every submit: the approver sees it as the only
explanation of why the operation was requested, so it should name the alert or
ticket being handled.

## Allowlist

Only operations passed to `--allow` can be submitted or previewed; the rest of
the catalog stays readable. The default is `RestartChangefeed`. Internal
operations (`internal: true`, created only by the DevOps server itself) are
rejected outright.

## Start policy

Actions default to `start_policy: immediate`, meaning they run as soon as the
approval passes. The operation definitions often prefer `manual`, which is right
for the UI but would leave an agent-submitted instance parked in `pending`
waiting for a second, human-only gate. Pass `start_policy` explicitly to change
it per submission.

## Configuration

| Flag | Env | Default |
| --- | --- | --- |
| `--env` | `DEVOPS_ENV` | `prod` |
| `--base-url` | `DEVOPS_BASE_DOMAIN` | derived from `--env` |
| `--ops-portal-url` | `DEVOPS_OPS_PORTAL_URL` | derived from `--env` |
| `--operations-file` | `DEVOPS_OPERATIONS_FILE` | unset; catalog comes from the API |
| `--enable-write` | `DEVOPS_OPERATION_MCP_ENABLE_WRITE` | `false` |
| `--allow` | — | `RestartChangefeed` |
| `--timeout` | — | `30s` |

The operation catalog is read from the DevOps API at startup, which is the
source of truth for what the server will accept. `--operations-file` reads an
`operations.yaml` instead, for offline work; it can drift from the deployment.
