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:
- Tool Definitions are Scattered – Specifications live in code comments, README files, and developer knowledge
- Maintenance Burden – Each new tool requires manual LangChain
Toolobject creation - Framework Lock-in – Your tools only work with LangChain; they’re not portable to other AI frameworks
- Discovery Challenges – New team members don’t know what tools exist or how to use them
- 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:
- Discovers tools from the MCP server (getPokemon, getType, etc.)
- Chains multiple calls:
getPokemon("pikachu")→getType("electric") - Reasons about the data to answer your question
- 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 movessummarizePokemonSpecies()extracts: id, name, color, habitat, generation, evolution chain URL, and descriptionsummarizeType()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 abilitysummarizeMove()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 tools3. 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 / protectHow 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:
- Stripe → Payment tool integration
- GitHub → Repository automation tools
- Weather APIs → Real-time environment context
- Database APIs → SQL query tools
- 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!

Leave a Reply