PagerDuty Workflow Automation MCP Server
README.md
# PagerDuty Workflow Automation MCP Server
**Unofficial** MCP server for PagerDuty Workflow Automation (formerly Catalytic). This project is not affiliated with, endorsed by, or supported by PagerDuty. It uses the legacy Catalytic v1 API that PagerDuty may change or remove at any time. Use at your own risk.
MCP server that connects Claude to PagerDuty Workflow Automation (formerly Catalytic). Lets Claude find processes (workflows), trigger runs with input fields, and check or abort running instances.
> **Requires username/password login. SSO teams are not supported.** The v1 API authenticates with a user key obtained by posting a username and password, and there is currently no working way to get that key on a team that logs in through SSO. See [Authentication](#authentication) for details.
Built against the Catalytic internal v1 API (swagger spec in the local `legacy_api/` folder, which is gitignored since it is internal PagerDuty documentation). Base URL is `https://<team>.pushbot.com/v1` and every request authenticates with an `x-user-key` header.
## Setup
Install the dependencies:
```
pip install -r requirements.txt
```
Get your User Key. The script POSTs your username and password to `/v1/teams/<team>/auth` (you type them locally, and they go only to your team's endpoint), then prints a ready-to-paste config block:
```
python3 get_token.py
```
Create your config file and paste that block in:
```
cp config.json.example config.json
vi config.json
```
```json
{
"default": "my-team",
"teams": {
"my-team": "<userKey>"
}
}
```
`config.json` is gitignored, lives next to `server.py`, and holds every team you work with. See [Configuration](#configuration) for working with multiple teams.
## Configuration
Add a line per team to `config.json` and set `default` to the one you use most:
```json
{
"default": "my-team",
"teams": {
"my-team": "<userKey>",
"other-team": "<userKey>"
}
}
```
Every tool accepts an optional `team` argument; when omitted, the `default` team is used. In Claude you can just say "list the workflows on other-team" and the right credentials are used. Run `list_teams` to see what the server loaded.
Each team needs its own User Key, so run `get_token.py` once per team.
`config.json` is the only supported configuration source. The server resolves it next to `server.py`, so it works regardless of which directory the MCP client launches the server from.
## Authentication
Every request carries an `x-user-key` header holding a 64-character user key. The only known way to obtain one is `POST /v1/teams/<team>/auth` with a username and password, which is what `get_token.py` does.
**SSO teams do not work today.** If your team logs in through an identity provider, that endpoint rejects your credentials and there is no substitute. Approaches that were tried and failed:
- **Access tokens created in the web UI.** These are a different credential format (a ~180-character serialized token). They are rejected by the v1 API as an `x-user-key`, and rejected as a Bearer token by the newer `/api` surface.
- **The user-token request/approve/poll endpoints** documented in the swagger spec (`POST /teams/<team>/user-tokens`, `:approve`, `:poll`). Only partially deployed: creation works, but polling returns 401 and approve/revoke/list return 404.
- **Copying the key out of the web app.** The current web UI no longer uses the v1 API. It authenticates with a `catalytic.sid` session cookie against a GraphQL endpoint, so no `x-user-key` header ever appears in its traffic.
- **Reusing the browser session.** The v1 API ignores session cookies and returns 403 without the header.
- **Capturing the key from the SAML exchange.** The spec's `saml-auth` schema shows a `userKey` in the response to `POST /saml/auth`, but on current deployments that request returns a 302 with no key in the redirect.
If you find a working method on an SSO team, please open an issue.
## Use with Claude Code
The included `.mcp.json` registers the server for this project automatically. To register it globally instead:
```
claude mcp add "PagerDuty Workflow Automation" -- python3 /path/to/pagerduty-wfa-mcp-server/server.py
```
## Use with Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"PagerDuty Workflow Automation": {
"command": "python3",
"args": ["/path/to/pagerduty-wfa-mcp-server/server.py"]
}
}
}
```
## Tools
Every tool takes an optional `team`. API paths below are relative to `/v1/teams/{team}`.
| Tool | API call | Purpose |
|---|---|---|
| `list_teams` | (local) | Show configured teams and the default |
| `search` | `GET /search` | Search processes, runs, tasks, users, and tables by name |
| `list_tables` | `GET /tables` | Data tables on the team |
Workflows (processes):
| Tool | API call | Purpose |
|---|---|---|
| `list_workflows` | `GET /processes` | List processes, filter by category, owner, published |
| `get_workflow` | `GET /processes/{id}` | Process details, including expected fields |
| `list_workflow_steps` | `GET /processes/{name}/steps` | The steps that make up a process |
| `get_workflow_step` | `GET /processes/{name}/steps/{step}` | One step's configuration |
| `list_child_workflows` | `GET /processes/{id}/childprocesses` | Processes this one can start |
| `list_parent_workflows` | `GET /processes/{id}/parentprocesses` | Processes that can start this one |
| `list_workflow_tables` | `GET /processes/{id}/tables` | Data tables tied to a process |
Runs (instances):
| Tool | API call | Purpose |
|---|---|---|
| `start_workflow` | `POST /runs` | Trigger a run with optional name, description, and inputs |
| `get_instance` | `GET /runs/{id}` | Status, tasks, and progress of a run |
| `get_instance_fields` | `GET /runs/{id}/fields` | Field values of a run |
| `find_instances` | `GET /runs` | Search runs by owner, status, or date range |
| `list_workflow_instances` | `GET /processes/{name}/runs` | Runs of one specific process |
| `list_instance_tasks` | `GET /runs/{id}/tasks` | Tasks in a run, with status and assignees |
| `get_instance_task` | `GET /runs/{id}/tasks/{task}` | One task, including failure detail |
| `get_instance_log` | `GET /runs/{id}/log` | Execution log, for diagnosing failures |
| `list_instance_comments` | `GET /runs/{id}/comments` | Comments on a run |
| `add_instance_comment` | `POST /runs/{id}/comments` | Post a comment, visible to everyone with access |
| `complete_task` | `POST /runs/{id}/tasks/{task}/complete` | Complete a task and advance the workflow |
| `update_instance_field` | `PUT /runs/{id}/fields/{field}` | Set one field on a run |
| `stop_instance` | `PUT /runs/{id}` | Abort a running instance |
Triggers and webhooks:
| Tool | API call | Purpose |
|---|---|---|
| `list_triggers` | `GET /triggers` | All triggers on the team |
| `list_workflow_triggers` | `GET /processes/{name}/triggers` | Triggers on one process |
| `list_workflow_webhooks` | `GET /processes/{name}/webhooks` | Webhooks on one process |
Users:
| Tool | API call | Purpose |
|---|---|---|
| `list_users` | `GET /users` | Users on the team |
| `get_user` | `GET /users/{username}` | One user by username, email, or ID |
| `update_user` | `PUT /users/{username}` | Change user fields, including deactivation |
| `delete_user` | `DELETE /users/{username}` | **Destructive.** Permanently removes a user |
`start_workflow` takes inputs as a plain dict of field names to values:
```
start_workflow(
process_id="8bf3db91-a8b2-4e34-aade-38c08a49170e",
name="Renewal for Acme",
inputs={"customer-name": "Acme", "priority": "High"}
)
```
## API notes
- Run statuses: `running`, `completed`, `failed`, `aborted`.
- The swagger spec does not formally define the `POST /runs` request body; the server sends `{processID, displayName, description, fields: [{fieldName, value}]}` based on the run schema's writable fields. If the API rejects it, compare against a request the web UI makes (browser dev tools, Network tab) and adjust `start_workflow` in [server.py](server.py).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues