From e6c32a2b54d719194d4f24d77f9ea5907dd1c6c2 Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Wed, 6 May 2026 19:33:39 -0700 Subject: [PATCH 01/11] feat: expand to 19 skills, add contributor infrastructure, comprehensive rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 9 new service skills: DNS (Designate), load balancer (Octavia), images (Glance), object storage (Swift), secrets (Barbican), autoscaling (Castellum), shared storage (Manila), baremetal (Ironic), email (Cronus) — all with parameters verified against MCP server source - Fix parameter errors: attribute_name→attribute (hermes), id-only for app credential delete (keystone), remove phantom resource param (limes) - Add CONTRIBUTING.md, SECURITY.md, CHANGELOG.md for v0.1.0 - Expand rules from 8 lines to comprehensive guidance: error handling patterns (401-503), rate limiting, pagination, destructive operation gates, stop conditions, max-depth directives, role awareness table - Add CADF event format reference from sapcc/go-api-declarations - Update README: prerequisites, MCP server setup, credential setup, smoke test, task-routing table for all 19 skills, related tools - Update knowledge/services.md: all services with MCP prefixes, CLI tools (hermescli, limesctl), go-api-declarations, OpenStack API reference links - Update marketplace descriptions to reflect 19-skill coverage --- .claude-plugin/marketplace.json | 2 +- .claude/session-reads.txt | 64 +++++ CHANGELOG.md | 37 +++ CONTRIBUTING.md | 110 ++++++++ README.md | 136 ++++++++-- SECURITY.md | 58 ++++ knowledge/sapcc/services.md | 53 +++- plugins/sapcc/.codex-plugin/plugin.json | 2 +- plugins/sapcc/skills/README.md | 26 ++ plugins/sapcc/skills/sapcc-audit/SKILL.md | 14 +- .../references/cadf-event-format.md | 251 +++++++----------- .../sapcc/skills/sapcc-autoscaling/SKILL.md | 163 +++--------- plugins/sapcc/skills/sapcc-baremetal/SKILL.md | 147 +++------- plugins/sapcc/skills/sapcc-dns/SKILL.md | 117 +++----- plugins/sapcc/skills/sapcc-email/SKILL.md | 66 +++++ plugins/sapcc/skills/sapcc-identity/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-images/SKILL.md | 134 ++-------- .../sapcc/skills/sapcc-loadbalancer/SKILL.md | 159 +++-------- .../skills/sapcc-object-storage/SKILL.md | 113 ++------ plugins/sapcc/skills/sapcc-quota/SKILL.md | 4 +- plugins/sapcc/skills/sapcc-secrets/SKILL.md | 114 +++----- .../skills/sapcc-shared-storage/SKILL.md | 70 +++++ rules/sapcc-agent-rules.md | 119 ++++++++- 23 files changed, 1026 insertions(+), 935 deletions(-) create mode 100644 .claude/session-reads.txt create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 plugins/sapcc/skills/sapcc-email/SKILL.md create mode 100644 plugins/sapcc/skills/sapcc-shared-storage/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 30e9c9e..45c7460 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "category": "cloud", - "description": "Operate SAP Converged Cloud: compute, networking, storage, identity, quota, audit, metrics, registry, and endpoint services.", + "description": "Operate SAP Converged Cloud: 19 skills covering compute, networking, storage, identity, quota, audit, metrics, registry, connectivity, DNS, load balancers, images, object storage, secrets, autoscaling, shared file systems, baremetal, and email.", "keywords": [ "openstack", "sapcc", diff --git a/.claude/session-reads.txt b/.claude/session-reads.txt new file mode 100644 index 0000000..abe68e3 --- /dev/null +++ b/.claude/session-reads.txt @@ -0,0 +1,64 @@ +/tmp/agent-toolkit-for-aws/README.md +/tmp/agent-toolkit-for-aws/rules/aws-agent-rules.md +/tmp/agent-toolkit-for-aws/skills/README.md +/tmp/agent-toolkit-for-aws/plugins/aws-core/.claude-plugin/plugin.json +/tmp/agent-toolkit-for-aws/plugins/aws-core/.mcp.json +/tmp/agent-toolkit-for-aws/plugins/aws-core/skills/aws-containers/SKILL.md +/Users/I810033/pgh/openstack-mcp-server/internal/tools/neutron/neutron.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/nova/nova.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/cinder/cinder.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/keystone/keystone.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/limes/limes.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/maia/maia.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/hermes/hermes.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/archer/archer.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/keppel/keppel.go +/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/services.md +/Users/I810033/pgh/openstack-agent-toolkit/skills/credential-setup/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/README.md +/tmp/agent-toolkit-for-aws/.claude-plugin/marketplace.json +/tmp/agent-toolkit-for-aws/tools/validate.py +/Users/I810033/pgh/openstack-agent-toolkit/docs/adr/001-toolkit-architecture.md +/Users/I810033/pgh/vexjoy-agent/docs/PHILOSOPHY.md +/Users/I810033/.claude/skills/meta/skill-creator/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/.claude-plugin/plugin.json +/Users/I810033/pgh/openstack-agent-toolkit/rules/sapcc-agent-rules.md +/Users/I810033/pgh/openstack-agent-toolkit/skills/credential-setup/references/auth-methods.md +/Users/I810033/pgh/openstack-agent-toolkit/docs/implementation-plan.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/.mcp.json +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-quota/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-compute/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-compute/references/flavor-families.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-storage/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-networking/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-networking/references/security-group-patterns.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-connectivity/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/credential-setup/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/credential-setup/references/auth-methods.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-metrics/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-audit/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-identity/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-registry/SKILL.md +/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/README.md +/Users/I810033/pgh/openstack-agent-toolkit/.claude-plugin/marketplace.json +/Users/I810033/pgh/openstack-agent-toolkit/tools/validate.py +/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/troubleshooting-flows.md +/Users/I810033/pgh/openstack-agent-toolkit/LICENSE +/Users/I810033/pgh/openstack-agent-toolkit/CODE_OF_CONDUCT.md +/Users/I810033/pgh/openstack-agent-toolkit/.editorconfig +/Users/I810033/pgh/openstack-agent-toolkit/.gitignore +/Users/I810033/pgh/openstack-agent-toolkit/.github/workflows/validate.yml +/Users/I810033/pgh/openstack-agent-toolkit/.github/CODEOWNERS +/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/architecture.md +/Users/I810033/pgh/openstack-mcp-server/internal/tools/glance/glance.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/manila/manila.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/swift/swift.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/ironic/ironic.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/octavia/octavia.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/barbican/barbican.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/designate/designate.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/castellum/castellum.go +/Users/I810033/pgh/openstack-mcp-server/internal/tools/cronus/cronus.go +/Users/I810033/pgh/openstack-mcp-server/internal/server/server.go +/Users/I810033/pgh/openstack-agent-toolkit/.agents/plugins/marketplace.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b7b8491 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,37 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [0.1.0] - 2026-05-06 + +### Added + +- Initial release of the SAP Converged Cloud Agent Toolkit +- Plugin structure supporting Claude Code and Codex +- **10 service skills**: compute, networking, storage, identity, quota, audit, metrics, registry, connectivity, credential-setup +- **9 additional service skills**: DNS, load balancer, images, object storage, secrets, autoscaling, shared storage, baremetal, email notifications +- Rules file with baseline agent behavior guidance +- Knowledge base with service reference +- MCP server configuration for openstack-mcp-server +- Validation tooling (`tools/validate.py`) +- GitHub Actions CI for manifest validation +- Contributor infrastructure (CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md) +- Multi-agent marketplace manifests (.claude-plugin, .codex-plugin, .agents) + +### Security + +- Credential isolation: secrets never reach the LLM context +- Application credential guidance over password authentication +- Keychain-backed secret storage patterns +- Response sanitization in MCP server +- Project-scoped access enforcement + +[0.1.0]: https://github.com/notque/openstack-agent-toolkit/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6e9e12f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ + + +# Contributing to SAP Converged Cloud Agent Toolkit + +## How to Contribute + +We welcome contributions — especially new service skills, gotcha documentation from production experience, and error-handling improvements. + +### Adding a New Skill + +1. **Create the directory:** + ```bash + mkdir -p plugins/sapcc/skills/ + ``` + +2. **Create `SKILL.md`** with required frontmatter: + ```yaml + --- + name: # must be kebab-case, match directory name + description: >- + One paragraph describing the skill. Include trigger phrases + that agents should match on. + version: 1.0.0 + metadata: + service: [service-name] # MCP tool prefix (nova, neutron, etc.) + task: [list, inspect, debug, lifecycle] + persona: [developer, platform-engineer] + --- + ``` + +3. **Required sections** (in order): + - `## MCP Tools` — table of tools with key parameters + - `## Gotchas` — numbered list of pitfalls (aim for 5-10) + - `## Common Workflows` — step-by-step recipes + - `## Troubleshooting` — diagnosed failure scenarios + - `## Security Considerations` — data sensitivity, confirmation requirements + - `## Cross-Service References` — table linking to other skills + - `## Routing` — when to load reference files + +4. **Add reference files** (if needed): + ```bash + mkdir -p plugins/sapcc/skills//references/ + ``` + Keep SKILL.md under 500 lines. Extract deep content to references/. + +5. **Validate:** + ```bash + python3 tools/validate.py --plugin sapcc + ``` + +6. **Register in marketplace** (both formats): + - `.claude-plugin/marketplace.json` — Claude Code + - `.agents/plugins/marketplace.json` — Codex/Agents + +### Improving an Existing Skill + +The most valuable contributions are: + +- **New gotchas** from production experience (with concrete examples) +- **Workflow improvements** based on real debugging sessions +- **Parameter corrections** when MCP server tools change +- **Cross-service references** connecting related skills + +### Rules Contributions + +The rules file (`rules/sapcc-agent-rules.md`) provides baseline agent behavior. Changes here affect ALL skill invocations — keep rules universal and test carefully. + +## Quality Standards + +| Requirement | Check | +|-------------|-------| +| Frontmatter valid | `python3 tools/validate.py` passes | +| Name is kebab-case | Matches `^[a-z][a-z0-9]*(-[a-z0-9]+)*$` | +| Name matches directory | Skill name == parent directory name | +| Description ≥ 20 chars | Meaningful for progressive disclosure | +| Under 500 lines | Extract to `references/` if longer | +| Parameters verified | Cross-check against MCP server source | +| No secrets in examples | Use placeholders, never real credentials | + +## Development Workflow + +```bash +# Fork and clone +git clone https://github.com//openstack-agent-toolkit +cd openstack-agent-toolkit + +# Create feature branch +git checkout -b add-sapcc- + +# Make changes, validate +python3 tools/validate.py + +# Commit (conventional commits preferred) +git commit -m "feat(skills): add sapcc- skill" + +# Push and create PR +git push origin add-sapcc- +``` + +## Developer Certificate of Origin (DCO) + +Due to legal reasons, contributors will be asked to accept a DCO when they create their first pull request. This happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). + +## Code of Conduct + +We follow the [SAP Open Source Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/README.md b/README.md index e9e0ea6..53956c9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,56 @@ + + # SAP Converged Cloud Agent Toolkit Help AI coding agents operate SAP Converged Cloud infrastructure. The Agent Toolkit gives AI agents the skills, knowledge, and guardrails to work with SAP CC services effectively. It works with Claude Code, Codex, and any agent supporting the skills format. +## Prerequisites + +| Requirement | Purpose | +|-------------|---------| +| [openstack-mcp-server](https://github.com/notque/openstack-mcp-server) | Runtime providing authenticated API tools (required) | +| SAP CC credentials | Application credential or username+password for your region | +| OS keychain | macOS Keychain, Linux `pass`, or `secret-tool` for secret storage | +| Claude Code ≥ 1.0 or Codex | Agent runtime supporting plugin format | + +### MCP Server Setup + +```bash +# Install the MCP server binary +go install github.com/notque/openstack-mcp-server@latest + +# Or download pre-built binary from releases +# https://github.com/notque/openstack-mcp-server/releases +``` + +### Credential Setup + +After installing the plugin, run the `credential-setup` skill: +``` +/sapcc:credential-setup +``` + +This guides you through storing credentials in your OS keychain. Supports: +- **Application credentials** (recommended) — scoped, revocable +- **Username + password** — via keychain-backed retrieval commands + +Environment variables used by the MCP server: +| Variable | Description | +|----------|-------------| +| `OS_AUTH_URL` | Keystone endpoint (e.g., `https://identity-3..cloud.sap/v3`) | +| `OS_USERNAME` | SAP CC username (if using password auth) | +| `OS_PW_CMD` | Command to retrieve password from keychain | +| `OS_APPLICATION_CREDENTIAL_ID` | App credential ID (if using app creds) | +| `OS_APPCRED_SECRET_CMD` | Command to retrieve app credential secret | +| `OS_PROJECT_NAME` | Default project scope | +| `OS_DOMAIN_NAME` | Domain (e.g., `monsoon3`) | + ## Quick Start ### Claude Code @@ -22,6 +69,22 @@ Copy skills to your agent's skills location: | Claude Code | `~/.claude/skills/` or `.claude/skills/` | | Codex | `~/.codex/skills/` or `.agents/skills/` | +## Verify Installation + +Quick smoke test to confirm everything works: + +```bash +# 1. Check plugin validates +python3 tools/validate.py --plugin sapcc + +# 2. Confirm MCP server starts (should show tool list) +openstack-mcp-server --list-tools + +# 3. Test read-only API call (requires valid credentials) +# In your agent, ask: "What project am I authenticated to?" +# Expected: agent calls keystone_token_info and reports project/domain +``` + ## Architecture ``` @@ -36,18 +99,44 @@ Copy skills to your agent's skills location: └─────────────────────────────────────────────────────────────────┘ ``` -**MCP Server** ([openstack-mcp-server](https://github.com/notque/openstack-mcp-server)) = runtime providing typed tools (55 API operations) +**MCP Server** ([openstack-mcp-server](https://github.com/notque/openstack-mcp-server)) = runtime providing typed tools (47+ API operations across 17 services) **Agent Toolkit** (this repo) = intelligence layer teaching agents *when* and *how* to use those tools +## Task Routing + +When you ask the agent a question, it auto-selects the appropriate skill: + +| Your question is about... | Skill loaded | MCP tools used | +|---------------------------|-------------|----------------| +| Servers, VMs, flavors, start/stop | `sapcc-compute` | `nova_*` | +| Networks, subnets, ports, security groups | `sapcc-networking` | `neutron_*` | +| Volumes, snapshots, attachments | `sapcc-storage` | `cinder_*` | +| Projects, domains, users, roles | `sapcc-identity` | `keystone_*` | +| Quota, usage, capacity | `sapcc-quota` | `limes_*` | +| Audit events, who changed what | `sapcc-audit` | `hermes_*` | +| Monitoring, PromQL, alerts | `sapcc-metrics` | `maia_*` | +| Container images, vulnerabilities | `sapcc-registry` | `keppel_*` | +| Private endpoints, service connectivity | `sapcc-connectivity` | `archer_*` | +| DNS zones, recordsets | `sapcc-dns` | `designate_*` | +| Load balancers, pools, listeners | `sapcc-loadbalancer` | `octavia_*` | +| Images, snapshots, image properties | `sapcc-images` | `glance_*` | +| Object storage, containers, objects | `sapcc-object-storage` | `swift_*` | +| Secrets, certificates, keys | `sapcc-secrets` | `barbican_*` | +| Autoscaling policies | `sapcc-autoscaling` | `castellum_*` | +| Shared file systems, shares, exports | `sapcc-shared-storage` | `manila_*` | +| Baremetal provisioning | `sapcc-baremetal` | `ironic_*` | +| Email notifications | `sapcc-email` | `cronus_*` | +| Auth setup, credential config | `credential-setup` | (guided wizard) | + ## What's Included ### Plugin | Plugin | Description | |--------|-------------| -| [sapcc](plugins/sapcc/) | All SAP CC skills and MCP server configuration. Covers compute, networking, storage, identity, quota, audit, metrics, registry, endpoint services, DNS, secrets, object storage, file systems, load balancing, images, bare metal, and autoscaling. | +| [sapcc](plugins/sapcc/) | All SAP CC skills and MCP server configuration. Covers compute, networking, storage, identity, quota, audit, metrics, registry, and endpoint services. | -### Skills +### Skills (19 total) | Skill | Service | Key Capability | |-------|---------|----------------| @@ -60,23 +149,26 @@ Copy skills to your agent's skills location: | [sapcc-metrics](plugins/sapcc/skills/sapcc-metrics/) | Maia | PromQL queries, metric discovery, monitoring | | [sapcc-registry](plugins/sapcc/skills/sapcc-registry/) | Keppel | Container images, vulnerability status, federation | | [sapcc-connectivity](plugins/sapcc/skills/sapcc-connectivity/) | Archer | Private endpoint services, service discovery | -| [sapcc-dns](plugins/sapcc/skills/sapcc-dns/) | Designate | DNS zones, recordsets, FQDN management | -| [sapcc-secrets](plugins/sapcc/skills/sapcc-secrets/) | Barbican | Secret metadata, certificate inventory, audit | -| [sapcc-object-storage](plugins/sapcc/skills/sapcc-object-storage/) | Swift | Containers, objects, storage inspection | -| [sapcc-filesystems](plugins/sapcc/skills/sapcc-filesystems/) | Manila | Shared NFS/CIFS file systems | -| [sapcc-loadbalancer](plugins/sapcc/skills/sapcc-loadbalancer/) | Octavia | Load balancers, listeners, pools | -| [sapcc-images](plugins/sapcc/skills/sapcc-images/) | Glance | VM images, snapshots, boot sources | -| [sapcc-baremetal](plugins/sapcc/skills/sapcc-baremetal/) | Ironic | Physical server nodes, provisioning | -| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | Castellum | Automatic quota scaling, resize operations | +| [sapcc-dns](plugins/sapcc/skills/sapcc-dns/) | Designate | DNS zones, recordsets, zone transfers | +| [sapcc-loadbalancer](plugins/sapcc/skills/sapcc-loadbalancer/) | Octavia | Load balancers, pools, health monitors | +| [sapcc-images](plugins/sapcc/skills/sapcc-images/) | Glance | Image management, properties, visibility | +| [sapcc-object-storage](plugins/sapcc/skills/sapcc-object-storage/) | Swift | Object storage, containers, large objects | +| [sapcc-secrets](plugins/sapcc/skills/sapcc-secrets/) | Barbican | Secret management, certificates, keys | +| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | Castellum | Autoscaling policies, resource operations | +| [sapcc-shared-storage](plugins/sapcc/skills/sapcc-shared-storage/) | Manila | Shared file systems, exports, share networks | +| [sapcc-baremetal](plugins/sapcc/skills/sapcc-baremetal/) | Ironic | Baremetal provisioning, node lifecycle | +| [sapcc-email](plugins/sapcc/skills/sapcc-email/) | Cronus | Email notifications, SMTP relay | | [credential-setup](plugins/sapcc/skills/credential-setup/) | Keystone | Guided auth setup with keychain storage | ### Rules The [rules file](rules/sapcc-agent-rules.md) provides baseline agent behavior: - Check quota before resource creation -- Use audit trail for debugging -- Never expose credentials to the LLM -- Load skills before guessing at SAP CC-specific behavior +- Error handling patterns (401/403/404/409/429/5xx) +- Rate limiting and pagination guidance +- Destructive operation confirmation requirements +- Stop conditions and maximum depth directives +- Role awareness for operations ### Knowledge @@ -93,7 +185,7 @@ Skills use progressive disclosure: 3. Reference files load on-demand for deep-dive content 4. Skill context releases when the task completes -18 skills installed = ~900 tokens at startup. Full context only when needed. +19 skills installed = ~950 tokens at startup. Full context only when needed. ## Security Philosophy @@ -102,6 +194,7 @@ Skills use progressive disclosure: - **Keychain storage** — secrets retrieved via system commands, never in config files - **Defense in depth** — response sanitization catches accidental leakage - **Destructive operations require confirmation** — skills enforce user consent +- **Audit trail** — all API actions logged to Hermes with credential identity ## Validation @@ -109,9 +202,20 @@ Skills use progressive disclosure: python3 tools/validate.py ``` -Validates all plugin manifests, skill frontmatter, and MCP configs. Runs in CI. +Validates all plugin manifests, skill frontmatter, and MCP configs. Runs in CI on every push and PR. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on adding skills and improving documentation. ## Related - [openstack-mcp-server](https://github.com/notque/openstack-mcp-server) — The Go MCP server this toolkit complements +- [go-api-declarations](https://github.com/sapcc/go-api-declarations) — Canonical Go type definitions for SAP CC APIs (CADF, Limes, Castellum) +- [hermescli](https://github.com/sapcc/hermescli) — CLI for Hermes audit service +- [limesctl](https://github.com/sapcc/limesctl) — CLI for Limes quota service - [AWS Agent Toolkit](https://github.com/aws/agent-toolkit-for-aws) — Similar pattern for AWS (reference architecture) + +## License + +Copyright 2026 SAP SE or an SAP affiliate company. Licensed under the [Apache License 2.0](LICENSE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a1bd76e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,58 @@ + + +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in this project, please report it responsibly. + +**Do NOT open a public GitHub issue for security vulnerabilities.** + +### How to Report + +Please report security issues via the SAP Trust Center at [https://www.sap.com/about/trust-center/security/incident-management.html](https://www.sap.com/about/trust-center/security/incident-management.html). + +Alternatively, you can email [secure@sap.com](mailto:secure@sap.com). + +### What to Include + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +### Response Timeline + +- **Acknowledgment**: Within 48 hours +- **Initial assessment**: Within 5 business days +- **Resolution target**: Varies by severity + +## Security Design + +This toolkit follows defense-in-depth principles: + +1. **Credentials never reach the LLM** — The MCP server holds secrets in process memory; only sanitized responses flow to the agent. +2. **Application credentials over passwords** — Scoped, revocable, auditable. +3. **Keychain storage** — Secrets retrieved via OS commands (`security find-generic-password` on macOS, `pass` on Linux), never stored in plaintext config. +4. **Response sanitization** — The MCP server strips sensitive fields before returning results. +5. **Project-scoped access** — All operations are scoped to the authenticated project. +6. **Audit trail** — All actions are logged to Hermes with the credential identity. + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| 0.1.x | ✅ | + +## Scope + +This policy covers: +- The agent toolkit (skills, rules, knowledge) +- Plugin manifests and configuration +- The validation tooling + +For vulnerabilities in the MCP server itself, please report to the [openstack-mcp-server](https://github.com/notque/openstack-mcp-server) repository. diff --git a/knowledge/sapcc/services.md b/knowledge/sapcc/services.md index 4c49334..54784b1 100644 --- a/knowledge/sapcc/services.md +++ b/knowledge/sapcc/services.md @@ -12,9 +12,9 @@ | DNS | Designate | v2 | `designate_` | | Load Balancing | Octavia | v2 | `octavia_` | | Image | Glance | v2 | `glance_` | -| Shared Filesystems | Manila | v2 | `manila_` | -| Bare Metal | Ironic | v1 | `ironic_` | | Key Manager | Barbican | v1 | `barbican_` | +| Shared File Systems | Manila | v2 | `manila_` | +| Bare Metal | Ironic | v1 | `ironic_` | ## SAP CC-Specific Services @@ -25,8 +25,8 @@ | Registry | Keppel | `keppel` | `keppel_` | Multi-tenant container image registry | | Endpoint Service | Archer | `endpoint-services` | `archer_` | Private network connectivity (like AWS PrivateLink) | | Metrics | Maia | `metrics` | `maia_` | Multi-tenant Prometheus-compatible metrics | -| Autoscaling | Castellum | `castellum` | `castellum_` | Automatic resource quota scaling | -| Email | Cronus | `email-aws` | `cronus_` | Email sending (SES-compatible) | +| Autoscaling | Castellum | `castellum` | `castellum_` | Resource autoscaling policies | +| Email | Cronus | `email-aws` | `cronus_` | Email notification service | ## Common Operations by Role @@ -35,18 +35,12 @@ - Review audit trail: `hermes_list_events` - Monitor metrics: `maia_query` with PromQL - Manage endpoints: `archer_list_services`, `archer_list_endpoints` -- Autoscaling status: `castellum_get_project_resources` -- Bare metal nodes: `ironic_list_nodes` -- Images: `glance_list_images` ### Developer - List servers: `nova_list_servers` - Check networking: `neutron_list_networks`, `neutron_list_ports` - View container images: `keppel_list_repositories` -- Check storage: `cinder_list_volumes`, `swift_list_containers` -- DNS records: `designate_list_zones`, `designate_list_recordsets` -- Load balancers: `octavia_list_loadbalancers` -- File shares: `manila_list_shares` +- Check storage: `cinder_list_volumes` ### Security/Compliance - Audit events: `hermes_list_events` with time/action/outcome filters @@ -71,4 +65,39 @@ Each SAP CC region is an independent OpenStack deployment: - Separate Keystone (identity) - Separate service catalog - Separate credentials required -- Region naming: `--` (e.g., `eu-de-1`, `na-us-1`) +- Region naming: `--` (e.g., `eu-de-1`, `qa-de-1`, `na-us-1`) + +## Related CLI Tools + +| Service | CLI | Repository | +|---------|-----|------------| +| Hermes (Audit) | hermescli | [sapcc/hermescli](https://github.com/sapcc/hermescli) | +| Limes (Quota) | limesctl | [sapcc/limesctl](https://github.com/sapcc/limesctl) | +| All OpenStack | openstackclient | [openstack/python-openstackclient](https://github.com/openstack/python-openstackclient) | + +## API Type Declarations + +Canonical Go structs for SAP CC APIs: [sapcc/go-api-declarations](https://github.com/sapcc/go-api-declarations) + +| Package | Covers | +|---------|--------| +| `cadf` | CADF audit event format (Hermes) | +| `limes` | Quota/usage resource models | +| `castellum` | Autoscaling resource declarations | +| `liquid` | Resource capacity protocol | + +## OpenStack API References + +| Service | API Docs | +|---------|----------| +| Nova | https://docs.openstack.org/api-ref/compute/ | +| Neutron | https://docs.openstack.org/api-ref/network/ | +| Cinder | https://docs.openstack.org/api-ref/block-storage/ | +| Keystone | https://docs.openstack.org/api-ref/identity/ | +| Glance | https://docs.openstack.org/api-ref/image/ | +| Designate | https://docs.openstack.org/api-ref/dns/ | +| Octavia | https://docs.openstack.org/api-ref/load-balancer/ | +| Barbican | https://docs.openstack.org/api-ref/key-manager/ | +| Manila | https://docs.openstack.org/api-ref/shared-file-system/ | +| Ironic | https://docs.openstack.org/api-ref/baremetal/ | +| Swift | https://docs.openstack.org/api-ref/object-store/ | diff --git a/plugins/sapcc/.codex-plugin/plugin.json b/plugins/sapcc/.codex-plugin/plugin.json index 81239ef..b5e4ebb 100644 --- a/plugins/sapcc/.codex-plugin/plugin.json +++ b/plugins/sapcc/.codex-plugin/plugin.json @@ -2,7 +2,7 @@ "author": { "name": "SAP Converged Cloud" }, - "description": "Operate SAP Converged Cloud: compute, networking, storage, identity, quota, audit, metrics, registry, and endpoint services.", + "description": "Operate SAP Converged Cloud: 19 skills covering compute, networking, storage, identity, quota, audit, metrics, registry, connectivity, DNS, load balancers, images, object storage, secrets, autoscaling, shared file systems, baremetal, and email.", "homepage": "https://github.com/notque/openstack-agent-toolkit", "keywords": [ "openstack", diff --git a/plugins/sapcc/skills/README.md b/plugins/sapcc/skills/README.md index 5243996..9949145 100644 --- a/plugins/sapcc/skills/README.md +++ b/plugins/sapcc/skills/README.md @@ -27,3 +27,29 @@ The `SKILL.md` includes YAML frontmatter with `name` and `description`, followed - Common Workflows - Troubleshooting - Security Considerations +- Cross-Service References +- Routing (to reference files) + +## Available Skills + +| Skill | Service | Description | +|-------|---------|-------------| +| credential-setup | Keystone | Guided auth setup with keychain storage | +| sapcc-audit | Hermes | CADF event investigation, compliance queries | +| sapcc-autoscaling | Castellum | Autoscaling policies, resource operations | +| sapcc-baremetal | Ironic | Bare metal node provisioning and lifecycle | +| sapcc-compute | Nova | Server lifecycle, flavors, actions | +| sapcc-connectivity | Archer | Private endpoint services, discovery | +| sapcc-dns | Designate | DNS zones and recordsets | +| sapcc-email | Cronus | Email notifications and SMTP relay | +| sapcc-identity | Keystone | Domain/project model, app credentials | +| sapcc-images | Glance | VM boot images, properties, visibility | +| sapcc-loadbalancer | Octavia | Load balancers, pools, health monitors | +| sapcc-metrics | Maia | PromQL queries, metric discovery | +| sapcc-networking | Neutron | Networks, subnets, ports, security groups | +| sapcc-object-storage | Swift | Object storage, containers, large objects | +| sapcc-quota | Limes | Quota interpretation, capacity planning | +| sapcc-registry | Keppel | Container images, vulnerabilities, federation | +| sapcc-secrets | Barbican | Secret management, certificates, keys | +| sapcc-shared-storage | Manila | Shared file systems, exports, share networks | +| sapcc-storage | Cinder | Block storage volumes, snapshots | diff --git a/plugins/sapcc/skills/sapcc-audit/SKILL.md b/plugins/sapcc/skills/sapcc-audit/SKILL.md index 96b020c..bea2771 100644 --- a/plugins/sapcc/skills/sapcc-audit/SKILL.md +++ b/plugins/sapcc/skills/sapcc-audit/SKILL.md @@ -21,7 +21,7 @@ Hermes is SAP CC's centralized audit service. It records all API actions across |------|---------|----------------| | `hermes_list_events` | Search/filter audit events | `target_type`, `target_id`, `initiator_name`, `action`, `outcome`, `time_gte`, `time_lte`, `limit`, `sort` | | `hermes_get_event` | Full CADF event by UUID | `event_id` | -| `hermes_list_attributes` | Discover valid filter values | `attribute_name` (one of: target_type, action, outcome, observer_type, initiator_type) | +| `hermes_list_attributes` | Discover valid filter values | `attribute` (one of: target_type, action, outcome, observer_type, initiator_type) | ## CADF Event Model @@ -49,7 +49,7 @@ See `references/cadf-event-format.md` for the full event schema. Correct: `compute/server`, `network/port`, `identity/project`, `dns/zone` Wrong: `nova/server`, `server`, `neutron/port`, `VM` -The format is `/`. Call `hermes_list_attributes` with `attribute_name=target_type` to discover valid values if unsure. +The format is `/`. Call `hermes_list_attributes` with `attribute=target_type` to discover valid values if unsure. ### 2. Time filters use PREFIX syntax @@ -63,13 +63,13 @@ The value is a plain ISO 8601 timestamp. Do NOT embed operators in the value str Valid outcomes: `success`, `failure`, `pending` -NOT: `200`, `404`, `500`, `created`, `error`. Use `hermes_list_attributes` with `attribute_name=outcome` to confirm. +NOT: `200`, `404`, `500`, `created`, `error`. Use `hermes_list_attributes` with `attribute=outcome` to confirm. ### 4. action values are present-tense verbs Valid: `create`, `update`, `delete`, `read`, `authenticate`, `start`, `stop` -NOT past tense: `created`, `updated`, `deleted`. NOT nouns: `creation`, `deletion`. Call `hermes_list_attributes` with `attribute_name=action` to see all tracked actions. +NOT past tense: `created`, `updated`, `deleted`. NOT nouns: `creation`, `deletion`. Call `hermes_list_attributes` with `attribute=action` to see all tracked actions. ### 5. hermes_list_attributes is your discovery tool — call it first @@ -144,9 +144,9 @@ Filter by human-readable username (e.g., `D012345`, `technical_user_xyz`), not t ### Discovery — what's tracked? ``` -1. hermes_list_attributes(attribute_name="target_type") → all audited resource types -2. hermes_list_attributes(attribute_name="action") → all tracked actions -3. hermes_list_attributes(attribute_name="outcome") → valid outcome values +1. hermes_list_attributes(attribute="target_type") → all audited resource types +2. hermes_list_attributes(attribute="action") → all tracked actions +3. hermes_list_attributes(attribute="outcome") → valid outcome values 4. Use results to construct precise queries ``` diff --git a/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md b/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md index e8e9215..36aaca5 100644 --- a/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md +++ b/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md @@ -1,195 +1,138 @@ # CADF Event Format Reference -CADF (Cloud Auditing Data Federation) is the DMTF standard used by Hermes to structure audit events. Every action in SAP Converged Cloud generates a CADF event. +Source: [sapcc/go-api-declarations/cadf/event.go](https://github.com/sapcc/go-api-declarations/blob/main/cadf/event.go) ## Event Structure ```json { + "typeURI": "http://schemas.dmtf.org/cloud/audit/1.0/event", "id": "event-uuid", + "eventTime": "2024-03-15T14:22:01.000000+00:00", "eventType": "activity", - "eventTime": "2024-03-15T14:22:01.234Z", "action": "update", "outcome": "success", + "reason": { + "reasonType": "HTTP", + "reasonCode": "200" + }, "initiator": { - "id": "user-uuid", - "name": "D012345", "typeURI": "service/security/account/user", - "domain_id": "domain-uuid", - "project_id": "project-uuid" + "id": "user-keystone-uuid", + "name": "I810033", + "domain": "monsoon3", + "host": { + "address": "10.0.0.1", + "agent": "python-openstackclient/6.0.0" + }, + "project_id": "project-uuid", + "domain_id": "domain-uuid" }, "target": { - "id": "resource-uuid", "typeURI": "compute/server", - "name": "my-server-01", - "project_id": "project-uuid" + "id": "resource-uuid", + "name": "my-server", + "project_id": "project-uuid", + "domain_id": "domain-uuid" }, "observer": { - "id": "observer-uuid", "typeURI": "service/compute", + "id": "nova-service-uuid", "name": "nova" }, + "requestPath": "/v2.1/servers/resource-uuid", "attachments": [ { "name": "payload", "typeURI": "mime:application/json", "content": "{\"server\": {\"name\": \"new-name\"}}" } - ], - "requestPath": "/v2.1/servers/resource-uuid" + ] } ``` -## Field Definitions +## Field Reference ### Top-Level Fields | Field | Type | Description | |-------|------|-------------| -| `id` | UUID | Unique event identifier. Use with `hermes_get_event`. | -| `eventType` | string | Always `activity` for API actions. | -| `eventTime` | ISO 8601 | UTC timestamp of when the action occurred. | -| `action` | string | The operation: `create`, `update`, `delete`, `read`, `authenticate`, etc. | -| `outcome` | string | `success`, `failure`, or `pending`. | -| `requestPath` | string | The API endpoint path that was called. | - -### Initiator (Who) - -| Field | Description | -|-------|-------------| -| `initiator.id` | Keystone user UUID | -| `initiator.name` | Human-readable username (this is what you filter on) | -| `initiator.typeURI` | Always `service/security/account/user` for human users | -| `initiator.domain_id` | Domain the user belongs to | -| `initiator.project_id` | Project scope of the action | - -### Target (What Was Acted On) - -| Field | Description | -|-------|-------------| -| `target.id` | UUID of the resource (server, port, volume, etc.) | -| `target.typeURI` | Resource type in slash format (see table below) | -| `target.name` | Human-readable resource name (if available) | -| `target.project_id` | Project that owns the resource | - -### Observer (Which Service Recorded It) - -| Field | Description | -|-------|-------------| -| `observer.id` | Service instance UUID | -| `observer.typeURI` | Service type (e.g., `service/compute`) | -| `observer.name` | Service name (e.g., `nova`, `neutron`) | - -### Attachments (Request/Response Details) - -| Field | Description | -|-------|-------------| -| `attachments[].name` | Typically `payload` or `request`/`response` | -| `attachments[].typeURI` | MIME type, usually `mime:application/json` | -| `attachments[].content` | JSON string of the request body or response | - -Attachments are the key to answering "what exactly changed?" — they contain the API request payload showing which fields were modified and to what values. +| `typeURI` | string | Always `http://schemas.dmtf.org/cloud/audit/1.0/event` | +| `id` | string | Unique event UUID | +| `eventTime` | string | ISO 8601 timestamp (UTC) when the event occurred | +| `eventType` | string | Usually `activity` | +| `action` | Action | The operation: `create`, `update`, `delete`, `read`, `authenticate`, `start`, `stop` | +| `outcome` | Outcome | Result: `success`, `failure`, `pending` | +| `reason` | Reason | HTTP-level result (reasonType + reasonCode) | +| `initiator` | Resource | Who performed the action | +| `target` | Resource | What was acted upon | +| `observer` | Resource | Which service recorded it | +| `requestPath` | string | Original HTTP request path | +| `attachments` | []Attachment | Request/response payloads (may be empty) | + +### Resource Fields (initiator, target, observer) -## Common target_type Values +| Field | Type | Description | +|-------|------|-------------| +| `typeURI` | string | Resource type in slash format (e.g., `compute/server`) | +| `id` | string | UUID of the resource | +| `name` | string | Human-readable name | +| `domain` | string | Domain name (for initiator) | +| `host` | Host | Client host info (for initiator) | +| `project_id` | string | OpenStack project UUID (SAP CC extension) | +| `domain_id` | string | OpenStack domain UUID (SAP CC extension) | +| `project_name` | string | Project name (SAP CC extension) | +| `domain_name` | string | Domain name (SAP CC extension) | +| `app_credential_id` | string | If authenticated via app credential | + +### Host Fields (initiator.host) -### Compute (Nova) - -| target_type | Resource | -|-------------|----------| -| `compute/server` | Virtual machine instance | -| `compute/keypair` | SSH keypair | -| `compute/server-group` | Server anti-affinity group | -| `compute/flavor` | Instance type definition | - -### Networking (Neutron) - -| target_type | Resource | -|-------------|----------| -| `network/port` | Virtual network interface | -| `network/network` | Virtual network | -| `network/subnet` | IP subnet | -| `network/router` | Virtual router | -| `network/security-group` | Security group | -| `network/security-group-rule` | Individual firewall rule | -| `network/floatingip` | Floating IP address | - -### Identity (Keystone) - -| target_type | Resource | -|-------------|----------| -| `identity/project` | Project/tenant | -| `identity/user` | User account | -| `identity/role-assignment` | Role grant/revoke | -| `identity/application-credential` | App credential | -| `identity/OS-TRUST/trust` | Trust delegation | - -### Block Storage (Cinder) - -| target_type | Resource | -|-------------|----------| -| `storage/volume` | Block volume | -| `storage/snapshot` | Volume snapshot | -| `storage/backup` | Volume backup | - -### DNS (Designate) - -| target_type | Resource | -|-------------|----------| -| `dns/zone` | DNS zone | -| `dns/recordset` | DNS record set | - -### Load Balancing (Octavia) - -| target_type | Resource | -|-------------|----------| -| `load-balancer/loadbalancer` | Load balancer | -| `load-balancer/listener` | LB listener | -| `load-balancer/pool` | LB backend pool | -| `load-balancer/member` | Pool member | - -### Object Storage (Swift) - -| target_type | Resource | -|-------------|----------| -| `object-store/container` | Swift container | -| `object-store/object` | Stored object | - -## Common action Values - -| Action | Meaning | -|--------|---------| -| `create` | New resource created | -| `update` | Existing resource modified | -| `delete` | Resource removed | -| `read` | Resource details retrieved (not always tracked) | -| `authenticate` | Login/token creation | -| `start` | Server/service started | -| `stop` | Server/service stopped | -| `reboot` | Server rebooted | -| `attach` | Volume/port attached | -| `detach` | Volume/port detached | -| `resize` | Server flavor changed | -| `migrate` | Server moved to different host | - -## Querying Tips - -**By resource lifecycle:** -``` -target_id=, sort=time:asc → full creation-to-deletion history -``` +| Field | Type | Description | +|-------|------|-------------| +| `address` | string | Client IP address | +| `agent` | string | User agent string (e.g., `python-openstackclient/6.0.0`) | -**By user activity:** -``` -initiator_name=, time_gte= → all actions by user in window -``` +### Attachment Fields -**By failure investigation:** -``` -outcome=failure, target_type=compute/server → all failed compute operations -``` +| Field | Type | Description | +|-------|------|-------------| +| `name` | string | Attachment label (e.g., `payload`, `request`, `response`) | +| `typeURI` | string | MIME type (e.g., `mime:application/json`) | +| `content` | any | Serialized content (usually JSON string) | -**By security review:** -``` -action=authenticate, outcome=failure → failed login attempts -action=delete, target_type=identity/role-assignment → permission removals +## Common target_type Values + +Obtained via `hermes_list_attributes(attribute="target_type")`: + +| target_type | Service | Resource | +|-------------|---------|----------| +| `compute/server` | Nova | Virtual machine | +| `network/port` | Neutron | Network port | +| `network/security-group` | Neutron | Security group | +| `network/floatingip` | Neutron | Floating IP | +| `network/router` | Neutron | Router | +| `volume/volume` | Cinder | Block storage volume | +| `identity/project` | Keystone | Project | +| `identity/user` | Keystone | User account | +| `identity/credential` | Keystone | Application credential | +| `dns/zone` | Designate | DNS zone | +| `dns/recordset` | Designate | DNS record | +| `image/image` | Glance | VM image | +| `key-manager/secret` | Barbican | Secret/certificate | +| `loadbalancer/loadbalancer` | Octavia | Load balancer | +| `share/share` | Manila | File share | + +## CLI Reference + +For command-line audit access outside of agents: [hermescli](https://github.com/sapcc/hermescli) + +```bash +# List recent events +hermescli event list --time '>=2024-03-15T00:00:00Z' + +# Filter by target type +hermescli event list --target-type compute/server + +# Get event detail +hermescli event show ``` diff --git a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md index 368925b..06a7881 100644 --- a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md +++ b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md @@ -1,168 +1,69 @@ --- name: sapcc-autoscaling description: > - Autoscaling operations via Castellum in SAP Converged Cloud. - Triggers: autoscaling, castellum, auto-resize, quota scaling, capacity management, resize operation + Autoscaling operations via Castellum. Triggers: autoscaling, castellum, resize, + scaling, threshold, auto-resize, capacity management. NOT for: manual quota + changes (use sapcc-quota/Limes). version: 1.0.0 metadata: service: [castellum] - task: [inspect, monitor, debug] - persona: [platform-engineer, devops] + task: [manage, inspect, debug] + persona: [developer, platform-engineer] --- # SAP CC Autoscaling (Castellum) -Inspect Castellum autoscaling: check resource configurations, view pending operations, and diagnose failed resize attempts. Castellum automatically adjusts project quotas and resource sizes based on configured thresholds. +Inspect Castellum autoscaling state: view resource configurations, check pending resize operations, diagnose failed scaling actions. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `castellum_get_project_resources` | Get autoscaling config and status for a project | `project_id` (UUID, required) | -| `castellum_list_pending_operations` | List scheduled but incomplete resize operations | `project_id`, `asset_type` | -| `castellum_list_recently_failed_operations` | List recent resize failures | `project_id`, `asset_type`, `max_age` (default: 1d) | - -## What Castellum Does - -Castellum watches resource usage and automatically resizes when thresholds are hit: - -``` -Usage crosses HIGH threshold → Castellum schedules UPSIZE -Usage drops below LOW threshold → Castellum schedules DOWNSIZE -``` - -Assets it manages: -- `project-quota:compute:cores` — vCPU quota -- `project-quota:compute:ram` — RAM quota -- `project-quota:compute:instances` — instance count quota -- `project-quota:block-storage:capacity` — volume storage -- NFS share sizes, server root disks, etc. +| `castellum_get_project_resources` | Get autoscaling config and resource status | `project_id` (UUID, required) | +| `castellum_list_pending_operations` | List scheduled but incomplete resizes | `project_id` (UUID), `asset_type` (e.g. `project-quota:compute:cores`) | +| `castellum_list_recently_failed_operations` | List recently failed resizes | `project_id` (UUID), `asset_type` | ## Gotchas -### 1. Castellum manages QUOTA, not individual resources - -Castellum doesn't scale your application. It adjusts quotas and resource sizes. For example, it can increase your project's compute cores quota when usage exceeds 80%, but it doesn't create new servers. - -### 2. project_id is required — you must know which project - -Unlike other tools, Castellum requires an explicit project_id. Get it from `keystone_token_info` or `keystone_list_projects`. +1. **project_id must be a valid UUID.** The tool validates UUID format and rejects anything else. You cannot pass a project name — resolve it to UUID first via Keystone if needed. -### 3. Operations can be PENDING without issues +2. **asset_type uses a colon-separated hierarchy.** Format is `project-quota::`, e.g., `project-quota:compute:cores` or `project-quota:network:floating_ips`. Getting this format wrong returns empty results without an error. -A pending operation means a resize is scheduled. This is normal — Castellum batches operations and may wait for cooldown periods between resizes. +3. **Castellum watches Limes, does not replace it.** Castellum monitors usage via Limes and triggers resize operations. The actual quota values live in Limes. To see current quota/usage, use `limes_get_project_quota`. Castellum shows the autoscaling configuration and pending/failed operations. -### 4. Failed operations have a reason +4. **Pending does not mean stuck.** Pending operations are queued resizes that have not yet been executed. Castellum processes these asynchronously. Only flag operations that have been pending for an unusually long time (hours). -`castellum_list_recently_failed_operations` shows WHY a resize failed. Common reasons: -- Quota exceeded at the domain level (project can't grow) -- Backend capacity exhausted -- Conflicting resize already in progress +5. **Failed operations include the failure reason.** The `recently-failed` endpoint shows why a resize failed (e.g., parent quota exhausted, constraint violation). This is the primary diagnostic tool for scaling failures. -### 5. max_age controls the failure lookback window +6. **Not all resources are auto-scalable.** Only resources that have Castellum configuration will appear. If `get_project_resources` returns empty or missing resources, those resources are not configured for autoscaling. -Default is `1d` (24 hours). Use `7d` for broader investigation, `12h` for recent issues only. Accepts Go duration format: `s`, `m`, `h`, `d`. - -### 6. asset_type filters are specific strings - -Format: `project-quota::`. Examples: -- `project-quota:compute:cores` -- `project-quota:compute:ram` -- `project-quota:block-storage:capacity` - -### 7. Castellum only acts on configured resources - -Not all resources have autoscaling configured. `castellum_get_project_resources` shows which resources ARE configured and their thresholds. No configuration = no autoscaling. - -### 8. Cooldown prevents thrashing - -After a resize, Castellum waits before acting again. If you see a resource at threshold but no pending operation, it may be in cooldown. +7. **Thresholds define when scaling triggers.** Each configured resource has usage thresholds (high/low) that trigger upscale/downscale. The `get_project_resources` response shows these thresholds alongside current usage. ## Common Workflows -### Check Autoscaling Configuration - -``` -1. keystone_token_info → get current project_id -2. castellum_get_project_resources(project_id=) -3. Review: which resources are configured, thresholds, current status -``` - -### Are There Pending Resizes? - -``` -1. castellum_list_pending_operations(project_id=) -2. If empty: no scheduled resizes (normal) -3. If populated: review what's being resized and when -``` - -### Diagnose Autoscaling Failures - -``` -1. castellum_list_recently_failed_operations(project_id=, max_age=7d) -2. Review failure reasons -3. Common: domain quota ceiling hit, backend capacity -4. Cross-reference with Limes: limes_get_project_quota → is project at domain cap? -``` - -### "Why didn't my quota grow?" - -``` -1. castellum_get_project_resources(project_id) → is the resource configured? -2. If not configured: autoscaling won't act -3. If configured: check thresholds — is usage actually above HIGH? -4. castellum_list_recently_failed_operations → did it try and fail? -5. Check cooldown — did it resize recently and is waiting? -``` - -### Correlate with Quota - -``` -1. castellum_get_project_resources(project_id) → autoscaling config -2. limes_get_project_quota → current quota and usage -3. Compare: is usage near threshold? Has quota been growing? -``` - -## Troubleshooting - -### No autoscaling configured for a resource - -- Castellum configuration is per-resource, per-project -- Not all projects have autoscaling enabled -- Configuration requires admin or project-admin access (not via MCP tools) - -### Operations keep failing - -- Check `castellum_list_recently_failed_operations(max_age=7d)` for patterns -- If "domain quota exceeded": the project has hit its domain-level cap. Need domain admin to increase domain quota. -- If "backend capacity": physical capacity exhausted in the region/AZ -- If "conflicting operation": wait for the existing operation to complete - -### Autoscaling seems too slow +### Check Autoscaling Configuration for a Project -- Castellum has deliberate cooldown periods (typically 5-15 minutes) -- It batches multiple threshold crossings -- For urgent needs: manual quota adjustment via Limes is faster +1. `castellum_get_project_resources` with `project_id=` — see all configured resources and their thresholds. +2. For each resource, note the high/low thresholds and current usage percentage. +3. Cross-reference with `limes_get_project_quota` to see actual quota values. -### Resource at threshold but no pending operation +### Diagnose Why a Resource Did Not Autoscale -- Cooldown period active (recent resize within last N minutes) -- Castellum may not have polled yet (typical interval: 5 minutes) -- Resource may not be configured for autoscaling +1. `castellum_list_recently_failed_operations` with `project_id=` — check for failures. +2. If failures exist, read the error reason (typically quota exhaustion at parent level). +3. If no failures and no pending ops, `castellum_get_project_resources` — check if thresholds are configured and if usage is actually above the high threshold. -## Security Considerations +### Monitor Pending Resize Operations -- Autoscaling configuration reveals capacity management strategy -- Failed operations reveal infrastructure limits and bottlenecks -- project_id in URLs is validated (UUID format) to prevent injection -- Autoscaling policies are read-only via MCP — no configuration changes possible +1. `castellum_list_pending_operations` — see all queued resizes (optionally filter by project or asset_type). +2. Check timestamps — operations pending for over an hour may indicate a processing backlog. +3. If a specific resource is pending, verify the parent quota has headroom via Limes. ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| Current quota and usage | Limes | `limes_get_project_quota(project_id=)` | -| Domain-level quota cap | Limes | `limes_get_domain_quota(domain_id=)` | -| Project identity | Keystone | `keystone_token_info`, `keystone_list_projects` | -| Who changed autoscaling config | Hermes | `hermes_list_events(target_type=castellum)` | -| Compute resources being scaled | Nova | `nova_list_servers` (to see actual usage) | +| Current quota and usage values | Limes | `limes_get_project_quota` | +| Project UUID from name | Keystone | `keystone_list_projects` | +| Audit trail of resize actions | Hermes | `hermes_list_events(initiator_name=castellum)` | +| Compute resource details | Nova | `nova_list_servers` | diff --git a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md index 9790ccf..e995b4e 100644 --- a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md +++ b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md @@ -1,152 +1,69 @@ --- name: sapcc-baremetal description: > - Bare metal node management via Ironic in SAP Converged Cloud. - Triggers: bare metal, ironic, physical server, baremetal, BMC, IPMI, redfish, hardware + Bare metal node operations via Ironic. Triggers: baremetal, bare metal, ironic, + node, provision state, hardware, physical server. NOT for: virtual servers + (use sapcc-compute/Nova). version: 1.0.0 metadata: service: [ironic] - task: [inspect, manage, debug] - persona: [platform-engineer] + task: [manage, inspect, debug] + persona: [platform-engineer, operator] --- # SAP CC Bare Metal (Ironic) -Inspect Ironic bare metal nodes: list nodes, check provision/power states, and understand maintenance status. Ironic manages physical servers in the cloud, enabling bare metal provisioning alongside VMs. +Inspect baremetal nodes: list nodes, check provision/power states, understand maintenance status and driver configuration. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `ironic_list_nodes` | List baremetal nodes | `provision_state`, `maintenance`, `driver`, `resource_class`, `instance_uuid`, `fault`, `owner` | -| `ironic_get_node` | Full detail for a single node | `node_id` (UUID or name) | - -## Security Note - -**BMC credentials (IPMI/Redfish passwords) are intentionally excluded from responses.** The MCP server strips `driver_info` and `properties` fields that may contain hardware management credentials. This is a security boundary. +| `ironic_list_nodes` | List baremetal nodes with filters | `provision_state` (active, available, deploying, error), `maintenance` ("true" for maintenance-only), `driver` (ipmi, redfish), `resource_class` | +| `ironic_get_node` | Full detail for a single node | `node_id` (UUID or name, required) | ## Gotchas -### 1. Provision state is the lifecycle — not power state - -| provision_state | Meaning | -|----------------|---------| -| `available` | Ready to be provisioned (no instance) | -| `active` | Running an instance | -| `deploying` | Instance being deployed onto the node | -| `cleaning` | Being wiped between tenants | -| `error` | Failed operation — needs investigation | -| `manageable` | Enrolled but not yet made available | - -### 2. Power state is separate from provision state - -A node can be `provision_state=active` but `power_state=power off` (unexpected shutdown). Power states: `power on`, `power off`, `None` (unknown). - -### 3. Maintenance mode = node excluded from scheduling - -When `maintenance=true`, Nova will not schedule new instances to this node. Existing instances may still be running. Maintenance is set manually by operators or automatically on repeated failures. +1. **Sensitive fields are redacted.** The MCP tool intentionally omits `driver_info`, `driver_internal_info`, `instance_info`, and `properties` from `ironic_get_node` responses. These may contain BMC credentials (IPMI passwords, Redfish secrets). Do not tell users these fields are accessible. -### 4. instance_uuid links to Nova +2. **maintenance filter is a string "true", not a boolean.** Pass `maintenance="true"` as a string value. There is no `"false"` filter — omit the parameter to see all nodes regardless of maintenance status. -When a node has an instance deployed, `instance_uuid` contains the Nova server UUID. Use `nova_get_server(instance_uuid)` to see the VM running on this hardware. +3. **provision_state vs power_state.** `provision_state` is the lifecycle state (available, deploying, active, error). `power_state` is the physical state (power on, power off). A node can be `active` provision but `power off` if manually shut down. -### 5. resource_class determines scheduling +4. **"available" means ready for deployment.** In Ironic, `available` does not mean "has capacity" — it means the node is enrolled, inspected, cleaned, and ready to receive a workload. `active` means already deployed. -Nodes declare their resource class (e.g., `baremetal`, `baremetal.large`). Nova flavors reference these classes to match workloads to appropriate hardware. +5. **instance_uuid links to Nova.** When a node is in `active` state, `instance_uuid` shows which Nova server is running on it. Use this to cross-reference with `nova_get_server`. -### 6. driver indicates management protocol +6. **node_id accepts UUID or name.** Unlike most OpenStack tools that require UUIDs, `ironic_get_node` accepts either the node UUID or its human-readable name. -Common drivers: `ipmi` (legacy BMC), `redfish` (modern REST-based BMC). The driver determines how the node is powered on/off and booted. - -### 7. fault indicates why a node is broken - -When a node enters error/maintenance, the `fault` field explains why: `power failure`, `clean failure`, `deploy failure`, etc. This is the first thing to check for broken nodes. - -### 8. Nodes are owned by projects - -The `owner` field shows which project can provision instances on this node. Filter by owner to see nodes allocated to your project. +7. **last_error contains deployment failure details.** When `provision_state=error`, the `last_error` field in the get response describes what went wrong (e.g., timeout during deploy, cleaning failure, network boot failure). ## Common Workflows -### Inventory Bare Metal Nodes - -``` -1. ironic_list_nodes() -2. Review: name/UUID, provision_state, power_state, maintenance -3. Flag any in error state or maintenance -``` - -### Find Available Nodes - -``` -1. ironic_list_nodes(provision_state=available, maintenance=false) -2. These nodes are ready for instance deployment -3. Check resource_class to match with desired flavor -``` - -### Check What Instance Runs on a Node - -``` -1. ironic_get_node(node_id=) → note instance_uuid -2. nova_get_server(server_id=) → instance details -``` - -### Troubleshoot a Node in Error - -``` -1. ironic_get_node(node_id=) → check fault field -2. Check maintenance flag — was it set automatically? -3. Check provision_state for last failed transition -4. hermes_list_events(target_type=baremetal/node, target_id=) -``` - -### Find Nodes by Owner Project - -``` -1. ironic_list_nodes(owner=) -2. Shows all nodes allocated to that project -3. Combine with provision_state filter for specific views -``` - -## Troubleshooting - -### Node in error state - -- Check `fault` field first — it describes the failure -- Common faults: `power failure` (BMC unreachable), `clean failure` (disk wipe failed), `deploy failure` (image deployment failed) -- Check Hermes for the triggering event - -### Node stuck in "deploying" - -- Deployment may have timed out -- Check if the node lost network connectivity during deploy -- BMC may be unresponsive — the node can't be power-cycled - -### Node in maintenance unexpectedly +### List Nodes Available for Deployment -- May have been set automatically after repeated failures -- Check `maintenance_reason` in node details -- Requires operator intervention to clear maintenance flag +1. `ironic_list_nodes` with `provision_state=available` — show nodes ready for workloads. +2. Filter by `resource_class` if you need a specific hardware profile. +3. Verify `maintenance=false` in results — maintenance nodes cannot be deployed to. -### Power state is "None" +### Diagnose a Node in Error State -- BMC is unreachable — can't determine actual power state -- Check network connectivity to the BMC/management network -- Driver may need reconfiguration +1. `ironic_list_nodes` with `provision_state=error` — find all error nodes. +2. `ironic_get_node` with `node_id=` — check `last_error` for the failure reason. +3. Check `maintenance` and `maintenance_reason` — the node may have been put in maintenance due to the error. +4. Check `fault` field for categorized fault information. -## Security Considerations +### Find the Physical Node Running a VM -- Node listings reveal physical infrastructure topology -- resource_class and driver info reveal hardware types and management protocols -- instance_uuid mapping reveals which workloads run on which physical hardware -- Physical access to BMC = full control of hardware — BMC credential exposure is critical -- Maintenance patterns reveal infrastructure health/reliability +1. Start with the Nova server UUID. +2. `ironic_list_nodes` — scan results for `instance_uuid` matching your server UUID. +3. Or if you know the node: `ironic_get_node` and check `instance_uuid`. ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| Instance on a node | Nova | `nova_get_server()` | -| Who modified node state | Hermes | `hermes_list_events(target_type=baremetal/node)` | -| Node network ports | Neutron | `neutron_list_ports(device_id=)` | -| Compute quota for baremetal | Limes | `limes_get_project_quota(service=compute)` | +| VM running on this node | Nova | `nova_get_server()` | +| Who modified node state | Hermes | `hermes_list_events(target_type=node)` | +| Compute quota (includes baremetal flavors) | Limes | `limes_get_project_quota(service=compute)` | +| Network ports attached to node | Neutron | `neutron_list_ports` | diff --git a/plugins/sapcc/skills/sapcc-dns/SKILL.md b/plugins/sapcc/skills/sapcc-dns/SKILL.md index 6dabf01..2e9fe5b 100644 --- a/plugins/sapcc/skills/sapcc-dns/SKILL.md +++ b/plugins/sapcc/skills/sapcc-dns/SKILL.md @@ -1,8 +1,9 @@ --- name: sapcc-dns description: > - DNS zone and recordset management via Designate in SAP Converged Cloud. - Triggers: dns, zone, recordset, designate, domain, A record, CNAME, MX, TXT, nameserver + DNS zone and recordset operations via Designate. Triggers: dns, zone, + recordset, domain, designate. NOT for: network ports, floating IPs (use + sapcc-networking). version: 1.0.0 metadata: service: [designate] @@ -12,115 +13,57 @@ metadata: # SAP CC DNS (Designate) -Manage DNS zones and recordsets: list zones, inspect zone details, and query recordsets. Designate is OpenStack's multi-tenant DNS-as-a-Service. +Manage DNS zones and recordsets: list zones, inspect zone details, query recordsets by type. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `designate_list_zones` | List DNS zones in current project | `name`, `status`, `type` (returns: ID, name, email, TTL, status, type, serial, created_at) | -| `designate_get_zone` | Full detail for a single zone | `zone_id` (UUID) | -| `designate_list_recordsets` | List recordsets in a zone | `zone_id` (required), `name`, `type`, `status`, `data` | +| `designate_list_zones` | List DNS zones with optional filters | `name`, `status` (ACTIVE, PENDING, ERROR), `type` (PRIMARY, SECONDARY) | +| `designate_get_zone` | Full detail for a single zone | `zone_id` (UUID, required) | +| `designate_list_recordsets` | List recordsets within a zone | `zone_id` (UUID, required), `name`, `type` (A, AAAA, CNAME, MX, TXT, etc.), `status` | ## Gotchas -### 1. Zones are project-scoped — you cannot see other projects' zones +1. **Zone names are FQDN with trailing dot.** Designate stores zone names like `example.com.` (note the trailing dot). When filtering by `name`, include the trailing dot or you will get zero results. -Each project manages its own DNS zones. If you expect to see a zone but don't, verify you're authenticated to the correct project with `keystone_token_info`. +2. **zone_id is required for recordsets.** You cannot list recordsets globally — you must first identify the zone UUID, then query recordsets within it. Always call `designate_list_zones` first if you only know the domain name. -### 2. Zone names MUST end with a dot +3. **Status PENDING means propagation in progress.** A zone or recordset in PENDING status has been accepted but is not yet live on nameservers. Do not treat PENDING as an error — wait and re-check. -DNS convention requires fully qualified domain names (FQDNs) to end with a trailing dot. `example.com.` is correct; `example.com` may not match filters. The API returns names with trailing dots. +4. **Recordset type must be uppercase.** The `type` filter expects uppercase values like `A`, `CNAME`, `MX`. Lowercase values will return no results without an error. -### 3. Recordsets require a zone_id — you cannot list all recordsets globally +5. **Multiple records per recordset.** A single recordset (e.g., type A) can contain multiple IP addresses in the `records` array. This is normal for round-robin DNS. -You must first identify the zone, then list its recordsets. Workflow: `designate_list_zones` → pick zone → `designate_list_recordsets(zone_id=...)`. +6. **SOA and NS recordsets are auto-managed.** Every zone has system-created SOA and NS recordsets. These cannot be modified and should be ignored when auditing user-created records. -### 4. Status transitions: PENDING → ACTIVE - -After creation or modification, zones and recordsets go through `PENDING` status before becoming `ACTIVE`. A `PENDING` record is not yet propagated to DNS servers. - -### 5. Recordset types matter for filtering - -Common types: `A` (IPv4), `AAAA` (IPv6), `CNAME` (alias), `MX` (mail), `TXT` (arbitrary text, SPF, DKIM), `SRV` (service locator), `NS` (nameserver delegation). - -### 6. The `data` filter searches record values - -Use `data` to find records pointing to a specific IP or target. For example, `data=10.0.1.5` finds all A records pointing to that IP. Useful for "what DNS names point to this server?" - -### 7. TTL controls cache duration - -TTL (Time To Live) in seconds controls how long resolvers cache the record. Low TTL (60-300s) = faster propagation of changes. High TTL (3600-86400s) = less DNS traffic but slower updates. Zone-level TTL is the default; recordset-level TTL overrides it. - -### 8. Zone type PRIMARY vs SECONDARY - -`PRIMARY` zones are authoritative — you manage records directly. `SECONDARY` zones are replicas of an external primary — records are read-only copies. Most user zones are PRIMARY. +7. **Serial number increments on every change.** The zone `serial` field is useful for verifying whether a recent change has been applied — compare before and after values. ## Common Workflows -### Discover DNS Zones in Project - -``` -1. designate_list_zones() -2. Review zones — note name, status, type -3. designate_get_zone(zone_id) for full detail -``` - -### Find All Records in a Zone - -``` -1. designate_list_zones() → identify target zone -2. designate_list_recordsets(zone_id=) -3. Scan results for A, CNAME, MX, TXT records -``` - -### "What DNS points to this IP?" - -``` -1. designate_list_zones() → get all zones -2. For each zone: designate_list_recordsets(zone_id=, data=) -3. Matches show which names resolve to that IP -``` - -### Verify DNS Configuration for a Service - -``` -1. designate_list_zones(name=) → find the zone -2. designate_list_recordsets(zone_id=, name=) → find specific record -3. Check: correct type, correct data, status=ACTIVE -``` - -## Troubleshooting - -### Zone not found - -- Verify zone name includes trailing dot: `example.com.` -- Check you're in the correct project: `keystone_token_info` -- Zone may be in another project — DNS is project-scoped - -### Recordset status is PENDING for > 5 minutes +### Find All Records for a Domain -- May indicate a backend issue — check Hermes audit trail -- `hermes_list_events(target_type=dns/recordset, outcome=failure)` +1. `designate_list_zones` with `name=example.com.` — get the zone UUID. +2. `designate_list_recordsets` with `zone_id=` — retrieve all recordsets. +3. Filter results by `type` if you only need specific record types (A, CNAME, etc.). -### DNS not resolving despite ACTIVE status +### Diagnose DNS Resolution Failure -- Check TTL — old cached value may not have expired at resolver -- Verify the zone's NS records point to correct nameservers -- Ensure the zone itself is ACTIVE (not just the recordset) +1. `designate_list_zones` with `name=` — confirm the zone exists and status is ACTIVE. +2. `designate_list_recordsets` with `zone_id=` and `name=` — check if the expected recordset exists. +3. If status is ERROR or PENDING, the issue is on the Designate side. If ACTIVE but resolution fails, the issue is downstream (caching, client config). -## Security Considerations +### Audit Zone Health -- DNS records reveal infrastructure topology (server IPs, service names) -- TXT records may contain verification tokens, SPF policies, or DKIM keys -- MX records expose mail server infrastructure -- Treat zone data as internal — it maps your service architecture +1. `designate_list_zones` with `status=ERROR` — find zones in error state. +2. For each error zone, `designate_get_zone` to inspect details and timestamps. +3. Cross-reference with `hermes_list_events` for the triggering action. ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| Server at an IP address | Nova | `nova_list_servers(ip=
)` | -| Who modified DNS records | Hermes | `hermes_list_events(target_type=dns/recordset)` | -| Load balancer VIP in DNS | Octavia | `octavia_get_loadbalancer` → check VIP address | -| Network for a DNS-referenced IP | Neutron | `neutron_list_ports` | +| Who modified a zone/recordset | Hermes | `hermes_list_events(target_type=zone)` | +| DNS quota for the project | Limes | `limes_get_project_quota(service=dns)` | +| Floating IP that should have a DNS record | Neutron | `neutron_list_floating_ips` | +| Server associated with an A record IP | Nova | `nova_list_servers` + filter by IP | diff --git a/plugins/sapcc/skills/sapcc-email/SKILL.md b/plugins/sapcc/skills/sapcc-email/SKILL.md new file mode 100644 index 0000000..4c20405 --- /dev/null +++ b/plugins/sapcc/skills/sapcc-email/SKILL.md @@ -0,0 +1,66 @@ +--- +name: sapcc-email +description: > + Email service operations via Cronus. Triggers: email, cronus, smtp, template, + email usage, sending. NOT for: monitoring alerts (use sapcc-metrics/Maia). +version: 1.0.0 +metadata: + service: [cronus] + task: [manage, inspect, debug] + persona: [developer, platform-engineer] +--- + +# SAP CC Email (Cronus) + +Inspect email service status: check sending usage and list available email templates for the current project. + +## MCP Tools + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `cronus_get_usage` | Get email sending usage and status | (none — scoped to current project) | +| `cronus_list_templates` | List available email templates | (none — scoped to current project) | + +## Gotchas + +1. **No parameters on either tool.** Both Cronus tools operate on the current project scope determined by the authenticated credentials. You cannot query a different project without re-scoping. + +2. **Usage is project-scoped, not user-scoped.** The usage endpoint shows aggregate email sending statistics for the entire project, not per-user breakdowns. + +3. **Templates are pre-configured, not arbitrary.** Cronus templates are set up by project administrators. You cannot create or modify templates via MCP tools — these are read-only inspection tools. + +4. **Cronus is SAP CC-specific.** This is not a standard OpenStack service. It is a SAP Converged Cloud extension for managed email sending. Do not confuse with external SMTP services or third-party email providers. + +5. **Rate limits and quotas are enforced server-side.** If usage shows high volume, the project may be approaching sending limits. The usage response typically includes rate/quota information. + +6. **Email sending failures are not shown here.** Cronus usage shows aggregate stats. For individual delivery failures or bounces, check application logs or the Cronus dashboard (not available via MCP). + +## Common Workflows + +### Check Email Service Status + +1. `cronus_get_usage` — inspect current sending volume, quotas, and status. +2. Verify the project has email sending enabled (non-error response). +3. Check usage against limits to determine remaining capacity. + +### List Available Templates for Integration + +1. `cronus_list_templates` — see all configured templates. +2. Note template names/IDs for use in application code. +3. Templates define the email structure; applications provide the dynamic content. + +### Diagnose Email Sending Issues + +1. `cronus_get_usage` — check if the project has hit sending limits. +2. If usage is near quota, sending may be throttled or blocked. +3. If the API returns an error, the Cronus service may not be enabled for this project. +4. For audit trail: `hermes_list_events` filtered to Cronus actions. + +## Cross-Service References + +| Need | Service | Tool | +|------|---------|------| +| Who configured email templates | Hermes | `hermes_list_events(target_type=template)` | +| Project identity and scope | Keystone | `keystone_list_projects` | +| Monitoring alerts (not email) | Maia | `maia_list_alerts` | +| Application credentials for SMTP auth | Keystone | `keystone_list_application_credentials` | diff --git a/plugins/sapcc/skills/sapcc-identity/SKILL.md b/plugins/sapcc/skills/sapcc-identity/SKILL.md index d48a284..fa7e781 100644 --- a/plugins/sapcc/skills/sapcc-identity/SKILL.md +++ b/plugins/sapcc/skills/sapcc-identity/SKILL.md @@ -22,7 +22,7 @@ metadata: | `keystone_list_projects` | List accessible projects | `domain_id`, `name` (optional filters) | | `keystone_create_application_credential` | Create app credential (secret shown once) | `name`, `description`, `expires_at`, `roles` | | `keystone_list_application_credentials` | List app creds for current user | None | -| `keystone_delete_application_credential` | Delete/revoke an app credential | `id` or `name` | +| `keystone_delete_application_credential` | Delete/revoke an app credential | `id` (UUID) | ## SAP CC Domain Model diff --git a/plugins/sapcc/skills/sapcc-images/SKILL.md b/plugins/sapcc/skills/sapcc-images/SKILL.md index ce280ef..a2384ac 100644 --- a/plugins/sapcc/skills/sapcc-images/SKILL.md +++ b/plugins/sapcc/skills/sapcc-images/SKILL.md @@ -1,144 +1,68 @@ --- name: sapcc-images description: > - Image management via Glance in SAP Converged Cloud. - Triggers: image, glance, VM image, OS image, snapshot, AMI, disk image, boot image + Image operations via Glance. Triggers: image, glance, ami, snapshot, + visibility, boot image. NOT for: container images (use sapcc-registry/Keppel). version: 1.0.0 metadata: service: [glance] - task: [inspect, manage, debug] + task: [manage, inspect, debug] persona: [developer, platform-engineer] --- # SAP CC Images (Glance) -Inspect Glance images: list available images, check details, and understand image properties. Glance stores disk images used to boot Nova servers. +Inspect and list VM images: find available boot images, check image status, understand visibility and format. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `glance_list_images` | List images available to current project | `name`, `status`, `visibility`, `owner` (returns: ID, name, status, visibility, disk/container format, size) | -| `glance_get_image` | Full detail for a single image | `image_id` (UUID) | +| `glance_list_images` | List images with optional filters | `name`, `status` (queued, saving, active, killed, deleted, deactivated), `visibility` (public, private, shared, community), `owner` (project ID) | +| `glance_get_image` | Full detail for a single image | `image_id` (UUID, required) | ## Gotchas -### 1. Visibility controls who can see and use an image +1. **Public images are shared across all projects.** When listing images without an `owner` filter, you will see both project-owned (private) and platform-provided (public) images. Use `visibility=private` to see only your project's images. -| Visibility | Who can see | Who can use | -|------------|-------------|-------------| -| `public` | Everyone | Everyone | -| `private` | Owner project only | Owner project only | -| `shared` | Owner + explicitly shared projects | Owner + shared projects | -| `community` | Everyone | Everyone (but not in default listings) | +2. **Size is in bytes, not GiB.** The `size` field is raw bytes. Divide by 1073741824 to get GiB. A null/zero size means the image data has not been uploaded yet (status will be `queued`). -Most production images are `public` (provided by platform team) or `private` (project-specific snapshots). +3. **min_disk and min_ram are constraints.** These values (GiB and MiB respectively) define the minimum flavor requirements to boot a server from this image. Nova will reject a boot request if the flavor does not meet these minimums. -### 2. Status "active" = ready to use +4. **Status "active" is the only bootable state.** Only images with `status=active` can be used to create servers. Images in `queued`, `saving`, or `deactivated` cannot be booted from. -Only `active` images can be used to boot servers. Other statuses: -- `queued`: Metadata created, no data uploaded yet -- `saving`: Data currently being uploaded -- `deactivated`: Administratively disabled (cannot boot, but data exists) -- `killed`: Upload failed +5. **Container image vs VM image confusion.** Glance manages VM/bare-metal boot images (qcow2, raw, vmdk). For OCI container images, use Keppel (`sapcc-registry`). Users frequently confuse these. -### 3. Size is in bytes — can be very large +6. **Deactivated images exist but cannot be used.** An admin can deactivate an image (e.g., due to CVE). It remains visible but cannot boot new servers. Existing servers using it are unaffected. -Image sizes are raw bytes. A typical Linux image is 2-10 GB. Convert: `size / 1024 / 1024 / 1024` for GiB. - -### 4. disk_format and container_format matter for compatibility - -| disk_format | Description | -|-------------|-------------| -| `vmdk` | VMware (most common in SAP CC) | -| `raw` | Uncompressed disk | -| `qcow2` | QEMU/KVM compressed | -| `vhd` | Hyper-V | - -Container format is almost always `bare` in practice. - -### 5. Public images are platform-provided — don't delete them - -Images with `visibility=public` are maintained by the SAP CC platform team. Your project uses them but doesn't own them. You can only modify/delete `private` images you own. - -### 6. Image properties contain OS metadata - -`glance_get_image` returns properties like `os_type`, `os_distro`, `os_version`, `hw_vif_model`, `hypervisor_type`. Use these to identify the operating system and compatibility requirements. - -### 7. Snapshots are private images created from servers - -When you snapshot a server, it creates a private Glance image. These consume image quota and storage. Old snapshots should be cleaned up. - -### 8. owner field is a project UUID - -The `owner` filter accepts a project UUID. Use `keystone_list_projects` to find project UUIDs if needed. +7. **disk_format determines hypervisor compatibility.** Common formats: `qcow2` (KVM), `vmdk` (VMware), `raw`. In SAP CC, most images are `vmdk` for vSphere-based regions. ## Common Workflows ### Find Available Boot Images -``` -1. glance_list_images(visibility=public, status=active) -2. Review: name, size, disk_format -3. Look for naming patterns: "Ubuntu 22.04", "SLES 15 SP5", "Windows 2022" -``` - -### Find Project Snapshots - -``` -1. glance_list_images(visibility=private, owner=) -2. These are your project's server snapshots -3. Check sizes and dates for cleanup candidates -``` - -### Inspect Image Before Booting - -``` -1. glance_get_image(image_id=) -2. Check: status=active, disk_format compatible with your hypervisor -3. Note: min_disk, min_ram requirements -4. Review os_distro, os_version for the OS -``` - -### Find Image Used by a Server - -``` -1. nova_get_server(server_id) → note image reference -2. glance_get_image(image_id) → full image details -``` - -## Troubleshooting - -### Image not found - -- Image may be private to another project -- Image may have been deleted — check Hermes audit trail -- Try without filters to see all accessible images - -### Cannot boot server from image - -- Check image status is `active` (not `deactivated` or `queued`) -- Check `min_disk` and `min_ram` — flavor must meet minimums -- Verify disk_format is compatible with the target hypervisor +1. `glance_list_images` with `status=active` and `visibility=public` — see platform-provided images. +2. Note `min_disk` and `min_ram` to determine flavor requirements. +3. Check `disk_format` matches the target hypervisor in your availability zone. -### Image in "queued" status for a long time +### Check Why a Server Boot Failed Due to Image -- Upload may have failed silently -- Check Hermes: `hermes_list_events(target_type=image, target_id=)` -- May need to delete and re-upload +1. `glance_get_image` with the image UUID from the failed server request. +2. Verify `status=active` — if not, the image is unusable. +3. Check `min_disk` and `min_ram` against the chosen flavor — insufficient resources cause boot failure. +4. Confirm `disk_format` is compatible with the target region's hypervisor. -## Security Considerations +### List Project-Owned Snapshots -- Private images may contain sensitive configurations or credentials baked in -- Image names and properties reveal infrastructure stack (OS versions, patch levels) -- Old, unpatched images are a security risk — check os_version against known CVEs -- Snapshots may capture ephemeral credentials or session data from running servers +1. `glance_list_images` with `visibility=private` and `owner=`. +2. These are typically server snapshots or custom-uploaded images. +3. Check `size` to understand storage consumption (counts toward image quota). ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| Servers using an image | Nova | `nova_list_servers(image=)` | -| Who created/deleted images | Hermes | `hermes_list_events(target_type=image)` | -| Image quota usage | Limes | `limes_get_project_quota(service=image)` | -| Flavors meeting min_disk/min_ram | Nova | `nova_list_flavors` | +| Server using this image | Nova | `nova_list_servers` (image field in response) | +| Image quota for the project | Limes | `limes_get_project_quota(service=image)` | +| Who uploaded/deleted an image | Hermes | `hermes_list_events(target_type=image)` | +| Container images (OCI) | Keppel | `keppel_list_repositories` | diff --git a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md index 022e4d0..3d9e607 100644 --- a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md +++ b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md @@ -1,164 +1,71 @@ --- name: sapcc-loadbalancer description: > - Load balancer management via Octavia in SAP Converged Cloud. - Triggers: load balancer, octavia, listener, pool, VIP, health monitor, L7, reverse proxy, LB + Load balancer operations via Octavia. Triggers: load balancer, lb, listener, + pool, vip, octavia, l7. NOT for: network ports or security groups (use + sapcc-networking). version: 1.0.0 metadata: service: [octavia] - task: [inspect, manage, debug] + task: [manage, inspect, debug] persona: [developer, platform-engineer] --- -# SAP CC Load Balancers (Octavia) +# SAP CC Load Balancer (Octavia) -Inspect Octavia load balancers: list LBs, view listeners and pools, and troubleshoot connectivity. Octavia provides L4/L7 load balancing as a service. +Manage Octavia load balancers: list/inspect LBs, listeners, and pools. Understand the LB topology and troubleshoot provisioning issues. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `octavia_list_loadbalancers` | List LBs in current project | `name`, `provisioning_status`, `operating_status`, `vip_address`, `vip_subnet_id`, `provider` | -| `octavia_get_loadbalancer` | Full detail for a single LB | `loadbalancer_id` (UUID) | -| `octavia_list_listeners` | List listeners | `loadbalancer_id`, `name`, `protocol`, `protocol_port` | -| `octavia_list_pools` | List backend pools | `loadbalancer_id`, `name`, `protocol`, `lb_algorithm` | - -## Octavia Object Model - -``` -Load Balancer (VIP address) -├── Listener (protocol:port — e.g., HTTPS:443) -│ └── Pool (backend group) -│ ├── Member (backend server:port) -│ ├── Member -│ └── Health Monitor (checks member health) -└── Listener (HTTP:80) - └── Pool - └── Members... -``` +| `octavia_list_loadbalancers` | List LBs with optional filters | `name`, `provisioning_status` (ACTIVE, PENDING_CREATE, ERROR), `vip_address` | +| `octavia_get_loadbalancer` | Full detail for a single LB | `loadbalancer_id` (UUID, required) | +| `octavia_list_listeners` | List listeners across LBs | `name`, `protocol` (TCP, HTTP, HTTPS, TERMINATED_HTTPS, UDP, SCTP), `loadbalancer_id` | +| `octavia_list_pools` | List backend pools | `name`, `protocol` (TCP, HTTP, HTTPS, PROXY, UDP, SCTP), `loadbalancer_id` | ## Gotchas -### 1. Two status fields — provisioning vs operating +1. **Two status fields: provisioning vs operating.** `provisioning_status` tracks the API/control plane state (ACTIVE, PENDING_CREATE, ERROR). `operating_status` tracks the data plane (ONLINE, DEGRADED, ERROR, NO_MONITOR). A LB can be ACTIVE provisioning but DEGRADED operating. -| Field | Meaning | -|-------|---------| -| `provisioning_status` | Infrastructure state: ACTIVE, PENDING_CREATE, PENDING_UPDATE, ERROR | -| `operating_status` | Traffic state: ONLINE, OFFLINE, DEGRADED, ERROR, NO_MONITOR | +2. **Immutable during PENDING states.** When `provisioning_status` is any PENDING_* value, no mutations are allowed on the LB or its children (listeners, pools). Wait for ACTIVE before making changes. -A LB can be `provisioning_status=ACTIVE` but `operating_status=DEGRADED` if some members are down. +3. **Listener protocol determines TLS handling.** `TERMINATED_HTTPS` means TLS terminates at the LB (requires certificate). `HTTPS` means passthrough — the LB forwards encrypted traffic without inspecting it. Do not confuse these. -### 2. VIP address is the entry point +4. **Pool lb_method values are algorithm names.** Common values: `ROUND_ROBIN`, `LEAST_CONNECTIONS`, `SOURCE_IP`. These are not free-text — use the exact enum values. -The Virtual IP (VIP) is what clients connect to. It's on a specific subnet. DNS should point to this address. The VIP does NOT change when backend members are added/removed. +5. **loadbalancer_id filter on listeners/pools is optional.** Without it, you get all listeners/pools in the project. Always filter by `loadbalancer_id` when investigating a specific LB to avoid confusion. -### 3. Listeners define what traffic to accept +6. **VIP address is on a subnet.** The `vip_address` is allocated from `vip_subnet_id`. If you need the network context, look up the subnet in Neutron. -Each listener binds protocol+port. You cannot have two listeners on the same port. Common patterns: -- HTTPS:443 (with TLS termination via Barbican cert) -- HTTP:80 (redirect to HTTPS or direct) -- TCP:3306 (database passthrough) - -### 4. Pools define where traffic goes - -A pool groups backend members (servers). Key attributes: -- `lb_algorithm`: ROUND_ROBIN, LEAST_CONNECTIONS, SOURCE_IP -- Members are server IP:port pairs -- Health monitor checks member availability - -### 5. TERMINATED_HTTPS means TLS terminates at the LB - -The LB decrypts HTTPS, forwards plain HTTP to backends. Requires a Barbican certificate reference. Backends only need to handle HTTP. - -### 6. Operating status NO_MONITOR = no health checks configured - -Without a health monitor, the LB cannot detect down members. Traffic goes to all members regardless of health. Always configure health monitors in production. - -### 7. Providers: amphora vs ovn - -- `amphora`: Full-featured (L7 rules, TLS termination, health monitors). Uses dedicated VMs. -- `ovn`: Lightweight L4 only. Lower overhead but fewer features. - -### 8. Filter listeners/pools by loadbalancer_id - -Without `loadbalancer_id` filter, you get ALL listeners/pools across all LBs in the project. Always filter by LB for a specific investigation. +7. **Topology: LB -> Listeners -> Pools -> Members.** Members are not exposed via MCP tools. You can see `default_pool_id` on listeners to trace which pool handles traffic. ## Common Workflows -### Inventory Load Balancers +### Map Full LB Topology -``` -1. octavia_list_loadbalancers() -2. Review: name, VIP address, provisioning/operating status -3. Flag any with operating_status != ONLINE -``` +1. `octavia_get_loadbalancer` with `loadbalancer_id` — note the VIP, status, and provider. +2. `octavia_list_listeners` with `loadbalancer_id=` — see all frontend listeners (protocol + port). +3. `octavia_list_pools` with `loadbalancer_id=` — see all backend pools and their algorithms. +4. Match `default_pool_id` from listeners to pool IDs to understand traffic flow. -### Full LB Topology +### Diagnose LB in ERROR State -``` -1. octavia_get_loadbalancer(loadbalancer_id=) → VIP, status -2. octavia_list_listeners(loadbalancer_id=) → what ports are open -3. octavia_list_pools(loadbalancer_id=) → backend groups + members -``` +1. `octavia_get_loadbalancer` — check `provisioning_status` and `operating_status`. +2. If provisioning ERROR: the control plane failed (network issue, quota, amphora boot failure). Check `hermes_list_events`. +3. If operating ERROR/DEGRADED: backend members are unhealthy. Pool health monitors are detecting failures. ### Find LB by VIP Address -``` -1. octavia_list_loadbalancers(vip_address=) -2. Or: neutron_list_ports() and match fixed_ips to the VIP -``` - -### Troubleshoot Degraded LB - -``` -1. octavia_get_loadbalancer(loadbalancer_id) → check operating_status -2. octavia_list_pools(loadbalancer_id) → check pool operating_status -3. If DEGRADED: some members are failing health checks -4. Verify backend servers are running: nova_list_servers -5. Check security groups allow health check traffic: neutron_list_security_groups -``` - -## Troubleshooting - -### LB provisioning_status is ERROR - -- Check Hermes: `hermes_list_events(target_type=loadbalancer)` for failure details -- Common causes: subnet full (no IP for VIP), quota exhausted, backend unavailable -- May need to delete and recreate - -### LB operating_status is OFFLINE - -- All members are failing health checks -- Check backend servers are running and healthy -- Verify security groups allow traffic from the LB subnet to member ports - -### operating_status is DEGRADED - -- Some but not all members are unhealthy -- Identify failing members via pool status -- Common: one server crashed or is overloaded - -### Listener on port 443 but no HTTPS - -- Check if listener protocol is `TERMINATED_HTTPS` (needs Barbican cert) -- Or `TCP` (passthrough — TLS handled by backend) -- `HTTP` on 443 works but is plain HTTP on a non-standard port - -## Security Considerations - -- VIP addresses reveal public-facing services -- Listener protocols reveal what services are exposed -- Pool members reveal backend server topology -- Health monitor endpoints may be unauthenticated — check they don't expose sensitive data -- TERMINATED_HTTPS references Barbican certificates — cert rotation matters +1. `octavia_list_loadbalancers` with `vip_address=` — returns the matching LB. +2. If no results, the IP may be a floating IP mapped to the VIP — check `neutron_list_floating_ips`. ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| VIP subnet details | Neutron | `neutron_list_subnets` | -| Backend server status | Nova | `nova_get_server()` | -| TLS certificates | Barbican | `barbican_get_secret` (cert referenced by listener) | -| Who modified the LB | Hermes | `hermes_list_events(target_type=loadbalancer)` | -| DNS pointing to VIP | Designate | `designate_list_recordsets(data=)` | -| LB quota | Limes | `limes_get_project_quota(service=network)` | +| Subnet details for VIP | Neutron | `neutron_get_subnet()` | +| Who created/modified the LB | Hermes | `hermes_list_events(target_type=loadbalancer)` | +| LB quota for the project | Limes | `limes_get_project_quota(service=network)` | +| Server behind a pool member | Nova | `nova_get_server()` | +| Floating IP pointing to VIP | Neutron | `neutron_list_floating_ips` | diff --git a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md index c9259ba..0d75501 100644 --- a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md @@ -1,126 +1,67 @@ --- name: sapcc-object-storage description: > - Object storage operations via Swift in SAP Converged Cloud. - Triggers: object storage, swift, container, blob, S3, bucket, object, file upload + Object storage operations via Swift. Triggers: object storage, swift, + container, bucket, blob, s3. NOT for: block storage/volumes (use + sapcc-storage/Cinder). version: 1.0.0 metadata: service: [swift] - task: [inspect, manage, debug] + task: [manage, inspect, debug] persona: [developer, platform-engineer] --- # SAP CC Object Storage (Swift) -Inspect Swift object storage: list containers, browse objects, and retrieve object metadata. Swift provides S3-compatible, project-scoped object/blob storage. +Inspect Swift containers and objects: list containers, browse object listings, check object metadata. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `swift_list_containers` | List containers in current account | `prefix`, `limit` (returns: name, object count, total bytes) | -| `swift_list_objects` | List objects in a container | `container` (required), `prefix`, `delimiter`, `limit` (returns: name, bytes, content_type, last_modified, hash) | -| `swift_get_object_metadata` | Get metadata for a specific object | `container` (required), `object` (required) (returns: content_type, content_length, etag, last_modified) | +| `swift_list_containers` | List containers in the account | `prefix` (name prefix filter), `limit` (number, default 100) | +| `swift_list_objects` | List objects in a container | `container` (name, required), `prefix`, `delimiter` (e.g. `/` for pseudo-dirs), `limit` (number, default 100) | +| `swift_get_object_metadata` | Get metadata for a specific object | `container` (name, required), `object` (name, required) | ## Gotchas -### 1. These tools provide metadata only — no object content retrieval +1. **This tool returns metadata only, never object content.** `swift_get_object_metadata` returns content_type, content_length, etag, and last_modified. It does NOT download or display the object body. You cannot read file contents through MCP tools. -You can list containers, list objects, and get object metadata. You CANNOT download or read object content through these tools. This prevents accidentally dumping large binary files into the LLM context. +2. **Container names are strings, not UUIDs.** Unlike most OpenStack resources, containers are identified by name, not UUID. Names are case-sensitive and URL-encoded. -### 2. Containers are flat namespaces with pseudo-directories +3. **Delimiter creates pseudo-directories.** Setting `delimiter=/` causes Swift to group objects by prefix and return `subdir` entries. This simulates directory browsing but Swift is a flat namespace internally. -Swift does not have real directories. Use `delimiter=/` to create pseudo-directory listings. Objects named `logs/2024/01/data.json` appear as directory `logs/` when using `delimiter=/`. +4. **Default limit is 100, not all objects.** Both list tools default to 100 results. Large containers may have thousands of objects. If you need to verify an object exists, use `prefix` to narrow results rather than paginating. -### 3. Object count and bytes are at container level +5. **Etag is MD5, not SHA.** The `etag` (hash) field in object listings and metadata is an MD5 digest. For large objects uploaded via SLO/DLO, the etag is an MD5 of the concatenated segment etags, not of the full content. -`swift_list_containers` shows aggregate stats per container. Use for quick capacity assessment. - -### 4. The `prefix` filter enables efficient browsing - -Use `prefix` to navigate pseudo-directories: -- `prefix=logs/` → all objects starting with "logs/" -- `prefix=logs/2024/` → narrow to a year -- Combined with `delimiter=/` → see only immediate "children" - -### 5. `limit` defaults to 100 - -Swift containers can hold millions of objects. Default returns first 100. Use prefix+delimiter for efficient navigation. - -### 6. `hash` is the MD5 of the object content (etag) - -Use it to verify object integrity or detect changes without downloading content. - -### 7. Container names are URL-safe strings, not UUIDs - -Unlike most OpenStack resources, containers are identified by name (not UUID). Case-sensitive. - -### 8. Object storage is eventually consistent for overwrites - -Read-after-write is consistent for new objects. Overwrites/deletes may take seconds to propagate. +6. **Bytes are raw bytes, not human-readable.** Container `bytes` and object `bytes`/`content_length` are in raw bytes. Divide by appropriate power of 1024 for KiB/MiB/GiB. ## Common Workflows -### Inventory Storage Containers - -``` -1. swift_list_containers() -2. Review: container names, object counts, total bytes -3. Identify large containers or unusual names -``` - ### Browse Container Contents -``` -1. swift_list_containers() → identify target -2. swift_list_objects(container=, delimiter="/") → top-level -3. swift_list_objects(container=, prefix=, delimiter="/") → drill down -``` +1. `swift_list_containers` — get an overview of all containers and their sizes. +2. `swift_list_objects` with `container=` and `delimiter=/` — browse top-level pseudo-directories. +3. `swift_list_objects` with `container=` and `prefix=subdir/` and `delimiter=/` — drill into a subdirectory. -### Check Object Details +### Check if a Specific Object Exists -``` -1. swift_list_objects(container=, prefix=) → find object -2. swift_get_object_metadata(container=, object=) -3. Review: size, content_type, last_modified -``` +1. `swift_list_objects` with `container=` and `prefix=`. +2. If the object appears in results, it exists. If empty results, it does not. +3. `swift_get_object_metadata` to confirm and get details (size, type, last modified). ### Assess Storage Usage -``` -1. swift_list_containers() → total bytes per container -2. Sum across containers for project total -3. Compare with quota: limes_get_project_quota(service=object-store) -``` - -## Troubleshooting - -### Container not found - -- Container names are case-sensitive — verify exact casing -- Check project scope: `keystone_token_info` - -### Object listing returns empty - -- Container may genuinely be empty -- `prefix` filter may be too restrictive — try without prefix - -### Large container — cannot see all objects - -- Use `prefix` + `delimiter` to navigate instead of listing all - -## Security Considerations - -- Object names and container names may reveal data classification -- `last_modified` timestamps reveal activity patterns -- Container listings expose data inventory — treat as confidential -- Never attempt to retrieve object content that might contain secrets +1. `swift_list_containers` — sum the `bytes` field across all containers for total usage. +2. Compare against quota from `limes_get_project_quota(service=object-store)`. +3. Identify large containers by `bytes` value and investigate with `swift_list_objects`. ## Cross-Service References | Need | Service | Tool | |------|---------|------| | Object storage quota | Limes | `limes_get_project_quota(service=object-store)` | -| Who uploaded/deleted objects | Hermes | `hermes_list_events(target_type=object-store/object)` | -| Container used by Keppel | Keppel | `keppel_list_accounts` (registry backing storage) | -| Project context | Keystone | `keystone_token_info` | +| Who modified containers/objects | Hermes | `hermes_list_events(target_type=container)` | +| Block storage (volumes) instead | Cinder | `cinder_list_volumes` | +| S3 compatibility endpoint | Keystone | `keystone_list_services` (look for s3 type) | diff --git a/plugins/sapcc/skills/sapcc-quota/SKILL.md b/plugins/sapcc/skills/sapcc-quota/SKILL.md index b751e26..0ac6de3 100644 --- a/plugins/sapcc/skills/sapcc-quota/SKILL.md +++ b/plugins/sapcc/skills/sapcc-quota/SKILL.md @@ -19,8 +19,8 @@ Limes is SAP CC's central quota management service. Not part of vanilla OpenStac | Tool | Purpose | Required Params | |------|---------|-----------------| | `limes_get_project_quota` | Quota + usage for a single project | `domain_id`, `project_id` (optional: `service`, `resource`) | -| `limes_get_domain_quota` | Aggregated quota for all projects in a domain | `domain_id` (optional: `service`, `resource`) | -| `limes_get_cluster_quota` | Cluster-wide capacity and usage | (optional: `service`, `resource`) | +| `limes_get_domain_quota` | Aggregated quota for all projects in a domain | `domain_id` (optional: `service`) | +| `limes_get_cluster_quota` | Cluster-wide capacity and usage | (optional: `service`) | ## Quota Model diff --git a/plugins/sapcc/skills/sapcc-secrets/SKILL.md b/plugins/sapcc/skills/sapcc-secrets/SKILL.md index 3a1b619..ada9434 100644 --- a/plugins/sapcc/skills/sapcc-secrets/SKILL.md +++ b/plugins/sapcc/skills/sapcc-secrets/SKILL.md @@ -1,120 +1,68 @@ --- name: sapcc-secrets description: > - Secret metadata management via Barbican (Key Manager) in SAP Converged Cloud. - Triggers: secret, key, certificate, barbican, key manager, encryption, passphrase, credential store + Secret management operations via Barbican. Triggers: secret, key, certificate, + barbican, key manager, encryption, tls cert. NOT for: application credentials + (use sapcc-identity/Keystone). version: 1.0.0 metadata: service: [barbican] - task: [inspect, audit, manage] - persona: [developer, security, platform-engineer] + task: [manage, inspect, debug] + persona: [developer, platform-engineer] --- # SAP CC Secrets (Barbican) -Inspect secret metadata stored in Barbican (OpenStack Key Manager). The MCP server provides metadata-only access — **secret payloads are never returned** for security. +Inspect secrets stored in the key manager: list secrets, check metadata, verify expiration. Payload is never exposed. ## MCP Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `barbican_list_secrets` | List secrets (metadata only) | `name`, `secret_type` (returns: secret_ref, name, status, secret_type, algorithm, bit_length, created, expiration) | -| `barbican_get_secret` | Get metadata for a single secret | `secret_id` (UUID) (returns: name, status, secret_type, algorithm, bit_length, mode, created, updated, expiration, content_types) | - -## Critical Security Note - -**The secret payload (the actual key/certificate/password value) is NEVER returned by these tools.** This is an intentional security boundary. The MCP server only exposes metadata — enough to inventory and audit secrets, but never enough to extract sensitive material. - -If a user asks to "show me the secret value" or "get the password from Barbican" — explain that this is not possible through these tools and is by design. +| `barbican_list_secrets` | List secrets (metadata only) | `name`, `secret_type` (symmetric, public, private, passphrase, certificate, opaque) | +| `barbican_get_secret` | Get metadata for a single secret | `secret_id` (UUID, required) | ## Gotchas -### 1. Payload is never returned — metadata only - -You will see name, type, algorithm, bit_length, expiration — but never the actual secret value. This is a security feature, not a limitation. - -### 2. Secret types determine usage context - -| Type | Typical Use | -|------|-------------| -| `symmetric` | Encryption keys (AES, etc.) | -| `public` | Public keys (RSA, EC) | -| `private` | Private keys (RSA, EC) | -| `passphrase` | Passwords, tokens | -| `certificate` | X.509 certificates | -| `opaque` | Arbitrary binary data | - -### 3. Status "ACTIVE" means ready for use - -Secrets go through: `PENDING` → `ACTIVE`. Only `ACTIVE` secrets can be consumed. - -### 4. Expiration is informational — Barbican does not auto-delete +1. **Payload is NEVER returned.** For security, the MCP tools only return secret metadata (name, type, algorithm, status, expiration). The actual secret value/payload is never exposed through these tools. Do not tell users you can retrieve secret contents. -A secret past its `expiration` date is still retrievable. The expiration field is advisory — it tells you the secret SHOULD have been rotated, but Barbican does not enforce it. +2. **secret_id is a UUID, not the secret_ref URL.** Barbican internally uses `secret_ref` (a full URL like `https://keymanager.example.com/v1/secrets/`). The MCP tool expects only the UUID portion, not the full URL. -### 5. secret_ref contains the UUID +3. **Expiration can be null.** Not all secrets have an expiration date. A null `expiration` means the secret never expires. Do not assume all secrets rotate automatically. -The `secret_ref` field is a full URL. Extract the UUID from the end for use with `barbican_get_secret`. +4. **secret_type determines usage context.** `certificate` = TLS certs (used by Octavia for TERMINATED_HTTPS). `symmetric` = encryption keys. `passphrase` = passwords. `opaque` = arbitrary data. Understanding the type helps identify what service consumes it. -### 6. Secrets are project-scoped +5. **Status "ACTIVE" means usable.** Secrets in non-ACTIVE states may have failed creation or been soft-deleted. Only ACTIVE secrets can be consumed by other services. -You only see secrets belonging to your current project. Cross-project secret sharing requires explicit ACLs. +6. **Certificates used by load balancers.** Octavia's TERMINATED_HTTPS listeners reference Barbican secrets of type `certificate`. To find which cert a listener uses, get the listener details and look for the certificate reference. -### 7. algorithm + bit_length describe cryptographic strength - -For symmetric keys: `AES` + `256` = AES-256. For asymmetric: `RSA` + `4096` = RSA-4096. Use this to audit security standards compliance. +7. **Algorithm and bit_length may be empty.** These fields are informational and only populated if the creator set them. They are not enforced by Barbican. ## Common Workflows -### Inventory All Secrets - -``` -1. barbican_list_secrets() -2. Review: name, type, status, expiration -3. Flag any with expired dates or weak algorithms -``` - -### Find Certificates Nearing Expiration - -``` -1. barbican_list_secrets(secret_type=certificate) -2. Check expiration field for each -3. Certificates past or near expiration need rotation -``` - -### Audit Secret Usage for Compliance - -``` -1. barbican_list_secrets() → full inventory -2. barbican_get_secret(secret_id) for each → detailed metadata -3. Cross-reference with Hermes: hermes_list_events(target_type=key-manager/secret) -``` - -## Troubleshooting - -### No secrets found +### List All Certificates Approaching Expiration -- Project may not use Barbican -- Check project scope: `keystone_token_info` -- Secrets may be stored under different names than expected +1. `barbican_list_secrets` with `secret_type=certificate` — get all certificate secrets. +2. Check `expiration` field on each result. +3. Flag any certificates expiring within 30 days for renewal. -### Secret status is not ACTIVE +### Find the Certificate Used by a Load Balancer -- `PENDING`: Store operation may have failed -- Check Hermes: `hermes_list_events(target_type=key-manager/secret, outcome=failure)` +1. `octavia_list_listeners` with `loadbalancer_id=` — find TERMINATED_HTTPS listeners. +2. Note the certificate container reference from the listener details. +3. `barbican_get_secret` with the secret UUID — verify certificate metadata and expiration. -## Security Considerations +### Audit Project Secrets -- Even metadata reveals security posture: weak algorithms, expired certs, naming patterns -- Secret names may reveal infrastructure details (service names, environments) -- Report expired secrets and weak algorithms (< AES-128, < RSA-2048) as findings -- Never suggest workarounds to extract secret payloads +1. `barbican_list_secrets` — enumerate all secrets in the project. +2. Group by `secret_type` to understand the distribution (certs vs keys vs passphrases). +3. Check `expiration` for any expired secrets that should be cleaned up. ## Cross-Service References | Need | Service | Tool | |------|---------|------| -| Who accessed/created secrets | Hermes | `hermes_list_events(target_type=key-manager/secret)` | -| Secret quota usage | Limes | `limes_get_project_quota(service=key-manager)` | -| TLS certificates for LBs | Octavia | `octavia_list_listeners` (TERMINATED_HTTPS uses Barbican) | -| Project identity context | Keystone | `keystone_token_info` | +| Which LB listener uses a certificate | Octavia | `octavia_list_listeners(loadbalancer_id=)` | +| Who created/accessed a secret | Hermes | `hermes_list_events(target_type=secret)` | +| Key manager quota | Limes | `limes_get_project_quota(service=key-manager)` | +| Application credentials (not secrets) | Keystone | `keystone_list_application_credentials` | diff --git a/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md new file mode 100644 index 0000000..1fff883 --- /dev/null +++ b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md @@ -0,0 +1,70 @@ +--- +name: sapcc-shared-storage +description: > + Shared file system operations via Manila. Triggers: shared file, manila, nfs, + cifs, file share, share network. NOT for: block storage (use sapcc-storage) + or object storage (use sapcc-object-storage). +version: 1.0.0 +metadata: + service: [manila] + task: [manage, inspect, debug] + persona: [developer, platform-engineer] +--- + +# SAP CC Shared Storage (Manila) + +Manage shared file systems: list shares, inspect share details, understand protocol and availability zone placement. + +## MCP Tools + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `manila_list_shares` | List shared file system shares | `name`, `status` (available, error, creating, deleting, error_deleting), `share_proto` (NFS, CIFS, GlusterFS, HDFS, CephFS) | +| `manila_get_share` | Full detail for a single share | `share_id` (UUID, required) | + +## Gotchas + +1. **share_proto filter is applied client-side.** The Manila API does not support `share_proto` as a query parameter. The MCP tool fetches all shares and filters locally. For large projects this may return more data than expected before filtering. + +2. **Size is in GiB.** The `size` field is in gibibytes, consistent with Cinder. Do not confuse with bytes or GB. + +3. **Status "available" = usable.** Only shares in `available` status can be mounted and accessed. Shares in `creating` are still being provisioned; shares in `error` need investigation. + +4. **Share type determines backend capabilities.** The `share_type_name` field indicates the storage backend and capabilities (e.g., replication, snapshots). Different types have different performance characteristics. + +5. **Availability zone matters for access.** A share in one AZ may not be directly accessible from compute instances in another AZ. Always check the `availability_zone` matches your server placement. + +6. **Export location not shown in list.** To get the mount path (export location), you need `manila_get_share` for the full detail. The list view shows metadata only. + +7. **Host field reveals backend placement.** The `host` field (e.g., `manila-host@backend#pool`) shows which storage backend serves the share. Useful for diagnosing backend-specific issues. + +## Common Workflows + +### List All NFS Shares and Their Status + +1. `manila_list_shares` with `share_proto=NFS` — get all NFS shares. +2. Group by `status`: `available` (healthy), `error` (needs attention), `creating` (in progress). +3. Note `size` and `availability_zone` for capacity planning. + +### Diagnose a Share in Error State + +1. `manila_get_share` with `share_id=` — get full details including error messages. +2. Check `status` and any error-related fields in the response. +3. `hermes_list_events` with `target_id=` — find the action that caused the error. +4. Verify quota was not exhausted at the time of creation. + +### Check Shared Storage Quota + +1. `limes_get_project_quota` with service `sharev2` — see share quota and usage. +2. Key resources: `shares` (count), `share_gigabytes` (total GiB), `share_snapshots`. +3. Compare usage against quota to determine headroom. + +## Cross-Service References + +| Need | Service | Tool | +|------|---------|------| +| Share quota and usage | Limes | `limes_get_project_quota(service=sharev2)` | +| Who created/modified a share | Hermes | `hermes_list_events(target_type=share)` | +| Server that mounts this share | Nova | `nova_get_server` (check mounted filesystems) | +| Block storage (per-server volumes) | Cinder | `cinder_list_volumes` | +| Network access for share | Neutron | `neutron_list_networks` | diff --git a/rules/sapcc-agent-rules.md b/rules/sapcc-agent-rules.md index d90f927..e1e399b 100644 --- a/rules/sapcc-agent-rules.md +++ b/rules/sapcc-agent-rules.md @@ -1,4 +1,6 @@ -# SAP Converged Cloud Guidance +# SAP Converged Cloud Agent Rules + +## Core Principles - Use the SAP CC MCP Server for all OpenStack/SAP CC interactions — it provides authenticated API access with credential isolation (secrets never reach the LLM). @@ -6,12 +8,113 @@ Load the skill and prefer its guidance over general knowledge. - SAP CC uses a Domain → Project hierarchy. Always be aware of the current project scope (check with `keystone_token_info` if uncertain). -- For any operation that creates or resizes resources, check quota first - via `limes_get_project_quota`. -- When debugging issues, check the audit trail (`hermes_list_events`) and - metrics (`maia_query`) before guessing at root causes. -- When uncertain about SAP CC-specific behavior (Limes, Hermes, Maia, - Keppel, Archer), load the relevant skill rather than guessing. -- Prefer application credentials over passwords. Use keychain storage for secrets. - SAP CC regions are independent deployments. Credentials and resources do not cross region boundaries. Region naming: `--` (e.g., `eu-de-1`). + +## Pre-Action Checks + +- **Check quota before creating resources.** Call `limes_get_project_quota` before + any Nova create, Cinder create, or Neutron resource creation. Quota errors are + confusing (generic 403/409) and avoidable. +- **Verify current state before mutating.** Call `nova_get_server`, `cinder_get_volume`, + etc. to confirm current status before performing actions. Stale assumptions cause + invalid state transitions. +- **Use audit trail for debugging.** Check `hermes_list_events` and metrics + (`maia_query`) before guessing at root causes. Evidence-based diagnosis > hypothesis. + +## Credential Safety + +- **Never expose credentials to the LLM.** Do not ask users to paste passwords, + tokens, or secrets into the conversation. The MCP server handles auth. +- **Prefer application credentials over passwords.** They are scoped, revocable, + and auditable. Guide users to `credential-setup` skill for initial auth. +- **Keychain storage is mandatory.** Secrets must be stored via OS keychain + (macOS Keychain, Linux `pass` or `secret-tool`). Never store in plaintext files. +- **Do not log or echo tokens.** If a tool response contains a token or password + field, do not repeat it in your response to the user. + +## Error Handling + +- **401 Unauthorized**: Credentials expired or invalid. Guide user to re-authenticate. + Do not retry — the token is dead. Run `credential-setup` skill. +- **403 Forbidden**: Insufficient permissions for this operation in the current project. + Check if the user has the correct role. Do not retry with same credentials. +- **404 Not Found**: Resource does not exist or is in a different project. Verify + the UUID and project scope before concluding it's deleted. +- **409 Conflict**: Resource is in a state that prevents the operation (e.g., volume + already attached, server in ERROR). Check current state and report why. +- **429 Too Many Requests**: Rate limited. Wait 10-30 seconds and retry once. + If still failing, reduce request frequency. Never tight-loop retry. +- **500/503 Server Error**: Infrastructure issue. Wait 30 seconds, retry once. + If persistent, inform user the service may be degraded. + +## Rate Limiting & Pagination + +- **Respect pagination.** Most list operations return limited results. If you need + comprehensive data (all servers, all ports), check for pagination markers and + make subsequent calls. Do not assume the first page is everything. +- **Avoid tight loops.** Space repeated API calls by at least 1-2 seconds. + Do not hammer endpoints with rapid sequential requests. +- **Use filters to reduce scope.** Prefer filtered queries (by status, name, ID) + over listing everything and filtering client-side. Less data = faster = fewer API calls. +- **Limit and sort.** Use `limit` parameters when you only need recent items + (e.g., `hermes_list_events(limit=20)` for recent audit events, not unlimited). + +## Destructive Operations + +- **Require user confirmation** before any operation that destroys data or interrupts + service: `delete`, `stop`, `HARD reboot`, `detach volume`, `remove security group`. +- **State what will happen** in the confirmation prompt: "This will permanently delete + server 'web-prod-1' (UUID: abc-123). All local disk data will be lost. Proceed?" +- **Never auto-chain destructive operations.** Do not delete-then-recreate without + explicit user approval of each step. +- **Inform about irreversibility.** Some operations cannot be undone: volume deletion + (data gone), security group removal (connectivity lost), project deletion. + +## Shared Resources & Multi-Tenancy + +- **Shared security groups affect multiple servers.** Before modifying a security group, + check which ports/servers reference it. A rule change can break other workloads. +- **Networks may be shared across projects.** The user may see networks owned by + other projects. Do not modify shared networks — those require network-admin role. +- **Floating IPs are a finite pool.** Do not allocate floating IPs speculatively. + Allocate only when the user has a clear external connectivity need. +- **RBAC visibility ≠ ownership.** Being able to see a resource does not mean you can + modify it. Check the resource's `project_id` against the current token scope. + +## Stop Conditions + +Stop and inform the user if: + +- **3 consecutive API errors** on the same operation — something is systemically wrong +- **Resource is in ERROR/irrecoverable state** — admin intervention required +- **Operation would exceed quota** — user needs to free resources or request increase +- **No audit events after 60 seconds** — event ingestion may be delayed, not missing +- **Operation requires admin role** — user credentials likely insufficient +- **You are unsure about a destructive action** — always ask, never assume + +## Maximum Depth Directives + +- **List operations**: Do not paginate more than 5 pages without asking the user + if they need the full list. Offer to filter instead. +- **Debugging workflows**: If you've checked 5+ potential causes without finding + the root cause, summarize findings and ask the user for additional context. +- **Retry loops**: Maximum 3 retries on any operation. After that, report the + persistent failure and suggest next steps. +- **Cross-service hops**: Maximum 3 levels of cross-service correlation without + surfacing findings. (e.g., server → ports → security groups: stop and report). + +## Role Awareness + +| Operation Category | Minimum Role | Note | +|-------------------|--------------|------| +| List/read operations | `member` | Most users have this | +| Create resources | `member` | But quota must allow it | +| Delete own resources | `member` | Only resources in current project | +| Modify shared resources | `network_admin` | Shared networks, RBAC policies | +| Cross-project operations | `admin` or `domain_admin` | Domain-level quota, user management | +| Image publishing | `cloud_image_admin` | Public image visibility | +| Audit log access | `audit_viewer` | May be restricted in some regions | + +If an operation requires a role the user likely doesn't have, inform them before +attempting it. Don't waste API calls on predictably-forbidden operations. From 15e17cda88f31a475aab9bbd02ee7ba821e8ffad Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Wed, 6 May 2026 19:34:33 -0700 Subject: [PATCH 02/11] docs: link service names to upstream repos in README skills table --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 53956c9..f2a8fc2 100644 --- a/README.md +++ b/README.md @@ -140,25 +140,25 @@ When you ask the agent a question, it auto-selects the appropriate skill: | Skill | Service | Key Capability | |-------|---------|----------------| -| [sapcc-compute](plugins/sapcc/skills/sapcc-compute/) | Nova | Server lifecycle, flavor selection, cross-service correlation | -| [sapcc-networking](plugins/sapcc/skills/sapcc-networking/) | Neutron | Network topology, security groups, connectivity debugging | -| [sapcc-storage](plugins/sapcc/skills/sapcc-storage/) | Cinder | Volume lifecycle, attachment states, performance tiers | -| [sapcc-identity](plugins/sapcc/skills/sapcc-identity/) | Keystone | Domain/project model, app credentials, service catalog | -| [sapcc-quota](plugins/sapcc/skills/sapcc-quota/) | Limes | Quota interpretation, capacity planning, usage tracking | -| [sapcc-audit](plugins/sapcc/skills/sapcc-audit/) | Hermes | CADF events, compliance queries, change investigation | -| [sapcc-metrics](plugins/sapcc/skills/sapcc-metrics/) | Maia | PromQL queries, metric discovery, monitoring | -| [sapcc-registry](plugins/sapcc/skills/sapcc-registry/) | Keppel | Container images, vulnerability status, federation | -| [sapcc-connectivity](plugins/sapcc/skills/sapcc-connectivity/) | Archer | Private endpoint services, service discovery | -| [sapcc-dns](plugins/sapcc/skills/sapcc-dns/) | Designate | DNS zones, recordsets, zone transfers | -| [sapcc-loadbalancer](plugins/sapcc/skills/sapcc-loadbalancer/) | Octavia | Load balancers, pools, health monitors | -| [sapcc-images](plugins/sapcc/skills/sapcc-images/) | Glance | Image management, properties, visibility | -| [sapcc-object-storage](plugins/sapcc/skills/sapcc-object-storage/) | Swift | Object storage, containers, large objects | -| [sapcc-secrets](plugins/sapcc/skills/sapcc-secrets/) | Barbican | Secret management, certificates, keys | -| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | Castellum | Autoscaling policies, resource operations | -| [sapcc-shared-storage](plugins/sapcc/skills/sapcc-shared-storage/) | Manila | Shared file systems, exports, share networks | -| [sapcc-baremetal](plugins/sapcc/skills/sapcc-baremetal/) | Ironic | Baremetal provisioning, node lifecycle | -| [sapcc-email](plugins/sapcc/skills/sapcc-email/) | Cronus | Email notifications, SMTP relay | -| [credential-setup](plugins/sapcc/skills/credential-setup/) | Keystone | Guided auth setup with keychain storage | +| [sapcc-compute](plugins/sapcc/skills/sapcc-compute/) | [Nova](https://github.com/openstack/nova) | Server lifecycle, flavor selection, cross-service correlation | +| [sapcc-networking](plugins/sapcc/skills/sapcc-networking/) | [Neutron](https://github.com/openstack/neutron) | Network topology, security groups, connectivity debugging | +| [sapcc-storage](plugins/sapcc/skills/sapcc-storage/) | [Cinder](https://github.com/openstack/cinder) | Volume lifecycle, attachment states, performance tiers | +| [sapcc-identity](plugins/sapcc/skills/sapcc-identity/) | [Keystone](https://github.com/openstack/keystone) | Domain/project model, app credentials, service catalog | +| [sapcc-quota](plugins/sapcc/skills/sapcc-quota/) | [Limes](https://github.com/sapcc/limes) | Quota interpretation, capacity planning, usage tracking | +| [sapcc-audit](plugins/sapcc/skills/sapcc-audit/) | [Hermes](https://github.com/sapcc/hermes) | CADF events, compliance queries, change investigation | +| [sapcc-metrics](plugins/sapcc/skills/sapcc-metrics/) | [Maia](https://github.com/sapcc/maia) | PromQL queries, metric discovery, monitoring | +| [sapcc-registry](plugins/sapcc/skills/sapcc-registry/) | [Keppel](https://github.com/sapcc/keppel) | Container images, vulnerability status, federation | +| [sapcc-connectivity](plugins/sapcc/skills/sapcc-connectivity/) | [Archer](https://github.com/sapcc/archer) | Private endpoint services, service discovery | +| [sapcc-dns](plugins/sapcc/skills/sapcc-dns/) | [Designate](https://github.com/openstack/designate) | DNS zones, recordsets, zone transfers | +| [sapcc-loadbalancer](plugins/sapcc/skills/sapcc-loadbalancer/) | [Octavia](https://github.com/openstack/octavia) | Load balancers, pools, health monitors | +| [sapcc-images](plugins/sapcc/skills/sapcc-images/) | [Glance](https://github.com/openstack/glance) | Image management, properties, visibility | +| [sapcc-object-storage](plugins/sapcc/skills/sapcc-object-storage/) | [Swift](https://github.com/openstack/swift) | Object storage, containers, large objects | +| [sapcc-secrets](plugins/sapcc/skills/sapcc-secrets/) | [Barbican](https://github.com/openstack/barbican) | Secret management, certificates, keys | +| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | [Castellum](https://github.com/sapcc/castellum) | Autoscaling policies, resource operations | +| [sapcc-shared-storage](plugins/sapcc/skills/sapcc-shared-storage/) | [Manila](https://github.com/openstack/manila) | Shared file systems, exports, share networks | +| [sapcc-baremetal](plugins/sapcc/skills/sapcc-baremetal/) | [Ironic](https://github.com/openstack/ironic) | Baremetal provisioning, node lifecycle | +| [sapcc-email](plugins/sapcc/skills/sapcc-email/) | [Cronus](https://github.com/sapcc/cronus) | Email notifications, SMTP relay | +| [credential-setup](plugins/sapcc/skills/credential-setup/) | [Keystone](https://github.com/openstack/keystone) | Guided auth setup with keychain storage | ### Rules From 13dcfbdf8c2da279f195165c9e347ae5a32daed1 Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Wed, 6 May 2026 19:36:14 -0700 Subject: [PATCH 03/11] feat: add PreToolUse hook to enforce destructive action approval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Behavioral rules in markdown are advisory — the LLM can rationalize past them. This hook is deterministic enforcement: it fires on every MCP tool call and blocks destructive operations (delete, stop, HARD reboot, security group removal, etc.) with exit code 2, requiring the user to individually approve each destructive action. Covers: Nova (stop/HARD reboot/delete), Cinder (delete volume/snapshot), Neutron (delete port/network/subnet/SG/FIP/router), Keystone (delete app cred/project), Designate (delete zone/recordset), Octavia (delete LB/pool/listener), Barbican (delete secret), Manila (delete share), Swift (delete container/object), Glance (delete image). Safe operations (list, get, create, soft reboot, start) pass through with exit 0 and no user friction. --- plugins/sapcc/.claude-plugin/settings.json | 12 ++ .../sapcc/hooks/destructive-action-gate.py | 132 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 plugins/sapcc/.claude-plugin/settings.json create mode 100755 plugins/sapcc/hooks/destructive-action-gate.py diff --git a/plugins/sapcc/.claude-plugin/settings.json b/plugins/sapcc/.claude-plugin/settings.json new file mode 100644 index 0000000..1d827e7 --- /dev/null +++ b/plugins/sapcc/.claude-plugin/settings.json @@ -0,0 +1,12 @@ +{ + "hooks": { + "PreToolUse": [ + { + "type": "command", + "command": "python3 plugins/sapcc/hooks/destructive-action-gate.py", + "timeout": 3000, + "matcher": "mcp__*" + } + ] + } +} diff --git a/plugins/sapcc/hooks/destructive-action-gate.py b/plugins/sapcc/hooks/destructive-action-gate.py new file mode 100755 index 0000000..776fac4 --- /dev/null +++ b/plugins/sapcc/hooks/destructive-action-gate.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +"""PreToolUse hook: Block destructive MCP tool calls unless user approves. + +This hook fires before any MCP tool invocation. If the tool is classified +as destructive (deletes data, stops services, modifies security), it blocks +the call and requires explicit user approval. + +The LLM's behavioral rules say "ask before destructive actions" — this hook +ENFORCES it. The LLM cannot rationalize past a hook. + +Exit codes: + 0 = allow (tool is safe or already approved) + 2 = block with message (destructive, needs approval) +""" +from __future__ import annotations + +import json +import os +import sys + +# --- Destructive tool patterns --- +# Format: tool_name → human-readable consequence +DESTRUCTIVE_TOOLS: dict[str, str] = { + # Nova - compute + "nova_server_action": None, # Special: only some actions are destructive + # Cinder - storage + "cinder_delete_volume": "Permanently deletes the volume and all data on it. Cannot be undone.", + "cinder_delete_snapshot": "Permanently deletes the volume snapshot. Cannot be undone.", + # Neutron - networking + "neutron_delete_port": "Removes the network port. Connected instances lose network connectivity.", + "neutron_delete_network": "Deletes the network and all associated subnets/ports.", + "neutron_delete_subnet": "Removes the subnet. Ports using this subnet lose IP assignments.", + "neutron_delete_security_group": "Removes the security group. Ports referencing it lose those rules.", + "neutron_delete_security_group_rule": "Removes a security group rule. May block traffic that was previously allowed.", + "neutron_delete_router": "Deletes the router. Connected subnets lose external connectivity.", + "neutron_delete_floatingip": "Releases the floating IP. External access to the instance is lost.", + # Keystone - identity + "keystone_delete_application_credential": "Revokes the application credential. Services using it will fail to authenticate.", + "keystone_delete_project": "Deletes the project and schedules all resources for cleanup.", + # Designate - DNS + "designate_delete_zone": "Deletes the DNS zone and ALL recordsets within it.", + "designate_delete_recordset": "Removes the DNS record. Services depending on this name will fail to resolve.", + # Octavia - load balancer + "octavia_delete_loadbalancer": "Deletes the load balancer. All traffic routing through it stops.", + "octavia_delete_pool": "Removes the backend pool. Load balancer has no targets.", + "octavia_delete_listener": "Removes the listener. Traffic on that port/protocol is no longer accepted.", + # Barbican - secrets + "barbican_delete_secret": "Permanently deletes the secret/certificate. Services referencing it will fail.", + # Manila - shared storage + "manila_delete_share": "Deletes the shared file system. All mount points become unavailable.", + # Swift - object storage + "swift_delete_container": "Deletes the container and all objects within it.", + "swift_delete_object": "Permanently deletes the object. Cannot be undone.", + # Glance - images + "glance_delete_image": "Permanently deletes the image. VMs using it as a base are unaffected, but no new VMs can boot from it.", +} + +# Nova server actions that are destructive (not all actions are) +DESTRUCTIVE_SERVER_ACTIONS = { + "stop": "Powers off the instance. All active connections are dropped.", + "reboot": None, # Special: only HARD reboot is destructive + "delete": "Permanently destroys the instance and its ephemeral disk.", + "force_delete": "Force-destroys the instance bypassing normal cleanup.", + "shelve": "Shelves the instance, releasing compute resources. May take time to unshelve.", +} + + +def get_tool_input() -> tuple[str, dict]: + """Read hook input from stdin (JSON with tool_name and tool_input).""" + try: + data = json.loads(sys.stdin.read()) + except (json.JSONDecodeError, EOFError): + return "", {} + + tool_name = data.get("tool_name", "") + tool_input = data.get("tool_input", {}) + return tool_name, tool_input + + +def check_nova_server_action(tool_input: dict) -> str | None: + """Check if a nova_server_action call is destructive.""" + action = tool_input.get("action", "").lower() + + if action in DESTRUCTIVE_SERVER_ACTIONS: + consequence = DESTRUCTIVE_SERVER_ACTIONS[action] + + # Special case: soft reboot is safe, hard reboot is destructive + if action == "reboot": + reboot_type = tool_input.get("type", "SOFT").upper() + if reboot_type == "HARD": + return "HARD reboot is equivalent to pulling the power cord. In-flight I/O is lost, filesystems may corrupt." + return None # SOFT reboot is safe + + return consequence + + return None + + +def main() -> None: + tool_name, tool_input = get_tool_input() + + if not tool_name: + # Can't determine tool — allow (don't break non-MCP tools) + sys.exit(0) + + # Check if it's a destructive tool + if tool_name == "nova_server_action": + consequence = check_nova_server_action(tool_input) + if consequence is None: + sys.exit(0) # Safe action + elif tool_name in DESTRUCTIVE_TOOLS: + consequence = DESTRUCTIVE_TOOLS[tool_name] + else: + sys.exit(0) # Not a destructive tool — allow + + # Build the block message + server_id = tool_input.get("server_id", tool_input.get("id", tool_input.get("volume_id", "unknown"))) + action_desc = tool_input.get("action", tool_name.split("_", 1)[-1] if "_" in tool_name else tool_name) + + msg = f"BLOCKED: Destructive action requires approval.\n" + msg += f" Tool: {tool_name}\n" + msg += f" Target: {server_id}\n" + if consequence: + msg += f" Impact: {consequence}\n" + msg += f"\nThe user must approve this action before it can proceed." + + print(msg, file=sys.stderr) + sys.exit(2) + + +if __name__ == "__main__": + main() From 5435efe55ba95938f8e34654a043b57cadfa1f5c Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Wed, 6 May 2026 19:40:58 -0700 Subject: [PATCH 04/11] feat: add PostToolUse hook for credential expiry detection When any MCP tool returns 401/auth error, injects context directing the agent to stop retrying and guide the user to re-authenticate via credential-setup skill. Prevents wasteful retry loops on dead tokens. --- plugins/sapcc/.claude-plugin/settings.json | 8 ++ .../sapcc/hooks/credential-expiry-detector.py | 81 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100755 plugins/sapcc/hooks/credential-expiry-detector.py diff --git a/plugins/sapcc/.claude-plugin/settings.json b/plugins/sapcc/.claude-plugin/settings.json index 1d827e7..7ac2c20 100644 --- a/plugins/sapcc/.claude-plugin/settings.json +++ b/plugins/sapcc/.claude-plugin/settings.json @@ -7,6 +7,14 @@ "timeout": 3000, "matcher": "mcp__*" } + ], + "PostToolUse": [ + { + "type": "command", + "command": "python3 plugins/sapcc/hooks/credential-expiry-detector.py", + "timeout": 3000, + "matcher": "mcp__*" + } ] } } diff --git a/plugins/sapcc/hooks/credential-expiry-detector.py b/plugins/sapcc/hooks/credential-expiry-detector.py new file mode 100755 index 0000000..b2fc974 --- /dev/null +++ b/plugins/sapcc/hooks/credential-expiry-detector.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""PostToolUse hook: Detect expired credentials and inject re-auth guidance. + +When any MCP tool call returns a 401 Unauthorized or auth-related error, +this hook injects context telling the agent to stop retrying and guide +the user to re-authenticate. + +Without this hook, the LLM would retry the same call 2-3 times, waste +API calls on a dead token, and confuse the user with "something went wrong." + +Exit codes: + 0 = always (PostToolUse hooks are advisory, never block) + +Output: + JSON with additionalContext when auth failure detected, empty otherwise. +""" +from __future__ import annotations + +import json +import sys + +# Patterns that indicate credential/auth failure +AUTH_FAILURE_PATTERNS = [ + "401", + "unauthorized", + "authentication required", + "token expired", + "invalid credentials", + "could not authenticate", + "auth_url", + "keystoneauth", + "re-authenticate", + "credential", +] + + +def main() -> None: + try: + data = json.loads(sys.stdin.read()) + except (json.JSONDecodeError, EOFError): + sys.exit(0) + + tool_name = data.get("tool_name", "") + tool_result = data.get("tool_result", "") + + # Only care about MCP tool results + if not tool_name or not tool_result: + sys.exit(0) + + # Convert result to string for pattern matching + result_str = str(tool_result).lower() + + # Check for auth failure patterns + auth_failure = False + for pattern in AUTH_FAILURE_PATTERNS: + if pattern in result_str: + auth_failure = True + break + + if not auth_failure: + # No auth issue — exit silently + sys.exit(0) + + # Inject guidance + guidance = { + "additionalContext": ( + "[credential-expiry-detected] The MCP server returned an authentication error. " + "STOP retrying the same call — the token is expired or invalid. " + "Guide the user to re-authenticate:\n" + "1. Check if OS_APPLICATION_CREDENTIAL_ID and OS_APPCRED_SECRET_CMD are set correctly\n" + "2. Run the credential-setup skill to reconfigure auth\n" + "3. Verify the keychain entry hasn't been cleared\n" + "Do NOT retry API calls until credentials are refreshed." + ) + } + print(json.dumps(guidance)) + sys.exit(0) + + +if __name__ == "__main__": + main() From 825bf495f80325304a2c46d76a7f703e3909537f Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Wed, 6 May 2026 19:46:35 -0700 Subject: [PATCH 05/11] feat(sapcc-audit): add 10k offset limit gotcha from hermescli source The Hermes API returns HTTP 500 when paginating past offset 10,000. hermescli handles this with --over-10k-fix (time-based cursoring). The MCP tool has no built-in workaround, so the skill must teach agents to keep queries scoped below 10k results. Also documents all valid sort keys (from hermescli ListOpts) and updates troubleshooting section with 500-on-large-query guidance. --- plugins/sapcc/skills/sapcc-audit/SKILL.md | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/sapcc/skills/sapcc-audit/SKILL.md b/plugins/sapcc/skills/sapcc-audit/SKILL.md index bea2771..0bb5edd 100644 --- a/plugins/sapcc/skills/sapcc-audit/SKILL.md +++ b/plugins/sapcc/skills/sapcc-audit/SKILL.md @@ -93,7 +93,23 @@ Events appear seconds to minutes after the action occurs. If you just performed Filter by human-readable username (e.g., `D012345`, `technical_user_xyz`), not the user's Keystone UUID. This is the name that appears in Keystone token info. -### 10. Full event detail includes request/response attachments +### 10. Hard limit at 10,000 events — API returns 500 beyond this offset + +The Hermes API has a hard ceiling at offset 10,000. If you set `limit=15000` or paginate past 10,000 events, the server returns HTTP 500 (not a helpful error). For large audit queries: + +- **Narrow with time ranges** — use `time_gte`/`time_lte` to window your query below 10k results +- **Narrow with filters** — add `target_type`, `action`, or `outcome` to reduce result set +- **Use time-based cursoring** — query a time window, note the last event's time, use it as the next window's boundary + +The CLI tool [hermescli](https://github.com/sapcc/hermescli) has an `--over-10k-fix` flag that automates this workaround. The MCP tool does not — you must manage it manually by keeping queries scoped. + +### 11. Sort supports multiple keys beyond just time + +Valid sort fields: `time`, `observer_type`, `target_type`, `target_id`, `initiator_type`, `initiator_id`, `outcome`, `action`. + +Each supports `:asc` or `:desc` suffix. Multiple sort keys can be comma-separated: `sort="target_type:asc,time:desc"`. Default direction is ascending if omitted. + +### 12. Full event detail includes request/response attachments `hermes_get_event` returns the complete CADF event including `attachments` — these contain the actual API request body and response. Essential for answering "what exactly changed?" (e.g., which field was updated, what value was set). @@ -162,12 +178,18 @@ Most common causes (check in order): 4. **Ingestion delay** — If the action just happened, wait 30-60 seconds. 5. **Wrong project scope** — Hermes returns events scoped to the authenticated project. Events in other projects are invisible. -### Too many results +### Too many results / HTTP 500 on large queries + +**If you get HTTP 500**: You've likely hit the 10,000 offset ceiling. The fix: +1. Add time range (`time_gte`/`time_lte`) to bound the window below 10k results +2. Use time-based cursoring: query a window, take last event's time as next `time_lte` +**To reduce results generally**: 1. Add `target_type` filter to narrow to specific service 2. Add time range (`time_gte`/`time_lte`) to bound the window 3. Add `action` filter if looking for specific operations (e.g., only `delete`) 4. Add `outcome` filter if only interested in failures +5. Never set `limit` above 10,000 — the API will 500 ### Event detail missing attachments From 231a635ac5b10361c5148235013be59a34a84d8a Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 11:54:27 -0700 Subject: [PATCH 06/11] feat: ADR-002 three-tier tool coverage model + rules + hook update ADR-002 establishes the architecture for documenting all 119 MCP tools across read/write/admin tiers, mirroring the MCP server's env-gated visibility model (MCP_READ_ONLY, MCP_ADMIN_TOOLS). Rules file updated with: - Three-tier model documentation - "Tool doesn't appear" troubleshooting guidance - Write tool safety protocol (confirmed two-call pattern) - Admin tool role awareness Hook updated with: - neutron_delete_floating_ip (new in PR #13) - ironic_node_power_state (admin tier, destructive) - Better target ID extraction (checks all common ID field names) --- docs/adr/002-three-tier-tool-coverage.md | 107 ++++++++++++++++++ .../sapcc/hooks/destructive-action-gate.py | 17 ++- rules/sapcc-agent-rules.md | 33 ++++++ 3 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 docs/adr/002-three-tier-tool-coverage.md diff --git a/docs/adr/002-three-tier-tool-coverage.md b/docs/adr/002-three-tier-tool-coverage.md new file mode 100644 index 0000000..3350590 --- /dev/null +++ b/docs/adr/002-three-tier-tool-coverage.md @@ -0,0 +1,107 @@ + + +# ADR-002: Three-Tier Tool Coverage Model + +## Status + +Accepted + +## Date + +2026-05-07 + +## Context + +The openstack-mcp-server implements a three-tier tool visibility model: + +``` +MCP_READ_ONLY=true (default): 91 read-only tools +MCP_READ_ONLY=false: +16 write tools (107 total) +MCP_ADMIN_TOOLS=true: +12 admin tools (119 total) +``` + +The agent toolkit must mirror this tiering in its skill documentation so that: +1. Engineers get complete documentation for every tool available to them +2. Skills don't reference tools that aren't visible in the current configuration +3. Admin-only operations are clearly marked to prevent confusion when tools don't appear +4. Write operations include safety guidance (pre-checks, confirmation, rollback) + +## Decision + +### Skill Structure + +Each service skill documents ALL tools for that service across all tiers, using clear markers: + +```markdown +## MCP Tools + +### Read Tools (always available) +| Tool | Purpose | Key Parameters | +... + +### Write Tools (requires MCP_READ_ONLY=false) +| Tool | Purpose | Key Parameters | +... + +### Admin Tools (requires MCP_ADMIN_TOOLS=true) +| Tool | Purpose | Key Parameters | +... +``` + +### Tier Markers + +- Read tools: No marker (default) +- Write tools: Section header states the env requirement +- Admin tools: Section header states the env requirement AND minimum role (cloud_admin) + +### Safety Layers for Write Tools + +Write tool documentation includes: +1. **Pre-check requirement** — what to verify before calling (quota, state, dependencies) +2. **Confirmation pattern** — what to tell the user and what approval looks like +3. **Post-check** — how to verify the action succeeded +4. **Rollback guidance** — what to do if it fails or was unintended + +### Hook Enforcement + +The `destructive-action-gate.py` PreToolUse hook enforces user approval for all write tools classified as destructive. This is the **enforcement layer** — skills provide the **guidance layer**. + +### Rules File + +The global rules file (`rules/sapcc-agent-rules.md`) documents: +- The three-tier model and how to detect which tier is active +- That tools may not appear if the tier isn't enabled +- That admin tools require cloud_admin role + +## Consequences + +### Positive +- 100% tool coverage — every engineer sees docs for every tool they can use +- Clear "why can't I see this tool?" answers (tier not enabled) +- Write operations have structured safety guidance +- Admin operations clearly marked to prevent role confusion + +### Negative +- Skills become longer (more tools = more lines) — mitigated by reference files +- Must sync with MCP server on every tool addition — mitigated by validate.py + +### Neutral +- Progressive disclosure still works — agent loads full skill only when needed +- Hook enforcement is independent of skill content (defense in depth) + +## Alternatives Considered + +1. **Separate skills per tier** (e.g., `sapcc-compute-admin`) — Rejected: splits related knowledge artificially, makes cross-referencing harder +2. **Only document read tools** — Rejected: engineers with write access get no guidance +3. **Document all tools flat without tier markers** — Rejected: confusing when tools don't appear + +## Implementation + +1. Update all 18 service skills with complete tool tables (read/write/admin sections) +2. Add three-tier model documentation to rules file +3. Update destructive-action-gate.py hook to cover new write tools +4. Update knowledge/services.md with tier legend diff --git a/plugins/sapcc/hooks/destructive-action-gate.py b/plugins/sapcc/hooks/destructive-action-gate.py index 776fac4..b05c890 100755 --- a/plugins/sapcc/hooks/destructive-action-gate.py +++ b/plugins/sapcc/hooks/destructive-action-gate.py @@ -53,6 +53,12 @@ "swift_delete_object": "Permanently deletes the object. Cannot be undone.", # Glance - images "glance_delete_image": "Permanently deletes the image. VMs using it as a base are unaffected, but no new VMs can boot from it.", + # Neutron - floating IPs (PR #13) + "neutron_delete_floating_ip": "Releases the floating IP back to the pool. External access via this IP is lost immediately.", + # Nova - create (not destructive but resource-consuming) + # Cinder - create (not destructive but resource-consuming) + # Ironic - admin power state changes + "ironic_node_power_state": "Changes physical server power state. May disrupt running workloads on bare metal.", } # Nova server actions that are destructive (not all actions are) @@ -114,12 +120,19 @@ def main() -> None: sys.exit(0) # Not a destructive tool — allow # Build the block message - server_id = tool_input.get("server_id", tool_input.get("id", tool_input.get("volume_id", "unknown"))) + # Extract target identifier from any common ID field + target_id = "unknown" + for key in ("server_id", "id", "volume_id", "floatingip_id", "node_id", + "zone_id", "event_id", "secret_id", "share_id", "loadbalancer_id", + "container", "object", "name"): + if key in tool_input and tool_input[key]: + target_id = str(tool_input[key]) + break action_desc = tool_input.get("action", tool_name.split("_", 1)[-1] if "_" in tool_name else tool_name) msg = f"BLOCKED: Destructive action requires approval.\n" msg += f" Tool: {tool_name}\n" - msg += f" Target: {server_id}\n" + msg += f" Target: {target_id}\n" if consequence: msg += f" Impact: {consequence}\n" msg += f"\nThe user must approve this action before it can proceed." diff --git a/rules/sapcc-agent-rules.md b/rules/sapcc-agent-rules.md index e1e399b..2f49793 100644 --- a/rules/sapcc-agent-rules.md +++ b/rules/sapcc-agent-rules.md @@ -11,6 +11,39 @@ - SAP CC regions are independent deployments. Credentials and resources do not cross region boundaries. Region naming: `--` (e.g., `eu-de-1`). +## Tool Visibility Tiers + +The MCP server exposes tools in three tiers based on environment configuration: + +| Tier | Env Variable | Tools Available | Use Case | +|------|-------------|-----------------|----------| +| **Read-only** (default) | `MCP_READ_ONLY=true` | 91 read tools | Safe exploration, investigation, monitoring | +| **Write-enabled** | `MCP_READ_ONLY=false` | +16 write tools (107 total) | Resource creation, modification, deletion | +| **Admin** | `MCP_ADMIN_TOOLS=true` | +12 admin tools (119 total) | Cloud admin operations (hypervisors, agents, services) | + +### If a tool doesn't appear + +- The tool may be gated behind a tier not currently enabled +- Write tools (marked with \* in skill docs) require `MCP_READ_ONLY=false` +- Admin tools (marked with † in skill docs) require `MCP_ADMIN_TOOLS=true` AND cloud_admin role +- Do NOT tell the user the tool doesn't exist — explain which tier enables it + +### Write tool safety protocol + +All write tools enforce a **confirmed two-call pattern**: +1. Read the resource first (verify state, confirm identity) +2. Only then perform the write operation +3. Verify the result after the write + +The `destructive-action-gate` hook blocks destructive writes until the user explicitly approves. + +### Admin tool awareness + +Admin tools require `cloud_admin` role. If the user's token doesn't have this role: +- The MCP server will return 403 even if the tool is visible +- Check `keystone_token_info` to see current roles before attempting admin operations +- Don't waste API calls on predictably-forbidden operations + ## Pre-Action Checks - **Check quota before creating resources.** Call `limes_get_project_quota` before From daf75e11ccf4bf98d86835678f46b261bc15210f Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 12:02:31 -0700 Subject: [PATCH 07/11] feat: complete three-tier tool coverage across all 19 skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Document all 119 MCP tools with proper tier markers (* write, † admin) - Add Guardrails sections documenting server-side validation (UUID, FQDN, etc.) - Add Security: Credential Isolation sections for Barbican, Ironic, Manila - Add MCP_READ_ONLY and MCP_ADMIN_TOOLS to .mcp.json env config - Add three-tier model explanation and env vars to README - Remove duplicate sapcc-filesystems (consolidated into sapcc-shared-storage) - Mark Castellum and Cronus as planned (not yet in MCP server) - Update plugin.json description to reflect 119-tool coverage --- README.md | 16 ++- plugins/sapcc/.claude-plugin/plugin.json | 2 +- plugins/sapcc/.mcp.json | 4 +- plugins/sapcc/skills/sapcc-audit/SKILL.md | 16 ++- .../sapcc/skills/sapcc-autoscaling/SKILL.md | 20 ++- plugins/sapcc/skills/sapcc-baremetal/SKILL.md | 30 ++++- plugins/sapcc/skills/sapcc-compute/SKILL.md | 32 ++++- .../sapcc/skills/sapcc-connectivity/SKILL.md | 9 +- plugins/sapcc/skills/sapcc-dns/SKILL.md | 24 +++- plugins/sapcc/skills/sapcc-email/SKILL.md | 18 ++- .../sapcc/skills/sapcc-filesystems/SKILL.md | 125 ------------------ plugins/sapcc/skills/sapcc-identity/SKILL.md | 20 ++- plugins/sapcc/skills/sapcc-images/SKILL.md | 19 ++- .../sapcc/skills/sapcc-loadbalancer/SKILL.md | 33 ++++- plugins/sapcc/skills/sapcc-metrics/SKILL.md | 11 +- .../sapcc/skills/sapcc-networking/SKILL.md | 23 +++- .../skills/sapcc-object-storage/SKILL.md | 23 +++- plugins/sapcc/skills/sapcc-quota/SKILL.md | 19 ++- plugins/sapcc/skills/sapcc-registry/SKILL.md | 21 ++- plugins/sapcc/skills/sapcc-secrets/SKILL.md | 21 ++- .../skills/sapcc-shared-storage/SKILL.md | 21 ++- plugins/sapcc/skills/sapcc-storage/SKILL.md | 22 ++- 22 files changed, 339 insertions(+), 190 deletions(-) delete mode 100644 plugins/sapcc/skills/sapcc-filesystems/SKILL.md diff --git a/README.md b/README.md index f2a8fc2..d40b75c 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Environment variables used by the MCP server: | `OS_APPCRED_SECRET_CMD` | Command to retrieve app credential secret | | `OS_PROJECT_NAME` | Default project scope | | `OS_DOMAIN_NAME` | Domain (e.g., `monsoon3`) | +| `MCP_READ_ONLY` | `true` (default) = read-only tools; `false` = enable write tools | +| `MCP_ADMIN_TOOLS` | `true` = enable admin tools (requires `cloud_admin` role) | ## Quick Start @@ -99,9 +101,21 @@ openstack-mcp-server --list-tools └─────────────────────────────────────────────────────────────────┘ ``` -**MCP Server** ([openstack-mcp-server](https://github.com/notque/openstack-mcp-server)) = runtime providing typed tools (47+ API operations across 17 services) +**MCP Server** ([openstack-mcp-server](https://github.com/notque/openstack-mcp-server)) = runtime providing 119 typed tools across 12 services, gated by environment variables **Agent Toolkit** (this repo) = intelligence layer teaching agents *when* and *how* to use those tools +### Three-Tier Tool Model + +Tools are exposed progressively based on environment configuration: + +| Tier | Env Variable | Tools | Use Case | +|------|-------------|-------|----------| +| **Read** (default) | `MCP_READ_ONLY=true` | 91 tools | Safe exploration, monitoring, investigation | +| **Write** | `MCP_READ_ONLY=false` | +16 tools | Resource creation, modification, deletion | +| **Admin** | `MCP_ADMIN_TOOLS=true` | +12 tools | Cloud admin operations (hypervisors, agents, chassis) | + +All write tools enforce a **confirmation pattern** — they return a preview unless `confirmed=true` is passed. The `destructive-action-gate` hook additionally blocks deletes and power-state changes until the user explicitly approves. + ## Task Routing When you ask the agent a question, it auto-selects the appropriate skill: diff --git a/plugins/sapcc/.claude-plugin/plugin.json b/plugins/sapcc/.claude-plugin/plugin.json index 81239ef..8ae94fb 100644 --- a/plugins/sapcc/.claude-plugin/plugin.json +++ b/plugins/sapcc/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "author": { "name": "SAP Converged Cloud" }, - "description": "Operate SAP Converged Cloud: compute, networking, storage, identity, quota, audit, metrics, registry, and endpoint services.", + "description": "Operate SAP Converged Cloud: 19 skills covering 119 MCP tools across compute, networking, storage, identity, quota, audit, metrics, registry, DNS, load balancers, images, object storage, secrets, autoscaling, shared file systems, baremetal, and connectivity.", "homepage": "https://github.com/notque/openstack-agent-toolkit", "keywords": [ "openstack", diff --git a/plugins/sapcc/.mcp.json b/plugins/sapcc/.mcp.json index 824146c..9452f51 100644 --- a/plugins/sapcc/.mcp.json +++ b/plugins/sapcc/.mcp.json @@ -6,7 +6,9 @@ "OS_AUTH_URL": "${OS_AUTH_URL}", "OS_APPLICATION_CREDENTIAL_ID": "${OS_APPLICATION_CREDENTIAL_ID}", "OS_APPCRED_SECRET_CMD": "${OS_APPCRED_SECRET_CMD}", - "OS_REGION_NAME": "${OS_REGION_NAME}" + "OS_REGION_NAME": "${OS_REGION_NAME}", + "MCP_READ_ONLY": "${MCP_READ_ONLY:-true}", + "MCP_ADMIN_TOOLS": "${MCP_ADMIN_TOOLS:-false}" } } } diff --git a/plugins/sapcc/skills/sapcc-audit/SKILL.md b/plugins/sapcc/skills/sapcc-audit/SKILL.md index 0bb5edd..0a67b42 100644 --- a/plugins/sapcc/skills/sapcc-audit/SKILL.md +++ b/plugins/sapcc/skills/sapcc-audit/SKILL.md @@ -17,11 +17,21 @@ Hermes is SAP CC's centralized audit service. It records all API actions across ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `hermes_list_events` | Search/filter audit events | `target_type`, `target_id`, `initiator_name`, `action`, `outcome`, `time_gte`, `time_lte`, `limit`, `sort` | -| `hermes_get_event` | Full CADF event by UUID | `event_id` | -| `hermes_list_attributes` | Discover valid filter values | `attribute` (one of: target_type, action, outcome, observer_type, initiator_type) | +| `hermes_list_events` | Search/filter audit events | `target_type`, `target_id`, `initiator_name`, `initiator_id`, `action`, `outcome`, `observer_type`, `time_gte`, `time_lte`, `limit`, `offset`, `sort` | +| `hermes_get_event` | Full CADF event by UUID | `event_id` (**required**) | +| `hermes_list_attributes` | Discover valid filter values | `attribute` (**required**: target_type, action, outcome, observer_type, initiator_type) | + +> All Hermes tools are read-only. No write or admin tiers exist — audit events are immutable. + +### Guardrails + +- **10,000 offset ceiling**: Queries beyond offset 10,000 return HTTP 500. Use time-based windowing to stay below this limit. +- **Time format**: ISO 8601 UTC timestamps (e.g., `2024-03-15T14:22:00Z`) +- **Sort format**: `field:direction` (e.g., `time:desc`, `target_type:asc,time:desc`) ## CADF Event Model diff --git a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md index 06a7881..d01ad88 100644 --- a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md +++ b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md @@ -17,11 +17,21 @@ Inspect Castellum autoscaling state: view resource configurations, check pending ## MCP Tools -| Tool | Purpose | Key Parameters | -|------|---------|----------------| -| `castellum_get_project_resources` | Get autoscaling config and resource status | `project_id` (UUID, required) | -| `castellum_list_pending_operations` | List scheduled but incomplete resizes | `project_id` (UUID), `asset_type` (e.g. `project-quota:compute:cores`) | -| `castellum_list_recently_failed_operations` | List recently failed resizes | `project_id` (UUID), `asset_type` | +> **Note**: Castellum MCP tools are planned but not yet implemented in the MCP server. The skill documents the service for reference. When tools become available, they will follow the `castellum_` prefix pattern. + +### Expected Tools (planned) + +| Tool | Purpose | Expected Parameters | +|------|---------|---------------------| +| `castellum_list_resources` | List autoscaling-managed resources | project_id | +| `castellum_get_resource` | Get autoscaling config for a resource | asset_type, asset_id | +| `castellum_list_operations` | List scaling operations history | asset_type, state | + +### Interim Workaround + +Until Castellum MCP tools are available: +- Use `maia_query` with PromQL to check Castellum metrics: `castellum_resource_*` +- Use `hermes_list_events(target_type=autoscaling/resource)` for scaling audit trail ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md index e995b4e..52d5e51 100644 --- a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md +++ b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md @@ -17,10 +17,36 @@ Inspect baremetal nodes: list nodes, check provision/power states, understand ma ## MCP Tools +### Read Tools (always available) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `ironic_list_nodes` | List baremetal nodes | `provision_state`, `maintenance` (true/false), `driver`, `resource_class`, `instance_uuid`, `fault`, `owner` | +| `ironic_get_node` | Full node detail (sensitive fields excluded) | `node_id` (**required**, UUID or name) | +| `ironic_list_node_ports` | List NICs for a node | `node_id` (**required**) | +| `ironic_list_allocations` | List node allocations | `node_id`, `resource_class` | +| `ironic_list_portgroups` | List port groups (bonded NICs) | `node_id` | + +### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `ironic_list_nodes` | List baremetal nodes with filters | `provision_state` (active, available, deploying, error), `maintenance` ("true" for maintenance-only), `driver` (ipmi, redfish), `resource_class` | -| `ironic_get_node` | Full detail for a single node | `node_id` (UUID or name, required) | +| `ironic_list_chassis`† | List baremetal chassis | (none) | +| `ironic_node_power_state`†* | Change node power state | `node_id` (**required**), `target` (**required**: `power on`/`power off`/`rebooting`), `confirmed` | + +### Security: Credential Isolation + +- **DriverInfo excluded**: Node detail NEVER exposes BMC credentials (IPMI passwords, Redfish creds, iDRAC secrets) +- **DriverInternalInfo excluded**: Internal provisioning state with potential secrets omitted +- **InstanceInfo excluded**: Nova instance provisioning details omitted +- **Properties excluded**: Hardware properties that may contain sensitive deployment info + +### Guardrails + +- **Path segment validation**: `node_id` accepts both UUID and human-readable names safely +- **Power state allowlist**: Only `power on`, `power off`, `rebooting` accepted as targets +- **Confirmation required**: Power state changes return preview unless `confirmed=true` +- **Destructive action gate**: The `destructive-action-gate` hook blocks `ironic_node_power_state` until user approves ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-compute/SKILL.md b/plugins/sapcc/skills/sapcc-compute/SKILL.md index 2c6fe35..c8d70d9 100644 --- a/plugins/sapcc/skills/sapcc-compute/SKILL.md +++ b/plugins/sapcc/skills/sapcc-compute/SKILL.md @@ -18,12 +18,32 @@ metadata: ## MCP Tools -| Tool | Purpose | -|------|---------| -| \`nova_list_servers\` | List instances. Filters: \`status\`, \`name\` (regex), \`limit\`. Returns ID, name, status, addresses. | -| \`nova_get_server\` | Full detail by UUID: addresses, flavor, image, host_id, metadata, created/updated timestamps. | -| \`nova_list_flavors\` | Available instance types with vCPUs, RAM (MiB), disk (GiB). Use for sizing decisions. | -| \`nova_server_action\` | Lifecycle actions: \`start\`, \`stop\`, \`reboot\` (type: SOFT/HARD), \`pause\`, \`unpause\`, \`suspend\`, \`resume\`. | +### Read Tools +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `nova_list_servers` | List instances with filtering | `status`, `name` (regex), `limit` | +| `nova_get_server` | Full detail by UUID: addresses, flavor, image, host_id, metadata | `server_id` | +| `nova_list_flavors` | Available instance types with vCPUs, RAM, disk | — | +| `nova_list_keypairs` | List SSH keypairs for current user | — | +| `nova_list_availability_zones` | List AZs and their state | — | +| `nova_get_quotas` | Compute quota usage and limits | `project_id` (optional) | +| `nova_list_instance_actions` | Action history for a server (start, stop, reboot, etc.) | `server_id` | +| `nova_list_server_groups` | List server groups (anti-affinity, affinity policies) | — | +| `nova_list_volume_attachments` | List volumes attached to a server | `server_id` | + +### Write Tools (requires MCP_READ_ONLY=false) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `nova_server_action` | Lifecycle actions: start, stop, reboot, pause, unpause, suspend, resume | `server_id`, `action`, `type` (for reboot) | +| `nova_create_server` | Create a new server instance | `name`, `flavor`, `image`, `network` | + +### Admin Tools (requires MCP_ADMIN_TOOLS=true) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `nova_list_hypervisors` | List all hypervisors in the deployment | — | +| `nova_get_hypervisor` | Detail for a specific hypervisor (capacity, VMs) | `hypervisor_id` | +| `nova_list_services` | List Nova services (compute, scheduler, conductor) and state | — | +| `nova_list_aggregates` | List host aggregates and their metadata | — | ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-connectivity/SKILL.md b/plugins/sapcc/skills/sapcc-connectivity/SKILL.md index 5a78a81..e104d7b 100644 --- a/plugins/sapcc/skills/sapcc-connectivity/SKILL.md +++ b/plugins/sapcc/skills/sapcc-connectivity/SKILL.md @@ -16,11 +16,12 @@ Archer is SAP CC's endpoint service for private network connectivity between pro ## MCP Tools -| Tool | Purpose | Required Params | -|------|---------|-----------------| -| `archer_list_services` | List services available for endpoint creation | (optional: `status`) | -| `archer_list_endpoints` | List endpoints in current project | (optional: `service_id`, `status`) | +### Read Tools +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `archer_list_services` | List services available for endpoint creation | `status` (optional) | | `archer_get_service` | Get service detail by UUID | `service_id` | +| `archer_list_endpoints` | List endpoints in current project | `service_id`, `status` (optional) | | `archer_get_endpoint` | Get endpoint detail by UUID | `endpoint_id` | ## Archer Model diff --git a/plugins/sapcc/skills/sapcc-dns/SKILL.md b/plugins/sapcc/skills/sapcc-dns/SKILL.md index 2e9fe5b..fa7bf58 100644 --- a/plugins/sapcc/skills/sapcc-dns/SKILL.md +++ b/plugins/sapcc/skills/sapcc-dns/SKILL.md @@ -17,11 +17,29 @@ Manage DNS zones and recordsets: list zones, inspect zone details, query records ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `designate_list_zones` | List DNS zones with optional filters | `name`, `status` (ACTIVE, PENDING, ERROR), `type` (PRIMARY, SECONDARY) | -| `designate_get_zone` | Full detail for a single zone | `zone_id` (UUID, required) | -| `designate_list_recordsets` | List recordsets within a zone | `zone_id` (UUID, required), `name`, `type` (A, AAAA, CNAME, MX, TXT, etc.), `status` | +| `designate_list_zones` | List DNS zones with optional filters | `name`, `status` (ACTIVE/PENDING/ERROR), `type` (PRIMARY/SECONDARY) | +| `designate_get_zone` | Full zone detail by UUID | `zone_id` (**required**) | +| `designate_list_recordsets` | List recordsets within a zone | `zone_id` (**required**), `name`, `type` (A/AAAA/CNAME/MX/TXT/SRV/NS), `status`, `data` | +| `designate_list_zone_transfer_requests` | List outgoing zone transfer requests | `zone_id`, `status` | +| `designate_list_zone_transfer_accepts` | List accepted zone transfers | (none) | + +### Write Tools* (require `MCP_READ_ONLY=false`) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `designate_create_recordset`* | Create a DNS recordset in a zone | `zone_id` (**required**), `name` (**required**, FQDN with trailing dot), `type` (**required**), `records` (**required**, comma-separated), `ttl`, `description`, `confirmed` | +| `designate_delete_recordset`* | Delete a DNS recordset | `zone_id` (**required**), `recordset_id` (**required**), `confirmed` | + +### Guardrails + +- **FQDN validation**: `name` must end with `.` (e.g., `app.example.com.`) +- **CNAME single-value**: CNAME records must have exactly one value in `records` +- **UUID validation**: All `*_id` parameters validated before API call +- **Confirmation required**: Write tools return preview unless `confirmed=true` ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-email/SKILL.md b/plugins/sapcc/skills/sapcc-email/SKILL.md index 4c20405..15b4be7 100644 --- a/plugins/sapcc/skills/sapcc-email/SKILL.md +++ b/plugins/sapcc/skills/sapcc-email/SKILL.md @@ -16,10 +16,20 @@ Inspect email service status: check sending usage and list available email templ ## MCP Tools -| Tool | Purpose | Key Parameters | -|------|---------|----------------| -| `cronus_get_usage` | Get email sending usage and status | (none — scoped to current project) | -| `cronus_list_templates` | List available email templates | (none — scoped to current project) | +> **Note**: Cronus MCP tools are planned but not yet implemented in the MCP server. The skill documents the service for reference. When tools become available, they will follow the `cronus_` prefix pattern. + +### Expected Tools (planned) + +| Tool | Purpose | Expected Parameters | +|------|---------|---------------------| +| `cronus_list_senders` | List verified sender addresses | project_id | +| `cronus_get_usage` | Get email sending usage/limits | project_id | + +### Interim Workaround + +Until Cronus MCP tools are available: +- Use `hermes_list_events(target_type=email/*)` for email audit trail +- Email configuration is typically managed via the SAP CC dashboard ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-filesystems/SKILL.md b/plugins/sapcc/skills/sapcc-filesystems/SKILL.md deleted file mode 100644 index c4013c8..0000000 --- a/plugins/sapcc/skills/sapcc-filesystems/SKILL.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -name: sapcc-filesystems -description: > - Shared file system management via Manila in SAP Converged Cloud. - Triggers: shared filesystem, manila, NFS, CIFS, file share, network storage, mount -version: 1.0.0 -metadata: - service: [manila] - task: [inspect, manage, debug] - persona: [developer, platform-engineer] ---- - -# SAP CC Shared Filesystems (Manila) - -Manage Manila shared file systems: list shares, inspect details, and understand access states. Manila provides network-attached storage (NFS, CIFS) that can be mounted by multiple servers simultaneously. - -## MCP Tools - -| Tool | Purpose | Key Parameters | -|------|---------|----------------| -| `manila_list_shares` | List file shares in current project | `name`, `status`, `share_proto` (returns: ID, name, status, protocol, size, availability zone) | -| `manila_get_share` | Full detail for a single share | `share_id` (UUID) | - -## Gotchas - -### 1. Shares are network-mounted — different from block storage - -Unlike Cinder volumes (attached to one server via iSCSI/FC), Manila shares are network file systems (NFS/CIFS) accessible by multiple servers simultaneously. Use Manila for shared data, Cinder for dedicated block devices. - -### 2. Protocol determines mount method - -| Protocol | Mount Style | Typical Use | -|----------|-------------|-------------| -| NFS | `mount -t nfs /mnt` | Linux servers, most common | -| CIFS | `mount -t cifs // /mnt` | Windows or mixed environments | -| GlusterFS | Gluster mount | Distributed storage | -| CephFS | Ceph FUSE/kernel mount | Ceph-backed storage | - -### 3. Status "available" = ready to mount - -Only shares in `available` status can be mounted. - -### 4. Size is in GiB - -Same as Cinder — all capacity reported in gibibytes. - -### 5. Shares need access rules to be mountable - -A share in `available` status still requires access rules (IP-based or user-based) before any server can mount it. - -### 6. Availability zone affects which servers can mount - -Cross-AZ mounting may not be supported. Ensure the share's AZ matches the servers that need it. - -### 7. Share network provides the connection path - -Shares are associated with a share network (a Neutron network/subnet). Servers must have connectivity to that network. - -## Common Workflows - -### List All File Shares - -``` -1. manila_list_shares() -2. Review: name, protocol, status, size, AZ -``` - -### Inspect a Specific Share - -``` -1. manila_list_shares(name=) → find the share -2. manila_get_share(share_id=) → full details -3. Note: export_location shows the mount path -``` - -### Find NFS Shares Available for Mounting - -``` -1. manila_list_shares(share_proto=NFS, status=available) -2. Note export_location for mount commands -3. Verify AZ matches your server's AZ -``` - -### Troubleshoot a Share in Error State - -``` -1. manila_get_share(share_id=) → check status -2. hermes_list_events(target_type=manila/share, target_id=) -3. Common causes: backend capacity, network issue, quota exceeded -``` - -## Troubleshooting - -### Share stuck in "creating" - -- Backend provisioning may be slow for large shares -- If > 10 minutes, likely a backend issue -- Check Hermes: `hermes_list_events(target_type=manila/share, outcome=failure)` - -### Cannot mount despite "available" status - -- Check access rules (not visible via MCP tools) -- Verify network connectivity between server and share network -- Check security groups allow NFS traffic (port 2049) or CIFS (port 445) -- Verify server is in the same AZ - -### Quota exhausted - -- `limes_get_project_quota(service=sharev2)` → check usage vs quota - -## Security Considerations - -- Share export locations reveal network topology and storage paths -- NFS shares may have permissive access rules (entire subnet) -- Shared access means data accessible to multiple servers — ensure restrictive access rules -- Audit share creation/deletion via Hermes for compliance - -## Cross-Service References - -| Need | Service | Tool | -|------|---------|------| -| Servers that can mount a share | Nova | `nova_list_servers` (filter by AZ) | -| Network for share connectivity | Neutron | `neutron_list_networks`, `neutron_list_subnets` | -| Who created/modified shares | Hermes | `hermes_list_events(target_type=manila/share)` | -| Share quota remaining | Limes | `limes_get_project_quota(service=sharev2)` | diff --git a/plugins/sapcc/skills/sapcc-identity/SKILL.md b/plugins/sapcc/skills/sapcc-identity/SKILL.md index fa7e781..e464fc9 100644 --- a/plugins/sapcc/skills/sapcc-identity/SKILL.md +++ b/plugins/sapcc/skills/sapcc-identity/SKILL.md @@ -16,13 +16,27 @@ metadata: ## MCP Tools +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `keystone_token_info` | Current auth context: user, project, domain, roles, catalog | None | | `keystone_list_projects` | List accessible projects | `domain_id`, `name` (optional filters) | +| `keystone_token_info` | Current auth context: user, project, domain, roles, catalog | — | +| `keystone_list_application_credentials` | List app credentials for current user | — | +| `keystone_list_domains` | List available domains | `name` (optional) | +| `keystone_list_users` | List users in a domain | `domain_id`, `name` (optional) | +| `keystone_list_roles` | List available roles | — | + +### Write Tools (requires MCP_READ_ONLY=false) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| | `keystone_create_application_credential` | Create app credential (secret shown once) | `name`, `description`, `expires_at`, `roles` | -| `keystone_list_application_credentials` | List app creds for current user | None | -| `keystone_delete_application_credential` | Delete/revoke an app credential | `id` (UUID) | +| `keystone_delete_application_credential` | Delete/revoke an app credential | `id` | + +### Admin Tools (requires MCP_ADMIN_TOOLS=true) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `keystone_list_role_assignments` | List role assignments across projects/domains | `project_id`, `user_id`, `role_id` (optional filters) | +| `keystone_list_groups` | List groups and their memberships | `domain_id` (optional) | ## SAP CC Domain Model diff --git a/plugins/sapcc/skills/sapcc-images/SKILL.md b/plugins/sapcc/skills/sapcc-images/SKILL.md index a2384ac..f862b4b 100644 --- a/plugins/sapcc/skills/sapcc-images/SKILL.md +++ b/plugins/sapcc/skills/sapcc-images/SKILL.md @@ -16,10 +16,25 @@ Inspect and list VM images: find available boot images, check image status, unde ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `glance_list_images` | List images with optional filters | `name`, `status` (queued, saving, active, killed, deleted, deactivated), `visibility` (public, private, shared, community), `owner` (project ID) | -| `glance_get_image` | Full detail for a single image | `image_id` (UUID, required) | +| `glance_list_images` | List available images | `name`, `status` (queued/saving/active/killed/deleted/deactivated), `visibility` (public/private/shared/community), `owner` | +| `glance_get_image` | Full image detail by UUID | `image_id` (**required**) | +| `glance_list_image_members` | List projects an image is shared with | `image_id` (**required**) | + +### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `glance_list_tasks`† | List image import tasks | `status` (pending/processing/success/failure), `type` | + +### Guardrails + +- **UUID validation**: `image_id` validated before API call +- **No write tools**: Image upload/delete not available via MCP (use OpenStack CLI) +- **Visibility meanings**: `public` = all projects see it; `shared` = explicitly shared via members; `community` = discoverable by all but not in default list ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md index 3d9e607..462fe1e 100644 --- a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md +++ b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md @@ -17,12 +17,37 @@ Manage Octavia load balancers: list/inspect LBs, listeners, and pools. Understan ## MCP Tools +### Read Tools (always available) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `octavia_list_loadbalancers` | List load balancers | `name`, `provisioning_status`, `vip_address`, `operating_status`, `vip_subnet_id`, `provider` | +| `octavia_get_loadbalancer` | Full LB detail by UUID | `loadbalancer_id` (**required**) | +| `octavia_list_listeners` | List listeners | `name`, `protocol` (TCP/HTTP/HTTPS/TERMINATED_HTTPS/UDP/SCTP), `loadbalancer_id`, `protocol_port` | +| `octavia_list_pools` | List backend pools | `name`, `protocol`, `loadbalancer_id`, `lb_algorithm` (ROUND_ROBIN/LEAST_CONNECTIONS/SOURCE_IP) | +| `octavia_list_members` | List pool members | `pool_id` (**required**), `name`, `address` | +| `octavia_list_healthmonitors` | List health monitors | `pool_id`, `type` (HTTP/HTTPS/PING/TCP/TLS-HELLO/UDP-CONNECT) | +| `octavia_list_l7policies` | List L7 routing policies | `listener_id`, `name` | +| `octavia_list_l7rules` | List rules for L7 policy | `l7policy_id` (**required**) | + +### Write Tools* (require `MCP_READ_ONLY=false`) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `octavia_list_loadbalancers` | List LBs with optional filters | `name`, `provisioning_status` (ACTIVE, PENDING_CREATE, ERROR), `vip_address` | -| `octavia_get_loadbalancer` | Full detail for a single LB | `loadbalancer_id` (UUID, required) | -| `octavia_list_listeners` | List listeners across LBs | `name`, `protocol` (TCP, HTTP, HTTPS, TERMINATED_HTTPS, UDP, SCTP), `loadbalancer_id` | -| `octavia_list_pools` | List backend pools | `name`, `protocol` (TCP, HTTP, HTTPS, PROXY, UDP, SCTP), `loadbalancer_id` | +| `octavia_create_loadbalancer`* | Create a new load balancer | `name` (**required**), `vip_subnet_id` (**required**), `description`, `confirmed` | +| `octavia_delete_loadbalancer`* | Delete a load balancer | `loadbalancer_id` (**required**), `cascade` (bool, deletes children), `confirmed` | + +### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `octavia_list_amphorae`† | List amphora instances | `loadbalancer_id`, `status` | + +### Guardrails + +- **UUID validation**: All `*_id` parameters validated +- **Confirmation required**: Write tools return preview unless `confirmed=true` +- **Cascade delete**: `octavia_delete_loadbalancer` with `cascade=true` deletes ALL child resources (listeners, pools, members, monitors) ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-metrics/SKILL.md b/plugins/sapcc/skills/sapcc-metrics/SKILL.md index c736c01..64bf5cf 100644 --- a/plugins/sapcc/skills/sapcc-metrics/SKILL.md +++ b/plugins/sapcc/skills/sapcc-metrics/SKILL.md @@ -16,12 +16,13 @@ Maia is SAP CC's multi-tenant Prometheus-as-a-Service. Same PromQL query languag ## MCP Tools -| Tool | Purpose | Required Params | -|------|---------|-----------------| -| `maia_metric_names` | List all available metric names for current project | (none) | -| `maia_label_values` | Get values for a specific label | `label` (e.g., `__name__`, `instance`, `job`) | -| `maia_query` | Execute instant PromQL query | `query` (optional: `time`) | +### Read Tools +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `maia_query` | Execute instant PromQL query | `query`, `time` (optional) | | `maia_query_range` | Execute range PromQL query over time window | `query`, `start`, `end`, `step` | +| `maia_label_values` | Get values for a specific label | `label` (e.g., `__name__`, `instance`, `job`) | +| `maia_metric_names` | List all available metric names for current project | — | ## Maia vs Regular Prometheus diff --git a/plugins/sapcc/skills/sapcc-networking/SKILL.md b/plugins/sapcc/skills/sapcc-networking/SKILL.md index e11c853..7e3d2d1 100644 --- a/plugins/sapcc/skills/sapcc-networking/SKILL.md +++ b/plugins/sapcc/skills/sapcc-networking/SKILL.md @@ -16,12 +16,31 @@ Investigate and debug network topology, port state, and security group rules in ## MCP Tools -| Tool | Purpose | Key Filters | -|------|---------|-------------| +### Read Tools +| Tool | Purpose | Key Parameters | +|------|---------|----------------| | `neutron_list_networks` | List networks | `name`, `status` | | `neutron_list_subnets` | List subnets (CIDR, gateway, DHCP) | `network_id` | | `neutron_list_ports` | List ports (MAC, fixed IPs, device_owner) | `network_id`, `device_id`, `status` | | `neutron_list_security_groups` | List security groups with rules | — | +| `neutron_list_routers` | List routers and their external gateway info | — | +| `neutron_list_floating_ips` | List floating IPs and their port associations | `status`, `floating_ip_address` | +| `neutron_list_trunks` | List trunk ports and their subports | — | +| `neutron_list_network_ip_availabilities` | IP usage statistics per network/subnet | `network_id` | +| `neutron_list_bgpvpn_interconnections` | List BGPVPN interconnections | — | + +### Write Tools (requires MCP_READ_ONLY=false) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `neutron_create_security_group_rule` | Add an ingress/egress rule to a security group | `security_group_id`, `direction`, `protocol`, `port_range_min`, `port_range_max` | +| `neutron_delete_security_group_rule` | Remove a security group rule | `rule_id` | +| `neutron_create_floating_ip` | Allocate a floating IP from an external network | `floating_network_id`, `port_id` (optional) | +| `neutron_delete_floating_ip` | Release a floating IP | `floating_ip_id` | + +### Admin Tools (requires MCP_ADMIN_TOOLS=true) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `neutron_list_agents` | List Neutron agents (DHCP, L3, OVS) and their state | — | ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md index 0d75501..253a5c7 100644 --- a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md @@ -17,11 +17,28 @@ Inspect Swift containers and objects: list containers, browse object listings, c ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `swift_list_containers` | List containers in the account | `prefix` (name prefix filter), `limit` (number, default 100) | -| `swift_list_objects` | List objects in a container | `container` (name, required), `prefix`, `delimiter` (e.g. `/` for pseudo-dirs), `limit` (number, default 100) | -| `swift_get_object_metadata` | Get metadata for a specific object | `container` (name, required), `object` (name, required) | +| `swift_list_containers` | List containers in account | `prefix`, `limit` (default 100) | +| `swift_list_objects` | List objects in a container | `container` (**required**), `prefix`, `delimiter` (e.g., `/` for pseudo-dirs), `limit` | +| `swift_get_object_metadata` | Get object metadata (not content) | `container` (**required**), `object` (**required**) | + +### Write Tools* (require `MCP_READ_ONLY=false`) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `swift_upload_object`* | Upload text content to a container | `container` (**required**), `object` (**required**), `content` (**required**), `content_type`, `safe_write` (bool, If-None-Match), `confirmed` | +| `swift_delete_object`* | Delete an object from a container | `container` (**required**), `object` (**required**), `confirmed` | + +### Guardrails + +- **Path segment validation**: Container and object names validated to prevent path traversal +- **safe_write mode**: When `safe_write=true`, upload fails if object already exists (prevents accidental overwrites) +- **Confirmation required**: Write tools return preview unless `confirmed=true` +- **Text-only uploads**: Only text content can be uploaded via MCP (binary files require CLI) +- **No container create/delete**: Container lifecycle management not available via MCP ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-quota/SKILL.md b/plugins/sapcc/skills/sapcc-quota/SKILL.md index 0ac6de3..74ce5c5 100644 --- a/plugins/sapcc/skills/sapcc-quota/SKILL.md +++ b/plugins/sapcc/skills/sapcc-quota/SKILL.md @@ -16,11 +16,20 @@ Limes is SAP CC's central quota management service. Not part of vanilla OpenStac ## MCP Tools -| Tool | Purpose | Required Params | -|------|---------|-----------------| -| `limes_get_project_quota` | Quota + usage for a single project | `domain_id`, `project_id` (optional: `service`, `resource`) | -| `limes_get_domain_quota` | Aggregated quota for all projects in a domain | `domain_id` (optional: `service`) | -| `limes_get_cluster_quota` | Cluster-wide capacity and usage | (optional: `service`) | +### Read Tools (always available) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `limes_get_project_quota` | Get quota and usage for a project | `service` (compute, volumev2, network, dns, sharev2, object-store, loadbalancing, keppel), `resource` | +| `limes_get_domain_quota` | Get domain-level quota allocation | `service`, `resource` | +| `limes_get_cluster_capacity` | Get cluster-wide capacity/usage | `service`, `resource` | + +> All Limes tools are read-only. Quota changes require Elektra dashboard or cloud-admin API access. + +### Guardrails + +- **Project-scoped by default**: Quota data is returned for the authenticated project scope +- **Service filter recommended**: Without `service` filter, returns ALL service quotas (can be large) ## Quota Model diff --git a/plugins/sapcc/skills/sapcc-registry/SKILL.md b/plugins/sapcc/skills/sapcc-registry/SKILL.md index 6e5fff2..602efdd 100644 --- a/plugins/sapcc/skills/sapcc-registry/SKILL.md +++ b/plugins/sapcc/skills/sapcc-registry/SKILL.md @@ -16,11 +16,22 @@ Keppel is SAP CC's multi-tenant container image registry. Not vanilla OpenStack. ## MCP Tools -| Tool | Purpose | Required Params | -|------|---------|-----------------| -| `keppel_list_accounts` | List registry accounts (namespaces) | — | -| `keppel_list_repositories` | List image repos within an account | `account` | -| `keppel_list_manifests` | List manifests (image versions) in a repo | `account`, `repository` | +### Read Tools (always available) + +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `keppel_list_accounts` | List container image registry accounts | (none) | +| `keppel_get_account` | Get account detail (registry endpoint, replication config) | `account_name` (**required**) | +| `keppel_list_repositories` | List repos within an account | `account_name` (**required**) | +| `keppel_list_manifests` | List image manifests (tags) in a repo | `account_name` (**required**), `repo_name` (**required**) | +| `keppel_get_vulnerability_report` | Get vulnerability scan results for an image | `account_name` (**required**), `repo_name` (**required**), `digest` (**required**) | + +> All Keppel tools are read-only. Image push/pull uses standard Docker/OCI registry protocol, not MCP. + +### Guardrails + +- **Path segment validation**: Account and repository names validated to prevent path traversal +- **Vulnerability data may be large**: Scan reports for images with many layers can be substantial ## Keppel Hierarchy diff --git a/plugins/sapcc/skills/sapcc-secrets/SKILL.md b/plugins/sapcc/skills/sapcc-secrets/SKILL.md index ada9434..580a19d 100644 --- a/plugins/sapcc/skills/sapcc-secrets/SKILL.md +++ b/plugins/sapcc/skills/sapcc-secrets/SKILL.md @@ -17,10 +17,27 @@ Inspect secrets stored in the key manager: list secrets, check metadata, verify ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `barbican_list_secrets` | List secrets (metadata only) | `name`, `secret_type` (symmetric, public, private, passphrase, certificate, opaque) | -| `barbican_get_secret` | Get metadata for a single secret | `secret_id` (UUID, required) | +| `barbican_list_secrets` | List secret metadata (payload never returned) | `name`, `secret_type` (symmetric/public/private/passphrase/certificate/opaque) | +| `barbican_get_secret` | Get single secret metadata | `secret_id` (**required**) | +| `barbican_list_containers` | List secret containers (cert bundles, RSA pairs) | `name`, `type` (generic/rsa/certificate) | +| `barbican_get_container` | Get container detail with secret refs | `container_id` (**required**) | +| `barbican_list_orders` | List secret generation orders | (none) | + +### Security: Credential Isolation + +- **Secret payloads are NEVER returned** by any Barbican MCP tool +- Only metadata is exposed: name, type, status, algorithm, bit_length, expiration +- To retrieve actual secret values, use the OpenStack CLI with appropriate RBAC +- No write or admin tiers exist for Barbican — all 5 tools are read-only + +### Guardrails + +- **UUID validation**: `secret_id` and `container_id` validated before API call +- **No payload access**: By design, the MCP server never fetches or returns secret payload content ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md index 1fff883..014b279 100644 --- a/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md @@ -17,10 +17,27 @@ Manage shared file systems: list shares, inspect share details, understand proto ## MCP Tools +### Read Tools (always available) + | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `manila_list_shares` | List shared file system shares | `name`, `status` (available, error, creating, deleting, error_deleting), `share_proto` (NFS, CIFS, GlusterFS, HDFS, CephFS) | -| `manila_get_share` | Full detail for a single share | `share_id` (UUID, required) | +| `manila_list_shares` | List file shares in project | `name`, `status` (available/error/creating/deleting/error_deleting), `share_proto` (NFS/CIFS/GlusterFS/HDFS/CephFS) | +| `manila_get_share` | Full share detail by UUID | `share_id` (**required**) | +| `manila_list_access_rules` | List access rules for a share | `share_id` (**required**) | +| `manila_list_share_networks` | List share networks (Neutron connectivity) | `name` | +| `manila_list_snapshots` | List share snapshots | `share_id`, `status` (available/error/creating/deleting) | +| `manila_list_security_services` | List LDAP/Kerberos/AD services | `type` (ldap/kerberos/active_directory), `name` | +| `manila_list_share_types` | List available share types with specs | (none) | + +### Security: Credential Isolation + +- **Security service passwords excluded**: Manila security services may reference LDAP/AD credentials — password fields are never returned +- No write or admin tiers exist for Manila — all 7 tools are read-only + +### Guardrails + +- **UUID validation**: `share_id` validated before API call +- **share_proto filter**: Applied client-side (Manila API doesn't support it as query param) ## Gotchas diff --git a/plugins/sapcc/skills/sapcc-storage/SKILL.md b/plugins/sapcc/skills/sapcc-storage/SKILL.md index ce163c9..387a4df 100644 --- a/plugins/sapcc/skills/sapcc-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-storage/SKILL.md @@ -16,10 +16,28 @@ Manage Cinder volumes: list, inspect, understand attachment state, and troublesh ## MCP Tools +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| -| `cinder_list_volumes` | List volumes with optional filters | `status`, `name` (returns: ID, name, status, size, volume_type, attachments) | -| `cinder_get_volume` | Full detail for a single volume | `volume_id` (UUID) | +| `cinder_list_volumes` | List volumes with optional filters | `status`, `name` | +| `cinder_get_volume` | Full detail for a single volume | `volume_id` | +| `cinder_list_snapshots` | List volume snapshots | `volume_id`, `status` | +| `cinder_get_snapshot` | Detail for a single snapshot | `snapshot_id` | +| `cinder_list_volume_types` | Available volume types and their properties | — | +| `cinder_get_quotas` | Block storage quota usage and limits | `project_id` (optional) | +| `cinder_list_backups` | List volume backups | `volume_id`, `status` | +| `cinder_list_transfers` | List pending volume transfers | — | + +### Write Tools (requires MCP_READ_ONLY=false) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `cinder_create_volume` | Create a new volume | `size`, `name`, `volume_type` | +| `cinder_delete_volume` | Delete a volume (must be in available state) | `volume_id` | + +### Admin Tools (requires MCP_ADMIN_TOOLS=true) +| Tool | Purpose | Key Parameters | +|------|---------|----------------| +| `cinder_list_services` | List Cinder services (scheduler, volume) and state | — | ## Gotchas From 14c06092ec5a7e83dfa5fe6bf54f49bc7c7bd4f1 Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 12:06:42 -0700 Subject: [PATCH 08/11] chore: remove session tracking file from repo .claude/session-reads.txt is per-session state that shouldn't be versioned. --- .claude/session-reads.txt | 64 --------------------------------------- 1 file changed, 64 deletions(-) delete mode 100644 .claude/session-reads.txt diff --git a/.claude/session-reads.txt b/.claude/session-reads.txt deleted file mode 100644 index abe68e3..0000000 --- a/.claude/session-reads.txt +++ /dev/null @@ -1,64 +0,0 @@ -/tmp/agent-toolkit-for-aws/README.md -/tmp/agent-toolkit-for-aws/rules/aws-agent-rules.md -/tmp/agent-toolkit-for-aws/skills/README.md -/tmp/agent-toolkit-for-aws/plugins/aws-core/.claude-plugin/plugin.json -/tmp/agent-toolkit-for-aws/plugins/aws-core/.mcp.json -/tmp/agent-toolkit-for-aws/plugins/aws-core/skills/aws-containers/SKILL.md -/Users/I810033/pgh/openstack-mcp-server/internal/tools/neutron/neutron.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/nova/nova.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/cinder/cinder.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/keystone/keystone.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/limes/limes.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/maia/maia.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/hermes/hermes.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/archer/archer.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/keppel/keppel.go -/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/services.md -/Users/I810033/pgh/openstack-agent-toolkit/skills/credential-setup/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/README.md -/tmp/agent-toolkit-for-aws/.claude-plugin/marketplace.json -/tmp/agent-toolkit-for-aws/tools/validate.py -/Users/I810033/pgh/openstack-agent-toolkit/docs/adr/001-toolkit-architecture.md -/Users/I810033/pgh/vexjoy-agent/docs/PHILOSOPHY.md -/Users/I810033/.claude/skills/meta/skill-creator/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/.claude-plugin/plugin.json -/Users/I810033/pgh/openstack-agent-toolkit/rules/sapcc-agent-rules.md -/Users/I810033/pgh/openstack-agent-toolkit/skills/credential-setup/references/auth-methods.md -/Users/I810033/pgh/openstack-agent-toolkit/docs/implementation-plan.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/.mcp.json -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-quota/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-compute/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-compute/references/flavor-families.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-storage/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-networking/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-networking/references/security-group-patterns.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-connectivity/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/credential-setup/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/credential-setup/references/auth-methods.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-metrics/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-audit/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-identity/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/sapcc-registry/SKILL.md -/Users/I810033/pgh/openstack-agent-toolkit/plugins/sapcc/skills/README.md -/Users/I810033/pgh/openstack-agent-toolkit/.claude-plugin/marketplace.json -/Users/I810033/pgh/openstack-agent-toolkit/tools/validate.py -/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/troubleshooting-flows.md -/Users/I810033/pgh/openstack-agent-toolkit/LICENSE -/Users/I810033/pgh/openstack-agent-toolkit/CODE_OF_CONDUCT.md -/Users/I810033/pgh/openstack-agent-toolkit/.editorconfig -/Users/I810033/pgh/openstack-agent-toolkit/.gitignore -/Users/I810033/pgh/openstack-agent-toolkit/.github/workflows/validate.yml -/Users/I810033/pgh/openstack-agent-toolkit/.github/CODEOWNERS -/Users/I810033/pgh/openstack-agent-toolkit/knowledge/sapcc/architecture.md -/Users/I810033/pgh/openstack-mcp-server/internal/tools/glance/glance.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/manila/manila.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/swift/swift.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/ironic/ironic.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/octavia/octavia.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/barbican/barbican.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/designate/designate.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/castellum/castellum.go -/Users/I810033/pgh/openstack-mcp-server/internal/tools/cronus/cronus.go -/Users/I810033/pgh/openstack-mcp-server/internal/server/server.go -/Users/I810033/pgh/openstack-agent-toolkit/.agents/plugins/marketplace.json From b7babf3b936099b069d97a12572fa0d39be76238 Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 12:07:17 -0700 Subject: [PATCH 09/11] chore: add .claude session state to gitignore Prevent session-specific files (plans, worktrees, scheduled tasks, session reads) from being accidentally committed. --- .gitignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d9a6543..ff0638b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,9 @@ __pycache__/ dist/ build/ -# Local Claude settings +# Claude Code session state +.claude/session-reads.txt .claude/settings.local.json +.claude/scheduled_tasks.json +.claude/worktrees/ +.claude/plans/ From 44d16acdab19d12c137dd65ed4ce2a2bfb0859bd Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 12:12:26 -0700 Subject: [PATCH 10/11] fix: address security review and correctness issues Security fixes: - Remove real employee I-number from CADF example (use D012345) - Replace cloud.sap with cloud.example.com in README examples - Replace monsoon3 domain with cc-demo/my-domain in examples Hook fixes: - Remove duplicate neutron_delete_floatingip entry (canonical name is neutron_delete_floating_ip with underscore) - Credential detector now uses strong/weak pattern tiers to reduce false positives (single weak match no longer triggers) Tool reference fixes: - Fix neutron_get_subnet -> neutron_list_subnets in loadbalancer skill - Fix limes_get_cluster_quota -> limes_get_cluster_capacity in quota skill - Mark castellum_* and cronus_* as (planned) in README routing table - Clarify autoscaling/email tools as NOT YET AVAILABLE --- README.md | 8 ++--- .../sapcc/hooks/credential-expiry-detector.py | 29 ++++++++++++------- .../sapcc/hooks/destructive-action-gate.py | 1 - .../references/cadf-event-format.md | 4 +-- .../sapcc/skills/sapcc-autoscaling/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-email/SKILL.md | 4 +-- .../sapcc/skills/sapcc-loadbalancer/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-quota/SKILL.md | 2 +- 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index d40b75c..73a324f 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,13 @@ This guides you through storing credentials in your OS keychain. Supports: Environment variables used by the MCP server: | Variable | Description | |----------|-------------| -| `OS_AUTH_URL` | Keystone endpoint (e.g., `https://identity-3..cloud.sap/v3`) | +| `OS_AUTH_URL` | Keystone endpoint (e.g., `https://identity-3..cloud.example.com/v3`) | | `OS_USERNAME` | SAP CC username (if using password auth) | | `OS_PW_CMD` | Command to retrieve password from keychain | | `OS_APPLICATION_CREDENTIAL_ID` | App credential ID (if using app creds) | | `OS_APPCRED_SECRET_CMD` | Command to retrieve app credential secret | | `OS_PROJECT_NAME` | Default project scope | -| `OS_DOMAIN_NAME` | Domain (e.g., `monsoon3`) | +| `OS_DOMAIN_NAME` | Domain (e.g., `my-domain`) | | `MCP_READ_ONLY` | `true` (default) = read-only tools; `false` = enable write tools | | `MCP_ADMIN_TOOLS` | `true` = enable admin tools (requires `cloud_admin` role) | @@ -136,10 +136,10 @@ When you ask the agent a question, it auto-selects the appropriate skill: | Images, snapshots, image properties | `sapcc-images` | `glance_*` | | Object storage, containers, objects | `sapcc-object-storage` | `swift_*` | | Secrets, certificates, keys | `sapcc-secrets` | `barbican_*` | -| Autoscaling policies | `sapcc-autoscaling` | `castellum_*` | +| Autoscaling policies | `sapcc-autoscaling` | `castellum_*` (planned) | | Shared file systems, shares, exports | `sapcc-shared-storage` | `manila_*` | | Baremetal provisioning | `sapcc-baremetal` | `ironic_*` | -| Email notifications | `sapcc-email` | `cronus_*` | +| Email notifications | `sapcc-email` | `cronus_*` (planned) | | Auth setup, credential config | `credential-setup` | (guided wizard) | ## What's Included diff --git a/plugins/sapcc/hooks/credential-expiry-detector.py b/plugins/sapcc/hooks/credential-expiry-detector.py index b2fc974..01c1077 100755 --- a/plugins/sapcc/hooks/credential-expiry-detector.py +++ b/plugins/sapcc/hooks/credential-expiry-detector.py @@ -19,18 +19,25 @@ import json import sys -# Patterns that indicate credential/auth failure -AUTH_FAILURE_PATTERNS = [ - "401", - "unauthorized", +# Strong patterns: a single match is sufficient to detect auth failure +STRONG_PATTERNS = [ + "401 unauthorized", "authentication required", "token expired", "invalid credentials", "could not authenticate", - "auth_url", + "keystoneauth1.exceptions", +] + +# Weak patterns: require 2+ matches to trigger (reduces false positives) +# e.g., a Hermes event mentioning "401" in its content should not trigger +WEAK_PATTERNS = [ + "401", + "unauthorized", "keystoneauth", "re-authenticate", "credential", + "auth_url", ] @@ -51,11 +58,13 @@ def main() -> None: result_str = str(tool_result).lower() # Check for auth failure patterns - auth_failure = False - for pattern in AUTH_FAILURE_PATTERNS: - if pattern in result_str: - auth_failure = True - break + # Strong patterns: single match sufficient + auth_failure = any(p in result_str for p in STRONG_PATTERNS) + + # Weak patterns: require 2+ matches to reduce false positives + if not auth_failure: + weak_hits = sum(1 for p in WEAK_PATTERNS if p in result_str) + auth_failure = weak_hits >= 2 if not auth_failure: # No auth issue — exit silently diff --git a/plugins/sapcc/hooks/destructive-action-gate.py b/plugins/sapcc/hooks/destructive-action-gate.py index b05c890..d1446fc 100755 --- a/plugins/sapcc/hooks/destructive-action-gate.py +++ b/plugins/sapcc/hooks/destructive-action-gate.py @@ -33,7 +33,6 @@ "neutron_delete_security_group": "Removes the security group. Ports referencing it lose those rules.", "neutron_delete_security_group_rule": "Removes a security group rule. May block traffic that was previously allowed.", "neutron_delete_router": "Deletes the router. Connected subnets lose external connectivity.", - "neutron_delete_floatingip": "Releases the floating IP. External access to the instance is lost.", # Keystone - identity "keystone_delete_application_credential": "Revokes the application credential. Services using it will fail to authenticate.", "keystone_delete_project": "Deletes the project and schedules all resources for cleanup.", diff --git a/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md b/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md index 36aaca5..fbde329 100644 --- a/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md +++ b/plugins/sapcc/skills/sapcc-audit/references/cadf-event-format.md @@ -19,8 +19,8 @@ Source: [sapcc/go-api-declarations/cadf/event.go](https://github.com/sapcc/go-ap "initiator": { "typeURI": "service/security/account/user", "id": "user-keystone-uuid", - "name": "I810033", - "domain": "monsoon3", + "name": "D012345", + "domain": "cc-demo", "host": { "address": "10.0.0.1", "agent": "python-openstackclient/6.0.0" diff --git a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md index d01ad88..634742f 100644 --- a/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md +++ b/plugins/sapcc/skills/sapcc-autoscaling/SKILL.md @@ -19,7 +19,7 @@ Inspect Castellum autoscaling state: view resource configurations, check pending > **Note**: Castellum MCP tools are planned but not yet implemented in the MCP server. The skill documents the service for reference. When tools become available, they will follow the `castellum_` prefix pattern. -### Expected Tools (planned) +### Expected Tools (NOT YET AVAILABLE) | Tool | Purpose | Expected Parameters | |------|---------|---------------------| diff --git a/plugins/sapcc/skills/sapcc-email/SKILL.md b/plugins/sapcc/skills/sapcc-email/SKILL.md index 15b4be7..5a3ef96 100644 --- a/plugins/sapcc/skills/sapcc-email/SKILL.md +++ b/plugins/sapcc/skills/sapcc-email/SKILL.md @@ -18,7 +18,7 @@ Inspect email service status: check sending usage and list available email templ > **Note**: Cronus MCP tools are planned but not yet implemented in the MCP server. The skill documents the service for reference. When tools become available, they will follow the `cronus_` prefix pattern. -### Expected Tools (planned) +### Expected Tools (NOT YET AVAILABLE) | Tool | Purpose | Expected Parameters | |------|---------|---------------------| @@ -55,7 +55,7 @@ Until Cronus MCP tools are available: ### List Available Templates for Integration -1. `cronus_list_templates` — see all configured templates. +1. `cronus_list_senders` — see all configured templates. 2. Note template names/IDs for use in application code. 3. Templates define the email structure; applications provide the dynamic content. diff --git a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md index 462fe1e..3ca0569 100644 --- a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md +++ b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md @@ -89,7 +89,7 @@ Manage Octavia load balancers: list/inspect LBs, listeners, and pools. Understan | Need | Service | Tool | |------|---------|------| -| Subnet details for VIP | Neutron | `neutron_get_subnet()` | +| Subnet details for VIP | Neutron | `neutron_list_subnets()` | | Who created/modified the LB | Hermes | `hermes_list_events(target_type=loadbalancer)` | | LB quota for the project | Limes | `limes_get_project_quota(service=network)` | | Server behind a pool member | Nova | `nova_get_server()` | diff --git a/plugins/sapcc/skills/sapcc-quota/SKILL.md b/plugins/sapcc/skills/sapcc-quota/SKILL.md index 74ce5c5..854bc4b 100644 --- a/plugins/sapcc/skills/sapcc-quota/SKILL.md +++ b/plugins/sapcc/skills/sapcc-quota/SKILL.md @@ -140,7 +140,7 @@ If any is insufficient, the server creation will fail with a quota error. ### Cluster-wide capacity check ``` -1. limes_get_cluster_quota(service="compute") +1. limes_get_cluster_capacity(service="compute") 2. Shows total cluster capacity, domains_quota, usage 3. capacity - domains_quota = unallocated headroom ``` From c60ca8c8ca587b95a98ea705b8d8b03d720b343f Mon Sep 17 00:00:00 2001 From: Nathan Oyler Date: Thu, 7 May 2026 12:13:09 -0700 Subject: [PATCH 11/11] style: normalize skill heading formats for consistency Standardize all skills to same section heading pattern: - ### Read Tools (no qualifier) - ### Write Tools (requires MCP_READ_ONLY=false) - ### Admin Tools (requires MCP_ADMIN_TOOLS=true) Also mark autoscaling/email as (MCP tools planned) in README skills table to set correct expectations. --- README.md | 4 ++-- plugins/sapcc/skills/sapcc-audit/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-baremetal/SKILL.md | 4 ++-- plugins/sapcc/skills/sapcc-dns/SKILL.md | 4 ++-- plugins/sapcc/skills/sapcc-images/SKILL.md | 4 ++-- plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md | 6 +++--- plugins/sapcc/skills/sapcc-object-storage/SKILL.md | 4 ++-- plugins/sapcc/skills/sapcc-quota/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-registry/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-secrets/SKILL.md | 2 +- plugins/sapcc/skills/sapcc-shared-storage/SKILL.md | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 73a324f..26fda95 100644 --- a/README.md +++ b/README.md @@ -168,10 +168,10 @@ When you ask the agent a question, it auto-selects the appropriate skill: | [sapcc-images](plugins/sapcc/skills/sapcc-images/) | [Glance](https://github.com/openstack/glance) | Image management, properties, visibility | | [sapcc-object-storage](plugins/sapcc/skills/sapcc-object-storage/) | [Swift](https://github.com/openstack/swift) | Object storage, containers, large objects | | [sapcc-secrets](plugins/sapcc/skills/sapcc-secrets/) | [Barbican](https://github.com/openstack/barbican) | Secret management, certificates, keys | -| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | [Castellum](https://github.com/sapcc/castellum) | Autoscaling policies, resource operations | +| [sapcc-autoscaling](plugins/sapcc/skills/sapcc-autoscaling/) | [Castellum](https://github.com/sapcc/castellum) | Autoscaling policies (MCP tools planned) | | [sapcc-shared-storage](plugins/sapcc/skills/sapcc-shared-storage/) | [Manila](https://github.com/openstack/manila) | Shared file systems, exports, share networks | | [sapcc-baremetal](plugins/sapcc/skills/sapcc-baremetal/) | [Ironic](https://github.com/openstack/ironic) | Baremetal provisioning, node lifecycle | -| [sapcc-email](plugins/sapcc/skills/sapcc-email/) | [Cronus](https://github.com/sapcc/cronus) | Email notifications, SMTP relay | +| [sapcc-email](plugins/sapcc/skills/sapcc-email/) | [Cronus](https://github.com/sapcc/cronus) | Email notifications (MCP tools planned) | | [credential-setup](plugins/sapcc/skills/credential-setup/) | [Keystone](https://github.com/openstack/keystone) | Guided auth setup with keychain storage | ### Rules diff --git a/plugins/sapcc/skills/sapcc-audit/SKILL.md b/plugins/sapcc/skills/sapcc-audit/SKILL.md index 0a67b42..414e24a 100644 --- a/plugins/sapcc/skills/sapcc-audit/SKILL.md +++ b/plugins/sapcc/skills/sapcc-audit/SKILL.md @@ -17,7 +17,7 @@ Hermes is SAP CC's centralized audit service. It records all API actions across ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md index 52d5e51..3804d06 100644 --- a/plugins/sapcc/skills/sapcc-baremetal/SKILL.md +++ b/plugins/sapcc/skills/sapcc-baremetal/SKILL.md @@ -17,7 +17,7 @@ Inspect baremetal nodes: list nodes, check provision/power states, understand ma ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| @@ -27,7 +27,7 @@ Inspect baremetal nodes: list nodes, check provision/power states, understand ma | `ironic_list_allocations` | List node allocations | `node_id`, `resource_class` | | `ironic_list_portgroups` | List port groups (bonded NICs) | `node_id` | -### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) +### Admin Tools (requires MCP_ADMIN_TOOLS=true) | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-dns/SKILL.md b/plugins/sapcc/skills/sapcc-dns/SKILL.md index fa7bf58..694e61e 100644 --- a/plugins/sapcc/skills/sapcc-dns/SKILL.md +++ b/plugins/sapcc/skills/sapcc-dns/SKILL.md @@ -17,7 +17,7 @@ Manage DNS zones and recordsets: list zones, inspect zone details, query records ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| @@ -27,7 +27,7 @@ Manage DNS zones and recordsets: list zones, inspect zone details, query records | `designate_list_zone_transfer_requests` | List outgoing zone transfer requests | `zone_id`, `status` | | `designate_list_zone_transfer_accepts` | List accepted zone transfers | (none) | -### Write Tools* (require `MCP_READ_ONLY=false`) +### Write Tools (requires MCP_READ_ONLY=false) | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-images/SKILL.md b/plugins/sapcc/skills/sapcc-images/SKILL.md index f862b4b..24f22fc 100644 --- a/plugins/sapcc/skills/sapcc-images/SKILL.md +++ b/plugins/sapcc/skills/sapcc-images/SKILL.md @@ -16,7 +16,7 @@ Inspect and list VM images: find available boot images, check image status, unde ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| @@ -24,7 +24,7 @@ Inspect and list VM images: find available boot images, check image status, unde | `glance_get_image` | Full image detail by UUID | `image_id` (**required**) | | `glance_list_image_members` | List projects an image is shared with | `image_id` (**required**) | -### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) +### Admin Tools (requires MCP_ADMIN_TOOLS=true) | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md index 3ca0569..e71feb0 100644 --- a/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md +++ b/plugins/sapcc/skills/sapcc-loadbalancer/SKILL.md @@ -17,7 +17,7 @@ Manage Octavia load balancers: list/inspect LBs, listeners, and pools. Understan ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| @@ -30,14 +30,14 @@ Manage Octavia load balancers: list/inspect LBs, listeners, and pools. Understan | `octavia_list_l7policies` | List L7 routing policies | `listener_id`, `name` | | `octavia_list_l7rules` | List rules for L7 policy | `l7policy_id` (**required**) | -### Write Tools* (require `MCP_READ_ONLY=false`) +### Write Tools (requires MCP_READ_ONLY=false) | Tool | Purpose | Key Parameters | |------|---------|----------------| | `octavia_create_loadbalancer`* | Create a new load balancer | `name` (**required**), `vip_subnet_id` (**required**), `description`, `confirmed` | | `octavia_delete_loadbalancer`* | Delete a load balancer | `loadbalancer_id` (**required**), `cascade` (bool, deletes children), `confirmed` | -### Admin Tools† (require `MCP_ADMIN_TOOLS=true`) +### Admin Tools (requires MCP_ADMIN_TOOLS=true) | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md index 253a5c7..7200a8e 100644 --- a/plugins/sapcc/skills/sapcc-object-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-object-storage/SKILL.md @@ -17,7 +17,7 @@ Inspect Swift containers and objects: list containers, browse object listings, c ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| @@ -25,7 +25,7 @@ Inspect Swift containers and objects: list containers, browse object listings, c | `swift_list_objects` | List objects in a container | `container` (**required**), `prefix`, `delimiter` (e.g., `/` for pseudo-dirs), `limit` | | `swift_get_object_metadata` | Get object metadata (not content) | `container` (**required**), `object` (**required**) | -### Write Tools* (require `MCP_READ_ONLY=false`) +### Write Tools (requires MCP_READ_ONLY=false) | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-quota/SKILL.md b/plugins/sapcc/skills/sapcc-quota/SKILL.md index 854bc4b..a3a0d45 100644 --- a/plugins/sapcc/skills/sapcc-quota/SKILL.md +++ b/plugins/sapcc/skills/sapcc-quota/SKILL.md @@ -16,7 +16,7 @@ Limes is SAP CC's central quota management service. Not part of vanilla OpenStac ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-registry/SKILL.md b/plugins/sapcc/skills/sapcc-registry/SKILL.md index 602efdd..2b5597d 100644 --- a/plugins/sapcc/skills/sapcc-registry/SKILL.md +++ b/plugins/sapcc/skills/sapcc-registry/SKILL.md @@ -16,7 +16,7 @@ Keppel is SAP CC's multi-tenant container image registry. Not vanilla OpenStack. ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-secrets/SKILL.md b/plugins/sapcc/skills/sapcc-secrets/SKILL.md index 580a19d..0737fca 100644 --- a/plugins/sapcc/skills/sapcc-secrets/SKILL.md +++ b/plugins/sapcc/skills/sapcc-secrets/SKILL.md @@ -17,7 +17,7 @@ Inspect secrets stored in the key manager: list secrets, check metadata, verify ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------| diff --git a/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md index 014b279..2e19d81 100644 --- a/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md +++ b/plugins/sapcc/skills/sapcc-shared-storage/SKILL.md @@ -17,7 +17,7 @@ Manage shared file systems: list shares, inspect share details, understand proto ## MCP Tools -### Read Tools (always available) +### Read Tools | Tool | Purpose | Key Parameters | |------|---------|----------------|