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_metaenvelope. New clients need no handshake at all. - 2025-11-25 and earlier — the classic
initializehandshake, 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:
- Client makes an unauthenticated request to
/api/mcp. - Server replies
401 UnauthorizedwithWWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource". - Client fetches the metadata, discovers the WorkOS authorization server, identifies itself (see below), and prompts the user to sign in.
- 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_idis an HTTPS URL hosting its own metadata document; the authorization server fetches and validates it. No registration round-trip, and the sameclient_idis portable across servers. - Dynamic Client Registration (deprecated by the spec, still fully supported) — the client registers itself at the authorization server’s
registration_endpointbefore 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.
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:
- Discover —
list_plugins,list_resource_types,list_accounts,list_resource_sidecars(which peer plugins a resource exposes — e.g.kuberneteson a managed cluster,postgreson a managed database). - Read —
search_resources,list_resources,get_resource,get_resource_inputs,get_resource_outputs,get_resource_stats,get_resource_metrics,describe_resource. - Impact —
get_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). Itsuncheckedfield says what the report could not look at — an empty dependant list next to a non-emptyuncheckedis not a clean bill of health, and a client should say so rather than call the resource safe. See Blast radius. - Mutate —
create_resource,delete_resource,invoke_action. Reach forget_blast_radiusbefore proposing a deletion. - Manifests —
get_manifest,apply_manifest(see Manifest editor). - Connections —
sql_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 & budgets —
query_costs(aggregate spend series with grouping, filters, previous-period comparison, and forecasts),list_cost_dimension_values,get_cost_status,list_cost_anomaliesandacknowledge_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 readsget_tag_compliance,query_untagged_spend, andquery_showback, andlist_billing_rules— the organization’s own adjustments to collected spend, and the reason an adjusted total may not match an invoice.query_costsandquery_showbackboth takeadjusted; 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 & invoices —
list_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 oninvoices:read, a family of its own rather thancosts:*because a managed account holds a customer’s contact details and the price they were quoted.get_invoicereturns 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 arenullin the list, not zero: drafts recompute on read and the list does not. See Managed accounts & invoices. - Cost reports —
list_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 schedules —
list_schedules(windows, next transitions, last outcomes, projected savings) andcreate_schedule(attach an off-at/on-at weekly window to a stoppable resource; audit-logged). See Sleep/wake schedules. - Moment view —
what_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 requiresresources: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. - Ownership —
list_resource_ownership(who owns each resource, what it is for, its ticket link; filter byownerUserIdorunownedOnly). A resource absent from the list is unowned, which is what makes this worth asking before proposing a deletion. See Resource ownership. - Status pages —
list_status_pages(what monitoring is published, whether each page is live, and its slug). Treat apublished: truepage as externally visible — anyone with its link can read it without signing in. See Public status pages. - Environment diff —
diff_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-sizing —
list_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. - Workflows —
list_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, pluslist_workflow_secrets,write_workflow_secret, anddelete_workflow_secret. Listing or exposing assigned secret metadata/typings requiressecrets:read; writing or deleting secret metadata requiressecrets:write.write_workflowassigns reusable secret ids and generated typings expose only those names underinfra.secrets. See Writing workflows with an AI client. - Custom graphs —
list_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. - Deployments —
list_deployments,get_deployment,list_deployable_repos,plan_deployment(runs a repo’splan()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 keys —
list_ssh_keys,create_ssh_key(generates an Ed25519 keypair; the private key stays encrypted server-side and is usable by id withssh_execand tunnels — it is never returned through a tool),import_ssh_key(public key only),delete_ssh_key. See SSH keys. - SSH host trust —
list_trusted_ssh_hosts,trust_ssh_host,remove_ssh_host_trust. Whenssh_exechits an untrusted host it fails with the presented fingerprint; verify it out-of-band, thentrust_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 awindowId),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 acenteryou can click),click_app_window,type_in_app_window,press_keys_in_app_window,scroll_app_window,list_app_windows, andclose_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 ansshKeyId(seelist_ssh_keys), the same key the terminal uses; a host whose plugin supplies SSH natively (Fly, Hetzner) needs none. All carryresources:executeand 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.
Tips & limits
- Each request is stateless — a fresh
McpServerper 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_URLto your public origin so the OAuth resource metadata advertises the correct URL (it falls back toAPP_URL), andWORKOS_AUTHKIT_DOMAIN(orWORKOS_ISSUER) so clients discover the right authorization server.WORKOS_AUTHKIT_DOMAINmust be your AuthKit domain —https://api.workos.com/user_managementdoes 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_supportedflag in the authorization server metadata, and DCR clients getdynamic_client_registration_disabledfrom/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.