Model Context Protocol (MCP) is how AI agents — Claude, Cursor, Windsurf — call external tools. If you write Python, you can expose your code as a tool that an AI agent calls on demand: query a database, run a calculation, check an API, process data.
The problem: running an MCP server requires a publicly accessible HTTPS endpoint, which means a server, a domain, SSL certificates, and uptime monitoring. For most developers, this overhead is the blocker.
This guide shows you how to deploy a Python MCP server in 5 minutes with zero infrastructure to manage.
What MCP Actually Does
An AI agent like Claude has a core limitation: it can only work with text. It can't run code, query your database, or call your private APIs. MCP bridges this gap.
With MCP configured, when you say to Claude:
"Check the current inventory for product SKU-9920"
Claude doesn't guess. It:
1. Identifies you have a tool named check-inventory
2. Calls your Python function with {"sku": "SKU-9920"}
3. Gets back the real result from your database
4. Uses that real data to answer you
Your Python function runs in the cloud, securely, with access to your environment variables (database URLs, API keys). Claude gets structured data back. No hallucination, no estimation.
Option 1: Self-Hosting an MCP Server (The Hard Way)
If you want full control, you can run your own MCP server using the mcp Python library:
pip install mcp
# server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
import mcp.types as types
app = Server("my-tools")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="check-inventory",
description="Check current inventory for a product SKU",
inputSchema={
"type": "object",
"properties": {
"sku": {"type": "string", "description": "Product SKU to check"}
},
"required": ["sku"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "check-inventory":
sku = arguments["sku"]
# Your actual logic here
result = query_database(sku)
return [types.TextContent(type="text", text=str(result))]
async def main():
async with stdio_server() as (read, write):
await app.run(read, write, app.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
To make this publicly accessible, you need:
- A server with a public IP (Hetzner, DigitalOcean, etc.) — ~$5–10/mo
- Nginx reverse proxy with SSL — ~30 min setup
- Process manager (systemd, supervisor) to keep it running
- Domain and DNS configuration
- Monitoring to know if it goes down
For a single tool, this is disproportionate overhead.
Option 2: Deploy Python as MCP on LiteLambda (The 5-Minute Way)
LiteLambda lets you deploy Python functions and expose them directly as MCP tools. No server, no SSL certificates, no process manager.
Step 1: Install the CLI
pip install litelambda-cli
litelambda login
Step 2: Write your Python handler
# inventory_checker.py
import os
import requests
def handler(event, context):
"""
Check current inventory for a product SKU.
Args:
event: dict with 'sku' (str) — the product SKU to check
context: execution context (provides env vars, kv store)
Returns:
dict with inventory status, quantity, and pricing
"""
sku = event.get("sku")
if not sku:
raise ValueError("'sku' parameter is required")
# Your actual data source — database, internal API, etc.
api_url = os.environ.get("INVENTORY_API_URL")
api_key = os.environ.get("INVENTORY_API_KEY")
response = requests.get(
f"{api_url}/products/{sku}",
headers={"Authorization": f"Bearer {api_key}"},
timeout=10
)
response.raise_for_status()
data = response.json()
return {
"sku": sku,
"status": "available" if data["quantity"] > 0 else "out_of_stock",
"quantity": data["quantity"],
"unit_price": data["price"],
"warehouse": data["location"]
}
Step 3: Deploy and expose as MCP tool
litelambda deploy check-inventory inventory_checker.py \
--description "Check current inventory quantity and pricing for a product SKU" \
--expose-as-mcp \
--env INVENTORY_API_URL=https://your-api.internal \
--env INVENTORY_API_KEY=sk_prod_xxxx
That's it. Your tool is now deployed and accessible at:
https://mcp.litelambda.in/mcp
Step 4: Connect to Claude, Cursor, or any MCP client
Cursor (~/.cursor/mcp.json):
{
"mcpServers": {
"my-tools": {
"url": "https://mcp.litelambda.in/mcp",
"headers": {
"Authorization": "Bearer YOUR_LITELAMBDA_API_KEY"
}
}
}
}
Claude Code (terminal):
claude mcp add my-tools https://mcp.litelambda.in/mcp \
--header "Authorization: Bearer YOUR_LITELAMBDA_API_KEY"
Restart Cursor or Claude. Your tool is now available. Ask Claude: "Check inventory for SKU-9920" — it will call your Python function and return real data.
A More Complete Example: Multi-Tool MCP Server
Real-world use case: a developer tools MCP server with three tools for an internal codebase.
# github_tools.py
import os
import requests
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
REPO = os.environ.get("GITHUB_REPO") # "org/repo-name"
def _github_headers():
return {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json"
}
def handler(event, context):
"""
Tool: GitHub repository utility
Supported actions (passed via event['action']):
- 'list_open_prs': List all open pull requests
- 'get_pr': Get details of a specific PR (requires 'pr_number')
- 'list_issues': List open issues (optional 'label' filter)
"""
action = event.get("action")
if action == "list_open_prs":
resp = requests.get(
f"https://api.github.com/repos/{REPO}/pulls?state=open",
headers=_github_headers(),
timeout=15
)
resp.raise_for_status()
prs = resp.json()
return {
"count": len(prs),
"prs": [
{"number": pr["number"], "title": pr["title"], "author": pr["user"]["login"]}
for pr in prs
]
}
elif action == "get_pr":
pr_number = event.get("pr_number")
resp = requests.get(
f"https://api.github.com/repos/{REPO}/pulls/{pr_number}",
headers=_github_headers(),
timeout=15
)
resp.raise_for_status()
pr = resp.json()
return {
"number": pr["number"],
"title": pr["title"],
"body": pr["body"],
"state": pr["state"],
"author": pr["user"]["login"],
"mergeable": pr["mergeable"],
"changed_files": pr["changed_files"]
}
elif action == "list_issues":
label = event.get("label", "")
url = f"https://api.github.com/repos/{REPO}/issues?state=open"
if label:
url += f"&labels={label}"
resp = requests.get(url, headers=_github_headers(), timeout=15)
resp.raise_for_status()
issues = [i for i in resp.json() if "pull_request" not in i]
return {
"count": len(issues),
"issues": [
{"number": i["number"], "title": i["title"], "author": i["user"]["login"]}
for i in issues
]
}
else:
raise ValueError(f"Unknown action: {action}. Use 'list_open_prs', 'get_pr', or 'list_issues'")
Deploy:
litelambda deploy github-tools github_tools.py \
--description "Query GitHub repository: list PRs, get PR details, list issues" \
--expose-as-mcp \
--env GITHUB_TOKEN=ghp_xxxx \
--env GITHUB_REPO=myorg/myrepo
Now in Claude: "How many open PRs do we have? Are any of them mergeable?"
Claude calls github-tools with {"action": "list_open_prs"}, gets the list back, then calls {"action": "get_pr", "pr_number": 142} for the interesting ones — all in the same conversation.
Security: Why Not Just Run This Locally?
When you run an MCP server locally:
- Your environment variables (database URLs, API tokens) are on your local machine
- AI agents have potential access to your local filesystem
- Team members need to set up identical environments to use the same tools
With LiteLambda:
- Environment variables are encrypted at rest and never exposed to AI clients
- Each function execution is sandboxed — agents cannot touch your filesystem
- All team members use the same deployed tools via a shared API key
- Execution logs show exactly what the agent called and what your code returned
When to Use MCP vs a Cron Job vs a Webhook
| Trigger | Use Case | LiteLambda Feature |
|---|---|---|
| AI agent asks a question | Real-time data lookup during a Claude conversation | MCP Tool (--expose-as-mcp) |
| Time-based schedule | "Run this every day at 8am" | Cron Job (--cron "0 8 * * *") |
| External event | "Run this when Stripe sends a webhook" | Webhook Function |
| Manual or API trigger | "Run this via HTTP from my CI pipeline" | Sync/Async Function |
All four are available in LiteLambda. A single Python handler file can be deployed as any of them.
Summary
Deploying a Python MCP server without infrastructure:
- Write a
handler(event, context)function — no special MCP library needed - Deploy with
litelambda deploy --expose-as-mcp - Add your LiteLambda MCP URL to Cursor or Claude config
- Your Python function is now callable by AI agents in real-time
The entire setup takes 5 minutes. Your tools run in an isolated cloud sandbox, with encrypted environment variables and full execution logs.
Start deploying Python MCP tools →
Related: Turn Any Python Function into a Claude/Cursor Tool · Python Webhook Trigger — No Server Required · LiteLambda MCP Documentation