Add agent provisioning capabilities

This commit is contained in:
rcourtman 2026-05-28 16:25:40 +01:00
parent f7a40f3a22
commit aa4a5fa631
12 changed files with 697 additions and 28 deletions

View file

@ -198,6 +198,33 @@ this writing, the published capabilities are:
(`not_an_issue`, `expected_behavior`, `will_fix_later`).
- `resolve_finding` manually marks a finding resolved.
**Actions (governed plan/approval/execute):**
- `plan_action` validates an action request, records the plan, and
returns approval policy, blast radius, plan hash, and preflight
summary.
- `decide_action` records an approval or rejection for a planned
action.
- `execute_action` executes a previously planned and approved action
and returns the persisted audit result.
**Provisioning (infrastructure onboarding):**
- `discover_lan` scans a subnet, or returns cached scan results, to
find candidate infrastructure hosts before import.
- `list_nodes` returns configured Pulse infrastructure sources with
credential secret values redacted.
- `add_node` adds a Proxmox VE, Proxmox Backup Server, or Proxmox Mail
Gateway source.
- `update_node` updates a configured source without changing omitted
fields.
- `remove_node` removes a configured source.
- `test_node_credentials` validates proposed source credentials without
saving them.
- `test_node_connection` validates a saved source by id.
- `refresh_node_cluster_membership` re-detects Proxmox VE cluster
endpoint metadata for an existing source.
Run `tools/list` from your MCP client to see the live set.
## Stable error envelope

View file

@ -75,15 +75,16 @@ import (
// agentCapability mirrors Pulse's manifest wire shape — defined
// inline so the adapter depends on nothing in the pulse module.
type agentCapability struct {
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Method string `json:"method"`
Path string `json:"path"`
Scope string `json:"scope"`
ResponseShape string `json:"responseShape,omitempty"`
ErrorCodes []string `json:"errorCodes,omitempty"`
RequestBodyShape string `json:"requestBodyShape,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Method string `json:"method"`
Path string `json:"path"`
Scope string `json:"scope"`
ResponseShape string `json:"responseShape,omitempty"`
ErrorCodes []string `json:"errorCodes,omitempty"`
RequestBodyShape string `json:"requestBodyShape,omitempty"`
InputSchema json.RawMessage `json:"inputSchema,omitempty"`
}
type agentCapabilitiesManifest struct {
@ -418,6 +419,10 @@ func (s *mcpServer) writeJSON(out io.Writer, v any) {
// MCP just needs enough shape so the agent knows which fields to
// pass.
func buildInputSchema(cap agentCapability) json.RawMessage {
if len(cap.InputSchema) > 0 {
return cap.InputSchema
}
properties := map[string]any{}
required := []string{}
for _, m := range pathPlaceholderRE.FindAllStringSubmatch(cap.Path, -1) {

View file

@ -88,6 +88,36 @@ func TestBuildInputSchema_NonGetCapabilitiesAcceptBody(t *testing.T) {
}
}
func TestBuildInputSchema_UsesManifestProvidedSchema(t *testing.T) {
rawSchema := json.RawMessage(`{
"type": "object",
"properties": {
"type": { "enum": ["pve", "pbs", "pmg"] },
"host": { "type": "string" }
},
"required": ["type", "host"],
"additionalProperties": false
}`)
cap := agentCapability{
Name: "add_node",
Path: "/api/config/nodes",
Method: http.MethodPost,
InputSchema: rawSchema,
}
got := buildInputSchema(cap)
var schema map[string]any
if err := json.Unmarshal(got, &schema); err != nil {
t.Fatalf("unmarshal schema: %v", err)
}
if schema["additionalProperties"] != false {
t.Errorf("manifest-provided schema must be preserved; got %v", schema)
}
if _, hasBody := schema["properties"].(map[string]any)["body"]; hasBody {
t.Errorf("manifest-provided schema should not be wrapped in body: %s", string(got))
}
}
func TestSubstitutePathParams_FillsAllPlaceholders(t *testing.T) {
got, err := substitutePathParams(
"/api/agent/resource-context/{resourceId}",