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 sessionless streamable HTTP — every JSON-RPC 2.0 request is an independent POST. Modern clients use protocol version 2026-07-28, attach protocol/client metadata to every request, and call server/discover instead of performing an initialize handshake. A response is either one JSON object or a request-scoped Server-Sent Events stream.

The server never issues or consumes an Mcp-Session-Id. Standalone GET and DELETE requests return 405 Method Not Allowed; Last-Event-ID and legacy session headers are ignored because streams are not resumable. Older initialize requests are still accepted for compatibility, but each creates only a temporary request-scoped server and does not return a session ID.

The server identifies itself as {"name":"agiler","version":"v1"} through discovery. It advertises tools with listChanged: false. Because the authorized catalog depends on the freshly verified token, every tools/list result has ttlMs: 0 and cacheScope: "private".


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 Profile → API Keys. The server re-verifies the token and rebuilds its authorized tool catalog on every JSON-RPC request, so token or scope revocation takes effect on the very next request.

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 — use protocol version 2026-07-28. Every POST must include MCP-Protocol-Version and Mcp-Method; tools/call also requires Mcp-Name. The protocol version in the HTTP header must match params._meta.io.modelcontextprotocol/protocolVersion in the JSON-RPC body. Each request also carries client information and capabilities in _meta; there is no session header or initialization state to retain.

A quick smoke test, no MCP client required:

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-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: server/discover" \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}}}'

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-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/call" \
  -H "Mcp-Name: whoami" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}}}'

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.wp:executewp_run, wp_list, wp_get, wp_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 — grant or issue the required scope, then list tools again.

Immediate revocation. There is no session-scoped catalog. If a scope is revoked, the next request rebuilds the tool set from the newly verified scopes. A stale invocation of the removed tool is rejected, while unscoped tools and tools covered by the remaining scopes continue to work. Clients should re-run tools/list after an authorization error; the private zero-TTL response is safe to refresh on every use.


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 or delete a SQL statement

WordPress (wp-cli) (projects.wp:execute):

ToolWhat it does
wp_runRun one wp-cli command — arguments without the leading wp — and supports async=true
wp_listList recent commands (slim projection: id, status, command_preview)
wp_getFetch a command plus a page of output lines (default 100, max 1000 per page)
wp_cancel_or_deleteCancel or delete a wp-cli command

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. An upstream REST API 429 Too Many Requests appears in the tool error envelope with its Retry-After and X-RateLimit-* metadata.

The MCP endpoint also has its own admission limits. Those checks run before tool execution and return an HTTP 429 directly with Retry-After: 1. Request-rate rejections also include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Retry after at least the advertised delay, preferably with jitter; repeated immediate retries remain subject to the same one-second window. A request body over the configured per-request cap returns HTTP 413 Request Entity Too Large instead — reduce or split the request rather than retrying it unchanged.


Admission limits

Admission is non-blocking: a request that cannot obtain rate, concurrency, or aggregate-body capacity is rejected immediately rather than waiting in an in-process queue. The default limits are per MCP server process:

ConfigurationDefault
max_request_body_bytes67,108,864 (64 MiB)
max_inflight_body_bytes1,073,741,824 (1 GiB)
global_rate_limit_per_second500
peer_rate_limit_per_second100
user_rate_limit_per_second30
max_concurrent_requests256
max_concurrent_requests_per_peer32
max_concurrent_requests_per_user16

In server configuration, zero selects the default and negative values are invalid. max_inflight_body_bytes must be at least max_request_body_bytes; peer and user concurrency limits cannot exceed the global concurrency limit.

Global and socket-peer checks run before bearer-token verification. The peer key comes only from the direct socket’s RemoteAddr; forwarded-address headers are intentionally not trusted. If every replica sees one load balancer as its direct peer, that traffic shares the peer quota. Authenticated-user limits use the verified user ID, so two API keys owned by the same user share one quota.

Known Content-Length requests reserve their declared length from the aggregate body budget. Chunked or otherwise unknown-length requests reserve the full per-request cap until the request completes. This budget counts raw request bytes, not decoded JSON or total heap use; decoded strings and tool arguments can use several times the raw size.

Limits are per process, so adding replicas increases fleet-wide capacity. Enforce a fleet-wide ceiling at the load balancer or gateway when needed.

When rolling out or tuning different values, monitor rejection scope/rate, token-verification latency and errors, heap usage, and goroutine count together. Raising the raw body budget alone does not guarantee equivalent heap headroom.


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.

Authorization refresh. Tool catalogs are request-scoped. When a required scope disappears, a stale tools/call is rejected and the next tools/list omits the tool. Re-list tools and continue with the remaining catalog; no server session needs to be reconnected or terminated.


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.