mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:51:30 +00:00
feat(mcp): add claim_discovered + dismiss_discovered MCP tools (I-2 closure)
Closes the LAST P1 in the 2026-04-24 audit (cat-i-b0924b6675f8). Pre-I-2
the README claimed "all API endpoints are exposed via MCP" but the
discovered-certificate lifecycle (HTTP handlers ClaimDiscovered +
DismissDiscovered at internal/api/handler/discovery.go:125,162) had
zero MCP tool wrappers — operators using Claude / Cursor / similar
MCP clients had no path to bring an out-of-band cert under management
or to mark a benign discovery as not-of-interest without dropping to
the REST API directly. The audit's count of 0 MCP discovery tools
was correct: `grep -niE 'discover|claim|dismiss' internal/mcp/tools.go`
returned only the pre-existing agent-retire tool's description text
mentioning sentinel discovery agents — no actual discovery-tool
registrations.
Added in internal/mcp/types.go:
- ClaimDiscoveredCertificateInput (id + managed_certificate_id)
- DismissDiscoveredCertificateInput (id)
Both follow the existing Go-doc / staticcheck convention (lead with
the type name + brief; closure-rationale prose follows). Pinned by
the existing L-1 staticcheck-fix lesson.
Added in internal/mcp/tools.go (slotted at end of file, after
certctl_auth_check):
- certctl_claim_discovered_certificate — POST /api/v1/discovered-certificates/{id}/claim
- certctl_dismiss_discovered_certificate — POST /api/v1/discovered-certificates/{id}/dismiss
Both wrap the existing HTTP handlers via the generic c.Post helper.
No backend changes; no openapi.yaml changes (both ops were already
in the spec from earlier work).
The audit's third name "acknowledge" is NOT closed: at recon, no
notification-acknowledge HTTP handler exists in the API surface
(grep across internal/api/handler/ returned zero hits for
"acknowledge"). The audit appears to have mis-quoted; "acknowledge"
isn't a real backend endpoint to wrap. If a future feature adds
notification acknowledgement, register it in the same shape.
Verification:
- go build ./... — clean
- go vet ./internal/mcp/... — clean
- go test ./internal/mcp/... -count=1 — pass
- golangci-lint v2.11.4 run ./... — 0 issues
- MCP tool count went from 85 → 87 (verify via `grep -cE 'gomcp\.AddTool\(' internal/mcp/tools.go`)
- S-1 + G-3 + D-1 + D-2 + B-1 + L-1 CI guardrails all still pass
Audit findings closed:
- cat-i-b0924b6675f8 (P1, MCP discovery completeness — last P1 in audit)
This brings the audit to ZERO REMAINING P1s.
Deferred follow-ups:
- Notification acknowledge MCP tool — add when a notification-ack
HTTP handler exists. Currently no such handler exists in the
API surface; treat as a separate feature, not an MCP gap.
This commit is contained in:
@@ -1238,4 +1238,36 @@ func registerHealthTools(s *gomcp.Server, c *Client) {
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
// I-2 closure (cat-i-b0924b6675f8): pre-I-2 the README claimed "all
|
||||
// API endpoints are exposed via MCP" but the discovered-certificate
|
||||
// lifecycle (claim + dismiss) was never wrapped — operators using
|
||||
// MCP clients (Claude, Cursor, etc.) had no path to bring an
|
||||
// out-of-band cert under management or to mark a benign discovery
|
||||
// as not-of-interest without dropping to the REST API directly.
|
||||
// These two tools wrap the existing HTTP handlers
|
||||
// (DiscoveryHandler.ClaimDiscovered + DismissDiscovered).
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_claim_discovered_certificate",
|
||||
Description: "Link a discovered certificate (dc-*) to an existing managed certificate (mc-*) via POST /api/v1/discovered-certificates/{id}/claim. Use this to bring an out-of-band cert (e.g. one found by an agent filesystem scan or a network scan) under certctl management without re-issuing — the discovered row is marked Managed and its managed_certificate_id is set so subsequent renewals/revocations on the managed cert update both rows.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ClaimDiscoveredCertificateInput) (*gomcp.CallToolResult, any, error) {
|
||||
body := map[string]string{"managed_certificate_id": input.ManagedCertificateID}
|
||||
data, err := c.Post("/api/v1/discovered-certificates/"+input.ID+"/claim", body)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_dismiss_discovered_certificate",
|
||||
Description: "Dismiss a discovered certificate (POST /api/v1/discovered-certificates/{id}/dismiss). Use this to mark a discovery as not-of-interest (e.g. expired self-signed test certs found by a network scan) — the row stops appearing in the unmanaged-list view but is preserved in the DB for audit history.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input DismissDiscoveredCertificateInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/discovered-certificates/"+input.ID+"/dismiss", nil)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -323,6 +323,29 @@ type TimelineInput struct {
|
||||
Days int `json:"days,omitempty" jsonschema:"Number of days to look back (default 30, max 365)"`
|
||||
}
|
||||
|
||||
// ── Discovered Certificates (I-2 closure) ──────────────────────────
|
||||
|
||||
// ClaimDiscoveredCertificateInput is the MCP tool input for claiming a
|
||||
// discovered certificate (POST /api/v1/discovered-certificates/{id}/claim).
|
||||
// I-2 closure (cat-i-b0924b6675f8). The HTTP handler at
|
||||
// internal/api/handler/discovery.go::ClaimDiscovered links the discovered
|
||||
// row (DC-*) to a managed certificate (mc-*); operators use this to
|
||||
// bring an out-of-band cert under management without re-issuing.
|
||||
type ClaimDiscoveredCertificateInput struct {
|
||||
ID string `json:"id" jsonschema:"Discovered certificate ID (dc-*)"`
|
||||
ManagedCertificateID string `json:"managed_certificate_id" jsonschema:"Existing managed certificate ID (mc-*) to link to"`
|
||||
}
|
||||
|
||||
// DismissDiscoveredCertificateInput is the MCP tool input for dismissing
|
||||
// a discovered certificate (POST /api/v1/discovered-certificates/{id}/dismiss).
|
||||
// I-2 closure (cat-i-b0924b6675f8). Marks the row as not-of-interest
|
||||
// (e.g. expired self-signed test certs found by a network scan); the row
|
||||
// stops appearing in the unmanaged-list view but is preserved in the DB
|
||||
// for audit history.
|
||||
type DismissDiscoveredCertificateInput struct {
|
||||
ID string `json:"id" jsonschema:"Discovered certificate ID (dc-*)"`
|
||||
}
|
||||
|
||||
// ── Empty ───────────────────────────────────────────────────────────
|
||||
|
||||
type EmptyInput struct{}
|
||||
|
||||
Reference in New Issue
Block a user