How to Turn OpenAPI Specs into AI Agent Tools with MCP: A Multi‑Language Guide

This article explains how the Model Context Protocol (MCP) bridges large language models and external services by converting OpenAPI specifications into callable tools, covering generation with openapi‑generator, mapping rules, three runtime modes (stdio, streamable, SSE), and implementation details in Java, Python, and Go.

360 Smart Cloud
360 Smart Cloud
360 Smart Cloud
How to Turn OpenAPI Specs into AI Agent Tools with MCP: A Multi‑Language Guide

Background and Industry Trend

In modern AI agent ecosystems, the Model Context Protocol (MCP) acts as a bridge between large language models (LLMs) and external tools/services, turning existing REST APIs (described by OpenAPI) into tools that models can invoke.

Overview

Use openapi-generator to automatically generate client SDKs for various languages.

Implement an MCP Handler that maps each operationId to an MCP tool.

Three runtime/transport modes are supported: stdio (stdin/stdout), HTTP streamable (chunked), and SSE (Server‑Sent Events), each with its own implementation challenges.

Key concerns include partial/delta streaming, multiplexing, header and auth propagation, error handling, retry strategies, and schema compatibility.

1. OpenAPI Generator Introduction

openapi-generator

(community edition) is the most common automatic code generation tool, supporting dozens of languages and frameworks.

Input : OpenAPI 3.x api.yaml or api.json.

Output : Client SDK (ApiClient, models, API classes) or server stub.

Advantages : Fast, reduces boilerplate, keeps contract.

Note : Generated code often needs secondary adaptation (baseURL configuration, timeout/retry, header propagation, streaming support).

Installation and usage example (CLI):

# Install (example)</code><code>brew install openapi-generator</code><code># Or download jar</code><code>wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.8.0/openapi-generator-cli-7.8.0.jar</code><code># Generate Python client</code><code>openapi-generator-cli generate \</code><code>  -i api.yaml \</code><code>  -g python \</code><code>  -o ./gen/python</code><code># Generate Java client</code><code>openapi-generator-cli generate \</code><code>  -i api.yaml \</code><code>  -g java \</code><code>  -o ./gen/java

2. Mapping Rules from OpenAPI to MCP

2.1 Overall Mapping Idea

Each operation becomes one MCP tool; use operationId as tool.name (fallback to method‑path summary).

Parameters and requestBody map to ToolDefinition.inputSchema (convert OpenAPI schema to JSON Schema).

Responses map to ToolDefinition.outputSchema; prefer 2xx JSON schema, record isStream if response is streaming (e.g., text/event-stream or application/stream+json).

Authentication: expose securitySchemes (bearer, apiKey, basic) in an auth field for runtime injection.

Headers/baseURL propagation: mark transmittable headers (e.g., Authorization, X-User-Id) and allow runtime baseURL overrides.

Examples, enums, required fields are carried into ToolDefinition for LLM introspection.

2.2 ToolDefinition Example (JSON)

{
  "name": "get_user_info",
  "description": "按 user_id 查询用户信息",
  "inputSchema": {
    "type": "object",
    "properties": {
      "user_id": {"type": "string", "description": "用户 ID"},
      "include_detail": {"type": "boolean", "default": false}
    },
    "required": ["user_id"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "id": {"type": "string"},
      "name": {"type": "string"},
      "email": {"type": "string"}
    }
  },
  "http": {
    "method": "GET",
    "path": "/api/v1/users/{user_id}",
    "headers_pass_through": ["Authorization", "X-Trace-Id"]
  },
  "auth": {"type": "bearer", "in": "header", "name": "Authorization"},
  "examples": [{"user_id": "12345"}]
}

2.3 Automatic Generation / Conversion Tips

Parameter locations (path, query, header, cookie) map to different layers of inputSchema; path parameters are mandatory.

RequestBody: prefer application/json schema; if multipart/form-data is supported, mark contentType and handle file streams in the handler.

Streaming response detection: if response content‑type is text/event-stream, application/stream+json, or application/x-ndjson, set isStream=true and forward each chunk as partial events.

Multi‑language adaptation: generated SDKs may lack native streaming; patch them (e.g., set responseType=stream, increase readTimeout).

Preserve enum, format, pattern information for LLM constraint checking.

3. MCP Three Modes – Deep Dive

3.1 STDIO (stdin/stdout) – Simple but Powerful

Principle : Process reads JSON‑RPC requests from stdin and writes responses to stdout.

Advantages : No network, minimal dependencies, low latency.

Message boundaries must be clearly defined (line‑delimited JSON or length‑prefixed protocol).

Concurrency is serial; to handle parallel requests, implement internal threading or coroutine dispatch and split/merge request IDs.

Implementation tip: use \n delimited lines or a Content‑Length prefix.

Responses must echo the original id to correlate.

Streaming returns can bundle multiple events in result.events or emit multiple partial responses with a done marker.

JSON‑RPC request example (stdin):

{"id":"req-1","jsonrpc":"2.0","method":"call_tool","params":{"name":"get_user_info","arguments":{"user_id":"123"}}}

JSON‑RPC response example (stdout):

{"id":"req-1","jsonrpc":"2.0","result":{"name":"get_user_info","content":{"id":"123","name":"Alice"}}}

3.2 HTTP Streamable (Chunked) – Real‑time Streaming

Principle : Uses HTTP chunked transfer; server writes chunks that the client can consume incrementally (typical for LLM token streaming).

Advantages : Works with existing HTTP clients, low latency, suitable for large tasks.

Client must support chunked responses (most HTTP libraries do).

Chunk format should be agreed upon – each chunk can be a JSON event or plain text token.

For parallel requests, separate HTTP connections are simpler.

Set appropriate Content‑Type (e.g., application/x-ndjson or text/event-stream).

Chunked fragment example (body):

{"event":"delta","content":"Hello"}{"event":"delta","content":" world"}{"event":"done"}

3.3 SSE (Server‑Sent Events) – Browser‑Friendly

Native principle : Browser creates an EventSource to a server‑side text/event-stream connection; server continuously pushes data: … events.

Limitation : Physically one‑way (server → client); client cannot send messages on the same connection.

MCP extension : Combine a control channel (HTTP POST or WebSocket) with an SSE channel for “pseudo‑bidirectional” interaction.

Assign a unique client‑id per SSE subscription; include it in the POST request so the server knows where to push results.

Handle reconnection, heartbeats, and timeout.

Browser CORS and TLS considerations apply.

SSE event format suggestion:

event: mcp
data: {"type":"response","id":"req-1","payload":{"event":"delta","content":"hello"}}
event: mcp
data: {"type":"done","id":"req-1"}

4. MCP Protocol Message Format (JSON‑RPC + Events)

All messages use JSON‑RPC 2.0 as the skeleton, with an optional stream‑event envelope.

Request (JSON‑RPC)

{
  "id":"req-1",
  "jsonrpc":"2.0",
  "method":"call_tool",
  "params":{
    "name":"get_user_info",
    "arguments":{"user_id":"123"},
    "meta":{"traceId":"t-1","clientId":"c-1","userContext":{...}}
  }
}

Normal Response (single)

{"id":"req-1","jsonrpc":"2.0","result":{"name":"get_user_info","content":{"id":"123","name":"Alice"}}}

Streaming Event (chunked or SSE)

{"type":"delta","id":"req-1","seq":1,"content":"Hello"}
{"type":"delta","id":"req-1","seq":2,"content":" world"}
{"type":"done","id":"req-1"}

Error Example (JSON‑RPC)

{
  "id":"req-1",
  "jsonrpc":"2.0",
  "error":{"code":500,"message":"UpstreamTimeout","data":{"upstream":"user-service"}}
}

5. Java Implementation Details

5.1 McpHandler Interface

public interface McpHandler {
    String getName();
    String handle(Map input) throws Exception;
    ToolDefinition getDefinition();
}

Key points:

Validate parameters against ToolDefinition.inputSchema to prevent malicious input.

Build HTTP request (method, path, baseURL, headers) with optional baseURL override.

Support timeout and retry (e.g., circuit‑breaker‑enabled HttpClient).

Detect streaming responses (Content‑Type) and forward upstream streams as partial events (SSE or chunked).

Concurrency model: thread pool + CompletableFuture for async handling.

Provide an /introspect endpoint returning registered ToolDefinition list for LLM discovery.

5.2 Streaming & SSE in Java

Streamable mode: use HttpServletResponse.getOutputStream() or Netty’s chunked writer, write JSON lines and flush.

SSE mode: use Spring’s SseEmitter or Netty’s text/event-stream, wrapping each event with type, id, seq, etc.

6. Python Implementation Details (fastmcp + openapi‑generator)

6.1 Key Points

Prefer asyncio / FastAPI + uvicorn for the HTTP layer; fastmcp or a custom MCP framework handles JSON‑RPC and tool registration.

Generated Python SDKs are usually synchronous; wrap them with httpx.AsyncClient or run in a thread pool ( anyio.to_thread.run_sync or asyncio.to_thread).

For streaming responses, use httpx.aiter_lines() or FastAPI’s StreamingResponse to forward upstream streams as ndjson/SSE.

6.2 fastmcp Example

from fastmcp import MCPServer
from gen.python.api import DefaultApi  # generated client (can be wrapped async)
api_client = DefaultApi()
server = MCPServer(name="qihoo-openapi")

@server.tool()
async def get_user_info(user_id: str):
    # parameter validation / conversion
    resp = await api_client.get_user_async(user_id=user_id)  # if async, otherwise wrap sync call
    return resp

if __name__ == "__main__":
    server.run(mode="sse", port=7330)  # or mode="stdio" / "streamable"

7. Go Implementation Highlights

Support baseURL and header propagation at operation level.

Cross‑platform binary builds via go build or goreleaser.

Switch between stdio, streamable, and SSE modes using command‑line flags (e.g., --http=:8080, --http-transport=sse).

Streamable HTTP uses net/http with Flush; SSE writes text/event-stream.

Built‑in health check ( /healthz) and metrics ( /metrics) for containerized deployment.

Startup examples:

# As stdio
qihoo-openapi-mcp
# As HTTP‑streamable server
qihoo-openapi-mcp --http=:8080
# As SSE server
qihoo-openapi-mcp --http=:8080 --http-transport=sse

8. Frequently Asked Questions

Q: Can SSE carry client‑to‑server messages on a single connection? A: Native SSE is one‑way. MCP typically uses a separate control channel (HTTP POST or WebSocket) for client requests and pushes progress/results via SSE. Some deployments proxy SSE data back to the backend to emulate bidirectional flow, but this relies on custom gateway logic.

Q: How to decide if an OpenAPI operation should be marked isStream ? A: Inspect the response content‑type. If it includes text/event-stream, application/x-ndjson, application/stream+json, or the spec mentions streaming, set isStream=true.

Q: How to handle evolving ToolDefinition schemas? A: Include a schemaVersion field and adopt backward‑compatible additive changes. Breaking changes require a new tool name or version and must be reflected in the introspect response.

9. Practical Recommendations

Start with a small API (e.g., get_user_info) to verify the OpenAPI → SDK → Handler → MCP (stdio + SSE) flow.

Automate the mapping script (OpenAPI → ToolDefinition → handler stub) to reduce maintenance.

Prioritize high‑value, low‑risk APIs for conversion, then expand to complex stateful or file‑upload scenarios.

Monitor latency, stream stability, and error rates in production; iteratively tune timeout and retry policies.

Generating MCP Service Source Code (APICloud)

APICloud can generate executable MCP services (Python, Java) from OpenAPI 3.0 specifications. After creating an API document project, click “Generate MCP” to obtain either a runnable binary or a source‑code archive that can be customized and uploaded to the MCP marketplace.

MCP Generator UI
MCP Generator UI
Select API Document
Select API Document
Choose MCP Service Type
Choose MCP Service Type
Download Executable or Source
Download Executable or Source

After downloading, the source package can be uploaded to the MCP Marketplace for deployment.

JavaPythonAI agentsMCPGoStreamingOpenAPIToolDefinition
360 Smart Cloud
Written by

360 Smart Cloud

Official service account of 360 Smart Cloud, dedicated to building a high-quality, secure, highly available, convenient, and stable one‑stop cloud service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.