Skip to content

Architecture Overview

Two thin interface layers (Typer CLIs and the FastMCP server) sit on shared HTTP clients. All Jira and Confluence logic lives in the clients and converters; the interfaces hold argument parsing and output formatting only.

graph TB
    subgraph Interfaces
        CLI[jira / confluence CLI]
        SHELL[jira shell]
        MCP[jira-mcp server]
    end
    subgraph Core
        JC[JiraClient]
        CC[ConfluenceClient]
        ADF[markdown to ADF]
        STO[markdown to storage format]
        Q[quality scoring]
    end
    CLI --> JC
    CLI --> CC
    SHELL --> JC
    MCP --> JC
    MCP --> CC
    CLI --> Q
    MCP --> Q
    JC --> ADF
    CC --> STO

Modules

Module Purpose
cli.py jira Typer application
shell.py Interactive shell (cmd.Cmd)
mcp.py FastMCP server, Jira tools, server instructions
client.py JiraClient REST wrapper and parameter dataclasses
models.py Pydantic models: Issue, Comment, Transition, User, Project, Attachment, Status, IssueType
adf.py Markdown to Atlassian Document Format
quality.py Issue quality scoring and JQL selection for reports
config.py Config file and environment resolution
display.py Rich rendering helpers
confluence_cli.py confluence Typer application
confluence_mcp.py Registers Confluence tools on the MCP server
confluence_client.py ConfluenceClient REST wrapper
confluence_models.py Space and Page models
confluence_storage.py Markdown to storage format and plain-text rendering

Surface parity

Every capability is exposed on both the CLI (jira, confluence) and the MCP server. tests/test_surface_parity.py declares the mapping from MCP tool name to CLI command path and fails when an MCP tool has no CLI command or a CLI command has no MCP tool. The only CLI commands outside the mapping are config and shell, which have no MCP equivalent by design. Adding a capability means adding it to the client, both surfaces, and the mapping in one change.

Data models

class Issue:
    key: str  # "PROJ-123"
    summary: str
    status: str
    assignee: str | None  # display name
    reporter: str | None  # display name
    project: str
    priority: str | None
    created: datetime
    updated: datetime
    due_date: date | None
    description: str | None
    attachments: list[Attachment]
    labels: list[str]
    components: list[str]  # component names
    fix_versions: list[str]  # version names


class Comment:
    id: str
    author: str
    body: str
    created: datetime


class User:
    account_id: str
    display_name: str
    email: str | None  # may be None for privacy
    active: bool
    avatar_url: str | None


class Space:
    id: str  # numeric
    key: str  # "DEV"
    name: str
    type: str  # "global", "personal"
    status: str


class Page:
    id: str  # numeric
    title: str
    space_id: str | None  # absent in search results
    status: str
    version: int | None  # absent in search results
    body: str | None  # storage XHTML, only when fetched in full
    url: str | None  # server-relative web UI path

Issue field writes

IssueCreateParams and IssueUpdateParams carry the same metadata fields. People fields (assignee, reporter) are account IDs; setting the reporter requires the "Modify Reporter" project permission. components and fix_versions are lists of names that must already exist in the project; an update replaces the stored list. due_date is an ISO date. Fields left as None are not sent. Subtasks are ordinary issues with parent set:

client.create_issue(
    IssueCreateParams(
        project="PROJ", summary="Subtask", issue_type="Sub-task", parent="PROJ-123"
    )
)

API endpoints

Jira uses REST API v3. Confluence CRUD uses REST API v2; CQL search uses the v1 endpoint, which has no v2 equivalent.

Operation Method Endpoint
Search issues POST /rest/api/3/search/jql
Get issue GET /rest/api/3/issue/{key}
Create issue POST /rest/api/3/issue
Update issue PUT /rest/api/3/issue/{key}
Delete issue DELETE /rest/api/3/issue/{key}
Get comments GET /rest/api/3/issue/{key}/comment
Add comment POST /rest/api/3/issue/{key}/comment
Update comment PUT /rest/api/3/issue/{key}/comment/{id}
Delete comment DELETE /rest/api/3/issue/{key}/comment/{id}
Get transitions GET /rest/api/3/issue/{key}/transitions
Do transition POST /rest/api/3/issue/{key}/transitions
Watchers POST / DELETE /rest/api/3/issue/{key}/watchers
List statuses GET /rest/api/3/status
List issue types GET /rest/api/3/issuetype
Search users GET /rest/api/3/users/search
Assignable users GET /rest/api/3/user/assignable/search
Current user GET /rest/api/3/myself
List projects GET /rest/api/3/project
Confluence search GET /wiki/rest/api/search?cql=...
Read page GET /wiki/api/v2/pages/{id}?body-format=storage
List spaces GET /wiki/api/v2/spaces
Resolve space key GET /wiki/api/v2/spaces?keys={key}
Create page POST /wiki/api/v2/pages
Update page PUT /wiki/api/v2/pages/{id}

Updating a page requires the current version number; the client fetches the page first and submits the incremented version.