v2.145.1

Jira โ€” Cloud & Data Center

One tool for Jira Cloud and self-hosted Jira Data Center / Server โ€” issues, epics, links, sprints, worklogs, and attachments from inside C3.

v2.56.0 integration v2.89.0 epics ยท links ยท sprints ยท worklogs Issue tracking Read & write REST v3 (Cloud) / v2 (Data Center) OS keyring

Overview

c3_jira is a single MCP tool that talks to Jira Cloud (REST v3, email + API token, ADF-aware) and Jira Data Center / Server (REST v2, Personal Access Token, plain-text bodies) behind one action-dispatched interface. Claude Code (and you, via the project dashboard) reads and writes issues without a browser context-switch.

Both deployments, one DTO. services/jira_client.py is a facade over two strategy backends (jira_cloud.py / jira_data_center.py) that normalize both APIs' responses into one shape, with an opaque pagination cursor covering Cloud's nextPageToken and Data Center's startAt offset. The deployment is inferred as cloud for *.atlassian.net / *.jira.com URLs; anything else requires --deployment data_center explicitly.

What you get

SurfaceWhereUse it for
c3_jira MCP tool Claude Code Action-dispatched issue, sprint, and worklog operations from inside an AI session
c3 jira CLI Terminal One-time login, account switching, default-project pinning
Jira tab Project dashboard (per-project) My Work board, JQL search, an issue drawer, and an activity view
/api/jira/* Project server REST endpoints backing the dashboard tab

Quick start

Three commands to connect a Jira site and pin a working project.

1

Log in

c3 jira login \
  --url https://yoursite.atlassian.net

Cloud is inferred from the *.atlassian.net host. Prompts for your email and an API token (getpass masked); the token goes straight into the OS keyring.

2

Pin a default project

c3 jira set-default --project PROJ

Lets c3_jira calls that accept project= skip it โ€” my_issues, list_boards, get_create_metadata, create_issue.

3

Verify

c3 jira status

Lists every configured account and, for the active one, a server-version + identity probe.

Self-hosted Data Center needs the deployment spelled out:

c3 jira login --url https://jira.example.com --deployment data_center

Anything you want reusable across every project (a personal Cloud account) belongs in the global config:

c3 jira login --global --url https://yoursite.atlassian.net

First call from Claude Code

# Connection + account probe, then your open work
c3_jira(action='status')
c3_jira(action='my_issues')

# Raw JQL
c3_jira(action='search', jql='project = PROJ AND status != Done ORDER BY updated DESC')

# Full detail on one issue
c3_jira(action='get_issue', issue='PROJ-123')

Authentication & security

API tokens / PATs

Cloud uses your email plus an API token from id.atlassian.com โ†’ Security โ†’ API tokens (Basic auth over HTTPS). Data Center uses a username plus a Personal Access Token from your profile's Personal Access Tokens page (Bearer auth). Neither surface scopes the token independently of the account's own Jira permissions โ€” whatever that user can do in the browser, c3_jira can do through the API. Use a dedicated service account with only the project access you intend to grant the agent, especially before enabling delete_issue.

HTTPS-only, enforced. validate_base_url rejects any base_url that isn't https:// unless --insecure is passed โ€” storing a token against a plaintext endpoint invites interception. --insecure is meant for local development servers only.

Where the token lives

OS keyring Token text, under service c3-jira, account {base_url}|{username}. Backed by Windows Credential Manager, macOS Keychain, or Linux Secret Service.
.c3/config.json Non-secret jira section: a named registry of accounts ({base_url, username, deployment, default_project, verify_tls, ca_bundle}) plus which one is the default. Never the token.

Deployment inference

_jira_deployment_for checks the host: *.atlassian.net or *.jira.com infers cloud automatically. Any other host needs --deployment data_center on the command line โ€” c3 jira login refuses with an explicit error rather than guessing.

TLS verification

Default: verify_tls = true. --insecure on c3 jira login disables verification (and allows http://); --ca-bundle <path> points at a custom CA bundle for a self-signed enterprise certificate without disabling verification entirely.

Named accounts, multiple sites

Every login is stored under a name (--name work, --name internal), defaulting to the URL's host label when omitted. Pass account=<name> on any c3_jira call to use a non-default account for that one call; switch the default with c3 jira use --name <name>.

Reusable across projects

c3 jira login --global writes the account to ~/.c3/config.json. Resolution mirrors Bitbucket: the project's jira section wins when its default_account resolves against its own accounts registry; otherwise C3 falls back to the home config. The section is taken wholesale from exactly one file โ€” project fields are never merged over a home-registered account. That's a deliberate security invariant: a repository's committed config must not be able to override the credential-bound base_url, username, deployment, or TLS settings of an account it did not register. Tokens are additionally keyed by (base_url, username) in the keyring, so a rewritten base_url can never retrieve the original server's token.


Action reference

The MCP tool dispatches by action. Twenty-five actions, grouped below by capability. project falls back to the account's default_project when the action takes one and omits it.

Read & discovery

ActionArgsDescription
status โ€” Every configured account, and for the active one a server-version + identity probe. Works without a valid token.
whoami โ€” Authenticated user โ€” display name, deployment-native id, email
search jql, fields_list?, limit?, cursor? Raw JQL, unquoted and passed straight through. limit default 25.
my_issues project?, status_category?, limit?, cursor? Builds assignee = currentUser(), optionally narrowed by project and status category
get_issue issue Full detail: type, status, priority, parent, links (with ids), attachments (id/filename/size), comments, description
list_projects query?, limit? Projects visible to the account, optionally filtered. limit default 50.
list_transitions issue Available workflow transitions โ€” id, name, target status
get_create_metadataproject?, issue_type Field configuration for a project + issue type โ€” see the createmeta caveat
search_users query, limit? Users matching a name/email fragment; returns the deployment-native id assign needs. limit default 10.
list_link_types โ€” Every issue-link type with its outward/inward phrasing
list_boards project?, limit? Agile boards, optionally scoped to a project. limit default 50.
list_sprints board_id, sprint_state?, limit? Sprints on a board; sprint_state is active, future, or closed
list_worklogs issue Time entries logged on an issue

Issue writes

ActionArgsDescription
create_issue project?, issue_type, summary, description?, fields?, parent?Pre-validated against create metadata โ€” missing required fields come back as machine-readable JSON instead of a guess. fields is a JSON object of field ids โ†’ values.
update_issue issue, summary?, description?, parent?, fields?Needs at least one of these. parent='none' clears the epic/parent link.
comment issue, body, body_format?Adds a comment. body_format defaults to text.
transition issue, transition, body?, fields?Moves the issue along its workflow. transition is an id or a name (resolved via list_transitions).
assign issue, useruser is the deployment-native id โ€” accountId on Cloud, username on Data Center. Find it via search_users.
link_issues issue, link_type, targetTyped link reading <issue> <link_type> <target>. link_type accepts a catalog name or either directional phrasing โ€” an inward match flips the pair automatically.
unlink_issueslink_idRemoves one typed link. Ids appear on get_issue's link lines.
delete_issue issue, delete_subtasks?Permanent, not recoverable via the API. Refuses an issue that still has subtasks unless delete_subtasks=true.

Sprints, worklogs, attachments

ActionArgsDescription
move_to_sprint issue, sprint_idissue is one key or a comma-list. Backed by /rest/agile/1.0, identical on Cloud and Data Center.
move_to_backlog issueComma-list ok. Moves issues out of any sprint.
add_worklog issue, time_spent, body?time_spent is Jira duration syntax โ€” '2h 30m', '1d'. body is the worklog comment.
attach_file issue, file_pathUploads one local file, multipart. Capped at 20MB locally โ€” Jira's own server-side cap still applies on top.

Argument fall-back. my_issues, list_boards, get_create_metadata, and create_issue fall back to the account's default_project from .c3/config.json when the call omits project=. Use c3 jira set-default --project to pin it.

The full parameter reference also lives on the tool card.


CLI commands

Five subcommands cover account lifecycle and project defaults. Run any with --help for full flags.

CommandDescription
c3 jira login --url <URL> [--deployment cloud|data_center] [--name <N>] [--username <U>] [--token <T>] [--no-set-default] [--no-verify-login] [--insecure] [--ca-bundle <PATH>] [--global]Interactive token prompt (getpass masked). Writes to keyring, registers the account under --name (derived from the host if omitted), and makes it the default unless --no-set-default. Runs a connection probe unless --no-verify-login.
c3 jira logout [--name <N>]Removes an account from keyring + registry. Defaults to the default account.
c3 jira statusLists every configured account, marks the default, and probes connectivity.
c3 jira use --name <N>Switch the default account (no token re-entry โ€” it's already in the keyring).
c3 jira set-default --project <KEY> [--name <N>]Pin the project key used by c3_jira calls that accept project= when it's omitted.

Every subcommand also takes a positional project_path (default: current directory) โ€” the C3 project whose .c3/config.json to write, unless --global redirects login to ~/.c3.


Project dashboard tab

This is the per-project dashboard, not the multi-project Hub. Run c3 serve (or open a project's session server) to see it โ€” the Jira tab sits in that sidebar, after Bitbucket and before Credentials. The multi-project Hub (c3 hub) has no Jira view of its own.

ViewShows
My Work board Your assigned issues grouped by status category into three columns โ€” To Do, In Progress, Done
Search Free-form JQL against /api/jira/search
Issue drawer Opens on click: full detail, transition buttons, a comment box, assignment
Activity Edit-ledger work linked to issue keys โ€” PROJ-123 detected in branch names and edit summaries. Works even before you log in.

Endpoints powering the tab live in cli/server.py under /api/jira/*: status, issues/my, board, search, issue/<key> (GET and POST for create), issue/<key>/transition, issue/<key>/comment, issue/<key>/assign, and activity.


Audit trail โ€” edit ledger integration

State changes on the Jira side aren't in your local working tree, so they're still appended to the C3 edit ledger. The path is virtual:

jira://<issue-or-project-key>

Logged actions โ€” identifiers only, bodies and arbitrary field contents are never logged:

ActionWhy it's logged
create_issue / update_issueIssue opened or its summary/description/parent/fields edited
commentDiscussion contribution worth recovering on
transitionWorkflow state changed
assignOwnership changed
link_issues / unlink_issuesRelationship to another issue created or removed
delete_issuePermanent โ€” worth remembering happened at all
move_to_sprint / move_to_backlogSprint membership changed
add_worklogTime logged against the issue
attach_fileEvidence attached

Validation failures never log. _log_mutation skips any response starting with [jira:error] or [jira:missing-fields] โ€” nothing actually changed in Jira, so nothing is recorded as a mutation. Read-only actions and status are never logged either.

Read it back with c3_edits(action='history', file='jira://PROJ-123'), or via the activity log's jira_action events (also what powers the dashboard's Activity view).


.c3/config.json โ€” jira section

Default shape (created on first c3 jira login):

{
  "jira": {
    "default_account": "yoursite",
    "accounts": {
      "yoursite": {
        "base_url": "https://yoursite.atlassian.net",
        "username": "alice@example.com",
        "deployment": "cloud",
        "default_project": "PROJ",
        "verify_tls": true,
        "ca_bundle": ""
      }
    }
  }
}
default_account Which entry in accounts a call resolves to when it doesn't pass account=.
accounts Named registry โ€” one entry per (base_url, username) pair you've logged in as. Never the token.
base_url Site URL, no trailing slash, always https:// unless the account was created with --insecure.
deployment cloud or data_center โ€” picks the REST v3 vs v2 backend and the auth header shape.
default_project Project key used when a call omits project=. Set via c3 jira set-default.
verify_tls false when the account was created with --insecure.
ca_bundle Path to a custom CA bundle, if one was passed at login.

Taken wholesale, never field-merged. When C3 falls back from a project's jira section to the home one, it uses the home section entirely โ€” it never takes base_url from one file and verify_tls from the other. A repository's config can't selectively override a globally registered account's credential-bound fields. See Authentication.


Example workflows

Triage your open work

c3_jira(action='my_issues', status_category='indeterminate')
c3_jira(action='get_issue', issue='PROJ-123')

File a bug โ€” pre-validated against metadata

# See what the project + issue type actually require
c3_jira(action='get_create_metadata', project='PROJ', issue_type='Bug')

# Create โ€” missing required fields come back instead of a guess
c3_jira(action='create_issue', project='PROJ', issue_type='Bug',
        summary='Login redirect loops on Safari',
        description='Repro steps in thread')

Put an issue under an epic, then link two issues

c3_jira(action='update_issue', issue='PROJ-124', parent='PROJ-42')
c3_jira(action='link_issues', issue='PROJ-1', link_type='blocks', target='PROJ-2')

A field gets rejected on create

# [jira:api-error] ... It is not on the appropriate screen
# Create without the field, then set it separately:
c3_jira(action='update_issue', issue='PROJ-124',
        fields='{"customfield_10014": "value"}')

Sprint flow: board โ†’ sprint โ†’ move an issue

c3_jira(action='list_boards')
c3_jira(action='list_sprints', board_id=7, sprint_state='active')
c3_jira(action='move_to_sprint', issue='PROJ-1,PROJ-2', sprint_id=42)

Log time and attach evidence

c3_jira(action='add_worklog', issue='PROJ-1', time_spent='2h 30m', body='Pairing on the redirect fix')
c3_jira(action='attach_file', issue='PROJ-1', file_path='logs/repro.log')

Move an issue through its workflow

c3_jira(action='list_transitions', issue='PROJ-123')
c3_jira(action='transition', issue='PROJ-123', transition='In Progress')
c3_jira(action='comment', issue='PROJ-123', body='Deployed to staging')

Troubleshooting

createmeta is not the create screen. get_create_metadata returns Jira's field configuration for a project + issue type, not what the create screen actually accepts. Jira can list a field as required or optional (Epic Link is the classic case) that create_issue then rejects with "cannot be set. It is not on the appropriate screen." When that happens: create the issue without the field, then set it afterward with update_issue โ€” the edit screen is configured separately from the create screen in Jira itself, and C3 can't see that difference ahead of time. The tool's own error message repeats this hint. Field entries in the metadata response carry both id and name; the fields JSON you pass to create_issue/update_issue takes ids (customfield_โ€ฆ), never display names.

SymptomCause & fix
[jira:no-account] No default account configured (or the named account= doesn't exist). Run c3 jira login --url <URL>.
[jira:no-token] The account is registered but the keyring entry is missing (e.g. .c3/ copied to a different machine). Re-run c3 jira login for that account to put the token back.
--deployment cloud|data_center is required for self-hosted URLs The host isn't *.atlassian.net / *.jira.com, so C3 can't infer it. Pass --deployment data_center explicitly.
HTTP 401 Bad or expired token, or the token belongs to a different site than base_url. Generate a fresh API token / PAT.
HTTP 403 on a write action The Jira account itself lacks the project permission โ€” c3_jira has no separate scope model, it acts as that user. Grant the permission in Jira, or use an account that has it.
get_create_metadata 404s on a Data Center 9.0โ€“11.x server Fixed: newer Data Center versions split the monolithic createmeta endpoint into a paginated pair. The client tries the split endpoints first and falls back to the legacy one on 404, so this should no longer surface โ€” if it does, the server may be older still.
SSL: CERTIFICATE_VERIFY_FAILED Self-signed enterprise CA. Pass --ca-bundle <path> at login (preferred), or --insecure for local dev only.
The 'keyring' package is required pip install keyring. Declared dependency, but an in-place upgrade can leave it missing.

Where to look

  • Activity log: project dashboard โ†’ Jira tab โ†’ Activity, plus .c3/activity_log.jsonl for raw events tagged jira_action.
  • Edit ledger: c3_edits(action='history', file='jira://<issue-or-project-key>') for every mutation.
  • Tool calls: c3_session(action='log') entries record every c3_jira dispatch with its action name and (sanitized) args.

Need an action this surface doesn't cover? File an issue or pull request โ€” the integration is intentionally thin around services/jira_client.py and its Cloud / Data Center backends.