jira-alerts-mcp

MCP server for Jira Service Management Operations — alerts, on-call schedules and responders

Documentation

Jira Alerts MCP

Find what is paging you, and who is on call — from your agent.

CI npm License: Apache 2.0 Node

An MCP server for Jira Service Management Operations — the alert surface that replaced Opsgenie, which no other Jira MCP server covers.

Search alerts and read their notes and activity timeline; acknowledge, close, annotate them and add responders; and look up who is on call now and next. Twelve tools, four of them writes.


Demo

An agent answering who is on call, listing the open alerts, then acknowledging one and reading the applied acknowledgement back

Three questions in one session, against a live JSM site: who is on call, what is open, and acknowledge what isn't. Watch the last answer in particular — the agent confirms the acknowledgement actually landed (ack landed 16:38:00.577Z) instead of assuming it did, which is the asynchronous-write behaviour described under What this server handles for you.


Quickstart

You need Node ≥ 24 and an Atlassian Cloud site with JSM Operations enabled. There is nothing to clone or build — your MCP client runs the published package.

1. Find your cloud id. Open this while logged in to your site:

https://<your-site>.atlassian.net/_edge/tenant_info

It answers with one line — {"cloudId":"..."} — and that UUID is what JSM_CLOUD_ID wants. If you'd rather not rely on that endpoint, the cloud id is also the segment after /s/ in the URL at admin.atlassian.com → Apps → Sites → your site.

2. Create an API token at id.atlassian.com.

3. Add the server.

Claude Code:

claude mcp add jira-alerts-mcp \
  --scope user \
  --env JSM_CLOUD_ID='your-cloud-id' \
  --env JSM_EMAIL='you@example.com' \
  --env JSM_API_TOKEN="${JSM_API_TOKEN}" \
  -- npx -y jira-alerts-mcp

--scope user registers the server for your whole account rather than only the directory you happened to run the command in. That is what you want for an alerts server — you want it in every session. Without the flag claude mcp add defaults to local scope, and the server exists in that one directory only.

Claude Desktop: open the config from the app rather than by hand — the Claude menu in your menu bar (not the settings inside the window) → Settings → Developer → Edit Config. That creates the file if it doesn't exist yet:

OSPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "jira-alerts-mcp": {
      "command": "npx",
      "args": ["-y", "jira-alerts-mcp"],
      "env": {
        "JSM_CLOUD_ID": "your-cloud-id",
        "JSM_EMAIL": "you@example.com",
        "JSM_API_TOKEN": "your-api-token"
      }
    }
  }
}

mcpServers is a top-level key, and the file holds every server you have configured. If it already has an mcpServers block, add jira-alerts-mcp as another entry inside it — pasting the whole block above over the file replaces whatever was already there.

Then quit Claude Desktop completely and reopen it — the file is read only at startup, and closing the window is not quitting. The server then appears under the connectors panel in the message composer.

Most other MCP clients accept that same JSON shape. There is no scope choice to make here — claude_desktop_config.json is already per-user, the same reach as --scope user on the CLI.

4. Check it works. Ask your agent to list your open alerts. That runs jsm_list_alerts, which needs no ids and confirms your credentials and the read:ops-alert scope that nine of the fourteen tools share.

Then ask who is on call, which runs jsm_list_schedules. That is a separate check, because schedules need read:ops-config — if alerts work and schedules return 401, nothing is wrong with your token; see Required scopes below.

Things that catch people out: with claude mcp add the server name is the first positional argument, before any flags; -y on npx skips the install prompt, which an MCP client has no way to answer; and in zsh ${VAR} needs quoting. A server added without --scope user works in the directory you added it from and is simply missing everywhere else, with no error to explain the absence — if it seems to have disappeared, run claude mcp list from a different directory before touching anything else. For GUI-launched sessions the token has to live in the env block of the config itself — the shell environment isn't inherited, which is why the JSON above carries the credentials inline.

If the server never shows up in Claude Desktop, two causes account for almost all of it, and neither announces itself:

  • npx wasn't on the PATH. A GUI app is launched by the window manager, not a shell, so a Node installed through nvm often isn't visible to it. Set "command" to the absolute path from which node and point "args" at the installed dist/index.js, or install Node system-wide. A Node older than 24 that is found fails as EBADENGINE rather than anything readable.
  • The server exited during startup. Credentials are validated before the handshake, so a bad cloud id or token stops it dead — and because stdout is the protocol channel, that message goes to stderr only. Claude Desktop keeps it at ~/Library/Logs/Claude/mcp-server-jira-alerts-mcp.log (Windows: %APPDATA%\Claude\logs\), named after the key you used under mcpServers. Look for Startup failed: — it names exactly what is wrong.
Came here from this repository's Packages panel?

You found @rrvrs/jira-alerts-mcp on GitHub Packages. That is a mirror of the same build, published so the panel is not empty. GitHub Packages requires a personal access token even for public packages, so installing from it needs auth that npmjs.com does not.

Use npx jira-alerts-mcp above — that is the package on npmjs.com, installable anonymously, and the only supported install route. The two are separate names on separate registries; nothing redirects between them.

Running from a clone instead

Only needed to work on the server itself, or to run a revision that has not been released:

git clone https://github.com/rrvrs/jira-alerts-mcp.git
cd jira-alerts-mcp
npm install
npm run build

Then point your client at the build rather than at npx, so edits take effect without republishing:

  -- node /absolute/path/to/jira-alerts-mcp/dist/index.js

Configuration

VariableRequiredNotes
JSM_CLOUD_IDyesYour Atlassian site's cloud id (a UUID)
JSM_EMAIL + JSM_API_TOKENone ofCreate a token
JSM_OAUTH_TOKENone ofOAuth 3LO bearer; takes precedence if set
JSM_TOOLSETSnoWhich tool families to register — see Choosing your toolsets. Unset registers responder
JSM_READ_ONLYnotrue withholds every write tool
TRANSPORTnostdio (default) or http
PORT / HOSTnoHTTP transport; defaults to 127.0.0.1:3000
ALLOWED_HOSTSnoComma-separated Host allowlist. Required if you set HOST beyond loopback — see SECURITY.md

Credentials are validated at startup, so a bad config fails immediately with an actionable message rather than on the first tool call.

.env.example lists these for reference. The server does not read .env itself — an MCP server is launched by its client, and the client owns the environment. Use the file as a checklist for your client's env block, or set -a; source .env; set +a for local development.

What your credentials can and cannot do

Both auth methods are not equivalent, and the difference is not documented by Atlassian. Verified against a live tenant on 2026-09-05:

The delete scopes are granted per token, not per authentication method. Two Atlassian account API tokens for the same account behave differently: one was refused on every DELETE with 401 Unauthorized; scope does not match — valid credentials, missing grant — and another completed the whole set. So a 401 on a delete is not a reason to abandon JSM_EMAIL + JSM_API_TOKEN. Reissue the token with the delete scopes included, or supply a 3LO or Forge OAuth token granted delete:ops-alert:jira-service-management as JSM_OAUTH_TOKEN. The 401 handler says exactly this, so the model reports it rather than retrying.

The delete-backed alert tools are jsm_delete_alert · jsm_delete_alert_note · jsm_remove_alert_tags · jsm_remove_alert_extra_properties · jsm_delete_alert_attachment.

The alert attachment endpoints are gated twice over. The API's own OpenAPI document maps them to no OAuth scope at all, so a token missing the delete scopes is turned away at the gateway with the same bare scope does not match — which reads like an auth dead end and is not one. A fully scoped token reaches the API and is told Feature not available in your plan instead. On a site whose plan excludes attachments, no token opens them, which is why they now live in their own quarantined attachments toolset that no profile loads. The handler reports the plan limit as a plan limit rather than sending you off to widen a token.

Some actions depend on your JSM plan, not on your scopes. On a Standard tenant, snooze, assign and custom actions are accepted and then fail out of band with Your account plan does not support …. The request is well-formed; the plan is the limit. This is exactly why writes are asynchronous and why jsm_get_request_status matters — the immediate response to all three is a successful receipt.

What has and has not been verified

Every tool in this server was run against a live Jira Service Management site before release. Every tool that a profile can load returned a real success — that is an invariant, and a test enforces it: a toolset marked unverified cannot appear in a profile.

Three families could not be verified, and they ship quarantined rather than removed. Nothing about them is known to be broken; they were untestable on the site available, and the code is very likely correct for a site where they are not blocked.

ToolsetWhat the API answeredWhat that means
heartbeats402 Please upgrade your pricing plan for Heartbeat Monitoring on every endpoint but the pingHeartbeat Monitoring is not in every JSM plan. jsm_ping_heartbeat does work — and answers PONG even for a heartbeat that does not exist, so a successful ping proves nothing on its own.
attachments403 Feature not available in your plan, for a fully scoped token holding Jira adminThe plan excludes attachments. The API also declares no OAuth scope for these four endpoints, so their listed scopes are inferred from the alert family.
forwarding422 Users cannot be forwarded back to themselvesA forwarding rule needs two distinct users and the test site had one, so only jsm_list_forwarding_rules could be exercised.

Enable one by naming it alongside whatever else you want:

"env": { "JSM_TOOLSETS": "all,heartbeats" }

jsm_list_capabilities reports the same thing at runtime, so an assistant asking "can you create a heartbeat?" is told the family exists, is off, how to turn it on, and that it was never seen to work — rather than guessing.

Two families were removed in 2.0.0 rather than quarantined. Alert policies (11 tools) and custom user roles (6 tools) answered 403 You are not authorized under two separate credentials, one of them holding Jira ADMINISTER. Custom user roles is an Opsgenie Enterprise feature, and the policy refusal looks like the same kind of limit. Shipping seventeen tools whose only evidence was that they compiled was not worth the tool-list weight, so they are gone. If you have a site where they work and want them back, open an issue — the code is in the history and the drift guard still knows the endpoints.

Choosing your toolsets

The JSM Operations API is roughly 240 operations. Registering all of them would hand your client a tool list it cannot choose from accurately, so the surface is cut into named toolsets and you pick:

NameWhat it registersToolsScope
alertsAlert reads: search, detail, notes, activity logs, request status5read:ops-alert:…
alert-actionsCreate, acknowledge, close, snooze, assign, escalate, annotate, tag, delete18read: + write:ops-alert:…, plus delete:ops-alert:… for the destructive ones
oncallWho is on call now and next, shift timelines, schedule discovery4read:ops-config:…
schedulesSchedules, rotations and overrides — create, edit, delete14read: + write:ops-config:…
teamsTeam discovery, team roles, contact methods13read: + write:ops-config:…
maintenanceMaintenance windows, site-wide or per team6read: + write:ops-config:…
routingEscalations, routing rules, notification rules and steps21read: + write:ops-config:…

Three more ship but no profile loads them — see What has and has not been verified:

NameWhat it registersToolsWhy it is quarantined
heartbeatsDead-man's switches that alert when a ping stops arriving5402 — not in every JSM plan
attachmentsList, download and delete alert attachments3403 — not in every JSM plan
forwardingForward one person's notifications to another5Needs two users; untested

Plus four profiles, which are bundles of the above:

ProfileContentsTools
responderThe default. alerts + alert-actions + oncall27
coreThe thirteen tools that shipped before toolsets existed, plus jsm_create_alert14
adminoncall + schedules + teams + maintenance + routing — configuration, not incidents58
allEvery verified toolset81
"env": { "JSM_TOOLSETS": "responder" }     // or "alerts,oncall", or "all"

Names combine freely, and the flags --toolsets=a,b and --read-only override the environment. A name that isn't in the tables above stops the server at startup with the valid names and a suggestion — a typo should not quietly leave you with fewer tools than you asked for.

core is a frozen list of names — the surface this server had before toolsets existed — kept so an install that wants exactly that can ask for it without listing thirteen tools. It keeps those fourteen when combined: core,schedules is core plus every schedule tool, not both families unrestricted, so adding a toolset beside it cannot widen what core itself contributes. responder is derived from its toolsets and widens as families land, which is why it is the default: an alerts server whose alert tools are mostly invisible until you reconfigure it is not much use.

all means every verified toolset, not every toolset. The three quarantined families have to be named on their own — JSM_TOOLSETS=all,heartbeats — so that asking for everything cannot hand you tools that have never been seen to work.

jsm_list_capabilities is always registered, whatever you select. It reports every toolset, whether it is loaded, its scopes, and the variable to change — so when you ask for something the current selection doesn't cover, you get "that's in the oncall toolset" rather than "this server can't do that". Changing JSM_TOOLSETS needs a restart; nothing can enable a toolset mid-conversation.

Required scopes. Alerts and on-call sit behind different scopes, which is the single most common setup mistake:

ToolsScope
The 5 alert readsread:ops-alert:jira-service-management
The alert writesread:ops-alert:… and write:ops-alert:… — both
The destructive alert toolsalso delete:ops-alert:jira-service-management
jsm_list_schedules, jsm_get_on_call, jsm_get_next_on_call, jsm_get_schedule_timelineread:ops-config:jira-service-management
Resolving responder ids to names (optional)read:jira-user

Three consequences worth knowing before you mint a token:

  • Writes need the read scope too. A token carrying only write:ops-alert:jira-service-management fails. Atlassian requires the read scope alongside it on every write endpoint.

  • ops-config is a separate grant, and a missing one returns 401, not 403. Omit it and the nine alert tools work perfectly while the four on-call tools fail — which reads like a broken credential and is not one. Both are supported configurations: granting only the read scopes, or only ops-alert, is a deliberate way to narrow what the agent can reach.

  • The Jira user scope is optional, and its absence is visible rather than silent. Every responder the Operations API returns is a bare account id (712020:9ae5385e-…); with read:jira-user the on-call tools resolve those to names and emails in the same call. Without it they still answer — you get the ids, plus one line saying which scope would have named them. Knowing who is on-call matters more than knowing their display name, so a missing scope here never turns into an error.

    Reach for read:jira-user, not read:user:jira. The granular scheme does cover these endpoints, but only as the complete set read:application-role:jira + read:group:jira + read:user:jira + read:avatar:jiraread:user:jira on its own is not sufficient, and Atlassian still marks the whole granular set Beta for this API.

Team visibility. The account also needs JSM Operations access on the relevant team. Alerts and schedules hang off a team's Operations page, so credentials that can't see the team will get empty lists rather than errors.


Example

Asking who is on call resolves to jsm_list_schedules, then jsm_get_on_call:

you — who's on call for payments right now?

# Currently on-call for Payments — Primary

- Dana Okafor

Acknowledging an alert returns a receipt, not the updated alert — because JSM applies alert actions out of band:

you — ack alert 4f2a9c1e-…-1718395200000, I'm looking at it

Acknowledge request accepted for alert `4f2a9c1e-…-1718395200000`.

- **Request id**: `c7b41f30-…`
- **Result**: Request will be processed

JSM applies alert actions asynchronously, so the alert may not reflect this
change immediately. Confirm with jsm_get_request_status using the request id
above, or re-read the alert after a moment.

That last paragraph is the point: without it an agent re-reads the alert, sees it still unacknowledged, and acknowledges it again.


Tools

Ninety-five tools across ten toolsets: alerts, alert-actions, oncall, schedules, teams, maintenance, routing, heartbeats, attachments and forwarding. The first three are registered by default; the rest load only when JSM_TOOLSETS names them, and jsm_list_capabilities reports at runtime which of them this install actually has.

TOOLS.md is the catalogue: every tool with the endpoint behind it, whether it reads or writes, which are marked destructive, and the caveats that come with each family.

Narrow the surface with JSM_TOOLSETS or JSM_READ_ONLY — see Choosing your toolsets.


What this server handles for you

Three API behaviours silently break naive integrations. Each is stated in the tool descriptions, where the model will actually read it:

  1. Writes are asynchronous. Every mutating endpoint returns { result, requestId, took } immediately and applies the change out of band. Re-reading the alert right after an ack will often show it still unacknowledged. jsm_get_request_status is the correct verification path, and each write tool points at it.

  2. tinyId is not an id. The short number in the JSM UI (#4821) is rejected by /v1/alerts/{id}, which accepts only the full uuid-timestamp id. Aliases need a different endpoint entirely (/v1/alerts/alias?alias=). Both the schema descriptions and the 404 handler say so explicitly, so the model self-corrects instead of retrying the same call.

  3. The search window caps at 20,000. offset + limit must stay under it. jsm_list_alerts rejects deeper paging locally with a message telling the model to narrow the query instead of burning a round trip on a guaranteed 400.

  4. Alert actions take no actor or note. Opsgenie accepted user, source and note alongside an acknowledge or a close, and JSM Operations is an Opsgenie rehost — but it declares no request body for those endpoints and discards the fields silently. Acknowledging with a note and reading the activity log back shows neither the note nor the actor. So these tools do not offer the parameters at all: a rejected argument is a fact the model can act on, where an ignored one looks like a recorded decision that has actually vanished. To leave a durable note, call jsm_add_alert_note. jsm_create_alert does take note and source, because CreateAlertRequest declares both and the API honours them — also verified.


Why this exists

Alerts are not work items. They live behind a different API — /jsm/ops/api, the rehosted Opsgenie surface — with its own scopes, its own id format and its own asynchronous write semantics. The MCP Registry lists 30 Jira servers; every one of them talks to work items. None can tell you what is paging you right now. atlassian/atlassian-mcp-server narrows the gap but does not close it. Since February 2026 it ships four JSM Operations tools — getJsmOpsAlerts, getJsmOpsScheduleInfo, getJsmOpsTeamInfo and updateJsmOpsAlert — and they are coarse: a single updateJsmOpsAlert covers acknowledge, unacknowledge, close and escalate, and nothing covers notes, logs, tags, attachments, snooze, assign, request status, timelines, rotations, overrides, heartbeats, maintenance, routing, integrations or audit logs. They are also absent from that repository's README, documented only on Atlassian's supported tools page, and were API-token-only at launch — an OAuth install sees none of them. Being a hosted, closed server, those gaps are Atlassian's to close rather than something a contribution can fix.

The Opsgenie MCP servers that do exist speak an API with an end date. giantswarm/mcp-opsgenie, burakdirin/opsgenie-mcp-server and daviddykeuk/opsgenie-mcp all call api.opsgenie.com with a GenieKey. Opsgenie reached end-of-sale on 4 June 2025 and shuts down on 5 April 2027, at which point those REST APIs stop responding. This server targets the surface that replaces them: https://api.atlassian.com/jsm/ops/api/{cloudId}/v1.

Compatibility. For Atlassian Cloud tenants with JSM Operations — sites already migrated off standalone Opsgenie, or provisioned after the merge. If your team still logs in at app.opsgenie.com and authenticates with a GenieKey, this server will not reach your data; one of the Opsgenie servers above will, until 2027.


Project layout

src/
├── index.ts                 # transports and startup credential validation
├── server.ts                # assembles the catalogue from the eight families
├── toolsets.ts              # toolsets, profiles, and selection resolution
├── constants.ts             # API root, limits
├── types.ts                 # JSM API interfaces
├── schemas/common.ts        # Zod fragments shared across families
├── services/
│   ├── client.ts            # auth, request, envelope normalisation, error mapping
│   ├── directory.ts         # resolves bare Atlassian ids to names
│   ├── name-cache.ts        # one registry for every process-wide cache
│   ├── format.ts            # markdown rendering, truncation, result envelopes
│   └── render/              # per-family renderers
└── tools/
    ├── define.ts            # defineTool() + registerTools()
    ├── family.ts            # the resource-family factory
    ├── execute-write.ts     # the shared write executor
    ├── list-executor.ts     # the shared list pipeline
    ├── paging.ts            # the paging dialects each endpoint wants
    ├── capabilities.ts      # jsm_list_capabilities
    ├── test-support.ts      # stub client and in-memory MCP harness
    ├── alerts/              # alert reads
    ├── actions/             # alert writes
    ├── oncall/              # who is on call now and next
    ├── schedules/           # schedules, rotations, overrides
    ├── teams/               # teams, roles, contact methods
    ├── maintenance/         # maintenance windows
    ├── heartbeats/          # heartbeat monitors
    └── routing/             # escalations, routing, notification, forwarding rules

The alert families are written one tool per file: a module owns its input shape, its description and its handler, and nothing else. The configuration families are generated instead — family.ts builds the mechanical list/get/create/update/delete shapes from a ResourceConfig, because writing ten of them by hand would be a hundred files whose differences are three lines each. Where an endpoint does not fit those five shapes, a hand-written tool sits beside the generated ones; teams/contacts.ts has both.

server.ts concatenates the eight families into allTools, the full catalogue. toolsets.ts cuts that down to what a process actually registers, and index.ts only knows about transports. The tool catalogue itself — every tool, grouped by family — is in TOOLS.md.

Three conventions in here are load-bearing, and changing them by accident is the most likely way to break the server subtly. They are written up, with the bugs that motivated each, under Conventions worth preserving.


Contributing

See CONTRIBUTING.md for the development loop, the conventions worth preserving, and how to add a tool. Issues and PRs must not contain cloud ids, tokens, or real alert data.

Security

This server holds Atlassian credentials, and the HTTP transport performs no authentication of its own — see SECURITY.md for the threat model, hardening notes, and how to report a vulnerability privately.

License

Apache-2.0