Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ or token is sent.
| Command | Purpose |
|---|---|
| `gv auth login` | Browser-driven PKCE flow, self-registers client, persists tokens |
| `gv auth whoami` | Calls `get_current_user`, prints JSON |
| `gv auth whoami` | Verify stored credentials against the server (auth-check; GitVelocity has no current-user tool) |
| `gv auth refresh` | Force a refresh-token exchange |
| `gv auth logout` | Delete stored credentials |

Expand Down
17 changes: 14 additions & 3 deletions cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,26 @@ func newAuthRefreshCmd() *cobra.Command {
func newAuthWhoamiCmd() *cobra.Command {
return &cobra.Command{
Use: "whoami",
Short: "Identify the currently signed-in user (calls get_current_user)",
Short: "Verify the stored credentials against the server",
// GitVelocity's MCP surface exposes no identity/current-user tool (unlike
// Searchlight's get_current_user), so there is nobody to "who am I" against.
// Instead we prove the stored token is valid by issuing the authenticated
// initialize handshake — a 401 here means logged-out/expired, success means
// the credentials work — and report the server it authenticated against.
RunE: func(_ *cobra.Command, _ []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
res, err := globals.MCP.CallTool(ctx, "get_current_user", map[string]any{})
init, err := globals.MCP.Initialize(ctx)
if err != nil {
return err
}
return writeToolResult(res)
return output.WriteValue(os.Stdout, map[string]any{
"status": "authenticated",
"server": map[string]any{
"name": init.ServerInfo.Name,
"version": init.ServerInfo.Version,
},
}, globals.Pretty)
},
}
}
26 changes: 17 additions & 9 deletions cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,23 +202,31 @@ func TestAuthWhoami_Integration(t *testing.T) {
return whoami.RunE(whoami, []string{})
})

// Did we hit the right tool?
var sawCall bool
// whoami verifies auth via the initialize handshake (GitVelocity has no
// current-user tool), so it must hit initialize and never a tools/call.
var sawInit bool
for _, c := range f.calls() {
if c.method == "tools/call" && c.name == "get_current_user" {
sawCall = true
if c.method == "initialize" {
sawInit = true
}
if c.method == "tools/call" {
t.Errorf("whoami should not call a tool, got %+v", c)
}
}
if !sawCall {
t.Errorf("expected tools/call get_current_user, got %+v", f.calls())
if !sawInit {
t.Errorf("expected initialize, got %+v", f.calls())
}
// Output should be the JSON pass-through.
// Output reports authenticated status + the server it reached.
var got map[string]any
if err := json.Unmarshal([]byte(out), &got); err != nil {
t.Fatalf("output is not JSON: %q", out)
}
if got["email"] != "user@example.com" {
t.Errorf("email = %v, want user@example.com", got["email"])
if got["status"] != "authenticated" {
t.Errorf("status = %v, want authenticated", got["status"])
}
server, _ := got["server"].(map[string]any)
if server == nil || server["version"] != "4.2.0" {
t.Errorf("server block = %v, want version 4.2.0", got["server"])
}
}

Expand Down
Loading