feat(agent): RFC §9 agentic_auth_login MCP tool + dedupe tool registrations
Three load-bearing gaps between the agent RFC and the MCP surface: - RFC §9 Fleet Mode describes the 6-digit pairing-code bootstrap as the primary way an unauthenticated node provisions its first AgentApiKey. `handleAuthLogin` existed as an Action but never surfaced as an MCP tool, so IDE/CLI callers had to shell out. Adds `agentic_auth_login` under `registerPlatformTools` with a thin wrapper over the existing handler so the platform contract stays single-sourced. - `RegisterTools` was double-registering `agentic_scan` (bare `mcp.AddTool` before the CORE_MCP_FULL gate, then again via `AddToolRecorded` inside the gate). The second call silently replaced the first and bypassed tool-registry accounting, so REST bridging and metrics saw a zero for scan. Collapses both into a single recorded registration before the gate. - `registerPlanTools` and `registerWatchTool` were also fired twice in the CORE_MCP_FULL branch. Removes the duplicates so the extended registration list mirrors the always-on list exactly once. - Switches `agentic_prep_workspace` from bare `mcp.AddTool` to `AddToolRecorded` so prep-workspace participates in the same accounting as every other RFC §6 tool. TestPrep_RegisterTools_Good_RegistersCompletionTool now asserts all three `agentic_auth_*` tools surface, covering the new login registration alongside provision/revoke. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
b338e12fbf
commit
e837a284af
3 changed files with 37 additions and 10 deletions
|
|
@ -120,6 +120,11 @@ func (s *PrepSubsystem) registerPlatformTools(svc *coremcp.Service) {
|
|||
Description: "Revoke a platform API key by key ID.",
|
||||
}, s.authRevokeTool)
|
||||
|
||||
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
||||
Name: "agentic_auth_login",
|
||||
Description: "Exchange a 6-digit pairing code (generated at app.lthn.ai/device) for an AgentApiKey. Bootstraps a fleet node without requiring an existing API key — RFC §9 Fleet Mode.",
|
||||
}, s.authLoginTool)
|
||||
|
||||
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
||||
Name: "agentic_fleet_register",
|
||||
Description: "Register a fleet node with models, capabilities, and platform metadata.",
|
||||
|
|
@ -256,6 +261,30 @@ func (s *PrepSubsystem) authRevokeTool(ctx context.Context, _ *mcp.CallToolReque
|
|||
return nil, output, nil
|
||||
}
|
||||
|
||||
// authLoginTool handles the MCP-side of the RFC §9 pairing-code bootstrap.
|
||||
// Callers pass a 6-digit pairing code generated at app.lthn.ai/device and
|
||||
// receive the provisioned AgentApiKey so the node can authenticate future
|
||||
// platform calls. The code itself is the proof — no existing API key is
|
||||
// required.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// out, _ := clientSession.CallTool(ctx, &mcp.CallToolParams{
|
||||
// Name: "agentic_auth_login",
|
||||
// Arguments: json.RawMessage(`{"code": "123456"}`),
|
||||
// })
|
||||
func (s *PrepSubsystem) authLoginTool(ctx context.Context, _ *mcp.CallToolRequest, input AuthLoginInput) (*mcp.CallToolResult, AuthLoginOutput, error) {
|
||||
result := s.handleAuthLogin(ctx, platformOptions(core.Option{Key: "code", Value: input.Code}))
|
||||
if !result.OK {
|
||||
return nil, AuthLoginOutput{}, resultErrorValue("agentic.auth.login", result)
|
||||
}
|
||||
output, ok := result.Value.(AuthLoginOutput)
|
||||
if !ok {
|
||||
return nil, AuthLoginOutput{}, core.E("agentic.auth.login", "invalid auth login output", nil)
|
||||
}
|
||||
return nil, output, nil
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) fleetRegisterTool(ctx context.Context, _ *mcp.CallToolRequest, input FleetNode) (*mcp.CallToolResult, FleetNode, error) {
|
||||
options := platformOptions(
|
||||
core.Option{Key: "agent_id", Value: input.AgentID},
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ func (s *PrepSubsystem) SetCore(c *core.Core) {
|
|||
// subsystem := agentic.NewPrep()
|
||||
// subsystem.RegisterTools(svc)
|
||||
func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) {
|
||||
mcp.AddTool(svc.Server(), &mcp.Tool{
|
||||
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
||||
Name: "agentic_prep_workspace",
|
||||
Description: "Prepare an agent workspace: clone repo, create branch, build prompt with context.",
|
||||
}, s.prepWorkspace)
|
||||
|
|
@ -617,7 +617,7 @@ func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) {
|
|||
s.registerWatchTool(svc)
|
||||
s.registerIssueTools(svc)
|
||||
s.registerPRTools(svc)
|
||||
mcp.AddTool(svc.Server(), &mcp.Tool{
|
||||
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
||||
Name: "agentic_scan",
|
||||
Description: "Scan Forge repos for open issues with actionable labels (agentic, help-wanted, bug).",
|
||||
}, s.scan)
|
||||
|
|
@ -642,14 +642,6 @@ func (s *PrepSubsystem) RegisterTools(svc *coremcp.Service) {
|
|||
s.registerContentTools(svc)
|
||||
s.registerLanguageTools(svc)
|
||||
s.registerSetupTool(svc)
|
||||
|
||||
coremcp.AddToolRecorded(svc, svc.Server(), "agentic", &mcp.Tool{
|
||||
Name: "agentic_scan",
|
||||
Description: "Scan Forge repos for open issues with actionable labels (agentic, help-wanted, bug).",
|
||||
}, s.scan)
|
||||
|
||||
s.registerPlanTools(svc)
|
||||
s.registerWatchTool(svc)
|
||||
}
|
||||
|
||||
// subsystem := agentic.NewPrep()
|
||||
|
|
|
|||
|
|
@ -739,6 +739,12 @@ func TestPrep_RegisterTools_Good_RegistersCompletionTool(t *testing.T) {
|
|||
assert.Contains(t, toolNames, "agent_inbox")
|
||||
assert.Contains(t, toolNames, "agentic_message_conversation")
|
||||
assert.Contains(t, toolNames, "agent_conversation")
|
||||
// RFC §9 pairing-code bootstrap exposes the login flow as an MCP tool so
|
||||
// IDE/CLI callers can exchange a 6-digit code for an AgentApiKey without
|
||||
// shelling out.
|
||||
assert.Contains(t, toolNames, "agentic_auth_login")
|
||||
assert.Contains(t, toolNames, "agentic_auth_provision")
|
||||
assert.Contains(t, toolNames, "agentic_auth_revoke")
|
||||
}
|
||||
|
||||
func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue