Repository navigation
feat(nodes): add tool_jira — JQL search, issue read, create, comment and transition as agent tools #2799
Description
Problem Statement
Pipelines and agents have no way to read or write Jira: a triage agent that lists the open P1s for a component, a standup pipeline that summarizes what changed since yesterday, a support agent that files a bug from a conversation, or a release-notes pipeline that reads the issues in a fix version. Jira Cloud exposes all of this over a documented REST API, and there is no Atlassian code anywhere in the repo. tool_http_request can reach the API today, but every pipeline would have to hand-roll basic auth, Atlassian Document Format bodies and the new search pagination. Jira is on RocketRide's GTM list.
Proposed Solution
Add a tool_jira agent-tool node (classType tool, register filter, lanes: {}), mirroring nodes/src/nodes/tool_notion/ (REST client module with no engine imports, API-token auth, the {success, error} result envelope, offline unit tests with faked HTTP).
API basis (Jira Cloud REST API v3, https://developer.atlassian.com/cloud/jira/platform/rest/v3/):
- Base URL:
https://<site>.atlassian.net/rest/api/3 - Auth: HTTP basic with
email:api_token(an unscoped token from https://id.atlassian.com/manage-profile/security/api-tokens). Scoped tokens must go through thehttps://api.atlassian.com/ex/jira/{cloudId}gateway; out of scope for v1. - Search:
POST /search/jql(aGETform takes the same parameters) withjql,fields,maxResults(default 50) andnextPageToken. The old/searchendpoint is marked deprecated and being removed (CHANGE-2046); Atlassian's KB says it has already been removed from Jira Cloud. The new endpoint returns only issue ids unlessfieldsis passed, returns nototal, pages withnextPageToken(tokens expire after 7 days) and setsisLaston the final page. - Issues:
GET /issue/{key}?fields=...,POST /issue,PUT /issue/{key} - Comments:
POST /issue/{key}/commentwith an ADFbody - Transitions:
GET /issue/{key}/transitions,POST /issue/{key}/transitionswith{"transition": {"id": "..."}} - Projects:
GET /project/search; connection check:GET /myself - Text bodies (issue description, comments) are Atlassian Document Format (ADF) in v3. The minimal document is
{"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "..."}]}]}. The client needs a small text-to-ADF converter (paragraphs, bullet lists, code blocks) and an ADF-to-text flattener, the waynotion_client.pyflattens block trees. - Rate limits: API-token traffic is governed by Atlassian's burst limits (100 requests per second for GET and POST, 50 for PUT and DELETE, and 20 writes per 2 seconds on one issue); the points-based hourly quota announced for March 2, 2026 applies to Forge, Connect and OAuth apps. A 429 carries
Retry-After, and Atlassian says to retry only idempotent calls that carry it.
Suggested tool operations (exposed to agents):
Must-have:
jira_check_connection—GET /myself; returns{ok, account_id, display_name, site}jira_search—jql(required),max_results(1-100, default from config),next_page_token; returnsissues[{key, summary, status, type, assignee, priority, updated, url}]andnext_page_token. The client always sendsfieldsfor this shape, since the endpoint otherwise returns ids only.jira_issue_get—key,include_comments(default false),max_comments(default 20); returns{key, summary, status, type, assignee, reporter, priority, labels, fix_versions, created, updated, description_text, comments[{author, created, body_text}], url}jira_projects_list— returnsprojects[{key, name, type}], filtered by the allowlistjira_issue_create—project,issue_type,summary(required);description,labels,priority,assignee_account_id; returns{key, url}(write)jira_comment_add—key,body(plain text or a markdown subset); returns{comment_id, url}(write)
Stretch:
jira_transitions_list—key; returnstransitions[{id, name, to_status}]jira_issue_transition—key,transition(name or id; a name is resolved through the list); returns{key, status}(write)jira_issue_update—key; any ofsummary,labels,priority,assignee_account_id; returns{key, updated_fields}(write)
Config schema (in services.json, field keys tool_jira.*):
siteUrl(string, required):https://<site>.atlassian.net, validated as httpsemail(string, required)apiToken(secret,ApiKeyWidget, required)readOnly(boolean, defaulttrue): blocks every write tool, the patterntool_n8nandtool_gituseprojects(string, default""): comma-separated project keys the node may touch; empty means all. Use a string, not an array: array-typed params reach Python nodes as JSON text inside a one-element list.maxResults(integer, default 25, clamped to 1-100)createdLabel(string, default""): a label added to every issue the node creates, for auditability
Env fallback (config wins): ROCKETRIDE_JIRA_SITE_URL, ROCKETRIDE_JIRA_EMAIL, ROCKETRIDE_JIRA_API_TOKEN. Use the ROCKETRIDE_ prefix; the test framework maps only those.
Guardrails:
readOnlydefaults totrue; write tools refuse with a message that names the config field.- Project allowlist on search: split off a trailing
ORDER BYclause, wrap the rest asproject in (<allowlist>) AND (<jql>), then re-append theORDER BY. No existing tool node has a project allowlist, so this is a new pattern; keep it in the client module with its own tests. - Project allowlist on key-based calls: fetch the issue and check
fields.project.keybefore acting. Do not trust the key prefix: an issue moved between projects still resolves under its old key. max_resultsis capped at 100 in the node regardless of the API maximum; description and comment bodies are capped at 32 KB.- No delete, bulk, user-admin, permission or attachment tools.
Error handling: the client raises JiraAPIError(status, messages) and the _run() envelope turns it into {success: false, error}. A 400 on bad JQL returns Jira's errorMessages verbatim so the agent can fix its query. 401 and 403 name the token and project permissions. 404 says the issue was not found or is not visible to this account. 429 honours Retry-After; reads retry up to twice, writes never retry. Reads retry once on 5xx.
Implementation notes:
- Files:
__init__.py,IGlobal.py,IInstance.py,jira_client.py(auth, request, retry, ADF helpers, JQL wrapping),services.json,jira.svg,requirements.txt(requests>=2.34.2; no Jira SDK, the communityjiralibrary adds nothing a 300-line client does not),README.mdwith the generated-params marker pair (params regenerate fromservices.jsonvianodes:docs-generate, which runs ondevelopafter merge). - Also touch: the credentials stub in
packages/ai/src/ai/modules/mcp/credentials.json(./builder nodes:credentials-generate; CI'sshell-contractjob checks it), an_LLM_MOCK_CREDENTIALSentry innodes/test/framework/pipeline.pyfor theservices.jsonsmoke test, and aCHANGELOG.mdline. - The Confluence tool node (separate issue) reuses the basic-auth, retry and ADF helpers. Decide at design review whether they move to a shared module under
packages/ai/src/ai/common/or stay duplicated (about 100 lines); do not import one node from the other. - Unit tests: follow
nodes/test/tool_notion/test_notion.py(engine stubs, fakedrequests): paging,fieldsalways sent, ADF round-trips, JQL wrapping with and withoutORDER BY, project checks on moved issues, read-only refusals, error mapping, 429 handling. Target 60+ tests. - Live test:
nodes/test/tool_jira/test_jira_live.py, skipped unlessROCKETRIDE_JIRA_*are set, against a free Atlassian site seeded with one project. Run it at least once before review and record the run in the PR. - Test ladder:
uvx ruff@0.16.5 checkandformat --check,./builder nodes:test --pytest-pattern=jira,python3 scripts/validate-node-readme.py nodes/src/nodes/tool_jira(Python 3.10+). - Ship with
capabilities: ["invoke", "experimental"]until the live run passes, then dropexperimental.
Acceptance criteria:
- All six must-have tools work against a live Jira Cloud site, recorded in the PR (tool, input, result)
-
jira_searchpages past 100 issues withnext_page_tokenand never returns an id-only issue - With
projectsset, a search for another project returns nothing andjira_issue_geton a key from another project is refused, including a key that was moved - With
readOnly=true(the default) every write tool refuses - A comment created from markdown with a bullet list and a code block renders correctly in Jira
- Bad JQL, bad token, unknown key and 429 each produce a
{success: false, error}result - README passes
validate-node-readme.py; "As a tool" table matches the exposed functions exactly - ruff clean, credentials stub present, CHANGELOG line added,
CI OKgreen
Size estimate: about 1,100 lines of node code (client 350, IInstance 450, services.json 120, README 100) plus 700 lines of tests; 12 to 18 person-days.
Open questions:
- Atlassian's pages do not state whether the Free plan allows REST API tokens; third-party sources say it does. A 15-minute check (free site, token,
GET /myself) settles it before anyone starts. - Atlassian's create-issue examples use numeric
project.idandissuetype.id.project.keyandissuetype.nameare in wide use but not shown officially; if the live test rejects them, resolve ids throughGET /project/{key}, which lists the project'sissueTypes. - Scoped API tokens and OAuth 2.0 (3LO) are deferred; say so in the README.
Alternatives Considered
tool_http_requestper pipeline: works, but every pipeline author re-learns basic auth, ADF andsearch/jqlpaging, and nothing is discoverable in the canvas.- Atlassian's hosted MCP server through
tool_mcp_client: fine for an interactive session, but it needs a browser OAuth login per user, which a scheduled pipeline cannot do, and it offers no read-only mode or project scoping. - The community
jiraPython library (3.10.5, BSD): mature, but it pulls in its own dependency set for about six calls, and its error types would need wrapping anyway.
Affected Modules
- server (C++ engine)
- client-typescript
- client-python
- client-mcp
- nodes (pipeline)
- ai
- vscode
Good community/hackathon candidate (see epic #1522; part of the GTM node integrations epic, #2798). Template node: tool_notion.
Activity
github-actions commentedon Oct 23, 2026
🤖 Internal: Discord sync marker
Auto-managed by the Discord notification workflow. Stores the linked Discord message ID and forum thread ID. Do not edit or delete.