forgejo-mcp/demos/issue-stopwatch.md
Christoph Görn d1a9437a97 feat: ✨ add issue/PR time tracking and stopwatch tools
Implements forgejo-mcp-ya6 per docs/plans/issue-time-tracking.md.

Ten new MCP tools wrapping forgejo-sdk/v3 tracked-time and stopwatch APIs:

Tracked time:
- list_issue_tracked_times, list_repo_tracked_times, list_my_tracked_times
- add_issue_time (accepts either 'seconds' int or 'duration' string like "15m")
- reset_issue_time, delete_issue_time_entry

Stopwatches:
- start_issue_stopwatch, stop_issue_stopwatch, cancel_issue_stopwatch
- list_my_stopwatches

Forgejo shares index namespace between issues and PRs, so one tool set
covers both — tool descriptions state this explicitly.

- New operation/tracking/ package with unit tests using httptest mock
- pkg/to/Float64Ok helper to distinguish 'absent' from 'malformed' in
  the add_issue_time seconds/duration validation
- README tool table updated
- Two Showboat demos: demos/issue-time-tracking.md, demos/issue-stopwatch.md
2026-04-21 22:49:07 +02:00

5.2 KiB

Demo: issue stopwatch tools

2026-04-21T22:45:30Z by Showboat 0.6.1

What these tools do

Four MCP tools let agents drive Forgejo's per-issue live stopwatch — useful when the agent wants to time in-progress work without computing the elapsed seconds itself:

  • start_issue_stopwatch — Begin timing against an issue or PR. Fails if one is already running on the same issue (Forgejo allows at most one per issue).
  • stop_issue_stopwatch — Stop the timer and write the elapsed time as a tracked-time entry. This is the primary path.
  • cancel_issue_stopwatch — Cancel the timer and discard the elapsed time. No tracked-time entry is created.
  • list_my_stopwatches — List every stopwatch currently running for the authenticated user, across all repositories.

Forgejo unifies issue and PR index namespace, so these work on both without parameter changes.

Setup

make build

CLI mode: parameter schemas

./forgejo-mcp --cli start_issue_stopwatch --help 2>/dev/null
Tool: start_issue_stopwatch
Description: Start a stopwatch on an issue or pull request. Only one stopwatch per issue; fails if one is already running.

Parameters:
  index                number     required   Issue or pull request index (Forgejo shares index namespace between the two)
  owner                string     required   Repository owner
  repo                 string     required   Repository name
./forgejo-mcp --cli list_my_stopwatches --help 2>/dev/null
Tool: list_my_stopwatches
Description: List all currently running stopwatches for the authenticated user

End-to-end workflow

Step 1: Create a scratch issue

./forgejo-mcp --cli create_issue \
  --args '{"owner":"goern","repo":"forgejo-mcp","title":"demo: stopwatch","body":"Scratch issue for a Showboat demo."}' 2>/dev/null
  Issue #201 created: demo: stopwatch

Step 2: Start a stopwatch

./forgejo-mcp --cli start_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  Stopwatch started

Step 3: Confirm it is running

./forgejo-mcp --cli list_my_stopwatches --args '{}' 2>/dev/null
  1 running stopwatch:
    repo=goern/forgejo-mcp  issue=#201  "demo: stopwatch"  elapsed≈3s

Step 4: Do some (simulated) work

sleep 3

Step 5: Stop the stopwatch — elapsed time is recorded as a tracked-time entry

./forgejo-mcp --cli stop_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  Stopwatch stopped; elapsed time recorded as a tracked time entry
./forgejo-mcp --cli list_issue_tracked_times \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  1 tracked time entry on issue #201:
    id=55  time=3s  user=goern  created=2026-04-21T22:45:38Z

Step 6: Second run — cancel discards instead of recording

./forgejo-mcp --cli start_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  Stopwatch started
sleep 2

./forgejo-mcp --cli cancel_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  Stopwatch cancelled
./forgejo-mcp --cli list_issue_tracked_times \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  1 tracked time entry on issue #201:
    id=55  time=3s  user=goern  created=2026-04-21T22:45:38Z

No new entry was created — the 2-second cancelled run was discarded as intended.

Step 7: Running-stopwatch list is empty again

./forgejo-mcp --cli list_my_stopwatches --args '{}' 2>/dev/null
  No running stopwatches.

Step 8: Try to start two on the same issue — expected error

./forgejo-mcp --cli start_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null

./forgejo-mcp --cli start_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null
  Stopwatch started
  Error: start issue stopwatch err: 400 Bad Request — stopwatch already running on this issue

Forgejo enforces one stopwatch per issue. The tool description flags this so the agent can recover (e.g. by stopping or cancelling first).

Step 9: Clean up

./forgejo-mcp --cli cancel_issue_stopwatch \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201}' 2>/dev/null

./forgejo-mcp --cli issue_state_change \
  --args '{"owner":"goern","repo":"forgejo-mcp","index":201,"state":"closed"}' 2>/dev/null

Notes

  • Stop vs cancel: stop writes a TrackedTime entry; cancel does not. Use cancel for false starts or when the elapsed time is noise (e.g. a test run).
  • Stop returns only a status: the SDK returns *Response on stop, not the created TrackedTime. If the agent needs the new entry's ID, call list_issue_tracked_times immediately after.
  • Ledger companion: see demos/issue-time-tracking.md for manual CRUD on tracked-time entries.