Automation & agents
MCP adapter
A built-in MCP server exposing a bounded slice of the Phelix machine interface to coding agents over local stdio, running the exact same pipeline as the equivalent CLI command.
Phelix ships a built-in MCP server that exposes a small, deliberately bounded slice of its machine interface to autonomous coding agents (Claude Code, Codex, and any MCP-compatible client) over local stdio. It is a transport boundary only - no new execution engine, no AI, no network listener. Every tool call runs the same validation, plan, authorization, approval, and idempotency pipeline as the equivalent phelix command and returns the same machine envelope.
AI client ──stdio(JSON-RPC)──▶ phelix mcp serve ──▶ existing Phelix pipeline (internal/mcp) (plans · authz · ops · deploy)Scope
In scope this phase: a local stdio server exposing read tools (context, inspect, plan show/list, operation status) and two controlled mutation tools (plan create, plan apply), plus the session tracking tools.
Explicitly out of scope: any LLM or embedded agent; network/remote MCP; RBAC or a policy DSL; new approval workflows; a new deployment engine; and direct rebuild/rollback/deploy/approval/shell/filesystem tools that would bypass plan-based authorization.
Starting the server
phelix mcp serve --stdioConfigure your agent
Use the absolute path to the phelix binary (which phelix). The server’s working directory is whatever the agent launches it in, so launch the agent from your project root for project context. Pass environment through the client’s env block (e.g. PHELIX_DATA_DIR). Never put real secrets in these files.
claude mcp add phelix -- /usr/local/bin/phelix mcp serve --stdio{ "mcpServers": { "phelix": { "command": "/usr/local/bin/phelix", "args": ["mcp", "serve", "--stdio"], "env": { "PHELIX_DATA_DIR": "/var/lib/phelix" } } }}[mcp_servers.phelix]command = "/usr/local/bin/phelix"args = ["mcp", "serve", "--stdio"]# env = { PHELIX_DATA_DIR = "/var/lib/phelix" }Verify from the client by listing tools and calling phelix_context: a clean envelope with "status": "succeeded" confirms the wiring.
Tools
| Tool | Mutates? | Purpose |
|---|---|---|
| phelix_context | no | One bounded composite snapshot of project, runtime, config, and app state |
| phelix_inspect | no | One topic: project, runtime, config, capabilities, operations, app, deployment, versions, health |
| phelix_plan_show / phelix_plan_list | no | Read a plan's content and applicability, or list recent plans |
| phelix_operation_status | no | Resolve an operation id to current state and result |
| phelix_plan_create | writes a plan artifact | Describe + persist an immutable plan (rebuild|rollback) - never executes |
| phelix_plan_apply | yes (executes) | Execute a previously created plan through the full pipeline |
| phelix_session_create / checkpoint / complete / fail | writes a tracking record | Track one unit of agent work (no execution) |
| phelix_session_show / phelix_session_list | no | Read a session and its resolved references, or list sessions |
Result contract
Every tool returns exactly one JSON document - the Phelix machine envelope - as the result’s text content (mirrored into structuredContent):
{ "schema_version": "1", "operation_id": "op_…", "status": "succeeded", "result": { … } }A Phelix failure is a normal result, not a protocol error: the envelope carries "status":"failed" with a stable error.code, error.exit_code, and error.retryable, and the tool result’s isError flag is set. Authorization denials, approval-required, stale plans, and build/deploy failures all arrive this way - never silently converted to success. Only genuine invocation problems (unknown tool, schema-invalid arguments, malformed id) surface as a tool/transport error.
Concurrency
A single plan apply can run for minutes (build, deploy, health). While it runs, the server keeps serving tool calls, with one deliberate limit:
phelix_session_showandphelix_session_listare served off the capture lock, so an agent can keep reading its session - and the plans and operations it references - while its own apply is in flight.- Every other tool - the other reads (
phelix_context,phelix_inspect,phelix_plan_show,phelix_plan_list,phelix_operation_status) and all mutations - is serialized behind the running apply, because the command code they run consults process-global state that is not yet concurrency-safe. Calls queue and drain in order; none is dropped or corrupted.
Authorization & approvals
The MCP server authorizes as a distinct, unauthenticated local MCP caller (actor type mcp), never as the local CLI. This is the whole security posture of the adapter:
To let an agent execute plans, the host operator opts in with an enforced policy that allows the mcp caller, ideally with require_approval. The flow then becomes: the agent calls phelix_plan_create, then phelix_plan_apply (which returns APPROVAL_REQUIRED - nothing executed); a human approves that exact plan out of band with phelix authz approve <plan-id>; the agent calls phelix_plan_apply again and it proceeds. Approval creation and revocation are human CLI actions, never MCP tools. See Execution authorization.
Security & trust boundaries
- Local boundary, not authenticated remote access. stdio MCP trusts the OS: any process able to launch
phelixas your user can call these tools with your permissions, exactly as it could runphelixdirectly. The honest trust boundary is write access to the Phelix data directory. - No caller-supplied identity. Tool inputs carry no actor, role, authenticated flag, approval, or authorization field, and the server ignores any such value - the authenticator is the only producer of an actor.
- Fail-closed and integrity-preserving. Plan-hash verification, stale-plan and capability checks, the authorization boundary, plan-bound approvals, and request-key idempotency are all preserved; a refused or interrupted apply mutates nothing and is never silently retried into execution.
- Redaction and bounds. Every envelope passes the shared secret redactor; all list/limit arguments are bounded, and a single JSON-RPC frame is capped.