Project Management
Jira and Linear toolsets.
#Project management
Issue trackers and team-planning tools. Every connector follows the standard @toolset / @toolify shape and the agent-ready toolset pattern (compact summaries by default, opt-in raw IDs, tolerant write inputs).
#JiraToolSet
JiraToolSet wraps the Atlassian Jira Cloud REST API v3 (with a small slice of the Jira Agile API for boards and sprints). It authenticates with HTTP Basic auth using your Atlassian email and an API token issued at https://id.atlassian.com/manage-profile/security/api-tokens.
from maivn import Agent
from maivn_tools import JiraToolSet, register_connector
connector = JiraToolSet(
base_url="https://acme.atlassian.net",
email="ops@acme.com",
api_token=secrets["JIRA_API_TOKEN"],
)
agent = Agent(name="jira-agent", model="auto")
register_connector(agent, connector)#Tools
Identity. myself.
Issues. search_issues (JQL), get_issue, list_transitions, create_issue, update_issue, transition_issue, assign_issue, delete_issue (destructive).
Comments and worklog. list_comments, add_comment, update_comment, delete_comment (destructive), list_worklogs, add_worklog.
Links and attachments. link_issues, delete_issue_link (destructive), list_attachments, delete_attachment (destructive).
Projects, users, metadata. list_projects, get_project, list_issue_types, get_user, search_users, list_statuses, list_priorities, list_resolutions, list_fields.
Agile (boards, sprints, versions). list_boards, list_sprints, get_sprint, list_versions.
extra_fields on create_issue is merged into the request payload so the caller can set custom fields (e.g. customfield_10010).
#Agent-ready behavior
search_issuesreturns compact summaries by default. Each summary carries a stableissue_ref(issue_1,issue_2, ...) plus the issuekey(e.g.ENG-123— the canonical user-facing handle),summary,status,assignee,priority,issue_type, andupdated. The issue key is safe to surface in final answers.list_projectsreturns the same compact shape with a stableproject_ref(project_1, ...) plus the projectkey,name,type, andlead.- Default
max_resultsis 25 onsearch_issues(max 100) and 25 onlist_projects. - Jira numeric
idvalues are hidden by default on both tools. Passinclude_ids=Trueto addissue_idorproject_idto each summary — the issue key is normally enough, raw IDs are internal handles. - Pass
include_metadata=Falseonsearch_issuesorlist_projectsto receive the raw Jira response, includingstartAt,total, andisLastfor pagination. get_issue,update_issue,add_comment,transition_issue,assign_issue,delete_issue,list_comments,list_transitions,list_worklogs,add_worklog, andlist_attachmentsaccept either a key string ("ENG-123"), a summary dict fromsearch_issues, or a list of such dicts. The connector resolves the key fromkey,issue_key, orissueKey.
#Destructive tools
delete_issue, delete_comment, delete_issue_link, and delete_attachment carry destructive=True. Issue and link deletes are permanent — confirm with the user, or filter them out with exclude_tags=["destructive"].
#Example: summary-mode search
agent.add_toolset(JiraToolSet(base_url="https://acme.atlassian.net",
email="ops@acme.com",
api_token=secrets["JIRA_API_TOKEN"]))
response = connector.search_issues(
"project = ENG AND status = 'In Progress' ORDER BY updated DESC",
max_results=10,
)
# response == {
# "issues": [
# {"issue_ref": "issue_1", "key": "ENG-512",
# "summary": "Fix login flakiness", "status": "In Progress",
# "assignee": "Casey K.", "priority": "High",
# "issue_type": "Bug", "updated": "..."},
# ...
# ],
# "startAt": 0, "maxResults": 10, "total": 3,
# }The key is enough to drive follow-up actions — no include_ids round trip required:
hits = connector.search_issues("assignee = currentUser()")["issues"]
connector.transition_issue(hits[0], transition_id="31")#Live tests
The bundled tests use mock transports. See testing.md for the distinction between offline checks and live provider validation.
#LinearToolSet
LinearToolSet wraps the Linear GraphQL API.
from maivn_tools import LinearToolSet
connector = LinearToolSet(api_key=secrets["LINEAR_API_KEY"])Pass use_bearer=True if the credential is an OAuth bearer rather than a personal API key.
#Tools
viewer, list_teams, list_users, list_projects, list_cycles, list_issues (filter follows Linear's IssueFilter shape), get_issue (UUID or shortcut like ENG-123), create_issue, update_issue, archive_issue (destructive), list_comments, create_comment, graphql (escape hatch for arbitrary queries / mutations).
#Agent-ready behavior
list_issuesreturns compact summaries by default. Each summary carries a stableissue_ref(issue_1,issue_2, ...) plus the Linearidentifier(e.g.ENG-123— the canonical user-facing handle),title,status,priority,assignee,teamkey,updated_at, andurl. The identifier is safe to show in final answers.- Default
firstis 25 onlist_issues,list_teams,list_users,list_projects, andlist_cycles(max 250). - The GraphQL
id(UUID) is hidden by default onlist_issues. Passinclude_ids=Trueto addissue_idto each summary — only needed for tools that explicitly require the UUID; most operations accept the identifier directly. - Pass
include_metadata=Falseonlist_issuesto receive the raw Linear{"issues": {"nodes": [...], "pageInfo": ...}}response. The other list tools always return the raw GraphQL payload. get_issue,update_issue,archive_issue,list_comments, andcreate_commentaccept either a Linear identifier string ("ENG-123"), a UUID string, a summary dict fromlist_issues, or a list of such dicts. The connector resolves the handle fromidentifier,issue_id, orid.
#Destructive tools
archive_issue carries destructive=True. Archive is a soft-delete (recoverable through the Linear UI), but should still be confirmed with the user, or filtered out with exclude_tags=["destructive"]. graphql is marked READ but forwards arbitrary queries and mutations. Exclude this method from read-only agents; use an explicit method allowlist and provider credentials restricted to reads. For fixed mutations, register a wrapper with the appropriate write permission.
#Example: summary-mode list
agent.add_toolset(LinearToolSet(api_key=secrets["LINEAR_API_KEY"]))
response = connector.list_issues(
filter={"team": {"key": {"eq": "ENG"}}, "state": {"name": {"eq": "Todo"}}},
first=10,
)
# response == {
# "issues": [
# {"issue_ref": "issue_1", "identifier": "ENG-512",
# "title": "Fix login flakiness", "status": "Todo",
# "priority": 2, "assignee": "Casey K.", "team": "ENG",
# "updated_at": "...", "url": "https://linear.app/..."},
# ...
# ],
# "pageInfo": {"hasNextPage": False, "endCursor": "..."},
# }The identifier is enough to drive follow-up actions:
hits = connector.list_issues(
filter={"assignee": {"isMe": {"eq": True}}}
)["issues"]
connector.create_comment(issue_id=hits[0], body="Picking this up.")