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

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, the /convert endpoint is defined as an async function that delegates work to the core processing logic:

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

This implementation in 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 (lines 80-88), the implementation uses aiohttp.ClientSession for fully non-blocking HTTP requests:

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 (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 (lines 58-62) wraps the FastAPI application using FastMCP:

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:


# 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:

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 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 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. 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, 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 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, 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →