Ops server
opsLet a coding agent ask a running app how it is, what it has seen and what a playbook would do, through nine MCP tools on a user-only socket, with every change waiting for a person.
ops-mcpFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
A running app can answer an MCP client such as Claude Code about its health, signals and playbooks. Anything that changes the app waits for a person to say yes.
The agent can also ask what a playbook would do now and why a reaction ran. The server listens only on this machine, can't call arbitrary actions, change config or read app data, and every call acts as the app agent ops, so its budget, kill switch and audit apply.
At a glance
ops:
mcp: { write: approve }
agent: ops// An agent starts the stdio server; the tools are listed by the MCP client.
// npx mainsdk ops mcpDrivers
1 drivers. Select one to see its line in main.yaml.
ops:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-OPS-0001 | Checkmainsdk check, before deploy | An ops value is invalid: write: auto, a port outside 1024 to 65535, or http: on in production without ops.agent declared under agents |
MAIN-OPS-0002 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0004 | RuntimeError responses and logs | See the spec |
MAIN-OPS-0005 | RuntimeError responses and logs | mainsdk ops mcp found no running app with an ops socket for the given folder or project |
MAIN-OPS-0006 | RuntimeError responses and logs | See the spec |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Read tools | Phase 1 | 0.4 | ops-mcp#tools-read |
| Approval-only run and revert | Phase 1 | 0.4 | ops-mcp#tools-write |
| mainsdk ops mcp | Phase 1 | 0.4 | ops-mcp#stdio |
Quick start
ops:
mcp: { write: approve }
agents:
ops: { budget: { calls: 500/day } }npx mainsdk dev # the ops socket is on by default in dev
npx mainsdk ops mcp # the stdio MCP server an agent starts; mainsdk init registers itConfiguration
| Key | What it does |
|---|---|
ops.mcp.socket | on or off. Default on in dev, off elsewhere. |
ops.mcp.write | approve (default) or off, which makes the write tools unavailable (MAIN-OPS-0004). There is no auto. |
ops.mcp.socket-ports | The loopback range used only where a Unix socket is refused. Default 47600-47699. |
ops.agent | The app agent clients act as. Default ops. |
| ops.mcp.http | on serves MCP over HTTP at http://127.0.0.1:<port>/mcp and, on a host with IPv6 loopback, [::1]. Default off. | | ops.mcp.port | The loopback HTTP port, 1024 to 65535. Default 4950. | | ops.card | on (default) serves the agent card at /.well-known/agent-card.json on the HTTP endpoint and as the resource mainsdk://ops/agent-card. |
The HTTP endpoint takes Authorization: Bearer <token> with the token in <run>/ops/<project>.token, and refuses a request with no token (401, MAIN-OPS-0002) or with a Host or Origin that is not loopback (403, MAIN-OPS-0003). It never goes through the app's public adapter, so the public app does not serve the agent card.
What you get
The app opens <run>/ops/<project-id>.sock (mode 0600 in a 0700 folder) and writes a fresh token file beside it at each start. A client presents the token first; a missing or wrong one is refused with MAIN-OPS-0002. The socket is on by default only when the environment is dev. Messages are JSON-RPC 2.0, one per line. Tool descriptions are signed with the token, so a client can spot a changed description.
| Tool | Risk | What it does |
|---|---|---|
ops.status | read | State, health, resources, posture, active runs and signal counts |
ops.signals | read | A page of recent signals |
ops.norms | read | What normal looks like for each metric |
ops.playbooks | read | The declared rules with step classes and modes |
ops.reactions | read | Runs with their overlays and expiries |
ops.plan | read | What a rule would change now and the guard's verdict; changes nothing |
ops.explain | read | Why a run happened: trigger, steps, guard, approval, rollback |
ops.run | write | Files a run that waits for a person |
ops.revert | write | Files a revert that waits for a person |
Nothing in an answer holds a tenant id, user id, config value or secret. ops.run always ends waiting, whatever the rule's run mode, and asks the person at the client to read the plan; a yes there is recorded in the audit and never approves. A person approves with mainsdk playbooks approve <run>, and an agent that tries gets MAIN-AGENT-0003. A rule with a step that is never a reaction is refused with MAIN-GUARD-0002.
Helpers for wiring and tests
| Call | What it does |
|---|---|
new OpsServer(opts) | The ops server of one app instance, for tests and custom wiring. |
parseOps(section, env) | Read the ops section of main.yaml for an environment. |
opsIssues(section, ctx) | List what is wrong in the ops section, without throwing. |
OPS_TOOL_NAMES(const) | The names of the nine ops tools. |
Errors and checks
MAIN-OPS-0001: anopsvalue is invalid (mainsdk check).MAIN-OPS-0002: no token or a wrong one.MAIN-OPS-0004: a write tool withwrite: off, or a name outside the nine tools.MAIN-OPS-0005:mainsdk ops mcpfound no running app.MAIN-OPS-0006: the ops agent's budget is spent.
See check codes.
Swapping drivers
ops has no drivers. It is a server over the app's own state, and the tools answer the same whatever drivers the app uses.
Testing
new OpsServer(opts) builds the server of one app instance for tests, and the testing kit's MCP client in @mainsdk/testing talks to it against a fixture app. Use parseOps and opsIssues to check the ops section without throwing. See Test your app.
Next
Was this page helpful?