# How the MCP Server Implements Pagination for Large OpenStack Resource Lists

> Learn how the MCP server implements limit/offset pagination for large OpenStack resource lists. Discover efficient client-side iteration with has_next, next_offset, and total_count metadata.

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

---

**The MCP server implements limit/offset pagination by validating parameters (limit capped at 200), slicing the full resource list from OpenStack SDK results, and returning rich metadata including `has_next`, `next_offset`, and `total_count` to enable efficient client-side iteration.**

The `call518/mcp-openstack-ops` repository provides a Model Context Protocol (MCP) server that interfaces with OpenStack clouds. When retrieving potentially massive collections of servers, load balancers, or volumes, the server employs a consistent **limit/offset pagination** strategy across all service modules rather than streaming entire datasets in a single response.

## Core Pagination Logic in the Compute Service

The primary pagination implementation resides in [`src/mcp_openstack_ops/services/compute.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/compute.py) within the `get_instance_details` function. This function retrieves server instances and applies consistent slicing logic regardless of the underlying OpenStack project size.

### Parameter Validation and Safety Caps

Before processing any requests, the function sanitizes incoming pagination parameters to prevent abuse and ensure stable performance. According to the source code in `call518/mcp-openstack-ops`, the implementation enforces hard boundaries:

- **Maximum limit**: 200 items per request
- **Minimum limit**: 1 item per request  
- **Minimum offset**: 0 (negative values reset to zero)

```python
def get_instance_details(..., limit: int = 50, offset: int = 0, include_all: bool = False) -> Dict[str, Any]:
    # Input sanitisation

    if limit > 200: limit = 200
    if limit < 1:   limit = 1
    if offset < 0:  offset = 0
    # ... additional implementation

```

### Resource Slicing and Metadata Generation

After fetching the complete server list from the OpenStack SDK, the function applies Python list slicing to return only the requested window. The implementation calculates comprehensive pagination metadata to guide subsequent client requests:

```python
    # Fetch all servers (project-scoped)

    all_servers = list(conn.compute.servers(details=True, all_projects=False))
    total_count = len(all_servers)

    # Apply pagination slice

    if include_all:
        paginated_servers = all_servers
    else:
        paginated_servers = all_servers[offset:offset + limit]

    # Build metadata flags

    has_next = (offset + limit) < total_count
    has_prev = offset > 0
    next_offset = offset + limit if has_next else None
    prev_offset = max(0, offset - limit) if has_prev else None

    result = {
        'instances': instances,
        'count': len(instances),
        'total_count': total_count,
        'limit': limit,
        'offset': offset,
        'has_next': has_next,
        'has_prev': has_prev,
        'next_offset': next_offset,
        'prev_offset': prev_offset,
    }
    return result

```

The slice operation `all_servers[offset:offset + limit]` performs the actual pagination, while the metadata dictionary enables callers to navigate forward and backward through the full dataset.

## Load Balancer Pagination Implementation

The load balancer service in [`src/mcp_openstack_ops/services/load_balancer/core.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/load_balancer/core.py) follows an identical pattern through the `get_load_balancer_list` function. As implemented in `call518/mcp-openstack-ops`, this module validates the `limit` parameter and applies the same slicing technique to load balancer collections:

```python
def get_load_balancer_list(limit: int = 50, offset: int = 0, include_all: bool = False) -> Dict[str, Any]:
    # Limit validation

    if not include_all:
        limit = max(1, min(limit, 200))

    # Collect project-scoped load balancers

    all_lbs = [lb for lb in conn.load_balancer.load_balancers()
               if getattr(lb, 'project_id', None) == current_project_id]

    # Apply pagination slice

    load_balancers = all_lbs if include_all else all_lbs[offset:offset + limit]

    # Build summary with pagination metadata

    result = {
        'success': True,
        'load_balancers': lb_details,
        'summary': {
            'total_returned': len(lb_details),
            'limit': limit if not include_all else 'all',
            'offset': offset if not include_all else 0,
            'has_more': (offset + limit) < len(all_lbs),
            'processing_time_seconds': round(processing_time, 2),
            'project_id': current_project_id,
        }
    }
    return result

```

Note that this service uses `has_more` rather than `has_next`, but provides equivalent functionality for determining if additional pages exist.

## Tool Wrappers and API Exposure

The MCP tool layer exposes these pagination parameters directly to clients. In [`src/mcp_openstack_ops/tools/get_instance.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/tools/get_instance.py), the tool wrapper forwards arguments to the underlying service functions:

| Tool | Service Function | Exposed Parameters |
|------|-----------------|-------------------|
| `get_instance` | `get_instance_details` | `limit`, `offset`, `all_instances` (maps to `include_all`) |
| `search_instances` | `search_instances` | `limit`, `offset` |
| `get_load_balancer_list` | `get_load_balancer_list` | `limit`, `offset`, `include_all` |

The `get_instance` tool specifically maps its `all_instances` boolean flag to the service's `include_all` parameter:

```python
if all_instances:
    result_data = _get_instance_details(
        instance_names=None,
        limit=limit,
        offset=offset,
        include_all=True
    )

```

This architecture ensures that pagination controls remain consistent across all OpenStack resource types exposed through the MCP server.

## Client-Side Pagination Workflow

To retrieve large datasets efficiently, clients should check the pagination metadata returned in each response and adjust the offset accordingly. Here is the standard iteration pattern for compute instances:

```python

# Retrieve first page (default 50 items)

first_page = mcp.tools.get_instance(limit=50, offset=0)

# Check for additional pages

if first_page['has_next']:
    second_page = mcp.tools.get_instance(
        limit=50, 
        offset=first_page['next_offset']
    )

```

For load balancers, the pattern is similar but uses the `has_more` field within the summary block:

```python

# Request specific window

lbs = mcp.tools.get_load_balancer_list(limit=20, offset=40)

# Continue if more data exists

if lbs['summary']['has_more']:
    next_page = mcp.tools.get_load_balancer_list(limit=20, offset=60)

```

Both patterns rely on the server-side metadata to safely traverse the entire resource inventory without missing items or exceeding API rate limits.

## Summary

- **Hard limits**: The MCP server enforces a maximum `limit` of 200 items and minimum of 1, with `offset` clamped to non-negative values across all services including [`src/mcp_openstack_ops/services/compute.py`](https://github.com/call518/mcp-openstack-ops/blob/main/src/mcp_openstack_ops/services/compute.py).
- **Slicing strategy**: Pagination occurs via Python list slicing (`items[offset:offset+limit]`) after retrieving the full resource list from the OpenStack SDK, not through OpenStack's native API pagination.
- **Metadata richness**: Every paginated response includes `total_count`, boolean flags (`has_next`/`has_more`), and calculated offsets (`next_offset`, `prev_offset`) to support bidirectional navigation.
- **Bypass option**: The `include_all` parameter (exposed as `all_instances` in some tools) allows internal processes to retrieve complete datasets without pagination overhead.
- **Consistent exposure**: All resource-specific services (compute, load balancer, storage) follow identical pagination patterns, ensuring predictable behavior across the `call518/mcp-openstack-ops` codebase.

## Frequently Asked Questions

### What is the maximum number of items the MCP server returns per page?

The server enforces a hard cap of **200 items** per request. If a client specifies a `limit` exceeding 200, the value is automatically reduced to 200. Conversely, values below 1 are raised to 1 to ensure at least one item returns.

### How does the `include_all` parameter affect pagination behavior?

When `include_all` is set to `True`, the server bypasses the slicing logic entirely and returns the complete resource list regardless of `limit` or `offset` values. This flag is primarily intended for internal administrative tools that require full dataset visibility without iterative API calls.

### What metadata fields indicate whether more results are available?

For compute resources, check `has_next` (boolean) and `next_offset` (integer or `None`). For load balancers, inspect `summary['has_more']` (boolean). Both patterns provide the next offset value required to fetch the subsequent page, or `None`/`False` when the current page represents the end of the dataset.

### Does the MCP server use OpenStack's native pagination APIs?

No. The server retrieves the **complete resource list** from the OpenStack SDK (e.g., `conn.compute.servers()` or `conn.load_balancer.load_balancers()`), then applies **client-side slicing** to implement pagination. This approach ensures consistent pagination behavior across different OpenStack services regardless of whether the underlying API supports native limit/offset parameters.