@doraincident/mcp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DoraIncident
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # @doraincident/mcp-server
2
+
3
+ Connect [DoraIncident](https://www.doraincident.io) to Claude, Cursor, and any MCP-compatible AI assistant. Query live incidents, acknowledge escalated alerts, declare major incidents, and track DORA regulatory deadlines — without leaving your AI tool.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@doraincident/mcp-server)](https://www.npmjs.com/package/@doraincident/mcp-server)
6
+ [![Listed on mcpservers.org](https://img.shields.io/badge/mcpservers.org-listed-green)](https://mcpservers.org/servers/www-doraincident-io-settings-mcp)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
+
9
+ ---
10
+
11
+ ## What is DoraIncident?
12
+
13
+ DoraIncident is an AI-native incident management platform for regulated payment firms. It combines:
14
+
15
+ - **On-call alerting** — ingest from Datadog, Prometheus, Grafana, OpsGenie, PagerDuty, New Relic, and Nagios
16
+ - **Automated ticketing** — AI creates and populates an incident ticket the moment the IM declares
17
+ - **DORA compliance** — live 4h/72h/30-day deadline tracking and AI-drafted regulatory report content
18
+ - **Public status pages** — with component tracking, subscriber email notifications, and AI chat
19
+
20
+ This MCP server exposes the live operational state of your DoraIncident instance to any connected AI.
21
+
22
+ **Security note:** All data stays within your own DoraIncident instance. Nothing is routed through Anthropic or OpenAI — LLM inference runs via Groq, and MCP calls go directly from your AI client to your instance over HTTPS using a scoped API key.
23
+
24
+ ---
25
+
26
+ ## Tools
27
+
28
+ ### Alerts (4 tools)
29
+
30
+ | Tool | Description |
31
+ |---|---|
32
+ | `list_alerts` | List alerts with optional `status`, `source`, and `limit` filters. Escalated alerts are P1/P2 signals waiting for a human decision. |
33
+ | `get_alert` | Full alert detail including raw payload, AI severity score, reasoning, and current triage status. |
34
+ | `acknowledge_alert` | Acknowledge an escalated alert. Pauses the escalation timer and signals active investigation. |
35
+ | `suppress_alert` | Suppress an alert (noise or false positive). Requires a written reason, stored in the audit log. |
36
+
37
+ ### Incidents, tickets, and DORA (8 tools)
38
+
39
+ | Tool | Description |
40
+ |---|---|
41
+ | `list_incidents` | List incidents filtered by `status` (active, resolved, closed). Active incidents have running DORA clocks. |
42
+ | `get_incident` | Full incident detail: event timeline, linked ticket, affected services, and DORA countdown timers. |
43
+ | `declare_incident` | Declare a major incident. Starts the DORA regulatory clock, auto-creates an AI-populated ticket, and opens a Slack war room. **Only call this when a human has confirmed the situation warrants declaration.** |
44
+ | `resolve_incident` | Mark an incident as resolved. Triggers optional RCA draft generation and surfaces DORA report drafts for IM review. |
45
+ | `list_tickets` | List incident tickets with `status` and `priority` filters. |
46
+ | `get_ticket` | Full ticket detail: description, status, assignee, comments, activity log, and DORA link panel. |
47
+ | `get_dora_deadlines` | DORA regulatory deadlines for all active P1/P2 incidents. Shows time remaining for Initial Notification (4h), Intermediate Report (72h), and Final Report (30 days). Flags overdue deadlines. |
48
+ | `get_on_call` | Current on-call person across all schedules. Returns name, email, and schedule details. |
49
+
50
+ ### Knowledge base (1 tool)
51
+
52
+ | Tool | Description |
53
+ |---|---|
54
+ | `search_knowledge_base` | Semantic search over runbooks, past post-mortems, and ingested docs/code. Returns the passages most relevant to a query with similarity scores and the retrieval method (vector or lexical) — the same retrieval that grounds resolution suggestions and RCA drafts. Use it to recall how a similar issue was handled before recommending remediation. |
55
+
56
+ ## Resources
57
+
58
+ | URI | Description |
59
+ |---|---|
60
+ | `doraincident://alerts/escalated` | Live feed of all alerts currently in escalated state requiring a human declare or dismiss decision. |
61
+ | `doraincident://incidents/active` | Live feed of all active incidents with DORA countdown timers. |
62
+
63
+ ---
64
+
65
+ ## Installation
66
+
67
+ ### Prerequisites
68
+
69
+ - Node.js 18 or higher
70
+ - A DoraIncident account — [request access at doraincident.io](https://www.doraincident.io)
71
+ - A DoraIncident API key — generate one in **Settings → API Keys**
72
+
73
+ ### Option 1 — Claude Desktop (recommended)
74
+
75
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "doraincident": {
81
+ "command": "npx",
82
+ "args": ["-y", "@doraincident/mcp-server"],
83
+ "env": {
84
+ "DORAINCIDENT_URL": "https://www.doraincident.io",
85
+ "DORAINCIDENT_API_KEY": "di_live_..."
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ Restart Claude Desktop. You should see the DoraIncident tools listed in the MCP panel.
93
+
94
+ ### Option 2 — Claude Code (CLI)
95
+
96
+ ```bash
97
+ claude mcp add doraincident \
98
+ -e DORAINCIDENT_URL=https://www.doraincident.io \
99
+ -e DORAINCIDENT_API_KEY=di_live_... \
100
+ -- npx -y @doraincident/mcp-server
101
+ ```
102
+
103
+ ### Option 3 — Install globally
104
+
105
+ ```bash
106
+ npm install -g @doraincident/mcp-server
107
+
108
+ # Then point your MCP client at the installed binary:
109
+ doraincident-mcp
110
+ # env: DORAINCIDENT_URL, DORAINCIDENT_API_KEY
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Environment variables
116
+
117
+ | Variable | Required | Description |
118
+ |---|---|---|
119
+ | `DORAINCIDENT_API_KEY` | Yes | API key from **Settings → API Keys**. Must start with `di_live_`. |
120
+ | `DORAINCIDENT_URL` | No | Base URL of your DoraIncident instance. Defaults to `https://www.doraincident.io`. |
121
+
122
+ ---
123
+
124
+ ## Example usage
125
+
126
+ Once connected, ask your AI assistant:
127
+
128
+ ```
129
+ "What incidents are active right now and do any have DORA deadlines approaching?"
130
+
131
+ "Acknowledge alert alt_abc123 — I'm looking into it."
132
+
133
+ "Declare a P1 incident: Payments gateway timeout affecting the checkout-service and 3ds-provider."
134
+
135
+ "What's the current DORA Initial Notification deadline for the active P1?"
136
+
137
+ "Who is on call right now?"
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Building from source
143
+
144
+ ```bash
145
+ git clone https://github.com/michaelngangom/doraincident.git
146
+ cd doraincident/packages/mcp-server
147
+ npm install
148
+ npm run build # compiles TypeScript to dist/
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Links
154
+
155
+ - **Platform:** [doraincident.io](https://www.doraincident.io)
156
+ - **MCP setup guide:** [doraincident.io/docs/mcp-server](https://www.doraincident.io/docs/mcp-server)
157
+ - **Full documentation:** [doraincident.io/docs](https://www.doraincident.io/docs)
158
+ - **Listed on mcpservers.org:** [mcpservers.org/servers/www-doraincident-io-settings-mcp](https://mcpservers.org/servers/www-doraincident-io-settings-mcp)
159
+ - **Issues:** [github.com/michaelngangom/doraincident/issues](https://github.com/michaelngangom/doraincident/issues)
160
+
161
+ ---
162
+
163
+ ## License
164
+
165
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,35 @@
1
+ export declare class DoraIncidentClient {
2
+ private baseUrl;
3
+ private apiKey;
4
+ constructor(baseUrl: string, apiKey: string);
5
+ private request;
6
+ listAlerts(params?: {
7
+ status?: string;
8
+ source?: string;
9
+ limit?: number;
10
+ }): Promise<unknown>;
11
+ getAlert(id: string): Promise<unknown>;
12
+ acknowledgeAlert(id: string): Promise<unknown>;
13
+ suppressAlert(id: string, reason: string): Promise<unknown>;
14
+ listIncidents(params?: {
15
+ status?: string;
16
+ }): Promise<unknown>;
17
+ getIncident(id: string): Promise<unknown>;
18
+ declareIncident(params: {
19
+ alert_id?: string;
20
+ title: string;
21
+ priority: string;
22
+ affected_services: string[];
23
+ dora_applicable: boolean;
24
+ declared_by?: string;
25
+ }): Promise<unknown>;
26
+ resolveIncident(id: string, resolution_summary: string): Promise<unknown>;
27
+ getDoraDeadlines(): Promise<unknown>;
28
+ getOnCall(): Promise<unknown>;
29
+ listTickets(params?: {
30
+ status?: string;
31
+ priority?: string;
32
+ }): Promise<unknown>;
33
+ getTicket(id: string): Promise<unknown>;
34
+ searchKnowledgeBase(query: string, limit?: number): Promise<unknown>;
35
+ }
package/dist/client.js ADDED
@@ -0,0 +1,109 @@
1
+ export class DoraIncidentClient {
2
+ baseUrl;
3
+ apiKey;
4
+ constructor(baseUrl, apiKey) {
5
+ this.baseUrl = baseUrl.replace(/\/$/, '');
6
+ this.apiKey = apiKey;
7
+ }
8
+ async request(path, options = {}) {
9
+ const url = `${this.baseUrl}${path}`;
10
+ const res = await fetch(url, {
11
+ ...options,
12
+ headers: {
13
+ Authorization: `Bearer ${this.apiKey}`,
14
+ 'Content-Type': 'application/json',
15
+ ...options.headers,
16
+ },
17
+ });
18
+ if (!res.ok) {
19
+ let errorMsg = res.statusText;
20
+ try {
21
+ const body = await res.json();
22
+ errorMsg = body.error ?? errorMsg;
23
+ }
24
+ catch {
25
+ // ignore parse failure
26
+ }
27
+ throw new Error(`DoraIncident API ${res.status}: ${errorMsg}`);
28
+ }
29
+ return res.json();
30
+ }
31
+ // --- Alerts ---
32
+ async listAlerts(params) {
33
+ const q = new URLSearchParams();
34
+ if (params?.status)
35
+ q.set('status', params.status);
36
+ if (params?.source)
37
+ q.set('source', params.source);
38
+ if (params?.limit)
39
+ q.set('limit', String(params.limit));
40
+ const qs = q.toString();
41
+ return this.request(`/api/alerts${qs ? `?${qs}` : ''}`);
42
+ }
43
+ async getAlert(id) {
44
+ return this.request(`/api/alerts/${id}`);
45
+ }
46
+ async acknowledgeAlert(id) {
47
+ return this.request(`/api/alerts/${id}/acknowledge`, {
48
+ method: 'POST',
49
+ body: JSON.stringify({}),
50
+ });
51
+ }
52
+ async suppressAlert(id, reason) {
53
+ return this.request(`/api/alerts/${id}/suppress`, {
54
+ method: 'POST',
55
+ body: JSON.stringify({ reason }),
56
+ });
57
+ }
58
+ // --- Incidents ---
59
+ async listIncidents(params) {
60
+ const q = new URLSearchParams();
61
+ if (params?.status)
62
+ q.set('status', params.status);
63
+ const qs = q.toString();
64
+ return this.request(`/api/incidents${qs ? `?${qs}` : ''}`);
65
+ }
66
+ async getIncident(id) {
67
+ return this.request(`/api/incidents/${id}`);
68
+ }
69
+ async declareIncident(params) {
70
+ return this.request('/api/declare', {
71
+ method: 'POST',
72
+ body: JSON.stringify(params),
73
+ });
74
+ }
75
+ async resolveIncident(id, resolution_summary) {
76
+ return this.request(`/api/incidents/${id}/resolve`, {
77
+ method: 'POST',
78
+ body: JSON.stringify({ resolution_summary }),
79
+ });
80
+ }
81
+ // --- DORA ---
82
+ async getDoraDeadlines() {
83
+ return this.request('/api/dora');
84
+ }
85
+ // --- On-call ---
86
+ async getOnCall() {
87
+ return this.request('/api/on-call');
88
+ }
89
+ // --- Tickets ---
90
+ async listTickets(params) {
91
+ const q = new URLSearchParams();
92
+ if (params?.status)
93
+ q.set('status', params.status);
94
+ if (params?.priority)
95
+ q.set('priority', params.priority);
96
+ const qs = q.toString();
97
+ return this.request(`/api/tickets${qs ? `?${qs}` : ''}`);
98
+ }
99
+ async getTicket(id) {
100
+ return this.request(`/api/tickets/${id}`);
101
+ }
102
+ // --- Knowledge base ---
103
+ async searchKnowledgeBase(query, limit) {
104
+ return this.request('/api/knowledge-base/search', {
105
+ method: 'POST',
106
+ body: JSON.stringify({ query, ...(limit ? { limit } : {}) }),
107
+ });
108
+ }
109
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { DoraIncidentClient } from './client.js';
5
+ import { alertToolDefs, handleAlertTool } from './tools/alerts.js';
6
+ import { incidentToolDefs, handleIncidentTool } from './tools/incidents.js';
7
+ import { knowledgeToolDefs, handleKnowledgeTool } from './tools/knowledge.js';
8
+ const DORAINCIDENT_URL = process.env.DORAINCIDENT_URL ?? 'https://www.doraincident.io';
9
+ const DORAINCIDENT_API_KEY = process.env.DORAINCIDENT_API_KEY ?? '';
10
+ if (!DORAINCIDENT_API_KEY) {
11
+ process.stderr.write('Error: DORAINCIDENT_API_KEY is not set.\n' +
12
+ 'Set it to a "di_live_..." API key from your DoraIncident settings.\n');
13
+ process.exit(1);
14
+ }
15
+ const client = new DoraIncidentClient(DORAINCIDENT_URL, DORAINCIDENT_API_KEY);
16
+ const ALL_TOOLS = [...alertToolDefs, ...incidentToolDefs, ...knowledgeToolDefs];
17
+ const ALERT_TOOL_NAMES = new Set(alertToolDefs.map(t => t.name));
18
+ const INCIDENT_TOOL_NAMES = new Set(incidentToolDefs.map(t => t.name));
19
+ const KNOWLEDGE_TOOL_NAMES = new Set(knowledgeToolDefs.map(t => t.name));
20
+ const server = new Server({ name: 'doraincident', version: '0.1.0' }, {
21
+ capabilities: {
22
+ tools: {},
23
+ resources: {},
24
+ },
25
+ });
26
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: ALL_TOOLS }));
27
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
28
+ resources: [
29
+ {
30
+ uri: 'doraincident://alerts/escalated',
31
+ name: 'Escalated Alerts',
32
+ description: 'All alerts currently in escalated state requiring human decision',
33
+ mimeType: 'application/json',
34
+ },
35
+ {
36
+ uri: 'doraincident://incidents/active',
37
+ name: 'Active Incidents',
38
+ description: 'All active incidents with DORA countdown timers',
39
+ mimeType: 'application/json',
40
+ },
41
+ ],
42
+ }));
43
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
44
+ const { uri } = request.params;
45
+ if (uri === 'doraincident://alerts/escalated') {
46
+ const data = await client.listAlerts({ status: 'escalated' });
47
+ return {
48
+ contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }],
49
+ };
50
+ }
51
+ if (uri === 'doraincident://incidents/active') {
52
+ const data = await client.listIncidents({ status: 'active' });
53
+ return {
54
+ contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }],
55
+ };
56
+ }
57
+ throw new Error(`Unknown resource: ${uri}`);
58
+ });
59
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
60
+ const { name, arguments: args } = request.params;
61
+ const safeArgs = (args ?? {});
62
+ try {
63
+ let text;
64
+ if (ALERT_TOOL_NAMES.has(name)) {
65
+ text = await handleAlertTool(name, safeArgs, client);
66
+ }
67
+ else if (INCIDENT_TOOL_NAMES.has(name)) {
68
+ text = await handleIncidentTool(name, safeArgs, client);
69
+ }
70
+ else if (KNOWLEDGE_TOOL_NAMES.has(name)) {
71
+ text = await handleKnowledgeTool(name, safeArgs, client);
72
+ }
73
+ else {
74
+ throw new Error(`Unknown tool: ${name}`);
75
+ }
76
+ return { content: [{ type: 'text', text }] };
77
+ }
78
+ catch (err) {
79
+ const message = err instanceof Error ? err.message : String(err);
80
+ return {
81
+ content: [{ type: 'text', text: `Error: ${message}` }],
82
+ isError: true,
83
+ };
84
+ }
85
+ });
86
+ const transport = new StdioServerTransport();
87
+ await server.connect(transport);
@@ -0,0 +1,4 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { DoraIncidentClient } from '../client.js';
3
+ export declare const alertToolDefs: Tool[];
4
+ export declare function handleAlertTool(name: string, args: Record<string, unknown>, client: DoraIncidentClient): Promise<string>;
@@ -0,0 +1,100 @@
1
+ export const alertToolDefs = [
2
+ {
3
+ name: 'list_alerts',
4
+ description: 'List alerts from DoraIncident. Returns alerts sorted by received_at descending. ' +
5
+ 'Escalated alerts require immediate attention - they are P1/P2 signals waiting for a human to declare or dismiss.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ status: {
10
+ type: 'string',
11
+ enum: ['pending', 'filtered', 'correlated', 'escalated', 'acknowledged', 'suppressed'],
12
+ description: 'Filter by triage status. Omit to return all.',
13
+ },
14
+ source: {
15
+ type: 'string',
16
+ description: 'Filter by alert source (datadog, prometheus, grafana, generic, etc.)',
17
+ },
18
+ limit: {
19
+ type: 'number',
20
+ description: 'Maximum number of alerts to return. Default is 20.',
21
+ },
22
+ },
23
+ },
24
+ },
25
+ {
26
+ name: 'get_alert',
27
+ description: 'Get full details of a single alert including raw payload, triage result (AI severity score and reasoning), ' +
28
+ 'and current status. Use this before deciding to acknowledge, suppress, or declare an incident.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ id: {
33
+ type: 'string',
34
+ description: 'Alert UUID',
35
+ },
36
+ },
37
+ required: ['id'],
38
+ },
39
+ },
40
+ {
41
+ name: 'acknowledge_alert',
42
+ description: 'Acknowledge an escalated alert. This pauses the escalation timer and signals that a human is actively looking at it. ' +
43
+ 'Use this when you have confirmed the alert is real and are investigating, but are not yet ready to declare a full incident.',
44
+ inputSchema: {
45
+ type: 'object',
46
+ properties: {
47
+ id: {
48
+ type: 'string',
49
+ description: 'Alert UUID to acknowledge',
50
+ },
51
+ },
52
+ required: ['id'],
53
+ },
54
+ },
55
+ {
56
+ name: 'suppress_alert',
57
+ description: 'Suppress an alert - marks it as noise and stops further escalation. ' +
58
+ 'Use this when the alert is a known false positive or a low-signal event that does not require action.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ id: {
63
+ type: 'string',
64
+ description: 'Alert UUID to suppress',
65
+ },
66
+ reason: {
67
+ type: 'string',
68
+ description: 'Why this alert is being suppressed. Stored in the audit log.',
69
+ },
70
+ },
71
+ required: ['id', 'reason'],
72
+ },
73
+ },
74
+ ];
75
+ export async function handleAlertTool(name, args, client) {
76
+ switch (name) {
77
+ case 'list_alerts': {
78
+ const result = await client.listAlerts({
79
+ status: args.status,
80
+ source: args.source,
81
+ limit: args.limit,
82
+ });
83
+ return JSON.stringify(result, null, 2);
84
+ }
85
+ case 'get_alert': {
86
+ const result = await client.getAlert(args.id);
87
+ return JSON.stringify(result, null, 2);
88
+ }
89
+ case 'acknowledge_alert': {
90
+ const result = await client.acknowledgeAlert(args.id);
91
+ return JSON.stringify(result, null, 2);
92
+ }
93
+ case 'suppress_alert': {
94
+ const result = await client.suppressAlert(args.id, args.reason);
95
+ return JSON.stringify(result, null, 2);
96
+ }
97
+ default:
98
+ throw new Error(`Unknown alert tool: ${name}`);
99
+ }
100
+ }
@@ -0,0 +1,4 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { DoraIncidentClient } from '../client.js';
3
+ export declare const incidentToolDefs: Tool[];
4
+ export declare function handleIncidentTool(name: string, args: Record<string, unknown>, client: DoraIncidentClient): Promise<string>;
@@ -0,0 +1,191 @@
1
+ export const incidentToolDefs = [
2
+ {
3
+ name: 'list_incidents',
4
+ description: 'List incidents. Active incidents have running DORA clocks - P1 and P2 incidents must have an ' +
5
+ 'Initial Notification filed within 4 hours of declaration.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ status: {
10
+ type: 'string',
11
+ enum: ['active', 'resolved', 'closed'],
12
+ description: 'Filter by incident status. Omit to return all.',
13
+ },
14
+ },
15
+ },
16
+ },
17
+ {
18
+ name: 'get_incident',
19
+ description: 'Get full incident details: timeline of events, linked ticket, DORA applicability, affected services, ' +
20
+ 'and countdown to regulatory deadlines.',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ id: {
25
+ type: 'string',
26
+ description: 'Incident UUID',
27
+ },
28
+ },
29
+ required: ['id'],
30
+ },
31
+ },
32
+ {
33
+ name: 'declare_incident',
34
+ description: 'Declare a major incident. This starts the DORA regulatory clock for P1 and P2 incidents. ' +
35
+ 'A ticket is automatically created and populated with an AI-generated description. ' +
36
+ 'IMPORTANT: this action should only be taken when a human has confirmed the situation warrants declaration.',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ title: {
41
+ type: 'string',
42
+ description: 'Short, descriptive incident title. Example: "Payment API 3DS authentication failure"',
43
+ },
44
+ priority: {
45
+ type: 'string',
46
+ enum: ['P1', 'P2', 'P3', 'P4'],
47
+ description: 'P1 = Critical (payment processing down, all customers). ' +
48
+ 'P2 = Major (significant degradation, large segment). ' +
49
+ 'P3 = Minor (partial, workaround available). ' +
50
+ 'P4 = Low (no customer impact). ' +
51
+ 'P1 and P2 trigger DORA reporting obligations.',
52
+ },
53
+ affected_services: {
54
+ type: 'array',
55
+ items: { type: 'string' },
56
+ description: 'List of affected service names. Example: ["checkout-service", "3ds-provider"]',
57
+ },
58
+ dora_applicable: {
59
+ type: 'boolean',
60
+ description: 'Whether this incident triggers DORA reporting. Auto-true for P1/P2, but the IM can override.',
61
+ },
62
+ alert_id: {
63
+ type: 'string',
64
+ description: 'Optional: UUID of the originating alert to link to this incident.',
65
+ },
66
+ },
67
+ required: ['title', 'priority', 'affected_services', 'dora_applicable'],
68
+ },
69
+ },
70
+ {
71
+ name: 'resolve_incident',
72
+ description: 'Mark an incident as resolved. This triggers AI generation of an RCA (Root Cause Analysis) draft ' +
73
+ 'and surfaces the DORA Initial Notification draft for IM review if the incident is DORA-applicable. ' +
74
+ 'A resolution summary is required.',
75
+ inputSchema: {
76
+ type: 'object',
77
+ properties: {
78
+ id: {
79
+ type: 'string',
80
+ description: 'Incident UUID to resolve',
81
+ },
82
+ resolution_summary: {
83
+ type: 'string',
84
+ description: 'Brief description of what caused the incident and how it was resolved. ' +
85
+ 'This feeds the AI RCA generation prompt. Be factual and specific.',
86
+ },
87
+ },
88
+ required: ['id', 'resolution_summary'],
89
+ },
90
+ },
91
+ {
92
+ name: 'list_tickets',
93
+ description: 'List tickets linked to incidents. Tickets track status, assignee, comments, and attachments.',
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ status: {
98
+ type: 'string',
99
+ enum: ['open', 'in_progress', 'waiting_external', 'waiting_internal', 'waiting_deployment', 'resolved', 'closed'],
100
+ description: 'Filter by ticket status.',
101
+ },
102
+ priority: {
103
+ type: 'string',
104
+ enum: ['P1', 'P2', 'P3', 'P4'],
105
+ description: 'Filter by priority.',
106
+ },
107
+ },
108
+ },
109
+ },
110
+ {
111
+ name: 'get_ticket',
112
+ description: 'Get full ticket details: description, status, assignee, comments, activity log, and DORA link panel.',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: {
116
+ id: {
117
+ type: 'string',
118
+ description: 'Ticket UUID',
119
+ },
120
+ },
121
+ required: ['id'],
122
+ },
123
+ },
124
+ {
125
+ name: 'get_dora_deadlines',
126
+ description: 'Get DORA regulatory deadlines for all active P1 and P2 incidents. ' +
127
+ 'Shows time remaining until Initial Notification (4h), Intermediate Report (72h), ' +
128
+ 'and Final Report (30 days). Overdue deadlines are flagged.',
129
+ inputSchema: {
130
+ type: 'object',
131
+ properties: {},
132
+ },
133
+ },
134
+ {
135
+ name: 'get_on_call',
136
+ description: 'Get the current on-call person across all schedules. Returns name, email, and schedule details.',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {},
140
+ },
141
+ },
142
+ ];
143
+ export async function handleIncidentTool(name, args, client) {
144
+ switch (name) {
145
+ case 'list_incidents': {
146
+ const result = await client.listIncidents({
147
+ status: args.status,
148
+ });
149
+ return JSON.stringify(result, null, 2);
150
+ }
151
+ case 'get_incident': {
152
+ const result = await client.getIncident(args.id);
153
+ return JSON.stringify(result, null, 2);
154
+ }
155
+ case 'declare_incident': {
156
+ const result = await client.declareIncident({
157
+ title: args.title,
158
+ priority: args.priority,
159
+ affected_services: args.affected_services,
160
+ dora_applicable: args.dora_applicable,
161
+ alert_id: args.alert_id,
162
+ });
163
+ return JSON.stringify(result, null, 2);
164
+ }
165
+ case 'resolve_incident': {
166
+ const result = await client.resolveIncident(args.id, args.resolution_summary);
167
+ return JSON.stringify(result, null, 2);
168
+ }
169
+ case 'list_tickets': {
170
+ const result = await client.listTickets({
171
+ status: args.status,
172
+ priority: args.priority,
173
+ });
174
+ return JSON.stringify(result, null, 2);
175
+ }
176
+ case 'get_ticket': {
177
+ const result = await client.getTicket(args.id);
178
+ return JSON.stringify(result, null, 2);
179
+ }
180
+ case 'get_dora_deadlines': {
181
+ const result = await client.getDoraDeadlines();
182
+ return JSON.stringify(result, null, 2);
183
+ }
184
+ case 'get_on_call': {
185
+ const result = await client.getOnCall();
186
+ return JSON.stringify(result, null, 2);
187
+ }
188
+ default:
189
+ throw new Error(`Unknown incident tool: ${name}`);
190
+ }
191
+ }
@@ -0,0 +1,4 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { DoraIncidentClient } from '../client.js';
3
+ export declare const knowledgeToolDefs: Tool[];
4
+ export declare function handleKnowledgeTool(name: string, args: Record<string, unknown>, client: DoraIncidentClient): Promise<string>;
@@ -0,0 +1,35 @@
1
+ export const knowledgeToolDefs = [
2
+ {
3
+ name: 'search_knowledge_base',
4
+ description: 'Search the DoraIncident knowledge base (runbooks, past post-mortems, and ingested docs/code) for the ' +
5
+ 'passages most relevant to a query. This is the same retrieval that grounds resolution suggestions and RCA ' +
6
+ 'drafts. Use it to find how a similar issue was handled before recommending remediation, or to answer ' +
7
+ '"what is our runbook for X". Returns the matching passages with a similarity score and the retrieval method ' +
8
+ '(vector = semantic, lexical = keyword fallback). Ground your answer in what it returns; do not invent steps ' +
9
+ 'it does not support.',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ query: {
14
+ type: 'string',
15
+ description: 'What to look for, in natural language (e.g. "payment gateway timing out under load").',
16
+ },
17
+ limit: {
18
+ type: 'number',
19
+ description: 'Maximum number of passages to return (1-20). Default is 6.',
20
+ },
21
+ },
22
+ required: ['query'],
23
+ },
24
+ },
25
+ ];
26
+ export async function handleKnowledgeTool(name, args, client) {
27
+ switch (name) {
28
+ case 'search_knowledge_base': {
29
+ const result = await client.searchKnowledgeBase(args.query, args.limit);
30
+ return JSON.stringify(result, null, 2);
31
+ }
32
+ default:
33
+ throw new Error(`Unknown knowledge tool: ${name}`);
34
+ }
35
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@doraincident/mcp-server",
3
+ "version": "0.2.0",
4
+ "description": "MCP server for DoraIncident — query live incidents, acknowledge alerts, and track DORA regulatory deadlines from any AI assistant.",
5
+ "type": "module",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "author": "DoraIncident <hello@doraincident.io>",
9
+ "homepage": "https://www.doraincident.io/docs/mcp-server",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/michaelngangom/doraincident.git",
13
+ "directory": "packages/mcp-server"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/michaelngangom/doraincident/issues",
17
+ "email": "hello@doraincident.io"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "incident-management",
23
+ "dora",
24
+ "dora-compliance",
25
+ "pagerduty",
26
+ "alerting",
27
+ "on-call",
28
+ "fintech",
29
+ "payments",
30
+ "regulatory",
31
+ "claude",
32
+ "ai"
33
+ ],
34
+ "bin": {
35
+ "doraincident-mcp": "dist/index.js"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "prepublishOnly": "npm run build",
45
+ "start": "node dist/index.js",
46
+ "dev": "node --loader ts-node/esm src/index.ts"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "typescript": "^5.0.0"
54
+ },
55
+ "engines": {
56
+ "node": ">=18"
57
+ }
58
+ }