mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Add agent provisioning capabilities
This commit is contained in:
parent
f7a40f3a22
commit
aa4a5fa631
12 changed files with 697 additions and 28 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue