# How the MCP 3D Relief Server Handles Concurrent Image Processing Requests

> Learn how the MCP 3D Relief server uses FastAPI's async architecture to handle concurrent image processing requests efficiently with non-blocking I/O.

- Repository: [bigchx/mcp_3d_relief](https://github.com/bigchx/mcp_3d_relief)
- Tags: internals
- Published: 2026-02-26

---

**The MCP 3D Relief server leverages FastAPI's asynchronous architecture to process multiple image-to-STL conversion requests concurrently, using non-blocking I/O for network operations while handling CPU-intensive tasks synchronously per request.**

The bigchx/mcp_3d_relief repository implements a high-performance 3D relief generation service that must efficiently handle concurrent image processing requests. Built on FastAPI and ASGI, the server orchestrates asynchronous HTTP handling with CPU-bound OpenCV operations to convert 2D images into STL 3D models without blocking the event loop.

## Asynchronous Request Handling Architecture

### FastAPI Event Loop and ASGI Foundation

The server relies on **FastAPI**, an asynchronous ASGI web framework that runs on an event loop provided by Uvicorn or Hypercorn. When deployed, each incoming HTTP request is scheduled as a coroutine, allowing the loop to interleave many concurrent image processing requests without blocking on I/O operations.

### The Convert Endpoint Implementation

In [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py), the `/convert` endpoint is defined as an async function that delegates work to the core processing logic:

```python
@app.post("/convert")
async def convert_image(...):
    result = await relief(...)
    return result

```

This implementation in [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py) (lines 16-55) ensures that while one request awaits the `relief` coroutine, the event loop can process other incoming connections.

## Concurrent Image Processing Pipeline

### Non-Blocking Network I/O with aiohttp

When processing remote images, the server must download data from URLs without stalling other concurrent image processing requests. Inside [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) (lines 80-88), the implementation uses `aiohttp.ClientSession` for fully non-blocking HTTP requests:

```python
async with aiohttp.ClientSession() as session:
    async with session.get(image_path) as response:
        image_data = await response.read()

```

This asynchronous download mechanism ensures that while one request fetches a large image from the network, the server continues accepting and processing other requests.

### CPU-Bound Operations and Event Loop Behavior

After downloading the image, the pipeline performs computationally intensive operations including OpenCV transformations (`cv2.resize`, `cv2.GaussianBlur`) and STL file generation using Python loops. These CPU-bound stages in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) (lines 67-78) run synchronously on the event loop thread.

While this means individual CPU tasks block their specific coroutine until completion, they do **not** prevent the event loop from accepting new concurrent image processing requests or handling I/O for other active connections. However, heavy CPU load may limit overall throughput since Python's Global Interpreter Lock (GIL) serializes CPU execution.

## FastMCP Integration and Transport Layer

The server initialization in [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py) (lines 58-62) wraps the FastAPI application using FastMCP:

```python
mcp = FastMCP.from_fastapi(app, "mcp_3d_relief")
mcp.run(transport="stdio")

```

FastMCP acts as a bridge between the FastAPI application and command-line interfaces without modifying the underlying async concurrency model. When deployed with a proper ASGI server like Uvicorn, the same concurrent image processing request handling characteristics apply.

## Testing Concurrent Requests

You can verify the server's concurrent handling capabilities using parallel `curl` commands or asynchronous Python clients:

```bash

# Run four concurrent conversions

for i in {1..4}; do
  curl -s -X POST http://localhost:8000/convert \
       -F "image_path=https://example.com/pic$i.jpg" &
done
wait

```

For programmatic testing with proper async handling:

```python
import asyncio
import httpx

async def call_convert(img_path: str):
    async with httpx.AsyncClient() as client:
        data = {"model_width": "50", "model_thickness": "5"}
        files = {"image_path": (None, img_path)}
        resp = await client.post(
            "http://localhost:8000/convert", 
            data=data, 
            files=files
        )
        return resp.json()

async def main():
    results = await asyncio.gather(
        call_convert("uploads/demo.png"),
        call_convert("https://example.com/remote1.jpg"),
        call_convert("https://example.com/remote2.jpg"),
    )
    print(results)

asyncio.run(main())

```

These examples demonstrate that the server accepts multiple requests simultaneously, processing network I/O asynchronously while handling CPU-intensive STL generation sequentially per request.

## Summary

- The server uses **FastAPI's async architecture** to handle multiple concurrent image processing requests without blocking on network I/O.
- **Non-blocking downloads** via `aiohttp.ClientSession` in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) allow the event loop to process other requests during network transfers.
- **CPU-bound operations** (OpenCV processing and STL generation) run synchronously per request but do not prevent the server from accepting new connections.
- The **FastMCP wrapper** in [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py) bridges the FastAPI application to CLI transports without affecting concurrency characteristics.

## Frequently Asked Questions

### Does the server block other requests during image processing?

No, the server does not block other requests during image processing, but with caveats. While **network I/O operations** (downloading images via `aiohttp`) are fully non-blocking and allow other requests to proceed, the **CPU-intensive stages** (OpenCV transformations and STL file generation) run synchronously on the event loop. This means a single heavy CPU task won't block new connections from being accepted, but it may limit overall throughput under extreme load due to Python's Global Interpreter Lock.

### How many concurrent requests can the MCP 3D Relief server handle?

The theoretical limit depends on the **ASGI server configuration** (e.g., Uvicorn workers) and available system resources. Since the application uses async I/O for network operations, it can maintain thousands of concurrent connections for the download phase. However, the **CPU-bound processing** stages become the bottleneck; each active request consumes CPU cycles for OpenCV operations and STL generation. For production deployments, running multiple worker processes behind a load balancer is recommended to scale beyond single-process CPU limitations.

### What happens if multiple clients upload large images simultaneously?

When multiple clients upload large images, the server handles each upload as a separate async task in [`server.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/server.py). The **FastAPI endpoint** accepts the multipart form data asynchronously, allowing the server to receive files from multiple clients concurrently without blocking. Once received, each image enters the processing pipeline in [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py), where downloads (if URLs are provided) use `aiohttp` for non-blocking retrieval. The subsequent CPU-intensive processing occurs sequentially per image, meaning large images will take longer to process but won't prevent the server from accepting new uploads.

### Is the image download process blocking or non-blocking?

The image download process is **fully non-blocking** when fetching from remote URLs. The [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py) module uses `aiohttp.ClientSession` with async context managers to perform HTTP GET requests, allowing the event loop to schedule other tasks while waiting for network data. This is implemented in lines 80-88 of [`relief.py`](https://github.com/bigchx/mcp_3d_relief/blob/main/relief.py), where `await session.get()` yields control back to the event loop. However, if the image is provided as a local file upload through the FastAPI endpoint, the initial file I/O is handled by Starlette's multipart parser, which is also non-blocking in the ASGI context.