MCP Reference

Drive Agiler from Claude, Cursor, and other MCP-aware agents — manage projects, files, SQL, backups, and more without leaving the chat.

Introduction

The Agiler MCP server lets AI agents — Claude Code, Claude Desktop, Cursor, and any other client that speaks the Model Context Protocol — read and write everything in your Agiler account: projects, files, SQL, backups, domains, environment variables, rules, logs, usage. It’s the same surface area as the REST API and the CLI, exposed as MCP tools so an agent can reason about your hosting and act on it in a single conversation.

If you’re new to MCP itself, the protocol’s introduction is the best starting point — it explains the JSON-RPC framing, the difference between tools and resources, and how clients negotiate capabilities.


Endpoint

https://mcp.agiler.io/v1

The transport is streamable HTTP — JSON-RPC 2.0 over POST, with server→client notifications delivered as Server-Sent Events on the same connection. Sessions are stateful: the server issues an Mcp-Session-Id header on the initial initialize request that the client must echo on every subsequent call.

Session timeout defaults to 15 minutes of inactivity. Re-initializing produces a fresh session ID; the previous one is silently dropped.

The server identifies itself as {"name":"agiler","version":"v1"} in the initialize response. It advertises tools with listChanged support, which the server uses to revoke tools mid-session when a token’s scopes change (see Scopes).


Authentication

Every request must carry a bearer token:

Authorization: Bearer ak_...

The token is an Agiler API key — the same kind the CLI and REST API accept. Generate one in the dashboard under Settings → API Keys. Token scopes are fixed at issue time and enforced per request: the server re-verifies the token against the public API on every JSON-RPC call, so revocation propagates within one round trip rather than waiting for a session to expire.

A request with no token, an invalid token, or a token that has been revoked returns 401 Unauthorized before any MCP message processing happens.


Setup

Point your MCP-capable agent at the endpoint above with your API key in the Authorization header. Configuration shape varies by client — three of the most common are below.

Claude Code — add to ~/.claude/mcp.json (global) or .mcp.json in the project root:

{
  "mcpServers": {
    "agiler": {
      "type": "http",
      "url": "https://mcp.agiler.io/v1",
      "headers": {
        "Authorization": "Bearer ak_..."
      }
    }
  }
}

Then run claude mcp list to confirm the server is reachable and claude mcp tools agiler to enumerate the tools your token’s scopes unlock.

Claude Desktop — open Settings → Developer → Edit Config and add the same mcpServers entry as above. Restart the app so the new server is loaded.

CursorSettings → MCP → Add new MCP server, pick the HTTP transport, paste the endpoint, and set the Authorization header.

Generic — any client implementing the streamable-HTTP transport. The minimum handshake is one POST to /v1 with a JSON-RPC initialize request, then subsequent tools/list and tools/call requests carrying the Mcp-Session-Id header returned in the initialize response.

A quick smoke test, no MCP client required:

SESSION=$(curl -sD - https://mcp.agiler.io/v1 \
  -H "Authorization: Bearer $AGILER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
  | awk '/Mcp-Session-Id/ {print $2}' | tr -d '\r')

curl -s https://mcp.agiler.io/v1 \
  -H "Authorization: Bearer $AGILER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: $SESSION" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

Scopes

Tools are gated by the token’s scopes. The base tools (whoami, regions_list, runtimes_list, rules_catalog, scopes_list) are always available to any authenticated user; everything else requires at least one scope from the table below.

ScopeTools gated
projects:readworkspaces_list, workspaces_get, projects_list, projects_get
projects:writeworkspaces_create, projects_create, projects_update, projects_delete
projects.files:readfiles_list, files_get
projects.files:writefiles_write, files_write_many, files_move, files_copy, files_delete
projects.sql:executesql_run, sql_list, sql_get, sql_cancel_or_delete
projects.domains:readdomains_list
projects.domains:writedomains_create, domains_update, domains_update_primary, domains_delete
projects.variables:readvariables_list
projects.variables:writevariables_create, variables_update, variables_delete
projects.rules:readrules_list, rules_get
projects.rules:writerules_create, rules_update, rules_delete
projects.backups:readbackups_list, backups_get_policy
projects.backups:writebackups_create, backups_update_policy, backups_delete, backups_restore
projects.logs:readlogs_list
projects.usage:readusage_get

Inheritance. Granting a broader scope implies the narrower ones, so most tokens only need a handful of explicit scopes:

  • projects:write implies projects:read and every projects.* scope (both :read and :write).
  • projects:read implies every projects.*:read scope.
  • A specific :write (e.g. projects.files:write) implies its corresponding :read.

Use scopes_list to see this table from inside an agent, and whoami to inspect the scopes the current token actually carries. If a tool the agent expects is missing from tools/list, the token is probably under-scoped — re-issue it with the right scope and reconnect.

Mid-session revocation. If the token’s scopes change while a session is active (a scope is revoked, or the whole token is rotated), the affected tools are removed from the server on their next call and the client receives a notifications/tools/list_changed notification. The conversational agent can then re-list tools and continue with whatever’s still authorized — no hard disconnect.


Tools

Run tools/list to see exactly which tools your token unlocks. The full inventory, grouped by scope domain:

Base (no scope required):

ToolWhat it does
whoamiReturn the authenticated user’s profile plus effective_scopes
regions_listRegions where projects can be hosted
runtimes_listAvailable runtime stacks (e.g. php85, php83)
rules_catalogValid fact / operator / action codes and ready-to-use rule templates
scopes_listEvery MCP scope, the tools each scope gates, and a one-line summary

Workspaces & projects (projects:read / projects:write):

ToolWhat it does
workspaces_listList workspaces the caller can access
workspaces_getFetch a single workspace
workspaces_createCreate a new workspace
projects_listList projects, optionally scoped to a workspace
projects_getFetch a single project (accepts UUID or slug)
projects_createCreate a project; bootstraps default rule, domain, and backup policy
projects_updateUpdate name, runtime, workspace_id, active
projects_deletePermanently delete a project; requires confirm_name

Files (projects.files:read / projects.files:write):

ToolWhat it does
files_listList files / directories under a project
files_getFetch a single file’s contents + metadata (size, etag, modified_at)
files_writeWrite or overwrite one file; supports if_match (CAS) and if_none_match="*" (create-only)
files_write_manyBatch write up to 100 files in one call; non-atomic — per-row results[]
files_moveAtomic move; returns the new entry’s etag
files_copyAtomic copy; returns the new entry’s etag
files_deleteDelete a single file

SQL (projects.sql:execute):

ToolWhat it does
sql_runRun one SQL statement; requires explicit read_only and supports async=true
sql_listList recent statements (slim projection: id, status, sql_preview)
sql_getFetch a statement plus a page of result rows (default 100, max 1000 per page)
sql_cancel_or_deleteCancel if running; once terminal, delete the record

Domains (projects.domains:read / projects.domains:write):

ToolWhat it does
domains_listList all domains attached to a project
domains_createAttach a new domain; optionally mark it primary
domains_updateRename a domain and/or update its primary flag
domains_update_primarySet or clear the primary flag (legacy single-purpose; prefer domains_update)
domains_deleteDetach a domain; auto-promotes another to primary if needed

Variables (projects.variables:read / projects.variables:write):

ToolWhat it does
variables_listList env variables; value is omitted when sensitive=true
variables_createCreate a variable; name must match ^[A-Z_][A-Z0-9_]*$
variables_updateUpdate name, value, or sensitive flag
variables_deleteDelete a variable

Rules (projects.rules:read / projects.rules:write):

ToolWhat it does
rules_listList rules in priority order
rules_getFetch a single rule
rules_createCreate a rule (call rules_catalog first for the value domain)
rules_updateUpdate fields of an existing rule
rules_deleteDelete a rule

Backups (projects.backups:read / projects.backups:write):

ToolWhat it does
backups_listList backups, most-recent-first
backups_get_policyRead frequency_days / retention_days
backups_createTrigger an immediate manual backup
backups_update_policyAdjust cadence (0–30 days) and retention (1–365 days)
backups_deleteDelete a backup record
backups_restoreRestore a project; requires confirm_backup_created_at; supports drain_requests

Observability (projects.logs:read / projects.usage:read):

ToolWhat it does
logs_listTail request and application logs; since / until / q filters
usage_getBucketed metrics (requests, bandwidth, compute, storage) with per-bucket and total summaries

Every tool returns the canonical envelope described in Response shapes; every tool documents its arguments and side effects through its MCP description, which surfaces in clients that show tool documentation (Claude Desktop’s tool panel, Cursor’s MCP inspector).


Response shapes

All tools return a JSON object inside MCP’s tools/call structuredContent field. The envelope always carries a status (the underlying HTTP status the public API returned, surfaced verbatim) and one of four payload shapes:

List response — for any tool that paginates:

{
  "status": 200,
  "items": [ /*  */ ],
  "pagination": {
    "has_more": true,
    "next_cursor": "abc123…"
  },
  "headers": { /* X-RateLimit-*, ETag, Last-Modified when present */ }
}

pagination.next_cursor is the canonical paging signal — pass it back as the cursor argument on the next call until has_more is false. The Link header that the underlying REST API returns is dropped here to avoid two competing cursor sources.

Single-resource response:

{
  "status": 200,
  "body": { /* the resource */ },
  "headers": { /*  */ }
}

body is the same JSON object the REST API returns for the corresponding GET / POST / PATCH request.

No-content response — for DELETE and other writes that don’t return a body:

{
  "status": 204,
  "ok": true,
  "headers": { /* present only when upstream returned metadata */ }
}

Batch response — only files_write_many returns this shape, since it fans out to N independent PUTs:

{
  "status": 200,
  "summary": { "ok": 12, "failed": 1 },
  "results": [
    { "path": "wp-content/style.css", "ok": true, "status": 200, "etag": "\"…\"", "modified_at": "2026-05-25T…" },
    { "path": "wp-content/locked.php", "ok": false, "status": 412, "error": { "code": "precondition_failed", "message": "…" } }
  ]
}

The aggregate status is always 200; check summary or iterate results for per-row outcomes.


Errors

Tool failures come back as JSON-RPC errors with a structured payload the client can render to the user:

{
  "code": "validation_error",
  "message": "name must not be empty",
  "field": "name"
}

Common codes:

CodeMeaning
validation_errorOne of the arguments is missing, malformed, or out of range — field points at the offending input
not_foundThe referenced resource doesn’t exist or isn’t visible to the caller
already_existsA uniqueness constraint was violated; the envelope often includes the existing resource’s id
precondition_failedif_match / if_none_match rejected the write — the envelope includes current_etag and current_modified_at
project_provisioningProject is still being created; honor Retry-After and re-check projects_get.body.status
project_in_maintenanceProject is offline for upgrade; honor Retry-After

The same HTTP status codes used by the REST API surface in status so an agent can react with the same logic it’d use against the API directly. 429 Too Many Requests is the one to watch for — the headers field includes Retry-After and X-RateLimit-Reset so the agent can back off cleanly.


Patterns

A few conventions show up repeatedly across the tool surface. Knowing them once means knowing them everywhere.

Pagination. Every list tool accepts optional cursor and limit arguments. The first call may omit both; the response’s pagination.next_cursor feeds the next call until pagination.has_more is false. limit is clamped server-side; agents that pass an out-of-range value get a single, consistent error envelope rather than a silent downgrade.

Idempotency. Destructive tools require a second argument that acknowledges the live resource state, not a remembered identifier:

  • projects_delete requires confirm_name equal to the project’s current body.name. The server re-fetches the project and rejects with validation_error if it doesn’t match — protection against acting on a stale id.
  • backups_restore requires confirm_backup_created_at equal to the backup’s current body.created_at. Same protection.

A second call after the resource is gone returns 404 not_found rather than re-deleting something new with the same name.

Compare-and-swap. File writes support optimistic concurrency:

  • if_match: "<etag>" — write only if the file’s current ETag matches; returns 412 precondition_failed otherwise. Pair with files_get (or the etag returned by a previous files_write / files_write_many / files_move / files_copy) to chain edits without a round-trip.
  • if_none_match: "*" — create-only; returns 412 if the file already exists.

On 412, the error envelope includes the current etag and modified_at so the agent can decide whether to retry, merge, or abort without re-fetching.

Async SQL. Long-running statements can be dispatched with async: true:

{ "name": "sql_run", "arguments": {
  "project": "<uuid>",
  "sql": "ALTER TABLE wp_posts ADD INDEX idx_status_date (post_status, post_date);",
  "read_only": false,
  "async": true
}}

The call returns 202 pending with the statement’s id; poll sql_get until status transitions to success, error, or cancelled. Synchronous statements are capped at 180 s and 1000 rows inline.

Project status. A project’s body.status is one of pending (still provisioning), ok (ready), or maintenance (offline for upgrade work). File, SQL, and backup operations against a non-ok project return 503 with code: project_provisioning or project_in_maintenance — check projects_get and honor Retry-After before retrying.

Tool deregistration. When a tool the agent has been calling loses its required scope mid-session, the tool is silently removed from the server on its next invocation and the next tools/call returns an error directing the client to reconnect. Agents that subscribe to notifications/tools/list_changed get an immediate signal and can re-list tools without an explicit error.


Feedback

Bug reports, feature requests, and questions about the MCP server are welcome via Contact Us.


© 2026 Agiler. All rights reserved.

The WordPress® trademark is the intellectual property of the WordPress Foundation, and the Woo® and WooCommerce® trademarks are the intellectual property of WooCommerce, Inc. Uses of the WordPress®, Woo®, and WooCommerce® names in this website are for identification purposes only and do not imply an endorsement by WordPress Foundation or WooCommerce, Inc. Agiler is not endorsed or owned by, or affiliated with, the WordPress Foundation or WooCommerce, Inc.