Problem: Your System Has No AI Access
Your Business System is Disconnected from AI
Bridging the gap between Claude and your live systems
You have an Odoo instance with thousands of customer records. Or a Shopify store with product catalogs. Or an internal PostgreSQL database with sales reports. Claude cannot see any of it — unless you build an MCP server to bridge the gap.
Example 1: MCP Server for REST API (Odoo)
If you followed the Claude Plugins course, you know that VaryShop uses Odoo as its CRM. The CRM Toolkit plugin used Python scripts to call the Odoo XML-RPC API. Now let us turn that into an MCP server.
import os, json, xmlrpc.client
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("odoo-connector")
ODOO_URL = os.environ.get("ODOO_URL")
ODOO_DB = os.environ.get("ODOO_DB")
ODOO_KEY = os.environ.get("ODOO_API_KEY")
common = xmlrpc.client.ServerProxy(
f"{ODOO_URL}/xmlrpc/2/common")
models = xmlrpc.client.ServerProxy(
f"{ODOO_URL}/xmlrpc/2/object")
UID = common.authenticate(ODOO_DB, "", ODOO_KEY, {})
def call(model, method, args, kw=None):
return models.execute_kw(
ODOO_DB, UID, ODOO_KEY,
model, method, args, kw or {})
@mcp.tool()
def search_contacts(query: str,
limit: int = 10) -> str:
"""Search contacts in Odoo CRM."""
results = call("res.partner", "search_read", [
["|", ["name", "ilike", query],
["email", "ilike", query]]
], {"fields": ["name", "email", "phone",
"city"], "limit": limit})
return json.dumps(results, indent=2,
ensure_ascii=False)
@mcp.tool()
def create_contact(name: str, email: str,
phone: str = "",
city: str = "") -> str:
"""Create a new contact in Odoo CRM."""
pid = call("res.partner", "create",
[{"name": name, "email": email,
"phone": phone, "city": city}])
return json.dumps({"id": pid, "status": "created"})Interactive Access
Now Claude can directly ask: "Search for contacts named John" or "Show me orders from the last week" — no scripts needed.
Example 2: Database Access
@mcp.tool()
def query_data(sql: str) -> str:
"""Execute a read-only SQL query.
Only SELECT statements are allowed."""
if not sql.strip().upper().startswith("SELECT"):
return json.dumps(
{"error": "Only SELECT queries allowed"})
conn = psycopg2.connect(DB_URL)
try:
cur = conn.cursor()
cur.execute(sql)
columns = [d[0] for d in cur.description]
rows = [dict(zip(columns, row))
for row in cur.fetchall()]
return json.dumps(rows[:100], indent=2,
default=str)
finally:
conn.close()Security Warning
Always restrict database MCP servers to read-only queries. Use a database user with only SELECT permissions. Never expose write access unless absolutely necessary.
Authentication and Security Best Practices
Environment Variables
Never hardcode API keys or passwords in server code. Always use env in .mcp.json to pass secrets securely.
Least Privilege
Create API keys with minimum required permissions. Use read-only database users when possible. Limit accessible endpoints.
Input Validation
Always validate and sanitize inputs from Claude. Use parameterized queries for SQL. Set reasonable limits on result sizes.
Error Handling
Always return structured error messages. Claude can then explain the issue to the user and suggest solutions.
Error Handling Pattern
@mcp.tool()
def safe_search(query: str) -> str:
"""Search with proper error handling."""
try:
results = call_external_api(query)
return json.dumps(results)
except ConnectionError:
return json.dumps(
{"error": "Cannot connect to the API."})
except TimeoutError:
return json.dumps(
{"error": "API request timed out."})
except Exception as e:
return json.dumps(
{"error": f"Unexpected error: {str(e)}"})Key Takeaway
Connecting to real business systems follows the same pattern as our JSON reader — define tools, implement handlers, configure the server. The difference is adding authentication, error handling, and security. Always start with read-only access and add write operations only when needed.
There are no comments for now.