Architecture
Layered Design
The server follows a modular with clean principles approach, separating concerns across three layers:
┌─────────────────────────────────────┐
│ Transport Layer (src/index.ts) │ MCP stdio JSON-RPC
├─────────────────────────────────────┤
│ Tool Layer (src/tools/) │ Zod schemas + LLM descriptions
├─────────────────────────────────────┤
│ Service Layer (src/services/) │ PagerDuty REST API wrapper
├─────────────────────────────────────┤
│ Common Layer (src/common/) │ Types + data transformation
└─────────────────────────────────────┘
Transport Layer (src/index.ts)
Handles the MCP stdio connection, registers tool schemas, and routes incoming tool calls to the appropriate handler. This is the entry point.
Tool Layer (src/tools/)
Each file defines:
- Schemas — Zod-validated input schemas with descriptions that help the LLM understand when and how to use each tool
- Handlers — thin functions that call the service layer and return MCP-formatted responses
Service Layer (src/services/)
Wraps the PagerDuty REST API v2. All HTTP calls, error handling, and URL construction lives here. The tool layer never touches the API directly.
Key design decisions:
Token token=auth format — PagerDuty uses a non-standardAuthorization: Token token=<key>header, not Bearer tokens- Configurable base URL — defaults to
https://api.pagerduty.com, but can be switched tohttps://api.eu.pagerduty.comfor EU accounts - Array parameter handling — PagerDuty uses repeated params for arrays (e.g.,
statuses[]=triggered&statuses[]=acknowledged), which the service handles transparently - Structured error handling — maps HTTP status codes (401, 403, 404, 429) to clear, actionable error messages with region-specific hints
Common Layer (src/common/)
Shared utilities for response transformation:
types.ts— shared TypeScript interfaces (PagerDutyConfig,McpToolResponse)utils.ts— data transformation pipeline
Response Transformation
All PagerDuty API responses pass through an automatic transformation pipeline before being returned to the AI assistant:
1. Escalation Policy Flattening
PagerDuty escalation policies contain deeply nested structures. The server flattens them into simple level-based lists:
Before: { escalation_rules: [{ targets: [{ type: "user_reference", summary: "Alice" }] }] }
After: { escalation_rules: [{ level: 1, targets: ["User: Alice"] }] }
This runs before metadata stripping because it relies on type fields (e.g., user_reference, schedule_reference) to classify targets.
2. Key Stripping
Internal PagerDuty fields that waste tokens and add no value for AI context are recursively removed:
self— API self-reference URLprivilege— access level metadataalert_counts— redundant countsincident_key— internal dedup keyis_mergeable— merge capability flagconference_bridge— conference bridge configlast_status_change_by— redundant actor info
Additionally, type fields ending in _reference (e.g., incident_reference, service_reference) are stripped as they are PagerDuty-specific discriminators with no value for AI context.
3. Log Entry Truncation
Incident log entries are truncated to the most recent 25 entries, sorted by timestamp descending, and simplified to: { timestamp, type, summary, agent }.
Security Model
- Read-only by design — no write operations are exposed
- API key auth — uses PagerDuty REST API keys (read-only keys are sufficient)
- No data persistence — the server is stateless and does not store any PagerDuty data
- Stdio transport — communication happens over stdin/stdout, no network ports opened