forgejo-mcp/pkg/to/number.go
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

57 lines
1.4 KiB
Go

package to
import (
"fmt"
"strconv"
)
// Float64 extracts a float64 from an interface{} value.
// Handles both JSON number (float64) and string-encoded numbers,
// which can occur when MCP clients serialize number parameters as strings.
func Float64(v interface{}) (float64, error) {
switch val := v.(type) {
case float64:
return val, nil
case int:
return float64(val), nil
case int64:
return float64(val), nil
case string:
f, err := strconv.ParseFloat(val, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse %q as number: %w", val, err)
}
return f, nil
case nil:
return 0, fmt.Errorf("parameter is nil")
default:
return 0, fmt.Errorf("unexpected type %T for number parameter", v)
}
}
// Float64Ok is like Float64 but returns (0, false) for nil or non-numeric
// values instead of an error. Use it when callers need to distinguish
// "parameter absent" from "parameter malformed" — e.g. a tool that accepts
// exactly one of two alternative numeric inputs.
// An empty string is treated as absent.
func Float64Ok(v interface{}) (float64, bool) {
switch val := v.(type) {
case float64:
return val, true
case int:
return float64(val), true
case int64:
return float64(val), true
case string:
if val == "" {
return 0, false
}
f, err := strconv.ParseFloat(val, 64)
if err != nil {
return 0, false
}
return f, true
default:
return 0, false
}
}