Connect AI agents (MCP)
Firefight ships a Model Context Protocol server, so AI agents like Claude can take part in your incidents directly. An agent connected over MCP can search incidents, pull a full incident timeline, inspect alerts and how they routed, look up ownership in the service catalog, and dry-run your alert routing rules. With permission it can also work an incident the way a person does: declare one, raise and pick up work, claim a runbook step, pull people into the channel, ask a named person to respond, and close it out. It can maintain your catalog, runbooks and routing rules, and resolve approvals, too. Everything an agent does is recorded under Gateway → Activity.
Endpoint
Section titled “Endpoint”The server speaks Streamable HTTP at a single endpoint.
POST https://app.firefight.app/mcpEvery request is self-contained, and all data is scoped to the workspace of the token you authenticate with.
Authenticating
Section titled “Authenticating”There are three ways to connect, depending on what is doing the connecting.
OAuth, for interactive clients
Section titled “OAuth, for interactive clients”Clients that support MCP OAuth, such as Claude Code, connect without you ever handling a token. Add the server and start using it.
claude mcp add --transport http firefight https://app.firefight.app/mcpOn first use, your browser opens Firefight’s consent screen, which names the client asking for access and the workspace it would reach. If you belong to more than one workspace, pick the one you want it to reach. Click Authorize and the client is connected. The client registers itself automatically through dynamic client registration, PKCE is required, and access tokens are short-lived with refresh rotation. The connection acts as you, with the same access you have in the workspace.
Agent token, for an AI acting as itself
Section titled “Agent token, for an AI acting as itself”An AI that takes part in incidents gets its own identity rather than borrowing yours. Create it under Gateway → Agents, which hands you its token once, and configure your client to send that.
claude mcp add --transport http firefight https://app.firefight.app/mcp \ --header "Authorization: Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA"Everything the agent does is recorded under its own name, so an incident it declared says the agent declared it, and an action item it picked up shows the agent holding it. It holds only the abilities you granted it, whatever access you have yourself. Agent tokens do not expire, because what a leaked one can do is bounded by its grants and its approval rules rather than by how soon it has to be renewed. Rotate one at any time from its row. See Agents.
API key, for pipelines and CI
Section titled “API key, for pipelines and CI”Automation with nothing to say on a timeline passes a Firefight API key as a Bearer token instead. Create one under Settings → API Keys, then configure your client to send it.
claude mcp add --transport http firefight https://app.firefight.app/mcp \ --header "Authorization: Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA"For Cursor, put the same thing in .cursor/mcp.json.
{ "mcpServers": { "firefight": { "url": "https://app.firefight.app/mcp", "headers": { "Authorization": "Bearer ff_4kWm2xPqR8vNcT6yBhJd3fLzGaU9sEnQoXwA" } } }}Any other MCP client works the same way, either through OAuth discovery at /.well-known/oauth-protected-resource or with the Authorization: Bearer header.
Multiple workspaces
Section titled “Multiple workspaces”A connection reaches one workspace, whichever way you authenticated. An OAuth connection is granted for the single workspace you picked on the consent screen, and an API key belongs to the workspace you created it in. There is no setting that widens a connection to cover more.
To give an agent a second workspace, add the server again under a different name.
claude mcp add --transport http firefight-acme https://app.firefight.app/mcpAuthorize that one and pick the second workspace on the consent screen. For a headless agent, create an API key in the second workspace and pass it the same way. Each connection stands on its own, so its tools appear under the name you gave it, and revoking one leaves the other working.
An agent is told which workspace it reaches as soon as it connects, so you can ask it directly rather than working it out from what it returns.
The Connected agents list under Settings → API Keys shows the connections for the workspace you are viewing. Switch workspaces to see the rest.
These tools read your workspace and change nothing.
| Tool | What it returns |
|---|---|
search_incidents | Incident summaries, newest first. Filter by status, severity, lifecycle stage, free-text query, and declared-at time range. |
get_incident | One incident in full, by ID or identifier like INC-42. Details, timeline events, postmortem status, attached alerts, every incident role with whoever holds it, the open action items, and the attached runbooks with their steps. |
search_alerts | Ingested alerts, newest first. Each shows its source, firing status, routing state, the rule that matched, and the incident it attached to. |
search_catalog | Catalog entries with their attributes and relationships, such as which team owns a service. Filter by type, name query, or exact slug. |
evaluate_routing | A dry run of alert routing against hypothetical alert fields. Returns the matching rule, the outcome, a per-condition trace, and warnings when a routing role your rules depend on is not set on any catalog attribute. Nothing is created or notified. |
search_runbooks | Runbook listings with name, summary, and step count. Filter by a name or summary query. |
get_runbook | One runbook in full, by slug. The complete procedure content, ordered steps, and external link. |
search_approvals | Approval requests and their status. |
list_abilities | Everything that can be granted, with its risk level and whether an approval rule can hold it. |
list_principals | The people, agents and service keys of the workspace, each with the grants it holds. |
search_activity | The gateway’s audit log, filtered by decision or ability. |
get_form | One lifecycle form and every field on it, hidden ones included, with each field’s type, whether it is visible, required, or locked, and any conditions gating it. |
get_workspace_config | How the workspace is set up, in one call: severities, statuses with their lifecycle stage, incident types, incident roles, alert sources and webhooks. Disabled entries are included and marked. |
list_agents | The agents in the workspace, each with its live tokens and how many abilities it holds. |
list_api_keys | The workspace’s service keys, with the abilities each holds and when it was last used. Never the token itself. |
get_postmortem | The postmortem written for an incident, as HTML, with its status and who wrote it. |
get_incident_transcript | What people said in the incident’s channel, oldest last, with who said it and when. Needs its own ability and the workspace setting. |
Tools that change things
Section titled “Tools that change things”These need a credential with permission to make the change, and every call is recorded.
| Tool | What it does |
|---|---|
upsert_catalog_entry | Creates or updates a catalog entry |
delete_catalog_entry | Removes a catalog entry |
upsert_catalog_type | Creates or changes a kind of thing the catalog holds, and the attributes its entries carry |
delete_catalog_type | Removes a catalog type and the entries under it |
upsert_routing_rule | Creates or updates an alert routing rule |
delete_routing_rule | Removes a routing rule |
update_routing_config | Changes routing configuration |
upsert_runbook | Creates or updates a runbook |
upsert_custom_field | Creates or updates a custom field and its options |
upsert_form_field | Attaches a field to a form, or changes its visibility, required setting, or conditions |
upsert_severity / delete_severity | Creates, changes or removes a severity |
upsert_status / delete_status | Creates, changes or removes a status |
upsert_incident_type / delete_incident_type | Creates, changes or removes an incident type |
upsert_incident_role / delete_incident_role | Creates, changes or removes an incident role |
upsert_alert_source / delete_alert_source | Creates, changes or removes an alert source |
upsert_webhook / delete_webhook | Creates, changes or removes an outbound webhook |
test_webhook | Sends a test delivery to an outbound webhook, replaying the newest matching event from your workspace |
upsert_agent / delete_agent | Creates, changes or removes an agent |
rotate_agent_token / revoke_agent_token | Issues a new token for an agent, or ends one |
upsert_api_key / delete_api_key | Creates, changes or removes a service key |
declare_incident | Opens a new incident and its channel |
post_incident_update | Posts an update on an incident |
resolve_incident | Closes an incident |
cancel_incident | Cancels an incident that turned out not to be one |
reopen_incident | Reopens an incident that came back |
create_action_item | Adds a piece of work to an incident and posts it in the channel |
assign_action_item | Takes a piece of work, or hands it to someone else |
complete_action_item | Marks a piece of work done |
claim_runbook_step | Takes one step of an attached runbook |
escalate_incident | Asks a named person to pick the incident up |
invite_responders | Brings people into the incident channel |
link_incident | Links two incidents, or marks one a duplicate of the other |
give_shoutout | Thanks someone for their work on the incident |
assign_incident_role | Puts one person in an incident role, or clears it |
attach_runbook | Attaches a runbook to an incident |
dismiss_timeline_note | Removes one AI note from an incident’s timeline |
approve_approval | Approves a pending request |
deny_approval | Denies a pending request |
upsert_permission_set | Creates or updates a permission set |
delete_permission_set | Removes a permission set from everyone holding it |
grant_ability | Grants an ability or a set to a person, agent or key |
revoke_grant | Revokes a grant |
upsert_approval_rule | Creates or updates an approval rule |
delete_approval_rule | Removes an approval rule |
start_postmortem | Opens the postmortem for a resolved incident, empty or drafted by AI |
update_postmortem | Replaces the body of a postmortem |
set_postmortem_status | Moves a postmortem to draft, in progress, in review or completed |
upsert_catalog_entry takes attributes by name, and an attribute pointing at another entry accepts that entry’s slug, so an agent can set which team owns a service without looking up an ID.
upsert_catalog_type shapes the catalog itself, adding a kind of thing such as Datastore and saying what every entry under it carries. Attributes are matched by name, so resending a list renames rather than replaces and the entries already holding a value keep it, and sending no attributes leaves the shape alone. A reference attribute names the type it points at by slug. Built-in types keep their slug and their own attributes, and delete_catalog_type refuses a type another type points at, naming the attribute in the way. See Entries, attributes & relationships.
upsert_custom_field and upsert_form_field let an agent change what responders are asked during an incident, so they are worth granting deliberately. Options are matched by label, which means resending a list renames rather than replaces, and the incidents already holding an option keep pointing at it. Conditions accept slugs, so you can gate a field on production without looking up an ID first. A condition on a custom field names that field by its key and takes option labels or catalog entry slugs as its values, so gating on an Affected service field being checkout needs no IDs either. Severity and Status refuse to be hidden or made optional here exactly as they do in the dashboard. Call get_form first to see what a form holds, including hidden fields, because an update replaces the set rather than adding to it.
The gateway tools let an agent administer access the same way an admin does on the dashboard: see who holds what, bundle abilities into sets, grant and revoke, and decide which abilities wait for approval. They check permissions, which only an admin holds and which cannot be granted to a key, so they work over a personal token or an OAuth connection made by an admin, never a service key. approve_approval and deny_approval decide as whoever is behind the token. Connected as you, an agent can be your chief of staff, spotting a request that names you in search_approvals and approving it once you say so. Connected with its own service key, it can decide only the rules that name that key and have Agents may decide this rule switched on, and the decision is recorded under the agent’s name. See Permissions and Approvals.
attach_runbook takes the incident and a runbook slug, and posts that runbook’s steps in the incident channel. Runbooks whose conditions match attach on their own, so this is for the ones that do not. Attaching the same runbook twice does nothing the second time. It needs incidents: update, not a runbooks permission, because it changes the incident rather than the runbook.
dismiss_timeline_note takes the incident and the id of a note, and removes that note from the timeline. It is for the case where a note reads a conversation wrong, crediting the wrong person or taking a joke for a decision. The note is kept and marked dismissed rather than deleted, and stops coming back from get_incident. Note ids come from that same timeline, where each note also names its kind, so an agent can ask for the root cause of a past incident directly. See Timeline notes.
Working an incident
Section titled “Working an incident”The incident tools are the same operations a person has in Slack, so an agent using them is a responder rather than a reporter. What it does shows up in the incident channel as it happens, and on the incident’s timeline under the agent’s own name.
declare_incident, post_incident_update, resolve_incident, cancel_incident and reopen_incident all ask exactly what your workspace’s incident forms ask a person, custom fields included. Call get_form first with the form you are about to submit, then pass the answers keyed the way it named them. A field the form does not ask for is refused rather than quietly dropped, and a missing required field comes back naming what is missing.
create_action_item adds a piece of work and posts it in the channel, exactly like /ff action. Pass kind as action for work during the incident or followup for work after it, and member to hand it straight to someone.
assign_action_item takes a piece of work when you leave member out, which is the I can take this button, and hands it over when you name someone else, which announces the handover in the channel. complete_action_item marks it done and posts the completion. Both take the item’s id, which comes from get_incident.
claim_runbook_step takes one step of a runbook already attached to the incident, which creates the action item behind that step, or hands over the one that already exists. Runbook and step ids come from get_incident.
invite_responders brings people into the incident channel so they can see what is happening. escalate_incident is the stronger one: it asks one named person to pick the incident up, posts the ask in the channel, messages them directly with an acknowledge button, and reminds them if they do not answer. Reach for it when the agent has gone as far as it can on its own.
Escalating, inviting and giving a shoutout all post in the incident channel, so all three refuse an incident that has been resolved or canceled, and one whose channel Firefight is still creating. The refusal names which it is.
link_incident records a related link on both incidents’ timelines and changes nothing else. Passing duplicate instead also cancels this incident, naming the one that absorbed it, so use it only when the two really are the same event seen twice.
give_shoutout thanks a named person in the incident channel, the same as /ff shoutout.
Every one of these needs incidents: create or incidents: update, so an agent granted only incidents: read can follow an incident and change nothing about it.
assign_incident_role takes the incident, the role slug, and the person as an email address or Slack user ID. Leave the person out to clear the role. Each role holds one person, so assigning hands the role over from whoever had it. Call get_incident first to see which roles exist and who currently holds them. Every role change announces itself in the incident channel, so the tool refuses any of them on a resolved or canceled incident and names the role it would have changed. See Incident roles.
Writing the postmortem
Section titled “Writing the postmortem”An incident that has been resolved can have its write-up started, edited and moved along without anyone opening the dashboard. start_postmortem opens it. Leave generate out for an empty one you write yourself, or pass it to have Firefight draft the first version from the incident.
Generating takes a moment, so the response comes back immediately with a generation state to poll on. Read it back with get_postmortem until the state clears.
update_postmortem sends the whole document as HTML and replaces what was there rather than appending to it, so read the current one first if you mean to add a section. Every version is kept, so a rewrite can be compared and rolled back from the dashboard. set_postmortem_status moves it to draft, in_progress, in_review or completed, which is how your team knows whether it still needs writing or reading.
get_postmortem gives you a version, and update_postmortem takes it back. This matters because a person is often writing the same document in the dashboard at the same time. If they saved while the agent was composing, the agent’s write is refused rather than replacing what they wrote, and the agent reads again and reapplies. Moving the status needs no version, since it changes nothing anybody is typing.
Starting one is refused while the incident is still open, and refused outright for a canceled incident, which has nothing to write up. An incident that already has a postmortem is refused too, so read before you start. All four tools check incidents, not a permission of their own. See Postmortems.
Reading the conversation
Section titled “Reading the conversation”get_incident_transcript returns what people actually said in the incident channel, which is where the reasoning lives that a timeline only summarises. Read it when you know what happened and need to know why.
It needs the Incident Transcripts ability, which is separate from incidents on purpose, and the workspace has to have turned transcript access on under Settings → Workspace. An agent granted every incident ability reads nothing until both are true. See Incident conversations.
Messages come back oldest last, 100 at a time and 500 at most, with a more_before cursor for walking further into the past. Each message says whether anything in it was redacted.
Setting the workspace up
Section titled “Setting the workspace up”Every settings screen has tools behind it. That means you can ask an agent connected as you to do the setup, rather than clicking through it. “Add a SEV0 above our current top severity, and a Mitigating status in the active stage” is one conversation instead of two screens.
Call get_workspace_config first. It returns your severities, statuses, incident types, incident roles, alert sources and webhooks in one response, and the slugs it gives back are what every other tool takes.
Each list has its own pair of tools rather than one tool with a type argument, because the lists do not ask for the same things. A status belongs to a lifecycle stage, a severity’s position says how severe it is, and only some are colored or have a default. Each of these four lists takes position, 1 being first, so “add a SEV0 above our current top severity” is position: 1. Separate tools mean the fields an agent is offered are the fields that list actually has.
Passing a slug changes the one that already has it, and leaving the slug out creates a new one. Renaming never moves the slug, because the slug is what your existing incidents point at. A slug that matches nothing comes back as an error rather than quietly creating a second entry under a fresh name.
Deleting is refused while anything still points at the entry, and the refusal says how many. Disabling is what you want then. A disabled entry keeps its slug and stops being offered to responders, and get_workspace_config still returns it so an agent can turn it back on.
Agents and keys
Section titled “Agents and keys”Creating an agent, rotating its token and minting a service key are all available here, and they behave differently from everything else on this page. They check permissions and api_keys, which are admin-only and cannot be granted to anyone. A connection acting as an admin reaches them. A service key or an agent never can, whatever it has been granted, so an agent cannot create another agent or widen its own access.
upsert_agent returns the new agent’s token once, in that response, and never again. So does rotate_agent_token. Neither list_agents nor list_api_keys ever carries a token.
Rotating is an overlap rather than a swap. The new token works immediately while the old one keeps working, so the agent stays up while you update its configuration, and revoke_agent_token ends the old one when you are ready. See Agents.
Tools from your integrations
Section titled “Tools from your integrations”Every capability you switch on under Configure → Integrations also appears here, so an agent can query PlanetScale or open a Linear issue through the same connection. Nothing appears until you enable it, and who may call it is set in Permissions. An agent’s tool list only carries the capabilities its key may call, so a narrowly scoped key never sees the rest of your connections. See Connect an integration.
Result caps
Section titled “Result caps”Search tools return 25 results by default and accept a limit of up to 50. When more results exist than the cap allows, the response includes a truncated flag set to true, so agents know to narrow their filters instead of assuming they saw everything. Incident timelines in get_incident are capped at 50 events with their own timeline_truncated flag. Each token may make up to 1,000 MCP requests per minute.
Permissions
Section titled “Permissions”What a connected agent can query depends on the credential it authenticates with.
| Credential | Access |
|---|---|
| OAuth connection | Acts as you, with your access |
| Personal token | Acts as you, with your access |
| Agent token | Acts as the agent, with only what the agent was granted |
| Service key | Only what it has been granted, and nothing by default |
Agent tokens and service keys map to tools by resource.
| Tool | Required permission |
|---|---|
search_incidents | incidents: read |
get_incident | incidents: read |
search_alerts | alerts: read |
search_catalog | catalog: read |
evaluate_routing | policies: read |
search_runbooks | runbooks: read |
get_runbook | runbooks: read |
search_approvals | approvals: read |
get_form | forms: read |
upsert_catalog_entry | catalog: create or catalog: update |
delete_catalog_entry | catalog: delete |
upsert_routing_rule | policies: create or policies: update |
delete_routing_rule | policies: delete |
update_routing_config | policies: update |
upsert_runbook | runbooks: create or runbooks: update |
upsert_custom_field | custom_fields: create or custom_fields: update |
get_workspace_config | incidents: read |
upsert_severity / delete_severity | severities: create, update or delete |
upsert_status / delete_status | statuses: create, update or delete |
upsert_incident_type / delete_incident_type | incident_types: create, update or delete |
upsert_incident_role / delete_incident_role | incident_roles: create, update or delete |
upsert_alert_source / delete_alert_source | alerts: create, update or delete |
upsert_webhook / delete_webhook | webhooks: create, update or delete |
test_webhook | webhooks: update |
list_agents, upsert_agent, rotate_agent_token, revoke_agent_token, delete_agent | permissions, admin-only and ungrantable |
list_api_keys, upsert_api_key, delete_api_key | api_keys, admin-only and ungrantable |
upsert_form_field | forms: update |
declare_incident | incidents: create |
post_incident_update | incidents: update |
resolve_incident | incidents: update |
cancel_incident | incidents: update |
reopen_incident | incidents: update |
create_action_item | incidents: update |
assign_action_item | incidents: update |
complete_action_item | incidents: update |
claim_runbook_step | incidents: update |
escalate_incident | incidents: update |
invite_responders | incidents: update |
link_incident | incidents: update |
give_shoutout | incidents: update |
assign_incident_role | incidents: update |
attach_runbook | incidents: update |
dismiss_timeline_note | incidents: update |
approve_approval | approvals: update |
deny_approval | approvals: update |
list_abilities | permissions: read |
list_principals | permissions: read |
search_activity | permissions: read |
upsert_permission_set | permissions: create or permissions: update |
delete_permission_set | permissions: delete |
grant_ability | permissions: create |
revoke_grant | permissions: delete |
upsert_approval_rule | permissions: create or permissions: update |
delete_approval_rule | permissions: delete |
A tool call without the needed permission returns an error naming the missing permission, and the agent can keep using the tools it does have. See API overview for how keys and permissions work.
Managing connected agents
Section titled “Managing connected agents”Everything connected over OAuth appears under Settings → API Keys in the Connected agents section, where you can revoke any connection. Revoking immediately invalidates its tokens, and the client has to go through consent again to reconnect. Automation using an API key is managed like any other key on the same page: deactivate or delete the key to cut it off. An AI with its own identity lives under Gateway → Agents instead, where you rotate or revoke its tokens, change what it may do, or switch it off without losing what it did. See Agents.