Skip to main content

ct-jsm-ops-mcp-server

CloudThinker MCP server for Jira Service Management Operations — alerts, on-call schedules, and an idempotent alert lifecycle, served over stdio.

Reads JSM_* env at startup, resolves the site's cloudId, proves the credential can actually read Operations, then serves tools. There is no connect or login tool: the first tool call works.

Why this exists

Atlassian's own Rovo MCP server exposes only four JSM ops tools (getJsmOpsAlerts, getJsmOpsScheduleInfo, getJsmOpsTeamInfo, updateJsmOpsAlert), and those are reachable only under API-token auth, not OAuth — which is the auth CloudThinker's atlassian connection uses. That surface also has no alert timeline, no responder management, and no assign or escalate. So this is a from-scratch wrapper over the JSM ops REST API (https://api.atlassian.com/jsm/ops/api/{cloudId}/v1) plus the Jira platform API for the incident issue itself.

Environment

Env var Required Default Purpose
JSM_SITE_URL yes Site origin, e.g. https://acme.atlassian.net. Project and board URLs are accepted and normalized back to the origin.
JSM_SITE_EMAIL yes The Atlassian account the API token belongs to.
JSM_API_TOKEN yes Atlassian API token from id.atlassian.com. Sent as Authorization: Basic base64(email:token).
LOG_LEVEL no info Also mirrored into FASTMCP_LOG_LEVEL.

cloudId is not an input. It is resolved from JSM_SITE_URL via /_edge/tenant_info; it is a UUID the user has no reason to know.

Startup preflight

The server exits non-zero rather than starting when Operations is not usable, so the CloudThinker connection test reports the real reason instead of letting the failure surface mid-incident:

Condition Exit Reported as
Missing env var 2 which var is unset
Unparseable JSM_SITE_URL 2 expected form
Site or cloudId unreachable 1 check the site URL
401 on the ops probe 1 API token, not a password, not a Rovo token
403 on the ops probe 1 Operations not usable: plan lacks it, or the account cannot open it
404 on the ops probe 1 that id does not exist on this site

A JSM site without Operations authenticates perfectly well and then answers every ops path with 403 {"code": 40301, "message": "Account does not have access to Opsgenie."} — verified against a live site. Auth success alone proves nothing, so the probe makes one real ops read (/alerts and /schedules, one record each).

The 403 does not distinguish "the plan has no Operations" from "this user cannot open Operations", so neither does the error. It names both and quotes the provider's own message.

Tools

Read-only (8): list_alerts, get_alert, get_alert_timeline, list_schedules, get_on_call, get_team, get_jsm_incident, get_action_status.

Idempotent writes (4): acknowledge_alert, assign_alert, add_alert_responder, add_alert_note.

Destructive writes (2): close_alert, escalate_alert.

Tool annotations are load-bearing. CloudThinker reads readOnlyHint and destructiveHint off the MCP definition to route human approval, so close_alert and escalate_alert — the two actions CloudThinker cannot undo — always reach the approval gate.

The write contract

Every write runs the same three steps, so retrying is safe by construction:

read alert  ->  decide  ->  post  ->  poll requestId to settled

Alert actions are processed asynchronously: the POST returns only a requestId, so "the action worked" is knowable only by polling /alerts/requests/{id}. Each write returns one of:

status meaning
applied the action ran and the provider confirmed it
noop_already_applied the alert was already in the target state; nothing was sent
conflict the alert moved somewhere the action does not apply from; nothing was sent, a recovery path is returned
pending accepted but still queued; poll get_action_status, do not re-send
failed the provider rejected it, or the alert was not found

Idempotency is read from the provider, not from local bookkeeping, so it stays correct when the same action arrives from two paths or after a restart. The server never forces: a closed alert is never reopened.

escalate_alert is the exception with no provider-side idempotency handle — no alert field says "already escalated to X" — so only the "still open" check applies, and a replay pages someone twice. That is why it is destructive.

Local checks

uv sync
uv run pytest -q                              # pure helpers + lifecycle, offline
uv run python tests/smoke_listtools.py        # tool names + annotations, no network

Live-site verification required before publishing

These are doc-derived and must be confirmed against a real JSM site with Operations. Fixtures built from documentation prove code against the wrong schema.

  1. Response envelopes. {"values": [...], "links": {...}} is assumed for /alerts and /schedules. as_values also accepts a bare array and data/named alternates — record which one is real.
  2. Alert logs and notes shapes. The note body key is assumed to be note with text as a fallback. add_alert_note dedupe correctness depends on this exact key.
  3. /teams/{id}. The team endpoint's path and response shape are the least documented of the set; confirm before trusting get_team.
  4. Action request bodies. {"owner": {...}} for assign, {"responder": {"id","type"}} for responders, {"escalation": {"id"}} for escalate, {"note": ...} for acknowledge/close/notes.
  5. Request-status envelope. isSuccess is assumed, flat or under data. If a queued request instead 404s for longer than the poll budget, tune ACTION_POLL_ATTEMPTS.
  6. listTools shows exactly the 14 tools and nothing else.

License

Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ct_jsm_ops_mcp_server-0.1.0.tar.gz (139.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ct_jsm_ops_mcp_server-0.1.0-py3-none-any.whl (24.6 kB view details)

Uploaded Python 3

File details

Details for the file ct_jsm_ops_mcp_server-0.1.0.tar.gz.

File metadata

  • Download URL: ct_jsm_ops_mcp_server-0.1.0.tar.gz
  • Upload date:
  • Size: 139.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ct_jsm_ops_mcp_server-0.1.0.tar.gz
Algorithm Hash digest
SHA256 72b8c6a8737a523d970a3a6591ba352b6539dcdb024b561a093a7523f31ad784
MD5 2e5bdd5119c126460744203e5302c2ec
BLAKE2b-256 9186d2ea72b1083a4adce395e17ff3f2011e2a4dc9c968d5022a7f156692fc96

See more details on using hashes here.

File details

Details for the file ct_jsm_ops_mcp_server-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ct_jsm_ops_mcp_server-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ct_jsm_ops_mcp_server-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 100927eee475e1ea71c2f73ed96afcb75d1f6f9c94d422a58310c97aaf04bc0b
MD5 887c7048da3b6467ad8acb01bd81536c
BLAKE2b-256 38c8730693955b0d22a5b3af86ccd068412acf165a98e35586483162017f5c98

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page