#006: Making JIRA time tracking suck less with AI
Tracking time on JIRA is a terrible experience. The UI is unintuitive, slow, and requires a lot of clicks. This is especially worse for an “on-premise” installation which sits behind a VPN, as it does at my place of work.
My role involves a fair bit of context switching, which means my time tracking consists of a lot of interleaved worklogs, making JIRA’s interface even more painful to deal with. To make this process nicer, I created a command line tool a while back: punchout. It’s entirely keyboard driven, and lets you track time on issues via a TUI. It’s proven useful — all of my time tracking in the last year has been done with it.
However, punchout’s TUI is optimised for live time tracking, ie, creating worklogs as you actually work. Over time I’ve realized that I rarely track time as I work on tasks. I do it either at the end of the day, or the end of the week. This is where the TUI falls short, as I need to create entries in the past, which, as seen in the video below, involves a lot of tedious datetime selections.
Using punchout is still faster than tracking time via JIRA’s UI for me, but I was feeling the need for a more ergonomic solution. I figured I could speed up this mundane process by enabling time tracking via plain conversations. For this, I added a Model Context Protocol (MCP) server to punchout, which I can then interact with using Claude Code or any other AI agent.
Here’s me adding worklogs for 3 days by just talking to Claude Code.
The explicit instructions and deliberate checking in the video are for demo
purposes. In practice, I put the instructions in CLAUDE.md
, and don’t ask for
the worklogs to be reviewed, saving me time.
The MCP server
Setting up a basic MCP server was straightforward enough. I provided it 4 tools that mimic the workflows already available via the TUI:
add_worklog
— Create a new work log entry locallyget_jira_issues
— Fetch JIRA issues and their detailsget_unsynced_worklogs
— Show local entries that haven’t been pushed to JIRAsync_worklogs_to_jira
— Push local entries to JIRA
Here’s the transcript of the conversation with the agent from the video linked above:
Me: Let’s record time for this week so far. We’ll begin with Monday, the 25th of this month. Just so you know, I begin my work at 9:00 and have lunch between 12:00 and 1:00. So don’t add any work logs between 12:00 and 1:00. Also, don’t add any comments to any work log. On Monday, I started with the EU-hosted endpoint for the knowledge graph for about two hours, and then spent an hour on non-capitalizable work. After lunch, I spent about an hour on the sidecar issue and then about two hours on the data log issue. Finally I did some non-cap work at the end of the day, about 1.5 hours. Show me these entries before you save them.
Agent: Called `get_jira_issues`, then presented entries for approval
Me: looks good
Agent: Made 5 `add_worklog` calls
Me: Tuesday was a bit weird. I juggled between all of these a lot more. There were no meetings. I spend roughly an equal amount of time on each, switching between them every 30-40 minutes.
Agent: Calculated 13 time blocks and presented the entries for approval
Me: looks good
Agent: Made 13 `add_worklog` calls
Me: All right, Wednesday was simpler. First half was all non-cap work. Second half was about 3.5 hours on the sidecar issue. Finally, I did some non-cap work about 1.5 hours.
Agent: Proposed 3 entries for Wednesday
Me: yup
Agent: Made 3 `add_worklog` calls
Me: Show me all unsynced entries.
Agent: Called `get_unsynced_worklogs` and displayed all 21 local entries
Me: They look good, sync them to Jira.
Agent: Called `sync_worklogs_to_jira` which pushed all entries to JIRA
MCP seems to be quite flexible, though the landscape surrounding it seems to
currently be in flux. The Go SDK I used had a few breaking changes between
0.2.0
and 0.3.0
(not unexpected, as it’s pre-1.0). Another quirk I came
across is how the SDK handles “Structured Content” in tool call responses. As
per the SPEC:
For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.
At the time of writing, the SDK does not seem to handle this duplication by itself, which breaks usage in Claude Code (VS Code’s agent seems to handle that fine). To fix that, the same content needs to be serialized manually in application code. I’m sure these kinks will be resolved as the SPEC and the SDKs mature.
There seems to be quite a lot of other functionality supported by MCP, and I’m interested in seeing how it can lead to more useful AI agents. I’m also curious to see how the SPEC is implemented in the Rust and TypeScript SDKs.
Summary
Overall, this experiment has me optimistic about the future of human-computer interaction. The broader implication of a transition like this is interesting: we’re moving toward a world where many administrative tasks can be handled through natural language rather than complex interfaces.
If that means I never have to navigate JIRA’s time tracking UI again, I’m all for it.