Turn Any Public API Into An Agentic Service: OpenAPI + MCP

The Secret Nobody Talks About: Your existing public API is already 90% of the way to being an autonomous agent. Here’s how to unlock that last 10%.

The Game-Changing Realization

Most businesses have built APIs. They’ve invested in documentation, SDKs, authentication, rate limiting—all the hard infrastructure work. But they’re missing one critical piece: agentic capability.

With MCP (Model Context Protocol) + OpenAPI specs, any company with a public API can instantly give LLMs autonomous access to their services. This isn’t a feature. This isn’t an optimization. This is a fundamental shift in how AI agents will interface with the business world.

Think about it:

  • Your SaaS API becomes an agentic service – Customers’ AI agents can autonomously query, create, and modify data
  • Your data platform becomes discoverable – No custom integrations; standard tool protocols
  • Your API documentation becomes executable – Your OpenAPI spec automatically generates MCP tools
  • Your competitors who don’t do this will be left behind

The Old Problem: Tool Integration Fragmentation

Today, most developers hardcode tools into their agents:

# Traditional approach: hardcoded tools (brittle, non-discoverable, non-portable)
tools = [
    Tool(name="search_wikipedia", func=wiki_search),
    Tool(name="fetch_arxiv", func=arxiv_fetch),
    Tool(name="web_search", func=web_search),
]

This creates massive problems at scale:

  1. Tool Definitions are Scattered – Specifications live in code comments, README files, and developer knowledge
  2. Maintenance Burden – Each new tool requires manual LangChain Tool object creation
  3. Framework Lock-in – Your tools only work with LangChain; they’re not portable to other AI frameworks
  4. Discovery Challenges – New team members don’t know what tools exist or how to use them
  5. Version Synchronization – When your backend API changes, your tool definitions may drift out of sync

The Solution: OpenAPI + MCP Servers

The Model Context Protocol (MCP) offers a standardized way to expose tools. By combining it with OpenAPI specifications, you can:

  • Define tools once in an OpenAPI spec
  • Auto-generate tool servers using tools like openapi-mcp-generator
  • Connect any AI framework to standardized tool endpoints
  • Maintain tool documentation as part of your OpenAPI spec
  • Enable tool discovery through standard MCP protocols

This transforms tool integration from a manual, fragile process into a specification-driven, maintainable infrastructure pattern.

How It Works: A Deep Dive

1. Start with an OpenAPI Specification

Define your API’s endpoints in OpenAPI format (the industry standard for REST API documentation):

{
  "openapi": "3.0.0",
  "info": {
    "title": "PokéAPI",
    "description": "All the Pokémon data you'll ever need",
    "version": "2.0.0"
  },
  "servers": [
    {"url": "https://pokeapi.co/api/v2"}
  ],
  "paths": {
    "/pokemon/{idOrName}": {
      "get": {
        "summary": "Get a specific Pokémon by ID or name",
        "operationId": "getPokemon",
        "parameters": [
          {
            "name": "idOrName",
            "in": "path",
            "required": true,
            "description": "The ID or name of the Pokémon",
            "schema": {"type": "string"}
          }
        ],
        "responses": {
          "200": {
            "description": "Pokémon data including stats, abilities, and moves"
          }
        }
      }
    }
  }
}

2. Generate an MCP Server

Use openapi-mcp-generator to automatically create a server:

openapi-mcp-generator \
  --input pokeapi-openapi.json \
  --output mcp-pokemon \
  --transport streamable-http \
  --port 3002

This generates a complete Node.js project that:

  • Validates requests against your OpenAPI spec
  • Proxies calls to the real API
  • Exposes tools via the MCP protocol
  • Includes a browser-based test client

3. Deploy in Docker

The generated project includes everything needed for containerization:

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci --only=production
EXPOSE 3002
HEALTHCHECK --interval=10s --timeout=5s CMD wget --quiet --spider http://localhost:3002/health
CMD ["npm", "run", "start:http"]

Add to your compose.yml:

services:
  mcp-pokemon:
    build:
      context: ./mcp-pokemon
      dockerfile: Dockerfile
    ports:
      - 3002:3002
    healthcheck:
      test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/health"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - net

4. Connect LangChain to MCP Tools

Your Python code now dynamically discovers and loads tools:

import asyncio
from deepagents import create_deep_agent
from langchain_core.tools import Tool
from langchain_mcp_adapters.client import MultiServerMCPClient

# Step 1: Auto-discover tools from MCP server
async def load_mcp_tools():
    client = MultiServerMCPClient({
        "pokemon": {
            "transport": "streamable_http",
            "url": "http://mcp-pokemon:3002/mcp"
        }
    })
    
    mcp_tools = await client.get_tools()
    
    # Wrap async MCP tools as sync Tool objects for Deep Agents
    wrapped_tools = []
    for mcp_tool in mcp_tools:
        def make_wrapper(tool):
            def sync_wrapper(tool_input):
                result = asyncio.run(tool.ainvoke(tool_input))
                return str(result)
            return sync_wrapper
        
        wrapped = Tool(
            name=mcp_tool.name,
            description=mcp_tool.description,
            func=make_wrapper(mcp_tool)
        )
        wrapped_tools.append(wrapped)
    
    return wrapped_tools, client

# Step 2: Create Deep Agents agent with auto-discovered MCP tools
tools, client = asyncio.run(load_mcp_tools())

agent = create_deep_agent(
    model="ollama:mistral",
    tools=tools,
    system_prompt="You are a Pokemon expert. Use available tools to answer questions."
)

# Step 3: Query the agent with natural language
response = agent.invoke({
    "messages": [{"role": "user", "content": "Tell me about Pikachu"}]
})

# Extract answer
answer = response["messages"][-1].get("content", "No response")
print(answer)

Real-World Example: Querying Pokémon

Here’s how this works in practice:

agent.invoke({
    "messages": [{"role": "user", "content": "Tell me about Pikachu and what type advantages it has"}]
})

The agent:

  1. Discovers tools from the MCP server (getPokemon, getType, etc.)
  2. Chains multiple calls: getPokemon("pikachu") → getType("electric")
  3. Reasons about the data to answer your question
  4. Returns a coherent response

All tool definitions come from your OpenAPI spec—no manual LangChain configuration needed!

Key Benefits

✅ Specification-Driven Development

  • Tools are defined once in OpenAPI format
  • Documentation is built-in
  • Changes are version-controlled

✅ Framework Agnostic

  • MCP is framework-agnostic—your tools work with any MCP client
  • Easily integrate with other AI frameworks (Anthropic’s SDK, LlamaIndex, etc.)

✅ Tool Discovery

  • Tools self-document through the MCP protocol
  • New developers can query available tools programmatically
  • No guessing about what tools exist

✅ Reduced Maintenance

  • Tool validation is automatic (OpenAPI schemas)
  • Breaking changes are caught early
  • Teams can update tools independently

✅ Scalability

  • Add new endpoints → automatically exposed as tools
  • Deploy tool servers independently
  • Load-balance tool servers separately from AI inference

Important Consideration: Payload Optimization for LLM Context

When exposing APIs through MCP, be mindful of response payload sizes. LLMs have finite context windows, and large API responses consume tokens quickly and reduce reasoning capacity.

Best Practices:

⚠️ Limit response fields – OpenAPI specs can define smaller response schemas for agent use vs. full client use

💡 Compress data – Strip unnecessary nesting, remove null fields, use abbreviations

🎯 Use dedicated agent endpoints – Consider creating lightweight API variants specifically for agent tool calls

🔄 UPDATE: TOON Format Implementation

For a structured approach to keeping payloads minimal, TOON (Token-Oriented Object Notation) provides a standard for creating compact, LLM-optimized responses. In our Pokemon MCP implementation, we achieved ~40% token savings by:

1. Tool-Specific Summarizers – Instead of returning full API responses, we created focused summarizer functions for each tool:

  • summarizePokemon() extracts: id, name, height, weight, types, stats, abilities, and top 10 moves
  • summarizePokemonSpecies() extracts: id, name, color, habitat, generation, evolution chain URL, and description
  • summarizeType() extracts: id, name, and damage relations (limited to 5 types per category)
  • summarizeAbility() extracts: id, name, is_main_series, effect, flavor text, and up to 10 Pokémon with that ability
  • summarizeMove() extracts: id, name, power, accuracy, priority, type, category, pp, and effect

2. Conditional TOON Encoding – In the MCP response handler, we route all 5 Pokemon tools through their specific summarizers, then encode the result using the TOON format:

if (toolName === 'getPokemon') {
    const summarized = summarizePokemon(response.data);
    responseText = encodeToon(summarized);
} else if (toolName === 'getPokemonSpecies') {
    const summarized = summarizePokemonSpecies(response.data);
    responseText = encodeToon(summarized);
}
// ... etc for remaining tools

3. Example – Before and After

// ❌ Full API response (500+ lines of JSON, extremely token-heavy)

{
  "id": 25,
  "name": "pikachu",
  "baseExperience": 112,
  "height": 4,
  "weight": 60,
  "is_main_series": true,
  "abilities": [
    {"ability": {"name": "static", "url": "..."}, "isHidden": false, "slot": 1},
    {"ability": {"name": "lightning-rod", "url": "..."}, "isHidden": true, "slot": 2}
  ],
  "forms": [...],
  "gameIndices": [...],
  "heldItems": [...],
  "locationAreaEncounters": "..."
}

// ✅ Agent-optimized TOON response (~150-200 lines, 40% token reduction)

id: 25
name: pikachu
height: 4
weight: 60
types[2]{name,slot}: electric,1 / fairy,3
stats[6]{stat,base_stat}: hp,35 / attack,55 / defense,40 / spa,50 / spd,50 / spe,90
abilities[2]{name,is_hidden}: static,false / lightning-rod,true
moves[10]{name}: thunder-punch / thunder-wave / thunderbolt / thunder / thunder-shock / spark / pin-missile / pound / pay-day / protect

How to Regenerate Tools from Different APIs

The real power comes from reusability. Want to add Pokémon evolution data? Or integrate food recipes? Here’s the pattern:

Step 1: Create an OpenAPI Spec

# For any REST API with OpenAPI documentation
curl https://api.example.com/openapi.json > my-api-spec.json

# Or manually write a spec for an undocumented API
cat > my-api-spec.json << 'EOF'
{
  "openapi": "3.0.0",
  "info": {"title": "My API", "version": "1.0.0"},
  "servers": [{"url": "https://api.example.com"}],
  "paths": {...}
}
EOF

Step 2: Generate the MCP Server

openapi-mcp-generator \
  --input my-api-spec.json \
  --output my-mcp-server \
  --transport streamable-http

Step 3: Add to Compose and Deploy

services:
  my-mcp-server:
    build: ./my-mcp-server
    ports:
      - 3002:3000

That’s it. You now have standardized tool access to any API.

Deployment Considerations

Health Checks

MCP servers include built-in /health endpoints. Configure Docker Compose to use them:

healthcheck:
  test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:3001/health"]
  interval: 10s
  timeout: 5s
  retries: 3

Extending the Example

The pattern works for any REST API:

  1. Stripe → Payment tool integration
  2. GitHub → Repository automation tools
  3. Weather APIs → Real-time environment context
  4. Database APIs → SQL query tools
  5. Slack → Channel management tools

Each follows the same pattern: OpenAPI → MCP server → LangChain integration.

What’s Coming

The MCP ecosystem is evolving rapidly:

  • LangChain integration – Direct MCP client support
  • Tool versioning – Semantic versioning for tool specs
  • Authentication standards – OAuth2, API key management
  • Caching layers – Reduce API calls and latency

Conclusion

Standardized tool loading via OpenAPI + MCP servers is a paradigm shift for AI development:

  • Before: Hardcoded tools, scattered documentation, manual maintenance
  • After: Specification-driven tools, auto-generated servers, framework-agnostic integration

This approach enables:

  • ✅ Faster development – Define once, generate everywhere
  • ✅ Better maintainability – Tools are versioned with specs
  • ✅ Easier collaboration – Tools are discoverable and documented
  • ✅ Future-proof – Works with any framework that supports MCP

Start small with a single API (like PokéAPI), then scale to your entire backend. Your future self will thank you.


Try it yourself: Check out the GenAI Starter Pokemon_MCP example to see this in action. Run docker compose up and query Pokémon through a standardized tool interface!


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *