Skip to main content

Jira

Search with JQL, read and edit issues, move them along their workflow, and comment. Descriptions and comments are markdown in both directions — Jira's own format is a JSON document tree, and you never see it.

npm i @forge/tools-jira

Jira and Confluence share one credential and one site host, so a deployment that wires this also wires Confluence at no extra cost.

Tools

ToolEffectApprovalNotes
jira_search_issuesreadneverJQL, sent as a POST body — a JQL string in a query parameter is the most common way this call fails
jira_get_issuereadneverFields, status, assignee, labels and recent comments, all as markdown
jira_list_projectsreadneverThe keys search and create take
jira_list_transitionsreadneverNot optional — see below. Gives each transition's id and the status it lands in
jira_create_issueexternal-writealwaysMarkdown description, converted for you
jira_update_issueexternal-writealwaysSummary, description, assignee, labels, priority. Never status
jira_transition_issueexternal-writealwaysTakes a transition id; refuses a status name
jira_commentexternal-writealwaysMarkdown in, ADF out

Wire it up

import { createAgent } from "@forge/agentkit/providers";
import { createStaticCredentialResolver } from "@forge/agentkit/tools";
import { createJiraToolkit } from "@forge/tools-jira";

// Basic, not bearer: Atlassian takes an account email and an API token.
const resolver = createStaticCredentialResolver({
atlassian: {
scheme: "basic",
username: process.env.ATLASSIAN_EMAIL ?? "",
password: process.env.ATLASSIAN_API_TOKEN ?? "",
},
});

const agent = createAgent({
manifest: {
id: "triage",
name: "Triage",
instructions:
"Help triage the ENG board. Before moving an issue, list its transitions and say which one you intend to use.",
modelPolicy: { role: "smart" },
},
tools: [
createJiraToolkit({
credentialRef: "atlassian",
resolver,
siteUrl: "https://acme.atlassian.net",
}),
],
});

Credentials and scopes

An account email plus an API token, presented as HTTP Basic. Create the token at id.atlassian.com/manage-profile/security/api-tokens. There are no scopes on an API token: it carries exactly the permissions of the account that created it, which is the single most important thing to know here.

What the agent should doWhat the account needs
Search and read issuesBrowse Projects on the projects in scope
Create issuesCreate Issues
Edit summary, description, labels, priorityEdit Issues
AssignAssign Issues (separate from Edit Issues)
TransitionTransition Issues, and the workflow condition on that specific transition
CommentAdd Comments

Two consequences worth planning around:

  • Use a dedicated Atlassian account, not a person's. An API token from an admin's account gives an agent admin's reach over every project, and revoking it later logs that person out of their own integrations.
  • A transition can be barred by the workflow itself, not by a permission — a condition like "only the assignee may start work". jira_list_transitions returns what is legal right now for this issue and this account, which is why reading it is the only reliable way to know.

A missing permission comes back as unauthorized and is not retryable, naming Atlassian's own message.

Behaviour worth knowing

A transition is not a field, and this toolkit will not guess. Jira's status moves along a workflow, and which moves are legal depends on the project's workflow, the issue's type and its current status. So jira_update_issue cannot change status at all, and jira_transition_issue takes a numeric transition id from jira_list_transitions.

Passing a status name is refused:

"Done" is not a transition id. Transition ids are numeric and specific to this issue's workflow —
call jira_list_transitions to get the ids available right now, then pass one of them. A status name
is not a transition id, and this tool will not guess between them.

That refusal is deliberate and it is the most opinionated thing in this package. Transition names and status names are different vocabularies that overlap confusingly — "Done" is usually a status and sometimes a transition, and they need not correspond. A fuzzy match would pick one silently, and the wrong pick succeeds: the issue lands somewhere nobody asked for and the tool reports success. One extra call cannot be wrong.

jira_list_transitions returns to alongside name because "move it to In Progress" is about the status a transition lands in, which is often not what the transition is called.

A transition is read back, not assumed. A workflow can carry a post-function that changes more than the status, so the tool re-reads the issue and reports where it actually ended up.

A 404 may mean permission, not absence. Jira answers 404 identically for an issue that does not exist and one this account cannot see. The failure says both, because reporting "not found" would send a model looking for a different key when the real problem is access.

A 409 or 412 is a conflict and is not retryable. Somebody edited the issue in between. Retrying the identical call would conflict again; the message says to re-read and re-apply.

Descriptions and comments are ADF, and conversion is lossy on purpose. Jira's format is a JSON document tree. Paragraphs, headings, bullet and ordered lists, code blocks, links, bold, italic, strikethrough and inline code round-trip exactly. Everything else — panels, tables, expands, media, status lozenges — degrades to its text rather than throwing. A tool that refused a document containing a panel would fail on the real issues in any real Jira project, and the information a model needs is in the text.

Mentions keep their display name where ADF carries one, so "somebody was named here" is not silently lost.

Issue content is untrusted. It arrives fenced. An issue description instructing the model to close something is data, and the close would still stop for approval.

Limits

Not offeredWhy
Deleting issues, comments or projectsIrreversible, and Jira's own UI hides it behind an admin role. Closing with not_planned is the reversible way to say "no"
Creating or editing workflows, screens, field configurationsInstance-wide changes that affect every project and every other integration
AttachmentsMultipart upload to a second host — the same deferral as Slack's upload_file
Worklogs and time trackingA timesheet is a claim about what a person did, which an agent should not be filing
Sprints and boards (Agile API)A separate API with its own permission model. Worth its own task rather than a partial version here
Custom fields by idcustomfield_10042 is exactly the opaque identifier this project refuses to put in a schema. Needs a name-resolving design first, like github_set_project_field
Bulk operationsOne call that changes fifty issues is one approval for fifty acts
Atlassian 3LO OAuthThe credentialRef seam makes this a resolver change rather than a toolkit change, so it waits for a registered app