mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 23:42:00 +00:00
a8fc177118
Root cause: certificate_versions.csr_pem is nullable in the schema but Go code scanned it into a plain string. Used sql.NullString in ListVersions and GetLatestVersion to handle NULL values correctly. Also includes: partial update fetch-merge-update pattern to prevent FK violations, nil directory guard in discovery service, diagnostic slog logging in handlers, export handler 422 for unparseable PEM, OpenAPI spec corrections, MCP tool description improvements, and test fixes. Rewrites the Release Sign-Off section in testing-guide.md to individual test-level granularity (320 rows) with smoke test results audited and checked off (121 pass, 5 skip, 194 manual remaining). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1094 lines
41 KiB
Go
1094 lines
41 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
gomcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
|
)
|
|
|
|
// RegisterTools registers all certctl API endpoints as MCP tools on the server.
|
|
func RegisterTools(s *gomcp.Server, client *Client) {
|
|
registerCertificateTools(s, client)
|
|
registerCRLOCSPTools(s, client)
|
|
registerIssuerTools(s, client)
|
|
registerTargetTools(s, client)
|
|
registerAgentTools(s, client)
|
|
registerJobTools(s, client)
|
|
registerPolicyTools(s, client)
|
|
registerProfileTools(s, client)
|
|
registerTeamTools(s, client)
|
|
registerOwnerTools(s, client)
|
|
registerAgentGroupTools(s, client)
|
|
registerAuditTools(s, client)
|
|
registerNotificationTools(s, client)
|
|
registerStatsTools(s, client)
|
|
registerMetricsTools(s, client)
|
|
registerDigestTools(s, client)
|
|
registerHealthTools(s, client)
|
|
}
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
|
|
func textResult(data json.RawMessage) (*gomcp.CallToolResult, any, error) {
|
|
return &gomcp.CallToolResult{
|
|
Content: []gomcp.Content{
|
|
&gomcp.TextContent{Text: string(data)},
|
|
},
|
|
}, nil, nil
|
|
}
|
|
|
|
func errorResult(err error) (*gomcp.CallToolResult, any, error) {
|
|
return nil, nil, fmt.Errorf("%w", err)
|
|
}
|
|
|
|
func paginationQuery(page, perPage int) url.Values {
|
|
q := url.Values{}
|
|
if page > 0 {
|
|
q.Set("page", strconv.Itoa(page))
|
|
}
|
|
if perPage > 0 {
|
|
q.Set("per_page", strconv.Itoa(perPage))
|
|
}
|
|
return q
|
|
}
|
|
|
|
// ── Certificates ────────────────────────────────────────────────────
|
|
|
|
func registerCertificateTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_certificates",
|
|
Description: "List managed certificates with optional filters for status, environment, owner, team, and issuer. Returns paginated results.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListCertificatesInput) (*gomcp.CallToolResult, any, error) {
|
|
q := paginationQuery(input.Page, input.PerPage)
|
|
if input.Status != "" {
|
|
q.Set("status", input.Status)
|
|
}
|
|
if input.Environment != "" {
|
|
q.Set("environment", input.Environment)
|
|
}
|
|
if input.OwnerID != "" {
|
|
q.Set("owner_id", input.OwnerID)
|
|
}
|
|
if input.TeamID != "" {
|
|
q.Set("team_id", input.TeamID)
|
|
}
|
|
if input.IssuerID != "" {
|
|
q.Set("issuer_id", input.IssuerID)
|
|
}
|
|
data, err := c.Get("/api/v1/certificates", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_certificate",
|
|
Description: "Get a specific certificate by ID. Returns full certificate details including status, expiry, owner, and tags.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/certificates/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_certificate",
|
|
Description: "Create a new managed certificate. Requires name, common_name, renewal_policy_id, issuer_id, owner_id, and team_id.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateCertificateInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/certificates", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_certificate",
|
|
Description: "Update an existing certificate's metadata (name, environment, owner, tags, etc.).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateCertificateInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/certificates/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_archive_certificate",
|
|
Description: "Archive (soft-delete) a certificate by ID.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/certificates/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_certificate_versions",
|
|
Description: "List all versions (renewals) of a certificate. Shows serial numbers, validity periods, and fingerprints.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListVersionsInput) (*gomcp.CallToolResult, any, error) {
|
|
q := paginationQuery(input.Page, input.PerPage)
|
|
data, err := c.Get("/api/v1/certificates/"+input.ID+"/versions", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_trigger_renewal",
|
|
Description: "Trigger immediate renewal of a certificate. Creates a renewal job (async, returns 202). Returns 404 if certificate not found, 400 if certificate is archived/expired, 409 if renewal already in progress.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/certificates/"+input.ID+"/renew", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_trigger_deployment",
|
|
Description: "Trigger deployment of a certificate to its targets. Optionally specify a single target.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input TriggerDeploymentInput) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{}
|
|
if input.TargetID != "" {
|
|
body["target_id"] = input.TargetID
|
|
}
|
|
data, err := c.Post("/api/v1/certificates/"+input.ID+"/deploy", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_revoke_certificate",
|
|
Description: "Revoke a certificate with an optional RFC 5280 reason code. Records in audit trail and notifies the issuer.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input RevokeCertificateInput) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{}
|
|
if input.Reason != "" {
|
|
body["reason"] = input.Reason
|
|
}
|
|
data, err := c.Post("/api/v1/certificates/"+input.ID+"/revoke", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── CRL & OCSP ──────────────────────────────────────────────────────
|
|
|
|
func registerCRLOCSPTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_crl",
|
|
Description: "Get the Certificate Revocation List in JSON format. Lists all revoked certificate serial numbers with reasons and timestamps.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/crl", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_der_crl",
|
|
Description: "Get DER-encoded X.509 CRL for a specific issuer. Returns binary CRL data signed by the issuing CA.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetDERCRLInput) (*gomcp.CallToolResult, any, error) {
|
|
raw, contentType, err := c.GetRaw("/api/v1/crl/" + input.IssuerID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return &gomcp.CallToolResult{
|
|
Content: []gomcp.Content{
|
|
&gomcp.TextContent{Text: fmt.Sprintf("DER CRL retrieved (%d bytes, content-type: %s)", len(raw), contentType)},
|
|
},
|
|
}, nil, nil
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_ocsp_check",
|
|
Description: "Check OCSP status for a certificate by issuer ID and hex serial number. Returns good, revoked, or unknown.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input OCSPInput) (*gomcp.CallToolResult, any, error) {
|
|
raw, contentType, err := c.GetRaw("/api/v1/ocsp/" + input.IssuerID + "/" + input.Serial)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return &gomcp.CallToolResult{
|
|
Content: []gomcp.Content{
|
|
&gomcp.TextContent{Text: fmt.Sprintf("OCSP response retrieved (%d bytes, content-type: %s)", len(raw), contentType)},
|
|
},
|
|
}, nil, nil
|
|
})
|
|
}
|
|
|
|
// ── Issuers ─────────────────────────────────────────────────────────
|
|
|
|
func registerIssuerTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_issuers",
|
|
Description: "List all configured issuer connectors (Local CA, ACME, step-ca).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/issuers", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_issuer",
|
|
Description: "Get issuer details including type, configuration, and enabled status.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/issuers/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_issuer",
|
|
Description: "Register a new issuer connector. Requires name and type (ACME, GenericCA, or StepCA).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateIssuerInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/issuers", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_issuer",
|
|
Description: "Update an issuer connector's configuration.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateIssuerInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/issuers/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_issuer",
|
|
Description: "Delete an issuer connector.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/issuers/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_test_issuer",
|
|
Description: "Test connectivity to an issuer connector. Returns success or error details.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/issuers/"+input.ID+"/test", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Targets ─────────────────────────────────────────────────────────
|
|
|
|
func registerTargetTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_targets",
|
|
Description: "List all deployment targets (NGINX, Apache, HAProxy, F5, IIS).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/targets", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_target",
|
|
Description: "Get deployment target details including type, agent, and configuration.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/targets/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_target",
|
|
Description: "Create a new deployment target. Requires name and type (NGINX, Apache, HAProxy, F5, IIS).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateTargetInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/targets", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_target",
|
|
Description: "Update a deployment target's configuration.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateTargetInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/targets/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_target",
|
|
Description: "Delete a deployment target.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/targets/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Agents ──────────────────────────────────────────────────────────
|
|
|
|
func registerAgentTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_agents",
|
|
Description: "List all registered agents with status, OS, architecture, and version info.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agents", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_agent",
|
|
Description: "Get agent details including status, last heartbeat, OS, architecture, IP, and version.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agents/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_register_agent",
|
|
Description: "Register a new agent. Requires name and hostname. Returns 409 if an agent with the same name already exists.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input RegisterAgentInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/agents", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_agent_heartbeat",
|
|
Description: "Send agent heartbeat with optional metadata (OS, architecture, IP, version). Returns 404 if agent not found.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input struct {
|
|
ID string `json:"id" jsonschema:"Agent ID"`
|
|
Version string `json:"version,omitempty" jsonschema:"Agent version"`
|
|
Hostname string `json:"hostname,omitempty" jsonschema:"Hostname"`
|
|
OS string `json:"os,omitempty" jsonschema:"Operating system"`
|
|
Architecture string `json:"architecture,omitempty" jsonschema:"CPU architecture"`
|
|
IPAddress string `json:"ip_address,omitempty" jsonschema:"IP address"`
|
|
}) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{}
|
|
if input.Version != "" {
|
|
body["version"] = input.Version
|
|
}
|
|
if input.Hostname != "" {
|
|
body["hostname"] = input.Hostname
|
|
}
|
|
if input.OS != "" {
|
|
body["os"] = input.OS
|
|
}
|
|
if input.Architecture != "" {
|
|
body["architecture"] = input.Architecture
|
|
}
|
|
if input.IPAddress != "" {
|
|
body["ip_address"] = input.IPAddress
|
|
}
|
|
data, err := c.Post("/api/v1/agents/"+input.ID+"/heartbeat", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_agent_submit_csr",
|
|
Description: "Submit a PEM-encoded CSR from an agent for signing.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input AgentCSRInput) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{"csr_pem": input.CSRPEM}
|
|
if input.CertificateID != "" {
|
|
body["certificate_id"] = input.CertificateID
|
|
}
|
|
data, err := c.Post("/api/v1/agents/"+input.AgentID+"/csr", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_agent_pickup_certificate",
|
|
Description: "Agent picks up a signed certificate after CSR has been processed.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input AgentPickupInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agents/"+input.AgentID+"/certificates/"+input.CertID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_agent_get_work",
|
|
Description: "Get pending work items (deployment jobs, AwaitingCSR jobs) for an agent.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agents/"+input.ID+"/work", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_agent_report_job_status",
|
|
Description: "Agent reports completion or failure of an assigned job.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input AgentJobStatusInput) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{"status": input.Status}
|
|
if input.Error != "" {
|
|
body["error"] = input.Error
|
|
}
|
|
data, err := c.Post("/api/v1/agents/"+input.AgentID+"/jobs/"+input.JobID+"/status", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Jobs ────────────────────────────────────────────────────────────
|
|
|
|
func registerJobTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_jobs",
|
|
Description: "List jobs with optional status and type filters. Job types: Issuance, Renewal, Deployment, Validation.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListJobsInput) (*gomcp.CallToolResult, any, error) {
|
|
q := paginationQuery(input.Page, input.PerPage)
|
|
if input.Status != "" {
|
|
q.Set("status", input.Status)
|
|
}
|
|
if input.Type != "" {
|
|
q.Set("type", input.Type)
|
|
}
|
|
data, err := c.Get("/api/v1/jobs", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_job",
|
|
Description: "Get job details including type, status, attempts, errors, and timestamps.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/jobs/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_cancel_job",
|
|
Description: "Cancel a pending or running job.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/jobs/"+input.ID+"/cancel", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_approve_job",
|
|
Description: "Approve a job that is in AwaitingApproval state.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/jobs/"+input.ID+"/approve", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_reject_job",
|
|
Description: "Reject a job in AwaitingApproval state with an optional reason.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input RejectJobInput) (*gomcp.CallToolResult, any, error) {
|
|
body := map[string]string{}
|
|
if input.Reason != "" {
|
|
body["reason"] = input.Reason
|
|
}
|
|
data, err := c.Post("/api/v1/jobs/"+input.ID+"/reject", body)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Policies ────────────────────────────────────────────────────────
|
|
|
|
func registerPolicyTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_policies",
|
|
Description: "List all policy rules. Policy types: AllowedIssuers, AllowedDomains, RequiredMetadata, AllowedEnvironments, RenewalLeadTime.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/policies", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_policy",
|
|
Description: "Get policy rule details including type, configuration, and enabled status.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/policies/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_policy",
|
|
Description: "Create a new policy rule. Requires name and type.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreatePolicyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/policies", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_policy",
|
|
Description: "Update a policy rule's name, type, configuration, or enabled status.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdatePolicyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/policies/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_policy",
|
|
Description: "Delete a policy rule.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/policies/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_policy_violations",
|
|
Description: "List violations for a specific policy. Shows affected certificates and severity (Warning, Error, Critical).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListViolationsInput) (*gomcp.CallToolResult, any, error) {
|
|
q := paginationQuery(input.Page, input.PerPage)
|
|
data, err := c.Get("/api/v1/policies/"+input.ID+"/violations", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Profiles ────────────────────────────────────────────────────────
|
|
|
|
func registerProfileTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_profiles",
|
|
Description: "List certificate enrollment profiles defining allowed key types, max TTL, and crypto constraints.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/profiles", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_profile",
|
|
Description: "Get certificate profile details including allowed algorithms, max TTL, EKUs, and SAN patterns.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/profiles/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_profile",
|
|
Description: "Create a certificate enrollment profile. Requires name.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateProfileInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/profiles", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_profile",
|
|
Description: "Update a certificate profile's constraints.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateProfileInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/profiles/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_profile",
|
|
Description: "Delete a certificate profile.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/profiles/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Teams ───────────────────────────────────────────────────────────
|
|
|
|
func registerTeamTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_teams",
|
|
Description: "List all teams for certificate ownership grouping.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/teams", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_team",
|
|
Description: "Get team details.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/teams/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_team",
|
|
Description: "Create a new team. Requires name.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateTeamInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/teams", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_team",
|
|
Description: "Update a team's name or description.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateTeamInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/teams/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_team",
|
|
Description: "Delete a team.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/teams/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Owners ──────────────────────────────────────────────────────────
|
|
|
|
func registerOwnerTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_owners",
|
|
Description: "List all certificate owners with email and team assignment.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/owners", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_owner",
|
|
Description: "Get owner details including email and team.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/owners/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_owner",
|
|
Description: "Create a new certificate owner. Requires name.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateOwnerInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/owners", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_owner",
|
|
Description: "Update an owner's name, email, or team assignment.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateOwnerInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/owners/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_owner",
|
|
Description: "Delete a certificate owner.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/owners/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Agent Groups ────────────────────────────────────────────────────
|
|
|
|
func registerAgentGroupTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_agent_groups",
|
|
Description: "List agent groups with dynamic matching criteria (OS, architecture, IP CIDR, version).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agent-groups", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_agent_group",
|
|
Description: "Get agent group details including matching criteria.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agent-groups/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_create_agent_group",
|
|
Description: "Create a new agent group with dynamic matching criteria. Requires name.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input CreateAgentGroupInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/agent-groups", input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_update_agent_group",
|
|
Description: "Update an agent group's name, description, or matching criteria.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input UpdateAgentGroupInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Put("/api/v1/agent-groups/"+input.ID, input)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_delete_agent_group",
|
|
Description: "Delete an agent group.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Delete("/api/v1/agent-groups/" + input.ID)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_agent_group_members",
|
|
Description: "List agents that are members of a group (by dynamic criteria and manual membership).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/agent-groups/"+input.ID+"/members", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Audit ───────────────────────────────────────────────────────────
|
|
|
|
func registerAuditTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_audit_events",
|
|
Description: "List immutable audit trail events. Shows actor, action, resource, and timestamp for all lifecycle operations.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/audit", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_audit_event",
|
|
Description: "Get a specific audit event by ID.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/audit/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Notifications ───────────────────────────────────────────────────
|
|
|
|
func registerNotificationTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_list_notifications",
|
|
Description: "List notification events (expiration warnings, renewal/deployment results, policy violations, revocations).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ListParams) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/notifications", paginationQuery(input.Page, input.PerPage))
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_get_notification",
|
|
Description: "Get notification event details.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/notifications/"+input.ID, nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_mark_notification_read",
|
|
Description: "Mark a notification as read.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input GetByIDInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/notifications/"+input.ID+"/read", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Stats ───────────────────────────────────────────────────────────
|
|
|
|
func registerStatsTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_dashboard_summary",
|
|
Description: "Get high-level dashboard metrics: total/expiring/expired/revoked certs, active/offline agents, pending/failed/completed jobs.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/stats/summary", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_certificates_by_status",
|
|
Description: "Get certificate counts grouped by status (Active, Expiring, Expired, Revoked, etc.).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/stats/certificates-by-status", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_expiration_timeline",
|
|
Description: "Get certificates expiring per day for the next N days (default 30, max 365).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input TimelineInput) (*gomcp.CallToolResult, any, error) {
|
|
q := url.Values{}
|
|
if input.Days > 0 {
|
|
q.Set("days", strconv.Itoa(input.Days))
|
|
}
|
|
data, err := c.Get("/api/v1/stats/expiration-timeline", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_job_trends",
|
|
Description: "Get job success/failure trends per day for the past N days (default 30, max 365).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input TimelineInput) (*gomcp.CallToolResult, any, error) {
|
|
q := url.Values{}
|
|
if input.Days > 0 {
|
|
q.Set("days", strconv.Itoa(input.Days))
|
|
}
|
|
data, err := c.Get("/api/v1/stats/job-trends", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_issuance_rate",
|
|
Description: "Get new certificate issuance count per day for the past N days (default 30, max 365).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input TimelineInput) (*gomcp.CallToolResult, any, error) {
|
|
q := url.Values{}
|
|
if input.Days > 0 {
|
|
q.Set("days", strconv.Itoa(input.Days))
|
|
}
|
|
data, err := c.Get("/api/v1/stats/issuance-rate", q)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Digest ──────────────────────────────────────────────────────────
|
|
|
|
func registerDigestTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_preview_digest",
|
|
Description: "Preview the scheduled certificate digest email in HTML format. Shows summary of certificate status, pending jobs, and expiring certificates.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/digest/preview", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_send_digest",
|
|
Description: "Trigger immediate sending of the certificate digest email to configured recipients. If no explicit recipients are configured, sends to certificate owners.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Post("/api/v1/digest/send", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Metrics ─────────────────────────────────────────────────────────
|
|
|
|
func registerMetricsTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_metrics",
|
|
Description: "Get system metrics snapshot: gauge metrics (cert/agent/job counts), counters (completed/failed totals), and server uptime.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/metrics", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|
|
|
|
// ── Health ──────────────────────────────────────────────────────────
|
|
|
|
func registerHealthTools(s *gomcp.Server, c *Client) {
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_health",
|
|
Description: "Check certctl server health status.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/health", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_ready",
|
|
Description: "Check certctl server readiness (database connectivity, etc.).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/ready", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_auth_info",
|
|
Description: "Get auth configuration (auth type and whether auth is required).",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/auth/info", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
|
|
gomcp.AddTool(s, &gomcp.Tool{
|
|
Name: "certctl_auth_check",
|
|
Description: "Validate that the configured API key is accepted by the server.",
|
|
}, func(ctx context.Context, req *gomcp.CallToolRequest, input EmptyInput) (*gomcp.CallToolResult, any, error) {
|
|
data, err := c.Get("/api/v1/auth/check", nil)
|
|
if err != nil {
|
|
return errorResult(err)
|
|
}
|
|
return textResult(data)
|
|
})
|
|
}
|