# Internal Logic for Bulk Operations Filtering in MCP OpenStack Ops

> Uncover the seven-step filtering pipeline in MCP OpenStack Ops for efficient bulk operations. Learn how name_contains and status filters streamline resource management and ensure accurate execution.

- Repository: [JungJungIn/mcp-openstack-ops](https://github.com/call518/mcp-openstack-ops)
- Tags: internals
- Published: 2026-02-26

---

**The MCP OpenStack Ops toolkit implements a seven-step filtering pipeline that validates input parameters, queries OpenStack APIs for candidate resources, applies secondary local filters like `flavor_contains`, deduplicates results, and executes bulk actions with post-operation status verification.**

The `mcp-openstack-ops` repository provides powerful bulk management capabilities for OpenStack resources through tools like `set_instance` and `set_networks`. Understanding the internal logic for bulk operations filtering reveals how parameters such as `name_contains` and `status` translate into efficient API queries and local processing. This analysis examines the source code implementation to show exactly how the toolkit discovers, filters, and acts upon large resource sets without requiring explicit name enumeration.

## The Seven-Step Filtering Pipeline

### Step 1: Targeting Mode Detection

The tools first determine whether the user provided direct target names or filter parameters. In [`src/mcp_openstack_ops/tools/set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_instance.py#L100-L106) and [`src/mcp_openstack_ops/tools/set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_networks.py#L12-L19), the code checks `has_direct_targets` against `has_filter_params`. If both are present, the tool raises a mutually-exclusive error; if neither is present, it returns an error requesting explicit input.

### Step 2: Building the Candidate Set

When operating in filter mode, the tools query OpenStack to build an initial candidate list. For instances, `set_instance` calls `_search_instances` from [`src/mcp_openstack_ops/services/compute.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/compute.py) with the `name_contains` parameter (lines L116-L130 in [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py)). If a `status` filter is provided, it additionally calls `_get_instances_by_status`, passing `status.upper()` to ensure case-insensitive matching.

### Step 3: Applying Secondary Filters

After retrieving the initial candidate set, the code applies additional criteria locally. In [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L33-L41), the implementation checks `flavor_contains` and `image_contains` by iterating through results and performing substring matches against `instance.get('flavor', '')` and `instance.get('image', '')`. This local filtering reduces API call overhead by refining results client-side.

### Step 4: Deduplication and Name Extraction

To handle resources that match multiple filter criteria, the tools maintain a `seen_ids` set. As implemented in [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L44-L51), the code tracks unique resource IDs before extracting the `name` field for each entry. This prevents duplicate operations when a resource matches both `name_contains` and `status` filters simultaneously.

### Step 5: Aborting on Empty Results

If filtering yields no matches, the tools assemble descriptive error messages. The implementation in [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L52-L60) and [`set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_networks.py#L43-L48) constructs a "no resources found matching filters" response that includes the specific filter descriptors provided by the user, enabling rapid troubleshooting of overly restrictive criteria.

### Step 6: Performing Bulk Operations

Once the final `name_list` is prepared, the tools iterate through each resource and execute the requested action. In [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L47-L74) and [`set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_networks.py#L24-L52), the code calls `_set_instance` or `_set_networks` for each target, recording success or failure states in a results aggregator.

### Step 7: Post-Action Status Verification

After completing all operations, the tools verify final states. The code in [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L83-L115) and [`set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_networks.py#L94-L115) sleeps briefly to allow OpenStack to process changes, then queries each resource using `_get_instance_by_name` or `_get_resource_status_by_name`. The final response includes a colored status indicator (🟢, 🟡, 🔴) for each resource along with operation summaries.

## Implementation Examples by Resource Type

### Instance Filtering in set_instance.py

The instance filtering logic combines API searches with local substring matching:

```python

# Detect filter mode

has_filter_params = any([name_contains, status, flavor_contains, image_contains])

# Build candidate list

if name_contains:
    result = _search_instances(search_term=name_contains,
                               search_fields=['name'],
                               limit=200,
                               include_inactive=True)
    # result may be a dict with 'instances' or a plain list

if status:
    status_instances = _get_instances_by_status(status.upper())
    search_results.extend(status_instances)

# Apply extra filters

if flavor_contains or image_contains:
    filtered_results = []
    for instance in search_results:
        if flavor_contains and flavor_contains.lower() not in instance.get('flavor', '').lower():
            continue
        if image_contains and image_contains.lower() not in instance.get('image', '').lower():
            continue
        filtered_results.append(instance)
    search_results = filtered_results

```

This implementation in [`src/mcp_openstack_ops/tools/set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_instance.py#L100-L130) demonstrates how the tool handles complex filtering scenarios while maintaining compatibility with varying OpenStack API response formats.

### Network Filtering in set_networks.py

Network filtering follows a similar pattern but operates against the full network list retrieved from [`src/mcp_openstack_ops/services/network.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/network.py):

```python

# Retrieve all networks, then apply the two possible filters

all_networks_info = _get_network_details("all")
for network in all_networks_info:
    network_name = network.get('name', '')
    network_status = network.get('status', '')

    if name_contains and name_contains.lower() not in network_name.lower():
        continue
    if status and status.upper() != network_status.upper():
        continue
    target_names.append(network_name)

```

As shown in [`src/mcp_openstack_ops/tools/set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_networks.py#L25-L45), this approach retrieves all networks once, then applies filters locally to minimize API traffic.

## Usage Examples

Practical implementations of the bulk operations filtering logic include:

```python

# Stop all ACTIVE instances whose name contains "dev"

await set_instance(name_contains="dev", status="ACTIVE", action="stop")

# Delete every network whose name includes "test" and is currently DOWN

await set_networks(action="delete", name_contains="test", status="DOWN")

```

Both calls trigger the seven-step pipeline automatically, discovering matching resources and applying the requested action without manual name enumeration.

## Key Files and Functions

Understanding the internal logic for bulk operations filtering requires familiarity with these components:

- [`src/mcp_openstack_ops/tools/set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_instance.py) - Implements bulk instance operations with support for `name_contains`, `status`, `flavor_contains`, and `image_contains` filters.
- [`src/mcp_openstack_ops/tools/set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_networks.py) - Handles bulk network operations using `name_contains` and `status` parameters.
- [`src/mcp_openstack_ops/tools/set_snapshot.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/set_snapshot.py) - Provides similar filter logic for OpenStack snapshots.
- [`src/mcp_openstack_ops/services/compute.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/compute.py) - Contains `_search_instances` used for name-based instance discovery.
- [`src/mcp_openstack_ops/services/network.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/network.py) - Provides `_get_network_details` for network retrieval.

## Summary

- The bulk operations filtering logic enforces mutual exclusivity between direct target names and filter parameters at validation time.
- Initial candidates are retrieved through OpenStack API calls (`_search_instances`, `_get_instances_by_status`), while secondary filtering occurs locally to optimize performance.
- A deduplication mechanism using `seen_ids` prevents duplicate operations when resources match multiple filter criteria.
- The pipeline includes comprehensive error handling for empty result sets and provides detailed post-operation status verification with visual indicators.
- Similar filtering architectures are implemented across `set_instance`, `set_networks`, and `set_snapshot` tools, ensuring consistent behavior across resource types.

## Frequently Asked Questions

### How does the toolkit handle case sensitivity in filter parameters?

The tools normalize case sensitivity by converting status parameters to uppercase using `.upper()` before comparison, while name-related filters use `.lower()` for substring matching. This ensures that filters like `status="active"` and `status="ACTIVE"` match identically, and `name_contains="Dev"` captures resources named "development" or "DEV-01".

### What happens if both direct names and filter parameters are provided?

The code explicitly prevents this combination. In [`set_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_instance.py#L100-L106) and [`set_networks.py`](https://github.com/call518/mcp-openstack-ops/blob/main/set_networks.py#L12-L19), the tool checks for the presence of both `has_direct_targets` and `has_filter_params`, raising a mutually-exclusive error if both are true. This design prevents ambiguous targeting and ensures predictable operation scopes.

### Can filters be combined to match multiple criteria simultaneously?

Yes, filters within the same category combine with AND logic. For example, providing both `name_contains="prod"` and `status="ACTIVE"` returns only instances that satisfy both conditions. The code iterates through all candidates and applies each active filter sequentially, keeping only resources that pass every check.

### Why does the network filter retrieve all networks instead of using a search API?

The `set_networks` implementation calls `_get_network_details("all")` from [`src/mcp_openstack_ops/services/network.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/network.py) and performs client-side filtering because OpenStack Neutron's API doesn't provide substring search capabilities equivalent to Nova's instance search. This approach trades bandwidth for functionality, retrieving the full list once and applying `name_contains` and `status` filters locally in the Python loop shown at lines L25-L45.