Agentic AI
Connecting MCP to the Real World with HTTPX (With Async Python)
In the last post, we built a bare-bones MCP server that checked the weather using a hardcoded Python dictionary. It demonstrated the protocol, but unless your company's entire database lives in a dictionary, you're going to need your MCP server to talk to actual REST APIs.
Whether you're fetching data from GitHub, calling internal microservices, or querying third-party SaaS endpoints, httpx is the standard tool for the job. Here is how to wire up real-world HTTP endpoints into clean, asynchronous MCP tools.
Why HTTPX instead of Requests?
If you've written Python for a while, your knee-jerk reaction for HTTP requests is probably import requests.
While requests is great, it is strictly synchronous and blocking. MCP servers, under the hood, run on Python's asyncio event loop so they can handle concurrent requests, streams, and system notifications without hanging.
httpx gives you the exact same friendly syntax as requests, but with native async/await support. It fits into the MCP ecosystem seamlessly.
Getting started
Install both mcp and httpx:
pip install mcp httpxA real MCP server fetching live data
Instead of returning fake dictionary data, let's build an MCP server that calls GitHub's public API to fetch user profile details in real-time.
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("github-user-server")
@mcp.tool()
async def get_github_user(username: str) -> str:
"""Fetch public profile details for a given GitHub username."""
url = f"[https://api.github.com/users/](https://api.github.com/users/){username}"
headers = {"User-Agent": "MCP-HTTPX-Client"}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=10.0)
response.raise_for_status()
data = response.json()
name = data.get("name", username)
bio = data.get("bio", "No bio provided")
repos = data.get("public_repos", 0)
followers = data.get("followers", 0)
return f"User: {name}\nBio: {bio}\nPublic Repos: {repos}\nFollowers: {followers}"
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
return f"Error: GitHub user '{username}' was not found."
return f"HTTP error occurred: {e.response.status_code}"
except httpx.RequestError as e:
return f"Network error while reaching GitHub API: {str(e)}"
if __name__ == "__main__":
mcp.run()Notice what changed from our previous sync example:
get_github_useris declared withasync def.- We wrap our request in
async with httpx.AsyncClient() as client. - We
await client.get(...)so the event loop remains free while waiting for network I/O.
Adding authentication with environment variables
Real-world APIs usually require bearer tokens or API keys. Never hardcode credentials in your tools — pass them through environment variables or client headers.
Here's how you wrap authenticated API calls safely:
import os
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("authenticated-api-server")
API_TOKEN = os.getenv("MY_SERVICE_API_KEY")
BASE_URL = "[https://api.myservice.com/v1](https://api.myservice.com/v1)"
@mcp.tool()
async def fetch_user_data(user_id: str) -> dict:
"""Fetch user account data from internal API."""
if not API_TOKEN:
return {"error": "MY_SERVICE_API_KEY environment variable is missing."}
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
async with httpx.AsyncClient(base_url=BASE_URL, headers=headers) as client:
try:
response = await client.get(f"/users/{user_id}", timeout=5.0)
response.raise_for_status()
return response.json()
except httpx.HTTPError as err:
return {"error": f"API request failed: {str(err)}"}You can pass environment variables in your client setup (claude_desktop_config.json):
{
"mcpServers": {
"myservice": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"MY_SERVICE_API_KEY": "your_secret_key_here"
}
}
}
}The mental model for wrapping APIs into MCP
Once you understand this pattern, every REST API becomes an MCP server in under 30 lines of code:
- Define your
FastMCP("server-name"). - Write an
async deffunction decorated with@mcp.tool(). - Give it a descriptive docstring and explicit type hints (this is what the model uses to understand when and how to call your tool).
- Use
httpx.AsyncClient()to make HTTP requests (GET, POST, PUT, DELETE). - Catch network or HTTP errors cleanly and return helpful string or JSON context back to the model.
That's it. Your existing microservices and web APIs are now native tools for any AI model speaking MCP.
Next in this series: "The Agent Loop — Why Every Claude Agent Is Just a While Loop."
Related articles
Agentic AI
What Is MCP, Actually? (Explained Like You're Five, With Code)
MCP explainers are usually either a marketing slide or a 40-page spec. Here's the plain version — one analogy, one 15-line server — and why it matters more than it sounds like it should.
- #mcp
- #claude
- #agentic-ai
- #python
- #model-context-protocol
Agentic AI
5 Agent Mistakes I Made So You Don't Have To
Every one of these cost me real debugging time. Each has a one-line fix — here they are, with the code.
- #claude
- #agentic-ai
- #python
- #debugging
- #best-practices
Agentic AI
I Connected Claude to My Terminal and Broke My Laptop (Lessons on Agent Guardrails)
A weekend project, a bash tool with no restrictions, and the exact three mistakes that let it happen — plus the guardrails that would have stopped every one of them.
- #claude
- #agentic-ai
- #safety
- #guardrails
- #python