Work in progressThese docs are still being written and are currently mostly AI-generated. Some details may be inaccurate or incomplete.

MCP server

Drive infrawrench from Claude Desktop, Cursor, or any other Model Context Protocol client.

Infrawrench exposes a hosted Model Context Protocol server at /api/mcp. Point an MCP client (Claude Desktop, Cursor, the MCP CLI, your own LLM agent) at it and the model can list, search, inspect, create, and edit resources in your organization — using your provider credentials, with every mutation written to the audit log.

Endpoint

POST https://app.infrawrench.com/api/mcp

The transport is Streamable HTTP — no WebSocket or stdio bridge required. The server speaks both current protocol generations and answers each request in whichever one the client uses:

  • 2026-07-28 (the current revision) — stateless per-request negotiation, server/discover, and the per-request _meta envelope. New clients need no handshake at all.
  • 2025-11-25 and earlier — the classic initialize handshake, served statelessly with plain JSON response bodies (enableJsonResponse: true), exactly as before. Existing clients keep working unchanged.

Authentication

Authentication is OAuth via WorkOS AuthKit. The flow is standards-compliant (RFC 9728 Protected Resource Metadata) so any OAuth-capable MCP client just works:

  1. Client makes an unauthenticated request to /api/mcp.
  2. Server replies 401 Unauthorized with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource".
  3. Client fetches the metadata, discovers the WorkOS authorization server, identifies itself (see below), and prompts the user to sign in.
  4. Client retries with Authorization: Bearer <token>.

The bearer token is verified against WorkOS JWKS on every request, then mapped to a user + organization — so each MCP connection only ever sees orgs the signed-in user is a member of.

How clients identify themselves

The 2026-07-28 spec deprecates OAuth Dynamic Client Registration in favour of Client ID Metadata Documents. All three registration mechanisms work against this server — clients pick whichever the authorization server advertises, preferring CIMD:

  • Client ID Metadata Documents (the current standard) — the client’s client_id is an HTTPS URL hosting its own metadata document; the authorization server fetches and validates it. No registration round-trip, and the same client_id is portable across servers.
  • Dynamic Client Registration (deprecated by the spec, still fully supported) — the client registers itself at the authorization server’s registration_endpoint before the first sign-in. Pre-2026 clients keep working unchanged.
  • Manual pre-registration — register an OAuth client in the WorkOS Dashboard yourself and paste its client ID into the MCP client’s connector settings.

Choosing an organization

MCP clients have no organization picker, and the OAuth token AuthKit issues them does not necessarily carry an org_id claim. So the server resolves the org itself:

  • If the token carries an org_id, it is used — after checking the caller is a member of it.
  • Otherwise the oldest organization you belong to becomes the default for the connection.

To work with a different org, every tool takes an optional org_id. Call list_organizations to see what you can address:

[
  { "org_id": "org_01H…", "name": "Acme", "role": "owner", "default": true },
  { "org_id": "org_01J…", "name": "Side project", "role": "member", "default": false }
]

Then pass it on any call — list_resources { "org_id": "org_01J…" }. An org_id you are not a member of is refused before the tool runs, so this can never reach beyond your own memberships.

org_id and list_organizations are MCP-only. The AI chat already knows its organization from your session and has no org switcher, so neither appears there.

Screenshot neededClaude Desktop MCP settings panel showing the infrawrench server configured with the /api/mcp URL

What the model can do

The MCP server registers the full shared tool registry — the same tools the AI chat uses — plus list_organizations (see Choosing an organization), which is MCP-only:

  • Discoverlist_plugins, list_resource_types, list_accounts, list_resource_sidecars (which peer plugins a resource exposes — e.g. kubernetes on a managed cluster, postgres on a managed database).
  • Readsearch_resources, list_resources, get_resource, get_resource_inputs, get_resource_outputs, get_resource_stats, get_resource_metrics, describe_resource.
  • Impactget_blast_radius (what breaks if a resource is deleted: transitive dependants, the dashboards, custom graphs, probes, status pages, firing metric alerts, leases, schedules, saved log queries and workflows that name it, what measurably talks to it, and its owner). Its unchecked field says what the report could not look at — an empty dependant list next to a non-empty unchecked is not a clean bill of health, and a client should say so rather than call the resource safe. See Blast radius.
  • Mutatecreate_resource, delete_resource, invoke_action. Reach for get_blast_radius before proposing a deletion.
  • Manifestsget_manifest, apply_manifest (see Manifest editor).
  • Connectionssql_query, sql_execute, introspect_sql_schema, kv_command, docker_command, ssh_exec, and the storage tools (list_storage_objects, make_storage_folder, delete_storage_object).
  • Costs & budgetsquery_costs (aggregate spend series with grouping, filters, previous-period comparison, and forecasts), list_cost_dimension_values, get_cost_status, list_cost_anomalies and acknowledge_cost_anomaly (record what a detected spike actually was — the explanation is published as a dated note on every cost chart covering that day; see Cost anomaly alerts), list_budgets, get_budget, create_budget, update_budget, delete_budget, plus the tag governance reads get_tag_compliance, query_untagged_spend, and query_showback, and list_billing_rules — the organization’s own adjustments to collected spend, and the reason an adjusted total may not match an invoice. query_costs and query_showback both take adjusted; the tools quote collected spend unless asked otherwise and always state both figures when they report an adjusted one. See Cloud costs and Billing rules.
  • Managed accounts & invoiceslist_managed_accounts, get_managed_account, list_invoices, get_invoice. Customers you bill for infrastructure you run on their behalf, and the invoices raised against them. All read-only on invoices:read, a family of its own rather than costs:* because a managed account holds a customer’s contact details and the price they were quoted. get_invoice returns the full derivation — collected, what the billing rules added, the rate and the day it was read — so “why is this customer being billed this” is answerable. A draft’s totals are null in the list, not zero: drafts recompute on read and the list does not. See Managed accounts & invoices.
  • Cost reportslist_cost_reports, get_cost_report, run_cost_report (takes only an id — the report is the query), create_cost_report, update_cost_report, delete_cost_report. Named saved cost graphs, so “run the monthly spend report” needs no filters restated. See Cost reports.
  • Sleep scheduleslist_schedules (windows, next transitions, last outcomes, projected savings) and create_schedule (attach an off-at/on-at weekly window to a stoppable resource; audit-logged). See Sleep/wake schedules.
  • Moment viewwhat_changed (everything that happened around a timestamp, merged chronologically across the change timeline, provider incidents, cost anomalies, workflow runs, deployments, audit entries, freezes and alert deliveries). Invoking it requires resources:read; feeds the caller can’t read are reported as omitted, while a feed whose query fails is reported as errored without losing the rest — so a client can tell authorization gaps from transient feed failures. See Investigate a moment.
  • Ownershiplist_resource_ownership (who owns each resource, what it is for, its ticket link; filter by ownerUserId or unownedOnly). A resource absent from the list is unowned, which is what makes this worth asking before proposing a deletion. See Resource ownership.
  • Status pageslist_status_pages (what monitoring is published, whether each page is live, and its slug). Treat a published: true page as externally visible — anyone with its link can read it without signing in. See Public status pages.
  • Environment diffdiff_environments (two accounts of one provider compared: resource types present in one and not the other, per-type count deltas, and the fields on which corresponding resources disagree). Takes the accounts by id or exact name. The tool to reach for when someone asks why one environment behaves differently from another. See Environment diff.
  • Right-sizinglist_oversized_resources (machines whose 14-day p95 utilisation sits well under their size, with the recommended smaller size and monthly saving; read-only — applying a resize is done from the app). See Right-sizing.
  • Workflowslist_workflows, get_workflow, get_workflow_typings (returned in parts for large organizations — the global scope first, then named interfaces on request), check_workflow_source, write_workflow, run_workflow, delete_workflow, plus list_workflow_secrets, write_workflow_secret, and delete_workflow_secret. Listing or exposing assigned secret metadata/typings requires secrets:read; writing or deleting secret metadata requires secrets:write. write_workflow assigns reusable secret ids and generated typings expose only those names under infra.secrets. See Writing workflows with an AI client.
  • Custom graphslist_custom_graphs, get_custom_graph, get_custom_graph_typings, write_custom_graph (type-checked before saving), render_custom_graph, delete_custom_graph. Script-defined dashboard charts with their own controls and refresh policy; paid plan only. See Custom graphs.
  • Deploymentslist_deployments, get_deployment, list_deployable_repos, plan_deployment (runs a repo’s plan() and renders the Dockerfile, building and shipping nothing), rollback_deployment. There is deliberately no tool that deploys — a human starts that from the app or the CLI. See Infrafile.
  • SSH keyslist_ssh_keys, create_ssh_key (generates an Ed25519 keypair; the private key stays encrypted server-side and is usable by id with ssh_exec and tunnels — it is never returned through a tool), import_ssh_key (public key only), delete_ssh_key. See SSH keys.
  • SSH host trustlist_trusted_ssh_hosts, trust_ssh_host, remove_ssh_host_trust. When ssh_exec hits an untrusted host it fails with the presented fingerprint; verify it out-of-band, then trust_ssh_host { host, port, fingerprint } and retry. See Trusted SSH hosts.
  • Linux applications — drive a graphical application on a resource’s host without a browser: list_apps_on_host, launch_app (returns a windowId), screenshot_app_window (a PNG the model can see), read_app_accessibility_tree (what a screen reader reads — every element’s role, name, text, state and on-screen bounds, each bounded node carrying a center you can click), click_app_window, type_in_app_window, press_keys_in_app_window, scroll_app_window, list_app_windows, and close_app_window. The screenshot pixels, a click’s coordinates, and the accessibility tree’s bounds are all one space, so read the tree (cheaper and exact) or a screenshot to find a target, then act on it by pixel. The host runs the applications; Infrawrench brings the display. A VM resource needs an sshKeyId (see list_ssh_keys), the same key the terminal uses; a host whose plugin supplies SSH natively (Fly, Hetzner) needs none. All carry resources:execute and are audit-logged. See Linux applications.

It also registers per-plugin create tools at server build time. For every resource type that supports creation, you get a typed tool like digitalocean_create_droplet or aws_create_s3_bucket with a Zod schema generated from the plugin’s field definitions — so the model can discover what to set without first round-tripping list_resource_types.

Create tools for VM types that install an SSH key at create time (DigitalOcean Droplets, Hetzner servers, EC2 instances, GCE instances, Scaleway instances) additionally accept sshKeyId — a stored org SSH key id (see list_ssh_keys) whose public key is injected into the provider’s SSH-key field server-side. The same parameter works on the generic create_resource. Only the public key is used; a raw OpenSSH public key can be passed instead.

Most resource tools take an optional parentResourceId to target a sidecar — the peer plugin a managed resource exposes through its outputs. “What’s running in my DOKS cluster?” is list_resource_sidecars on the cluster, then list_resources { pluginId: "kubernetes", resourceTypeId: "k8s-deployment", parentResourceId: <cluster id> } — the kubeconfig is resolved server-side from the cluster’s outputs, and the same pattern drives describe_resource, invoke_action, apply_manifest, and the per-plugin create tools inside the cluster or database.

The cost, budget, workflow, deployment, and SSH-key tools enforce the same role permissions as the web dashboard (costs:read, budgets:read, budgets:write, dashboards:read, dashboards:write, workflows:read, workflows:write, secrets:read, secrets:write, deployments:read, deployments:plan, deployments:write, ssh-keys:read, ssh-keys:write) — a member whose role can’t see spend or secret metadata in the UI can’t read it through MCP either. Deleting another member’s SSH key additionally requires team:role:write, matching the HTTP API.

Audit and safety

Every mutating tool call (create_resource, delete_resource, invoke_action, apply_manifest, the budget tools, the workflow tools, plan_deployment and rollback_deployment, the per-plugin create tools) writes a row to the audit log with source: "mcp". You can filter the audit log by source to see exactly what the model has done in your org.

There is no destructive-action confirmation step inside the protocol — that responsibility lives in the client. Claude Desktop and Cursor surface a permission prompt before each tool call by default; we recommend leaving those prompts on for the mutating tools at a minimum.

write_workflow_secret is deliberately metadata-only over MCP: its schema has no value argument, so a password cannot leak into model context or client logs. It creates or updates the named slot; set its value through Infrawrench’s workflow editor or secure in-chat password prompt, then use the returned id with write_workflow.

Connecting from Claude Desktop

Add an entry to your claude_desktop_config.json:

{
  "mcpServers": {
    "infrawrench": {
      "url": "https://app.infrawrench.com/api/mcp"
    }
  }
}

Restart Claude Desktop. The first request will trigger the OAuth browser flow.

Screenshot neededClaude Desktop authorization prompt for infrawrench MCP

Tips & limits

  • Each request is stateless — a fresh McpServer per call, whichever protocol revision the client speaks. There are no protocol-level sessions (the 2026-07-28 model), so long-running tools should return quickly and stream progress via subsequent calls rather than holding a request open.
  • Self-hosted infrawrench: set PUBLIC_BASE_URL to your public origin so the OAuth resource metadata advertises the correct URL (it falls back to APP_URL), and WORKOS_AUTHKIT_DOMAIN (or WORKOS_ISSUER) so clients discover the right authorization server. WORKOS_AUTHKIT_DOMAIN must be your AuthKit domain — https://api.workos.com/user_management does not serve OAuth authorization-server metadata, so discovery fails against it.
  • Self-hosted infrawrench: under Connect → Configuration in the WorkOS Dashboard, enable Client ID Metadata Document (the current MCP standard for client registration) and — for clients that haven’t adopted it yet — Dynamic Client Registration (deprecated by the 2026-07-28 spec, still fully supported). With neither enabled, clients without pre-registered credentials cannot connect: CIMD-capable clients find no client_id_metadata_document_supported flag in the authorization server metadata, and DCR clients get dynamic_client_registration_disabled from /oauth2/register. The server probes the authorization server’s metadata whenever OAuth discovery is served and logs a [mcp-auth] warning when neither mechanism is advertised — check the web pod’s logs when clients report they couldn’t register. As an alternative, register an OAuth client in WorkOS manually and paste its client ID into the MCP client’s connector settings.

Supported providers

44 providers · 340+ resource types across cloud, infrastructure, databases, and more.