Typical Workflow for Developing and Integrating a New Octavia Load Balancer Management Tool
Adding a new Octavia load balancer management tool to MCP-OpenStack-Ops requires implementing a synchronous core function in the service layer, exporting it via the module initializer, and wrapping it with an asynchronous MCP tool decorator that automatically registers at server startup.
The MCP-OpenStack-Ops repository provides a modular framework for managing OpenStack resources through the Model Context Protocol (MCP). When extending this server to support new Octavia load balancer operations—such as managing L7 rules, listeners, or pools—developers follow a consistent two-layer architecture that separates OpenStack SDK interactions from MCP protocol exposure. This workflow ensures that new capabilities remain project-isolated, safely gated, and automatically discoverable by MCP clients.
Understanding the Two-Layer Architecture
The codebase is structured around a service layer and a tool-wrapper layer. The service layer contains synchronous functions that interact directly with the OpenStack SDK, while the tool layer provides thin asynchronous wrappers that expose these functions to the MCP protocol.
Service Layer (src/mcp_openstack_ops/services/load_balancer/): Houses pure OpenStack-SDK interactions. Functions like set_load_balancer_l7_rule in services/load_balancer/l7_policies.py accept an action string and **kwargs, perform the SDK call, and return plain Python dictionaries. This design makes the core logic reusable by CLI tools, monitoring scripts, or other automation outside the MCP context.
Tool Layer (src/mcp_openstack_ops/tools/): Contains asynchronous coroutines decorated with @mcp.tool(). Each wrapper calls the corresponding service function, adds a timestamp, logs the operation, and returns a JSON string. The file src/mcp_openstack_ops/mcp_main.py automatically imports all modules under tools/ at startup, eliminating manual registration boilerplate.
Step-by-Step Implementation Workflow
Follow this sequence to add a new Octavia management capability, using the implementation of L7 rule management as a reference pattern.
Step 1: Implement Core Logic in the Service Module
Create or extend the appropriate service module in src/mcp_openstack_ops/services/load_balancer/. The function must use get_openstack_connection() to ensure project isolation and accept an action parameter to distinguish between create, update, and delete operations. This follows the pattern established by existing functions like set_load_balancer_l7_policy.
# src/mcp_openstack_ops/services/load_balancer/l7_policies.py
def set_load_balancer_l7_rule(action: str, **kwargs):
"""Create, update, or delete an Octavia L7 rule."""
conn = get_openstack_connection()
if action == "create":
rule = conn.load_balancer.create_l7_rule(
listener_id=kwargs["listener_id"],
l7policy_id=kwargs["policy_id"],
type=kwargs["type"],
compare_type=kwargs["compare_type"],
value=kwargs["value"],
)
return {"success": True, "rule_id": rule.id, "message": "L7 rule created"}
# Handle update and delete similarly...
Core functions like get_load_balancer_list in services/load_balancer/core.py (lines 34-38) demonstrate the standard logging pattern using logger.info for entry/exit tracking, which aids troubleshooting in large OpenStack deployments.
Step 2: Export the Service Function
Make the new function available to the tool layer by adding it to the export list in src/mcp_openstack_ops/services/load_balancer/__init__.py (lines 37-44).
# src/mcp_openstack_ops/services/load_balancer/__init__.py
from .l7_policies import (
get_load_balancer_l7_policies,
set_load_balancer_l7_policy,
set_load_balancer_l7_rule, # New export
)
This re-export pattern allows tool wrappers to import from the package root while maintaining clean separation of concerns.
Step 3: Create the MCP Tool Wrapper
Implement a thin asynchronous wrapper in src/mcp_openstack_ops/tools/. The wrapper must be decorated with @mcp.tool() to enable automatic MCP registration, handle exceptions, and return a timestamped JSON payload. This mirrors the pattern found in tools/set_load_balancer_listener.py.
# src/mcp_openstack_ops/tools/set_load_balancer_l7_rule.py
import json
from datetime import datetime
from ..functions import set_load_balancer_l7_rule as _set_load_balancer_l7_rule
from ..mcp_main import logger, mcp
@mcp.tool()
async def set_load_balancer_l7_rule(action: str, **kwargs) -> str:
"""MCP-exposed async wrapper for L7 rule management."""
try:
logger.info(f"Octavia L7 rule – action={action} kwargs={kwargs}")
result = _set_load_balancer_l7_rule(action, **kwargs)
return json.dumps(
{
"timestamp": datetime.now().isoformat(),
"result": result,
"success": True
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
err = f"Error: Failed to manage L7 rule – {e}"
logger.error(err)
return json.dumps(
{"timestamp": datetime.now().isoformat(), "error": err, "success": False},
indent=2,
)
Step 4: Verify Safety Gates and Environment Controls
The server startup logic in src/mcp_openstack_ops/mcp_main.py (lines 45-55) validates the ALLOW_MODIFY_OPERATIONS environment variable. Unless this flag is set to true, the server runs in read-only mode and blocks mutating SDK calls. Ensure your new tool follows this safety model by relying on the core service functions, which respect these server-wide safety controls.
Step 5: Test the New Tool via MCP Client
Once the server is running (uv run python -m mcp_openstack_ops), invoke the new tool through an MCP client. Listing operations work immediately, while mutating operations require ALLOW_MODIFY_OPERATIONS=true.
# List existing L7 rules for a listener
mcp-client get_load_balancer_l7_rules --lb-name-or-id my-lb --listener-name-or-id listener-1
# Create a new rule (requires ALLOW_MODIFY_OPERATIONS=true)
mcp-client set_load_balancer_l7_rule \
--action create \
--listener-id listener-1 \
--policy-id policy-42 \
--type PATH \
--compare_type STARTS_WITH \
--value /api/v2/
The client receives a JSON payload containing the operation result and an ISO timestamp.
Step 6: Update Documentation
Add the new tool to the load balancer reference table in README.md (section 7. Load Balancer (Octavia)) to maintain the discoverability standard shown in the existing load balancer documentation. Include natural-language usage examples in src/mcp_openstack_ops/prompt_template.md to help AI assistants generate correct command syntax.
Summary
- Implement core logic in
src/mcp_openstack_ops/services/load_balancer/<module>.pyusing synchronous OpenStack SDK calls that return plain dictionaries and respect project isolation viaget_openstack_connection(). - Export the function in
src/mcp_openstack_ops/services/load_balancer/__init__.py(lines 37-44) to make it available to the tool layer. - Create an async wrapper in
src/mcp_openstack_ops/tools/decorated with@mcp.tool()that returns timestamped JSON and handles logging, following the pattern in existing tool files. - Respect safety gates by ensuring mutating operations honor the
ALLOW_MODIFY_OPERATIONSenvironment variable enforced bymcp_main.py(lines 45-55). - Document the new capability in
README.mdandprompt_template.mdfor discoverability, and add unit tests that mock the OpenStack SDK to validate return dictionary shapes.
Frequently Asked Questions
How does the MCP server automatically register new tools?
The src/mcp_openstack_ops/mcp_main.py file scans and imports all modules under the tools/ package at startup. Any coroutine decorated with @mcp.tool() is automatically registered without requiring manual entry in a registry file or configuration list.
What safety mechanisms prevent accidental modifications to load balancers?
The server startup logic in mcp_main.py (lines 45-55) checks for ALLOW_MODIFY_OPERATIONS=true in the environment variables. Unless this flag is set, the server runs in read-only mode and blocks mutating SDK calls. Additionally, get_openstack_connection() enforces project isolation by binding to a specific OS_PROJECT_NAME, preventing cross-tenant operations.
Why separate the service layer from the tool layer?
This separation allows the synchronous OpenStack SDK logic to remain reusable by non-MCP components such as CLI scripts or monitoring automation, while the thin async wrappers handle MCP-specific concerns like JSON serialization, timestamping, and protocol-compliant error handling. It also keeps the codebase modular and easier to unit test.
Where should I add tests for a new Octavia management tool?
Create unit tests in the tests/ directory (if present) or alongside existing test files, mocking the OpenStack SDK to validate the shape of returned dictionaries. Test both the core service function (verifying SDK call parameters) and the tool wrapper (verifying JSON output structure and error handling).
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →