agentskills.codes
IN

integration-jira

Connect Jira (Atlassian Cloud) to a self-hosted Hermes Agent over SSH so the agent can search, create, and update issues. Wires Atlassian's official remote MCP server (Rovo) with a static API token. Idempotent and rollback-safe. Works from Claude Code, Codex, Cursor, Hermes itself, and Gemini CLI.

Install

mkdir -p .claude/skills/integration-jira && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16251" && unzip -o skill.zip -d .claude/skills/integration-jira && rm skill.zip

Installs to .claude/skills/integration-jira

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Connect Jira (Atlassian Cloud) to a self-hosted Hermes Agent over SSH so the agent can search, create, and update issues. Wires Atlassian's official remote MCP server (Rovo) with a static API token. Idempotent and rollback-safe. Works from Claude Code, Codex, Cursor, Hermes itself, and Gemini CLI.
298 charsno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

/integration-jira — connect Jira to a remote Hermes (SSH-first)

You are the engineer connecting Jira (Atlassian Cloud) to a self-hosted Hermes agent on the user's VPS. You (the AI agent — Hermes, Claude Code, Codex, Cursor, Gemini, any of them) work over SSH as root against the VPS. The user only does the things a machine cannot: minting the Atlassian API token, and (one time, org-wide) flipping the admin-console toggle that enables API-token auth for the Rovo MCP Server.

Everything else — token storage, MCP registration, gateway reload, verification — runs on the VPS via SSH, idempotently.

Honest auth picture (verified 2026-06): Atlassian ships a hosted remote MCP at https://mcp.atlassian.com/v1/mcp. It accepts two header shapes for headless use:

CredentialHeaderAccess
Personal API token (recommended)Authorization: Basic <base64(email:token)>Full — read and write (create/update issues)
Service-account API keyAuthorization: Bearer <api_key>Read-only tools

Default to Basic unless the user explicitly wants read-only. A common silent failure is sending a personal token as Bearer — it authenticates but exposes only read tools, so writes appear "missing." This is Atlassian Cloud only; Jira Data Center / Server is not served by the remote MCP endpoint (see Pitfalls). The legacy SSE transport https://mcp.atlassian.com/v1/sse is deprecated after 2026-06-30 — use /v1/mcp.


Before you start — gather (ask once, in one batch)

VariableWhatWhere to get it
$VPS_IPIP/hostname of the VPS running HermesUser's hosting dashboard
$VPS_USERSSH user (typically root)User's hosting dashboard
$ATLASSIAN_EMAILEmail of the Atlassian account the agent acts asThe user's Atlassian account
$JIRA_TOKENAtlassian API token (opaque string)https://id.atlassian.com/manage-profile/security/api-tokens → Create API token
Org-admin toggleAPI-token auth enabled for Rovo MCPAtlassian Administration → Settings → Rovo MCP Server

The MCP server runs within that user's Jira permissions — pick the right account.

Confirm SSH access before doing anything:

ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes \
    "$VPS_USER@$VPS_IP" "echo ok" 2>&1 | grep -q '^ok$' \
  || { echo "ABORT: SSH to $VPS_USER@$VPS_IP failed. Run /setup-ssh-keys first."; exit 1; }

Step 1 — verify Hermes is reachable on the VPS

ssh "$VPS_USER@$VPS_IP" '
  set -e
  if command -v hermes >/dev/null 2>&1; then
    hermes --version
  elif docker ps --format "{{.Names}}" | grep -q hermes; then
    AGENT=$(docker ps --filter name=hermes --format "{{.Names}}" | head -1)
    docker exec "$AGENT" hermes --version
  else
    echo "FAIL: hermes not found on host or in container"; exit 1
  fi
' || { echo "ABORT: Hermes is not installed/running. Run /hermes-install first."; exit 1; }

Expected: 0.15.x or 0.17.x.


Step 2 — idempotency check (skip if already wired)

ALREADY=$(ssh "$VPS_USER@$VPS_IP" "hermes mcp list 2>/dev/null | grep -ci jira" || echo 0)
if [ "$ALREADY" -gt 0 ] && [ "${FORCE:-0}" != "1" ]; then
  echo "Jira is already wired. Set FORCE=1 to rewire."
  exit 0
fi

Step 3 — DRY RUN preview (always show before writing)

Compute the Basic header value locally (no logging). The header value must include the literal Basic scheme prefix because Hermes passes it through as one opaque header value — email:token is not a bearer token, so Hermes cannot synthesize the scheme:

# GNU base64 (Linux): -w0 = no line wrap. macOS base64: drop -w0 (it does not wrap).
B64=$(printf '%s' "$ATLASSIAN_EMAIL:$JIRA_TOKEN" | base64 -w0)
HEADER_VALUE="Basic $B64"
cat <<EOF
DRY RUN — the following will happen on $VPS_USER@$VPS_IP:
  1. Write MCP_JIRA_API_KEY (length ${#HEADER_VALUE}, prefix Basic ${B64:0:4}...) via 'hermes config set'
  2. chmod 600 ~/.hermes/.env
  3. Register MCP: hermes mcp add jira --url https://mcp.atlassian.com/v1/mcp --auth-header Authorization
  4. Reload gateway: hermes gateway stop && hermes gateway run
  5. Verify in logs: grep -i "registered.*jira"
  6. Smoke test: GET https://mcp.atlassian.com/v1/mcp -> expect 200/401/403

The token and header value are NEVER printed in plaintext.
EOF

Wait for user confirmation (or skip if AUTO_APPROVE=1).


Step 4 — write the secret (chmod 600, no echo, no logging)

The secret stored is the whole Basic <b64> string, not just the token. The base64 blob can contain +, /, and = — this is exactly why all sed below uses the | delimiter.

ssh "$VPS_USER@$VPS_IP" "hermes config set MCP_JIRA_API_KEY '$HEADER_VALUE'"
ssh "$VPS_USER@$VPS_IP" "chmod 600 ~/.hermes/.env"

Verify (returns 1, NEVER the value):

WROTE=$(ssh "$VPS_USER@$VPS_IP" "grep -c '^MCP_JIRA_API_KEY=' ~/.hermes/.env" || echo 0)
[ "$WROTE" = "1" ] || { echo "FAIL: MCP_JIRA_API_KEY not written. Rolling back."; rollback; exit 1; }

If your Hermes build has no config set subcommand, use the safe sed pattern (pipe delimiter is required — the value contains /+=):

ssh "$VPS_USER@$VPS_IP" "
  grep -q '^MCP_JIRA_API_KEY=' ~/.hermes/.env || printf 'MCP_JIRA_API_KEY=\n' >> ~/.hermes/.env
  sed -i 's|^MCP_JIRA_API_KEY=.*|MCP_JIRA_API_KEY=$HEADER_VALUE|' ~/.hermes/.env
  chmod 600 ~/.hermes/.env
"

Step 5 — register the Jira MCP server

Pick the path that matches the Hermes build on the VPS. Path A is preferred.

Path A (preferred) — official Atlassian remote MCP (HTTP, header auth)

ssh "$VPS_USER@$VPS_IP" "
  hermes mcp add jira \
    --url 'https://mcp.atlassian.com/v1/mcp' \
    --auth-header 'Authorization' \
    --header-value '\${MCP_JIRA_API_KEY}'
"

The flag names vary by Hermes version. If unsure, run hermes mcp add --help first and match its HTTP-header syntax. The header value uses ${MCP_JIRA_API_KEY} indirection so the secret stays in ~/.hermes/.env and never appears in config.yaml.

The resulting config.yaml block should read (token stays out of it):

jira:
  url: https://mcp.atlassian.com/v1/mcp
  headers:
    Authorization: ${MCP_JIRA_API_KEY}
  enabled: true

Path B (fallback) — generic HTTP tool against the Jira REST API

If the Hermes build is stdio-MCP-only and cannot register an HTTP MCP, or if this is Jira Data Center / Server (no remote MCP):

  • Base URL: https://<your-domain>.atlassian.net/rest/api/3 (Cloud) or https://<your-domain>/rest/api/3 (self-hosted)
  • Auth header: Authorization: Basic ${MCP_JIRA_API_KEY_RAW} where MCP_JIRA_API_KEY_RAW = base64(email:token) (no Basic prefix when Hermes adds the scheme itself; otherwise pass the full Basic <b64> as above)
  • Content type: Content-Type: application/json

Do NOT try to register https://mcp.atlassian.com/v1/mcp against a Data Center instance — the hosted endpoint serves Cloud tenants only.


Step 6 — reload the gateway (stop + run, NOT restart)

gateway restart does NOT reliably re-read .env. Always use stop + run.

ssh "$VPS_USER@$VPS_IP" "hermes gateway stop || true"
sleep 2
ssh "$VPS_USER@$VPS_IP" "hermes gateway run --daemon"
sleep 5

Step 7 — verify registration in logs (poll up to 30s)

REGISTERED=0
for i in $(seq 1 6); do
  if ssh "$VPS_USER@$VPS_IP" "hermes logs 2>&1 | tail -200" \
       | grep -qiE "registered.*tool.*jira|MCP server.*jira.*(ok|ready)"; then
    REGISTERED=1
    echo "OK: jira registered in gateway logs."
    break
  fi
  sleep 5
done
[ "$REGISTERED" = "1" ] || { echo "FAIL: jira not in logs after 30s. Rolling back."; rollback; exit 1; }

Expect a line like MCP server 'jira' (HTTP): registered N tool(s): ....


Step 8 — live API smoke test (inside the container so the token stays on the VPS)

HTTP=$(ssh "$VPS_USER@$VPS_IP" "
  curl -sS -o /dev/null -w '%{http_code}' \
    -X GET 'https://mcp.atlassian.com/v1/mcp' \
    -H \"Authorization: \$MCP_JIRA_API_KEY\" \
    -H 'Accept: application/json'
")
case "$HTTP" in
  200) echo "OK: Jira MCP endpoint reachable and credential valid." ;;
  401) echo "FAIL: 401 — invalid header, wrong scheme, or org-admin toggle not enabled. Rolling back."; rollback; exit 1 ;;
  403) echo "FAIL: 403 — credential valid but missing scope/permission."; exit 1 ;;
  404) echo "WARN: 404 — probe path differs by tenant; check tools/list via the gateway." ;;
  *)   echo "WARN: unexpected HTTP $HTTP from Jira MCP. Check manually." ;;
esac

End-to-end smoke from chat:

@<agent> using jira, search for issues assigned to me

A real issue list — or an empty-but-valid result for an account with no issues — is a pass. "Tool not found" for create/update means the credential authenticated as Bearer read-only; switch to Basic (Step 3).


Rollback (auto-runs on any failure above)

rollback() {
  ssh "$VPS_USER@$VPS_IP" "hermes mcp remove jira 2>/dev/null || true"
  ssh "$VPS_USER@$VPS_IP" "hermes config unset MCP_JIRA_API_KEY 2>/dev/null || \
    sed -i '/^MCP_JIRA_API_KEY=/d' ~/.hermes/.env"
  ssh "$VPS_USER@$VPS_IP" "hermes gateway stop; sleep 2; hermes gateway run --daemon"
  echo "Rolled back. Jira is no longer wired."
}

Pitfalls

#PitfallWhy it bitesPrevention
1Org-admin API-token toggle not enabled#1 cause of "valid token, still 401"Org admin must enable API-token auth for Rovo MCP in Atlassian Administration before headless auth works
2Sending personal token as Bearer instead of BasicAuthenticates but exposes only read-only tools — writes appear "missing"Use Basic base64(email:token) for create/update access
3Forgetting the Basic scheme prefix in the stored valueHermes passes the header value t

Content truncated.

Search skills

Search the agent skills registry