Skip to content

MCP server

The Keel MCP server is a thin Model Context Protocol wrapper over the Keel REST API. It lets an MCP-compatible client, such as Claude Desktop, Claude Code, or Cursor, read and act on your Keel workspace in natural language. Every tool maps to a real API endpoint and is scoped to your API key’s organization, so the MCP server grants no more access than the key already has.

The server runs over stdio and is launched by your MCP client. It needs one environment variable, a workspace API key (create one in the app under Integrations).

Terminal window
KEEL_API_KEY=your_key npx keelgrc-mcp

Configuration (environment):

Variable Required Default Purpose
KEEL_API_KEY Yes none A workspace API key from Integrations
KEEL_BASE_URL No https://app.keelgrc.com Your workspace origin

Most MCP clients take a command, arguments, and environment. For example, a client configuration entry looks like:

{
"mcpServers": {
"keel": {
"command": "npx",
"args": ["keelgrc-mcp"],
"env": { "KEEL_API_KEY": "your_key" }
}
}
}

Point your client at that entry, restart it, and the Keel tools become available to the agent. This page describes keelgrc-mcp 0.8.0, the current release on npm.

If your client can reach a remote MCP server, you can skip the install. Keel runs the same tools over Streamable HTTP at:

https://app.keelgrc.com/mcp

Send your workspace API key on every request as Authorization: Bearer <key>. The endpoint keeps no session: each request is answered with the key it carried, and every tool call runs through the same REST API checks as a direct API call, so the hosted server grants no more access than the key has. It takes one JSON-RPC message per request. Its MCP Server Card is published at /.well-known/mcp/server-card.json.

Every tool calls a real REST API endpoint. Tools are grouped by resource rather than one tool per operation: you call keel_policies with an action of list, get, create, update or delete, instead of five separate tools. That keeps the tool list short enough for a client to use well, and it means a new verb on a resource does not add a new tool.

Where a resource is missing an action, the tool says so and why. keel_tasks has no delete because no Keel surface deletes a task, in the app or the API; close one by setting its status instead. keel_webhooks has no get or update because the REST API has neither; list the subscriptions to read one. create returns the subscription’s signing secret once, and rotate replaces it with a window in which both secrets sign; see rotating a secret. event must be “all” or a name Keel emits, and any other value is refused.

There are two ways to add controls. keel_controls create adds one control of your own, as Controls → Add a control does in the app. keel_starter_controls adds a framework’s recommended controls already mapped to its clauses, as Controls → Add recommended controls does.

Applying a framework stays in the app. keel_starter_controls and the map action refuse a framework the workspace has not applied.

Tool Actions
keel_whoami Connection test and the organization the key belongs to
keel_members The workspace’s members (id, email, name, role), read-only
keel_frameworks The frameworks the workspace has applied, each with its readiness
keel_readiness ISO/IEC 27001 readiness summary. This endpoint covers that framework only, not whichever framework the workspace has applied
keel_controls list, get, create, update, delete, mappings, map, unmap
keel_starter_controls None. Takes frameworkKey and adds that framework’s recommended controls, already mapped (owner or admin)
keel_tasks list, get, create, update
keel_risks list, get, create, update, delete
keel_vendors list, get, create, update, delete
keel_people list, get, create, update, delete
keel_policies list, get, create, update, delete
keel_evidence list, get, create, update, delete
keel_webhooks list, create, rotate, delete

Each tool refuses an argument it does not accept, so a misspelled or unsupported field is an error rather than something silently dropped. The REST API does the same on its side (see request rules).

  • keel_members lists the people with a Keel seat. Its emails are what assigneeEmail on keel_tasks and ownerEmail on keel_policies must match, or the call fails and nothing is written. keel_people is the HR directory, a different list. keel_people create, update and delete need a key created by an owner or admin.
  • keel_evidence returns each item’s linked controls, and update links and unlinks controls with linkControls and unlinkControls (control ids or keys). A control that is not in the workspace is an error, and nothing changes.
  • keel_controls create takes name (required), description and key, your own identifier for the control. Omit key and Keel generates one. A key the workspace already uses is an error that carries the existing control, and nothing is overwritten. The new control starts at not_started with no owner and no mappings: set those afterwards with update and map. Creating needs the owner or admin role.
  • keel_controls get returns crosswalks, the clauses the control is mapped to, in the shape mappings returns them. list does not.
  • keel_controls mappings lists the clauses a control is mapped to. map and unmap take the control id, a frameworkKey as keel_frameworks lists it, and a requirementRef written exactly as Keel writes it (for example 4.1). map refuses a section heading, because readiness never scores one, and mapping the same clause twice reports created: false. unmap on a clause the control is not mapped to is an error. Both need the owner or admin role.
  • keel_starter_controls is safe to repeat: a second call adds nothing, and the counts it returns (controlsAdded, mappingsAdded and the rest) say so. A control the workspace already has keeps its name, description and state and only gains the mappings.
  • keel_risks can return a risk with no score. A risk imported from another platform without a likelihood or impact has likelihood, impact, inherentScore and level set to null, and the register shows it as Needs scoring until someone rates it.
  • keel_tasks list takes an optional query, matched against the title. It takes assigneeEmail on create and update. A dueAt that is not a valid date is refused, and tasks have no start date.

The server is versioned independently of the app, so check the tool list your client reports rather than trusting this table.

A key acts as the member who created it, and carries that member’s role. A role-gated action refuses a key whose member cannot perform it in the app, so the MCP and the browser give the same person the same answer. The role is re-read on every call, so a change to somebody’s role takes effect immediately rather than when their key is next replaced.

A key created before this behavior existed carries no member and therefore no role. It can read, and it is refused on every action that changes anything, including creating a record and unsubscribing a webhook. Replace it under Settings if you hit that.

  • The MCP server holds no credentials of its own; it uses the API key you provide, and inherits that key’s workspace scope and permissions.
  • Because every call goes through the same API surface, the same tenant isolation applies. An agent can only see and change what the key already allows.
  • Treat the API key like a password. Rotate it under Integrations if it is exposed.