DEV Community

Lewis Sawe
Lewis Sawe Subscriber

Posted on

I Built an Agent That Assembles Incident War Rooms in Notion Through MCP

Notion MCP Challenge Submission 🧠

This is a submission for the Notion MCP Challenge

What I Built

Incident Runbook is an agent that turns Notion into a living incident response system. It watches an Incidents database, and when a new SEV1 or SEV2 appears, it cross-references three other databases (Services, Runbooks, On-Call), assembles a War Room page with everything the responder needs, and writes it back to Notion. When the incident is marked resolved, it generates an AI post-mortem with Gemini 2.5 Flash.

The whole thing runs through Notion's MCP server. No REST calls, no webhooks, no middleware layer.

I built this because incident response at most companies is still a manual scramble. The runbook exists somewhere, the on-call schedule is in a different tool, and the service dependency map is in someone's head. This agent pulls all of that together in seconds.

Video Demo

Show us the code

🚨 Incident Runbook β€” AI-Powered Incident Response for Notion

Built for the Notion MCP Hackathon β€” zero direct API calls, 100% MCP.

The Problem

When a SEV1 hits at 2am, engineers scramble: "Where's the runbook? Who's on-call? What depends on this service?" They're copy-pasting from scattered docs, pinging Slack, and losing precious minutes. Incident response shouldn't require tribal knowledge β€” it should be automated.

The Solution

Incident Runbook turns your Notion workspace into a living incident response system. Log an incident β†’ a full war room assembles itself in seconds.

It connects to Notion entirely through the Model Context Protocol (MCP) β€” reading databases, assembling pages, and writing post-mortems without a single direct API call.

How It Works

  1. Detect β€” Scans your Incidents database for new SEV1/SEV2/SEV3 entries
  2. Lookup β€” Finds the affected service, pulls its runbook, maps dependencies
  3. Assemble β€” Creates a War Room page with
    • Incident details and…

How I Used Notion MCP

The four-database pattern

The agent coordinates across four Notion databases in a single scan: Incidents, Services, Runbooks, and On-Call. Each scan starts with notion-search on the Incidents database to find new or resolved entries. For each incident, it follows the "Affected Service" relation to the Services database, then follows the "Runbook" relation from that service to the Runbooks database, and pulls contacts from the On-Call database filtered by role.

This means a single incident triggers reads across all four databases. The MCP call pattern looks like:

  1. notion-fetch on the database ID to get the collection:// data source URL
  2. notion-search with that URL to get page IDs
  3. notion-fetch on each page to get properties and content

For a scan with 1 incident, 3 services, 2 runbooks, and 4 on-call contacts, that's roughly 20 MCP calls. Chatty, but each call is fast and the total scan takes a few seconds.

Relation resolution was the trickiest part

Notion MCP returns relation properties as arrays of page URLs, not IDs. So an incident's "Affected Service" looks like ["https://www.notion.so/abc123def456"] rather than a clean ID. Matching that to the actual service record means parsing the URL, stripping dashes, and normalizing to a consistent format across all four databases.

I spent more time debugging ID mismatches than writing the actual war room assembly. Services fetched from the database had IDs in one format, while the relation URLs from incidents had them in another. The fix was normalizing everything to dashless hex strings on read.

Writing Markdown back to Notion

The war room page is a single Markdown string that Notion renders natively. It includes a details table, on-call contacts list, the full runbook steps, dependent services, a timeline, and an action checklist with checkboxes. Creating it is one notion-create-pages call with the incident page as the parent.

The post-mortem works the same way. The agent calculates MTTR from the incident creation time to resolution, feeds the incident details and timeline to Gemini 2.5 Flash, and writes the AI output into a new page.

Watch mode and connection reuse

The agent has a watch mode that polls every 30 seconds. An early version spawned a new mcp-remote process on every poll cycle, which ate system resources fast. The fix was extracting the scan logic into a function that accepts an existing MCP client, so watch creates one connection and reuses it across all cycles.

What I'd do differently

The property parsing is fragile. Properties come back inside a <properties> XML block as JSON, and the key names are case-sensitive and sometimes inconsistent (I found both "status" and "Status" depending on how the database was configured). A more robust version would normalize property keys on read. But for a hackathon, regex and case-checking got the job done

Top comments (0)