Source Video: FastAPI – MCP (Client – Server) – Odoo
The digital landscape is constantly evolving, pushing businesses to seek innovative ways to streamline operations and extract deeper insights from their data. For many, ERP systems like Odoo are the backbone of their operations, holding a wealth of critical information. But what if you could interact with this data using natural language, leveraging the intelligence of AI? This is where FastAPI Odoo MCP Integration comes into play – a game-changer for intelligent automation and intuitive data management.
Imagine asking your Odoo system, “How many contacts do we have in Chile?” and receiving an instant, accurate answer, or “What were the total sales invoices this month?” without writing a single line of code. This powerful capability is made possible through the Model Context Protocol (MCP), a framework designed to connect large language models (LLMs) with external services and tools. By integrating FastAPI as a high-performance backend with Odoo and an MCP server, you can transform your ERP experience, making it more accessible, efficient, and intelligent.
This comprehensive guide will walk you through building a robust FastAPI Odoo MCP Integration, providing a step-by-step tutorial with code examples. You’ll learn how to set up a FastAPI backend to communicate with an Odoo MCP server, enabling natural language queries and dynamic tool loading. Get ready to elevate your Odoo capabilities and embrace the future of AI-driven ERP interaction.
What is Model Context Protocol (MCP)?
The Model Context Protocol (MCP) acts as a crucial bridge, allowing Large Language Models (LLMs) to interact with the real world beyond their training data. Think of it as an interpreter that translates the LLM’s natural language requests into executable commands for external services, and then takes the service’s raw data back to the LLM for human-friendly summarization.
In the context of FastAPI Odoo MCP Integration, the MCP server provides the LLM with a set of “tools” or functions it can call. These tools are essentially wrappers around specific Odoo operations – like search_count on res.partner for contacts, or fetching invoice totals. When a user asks a question, the LLM analyzes it, determines which Odoo tool is most relevant, and instructs the MCP server to execute it. This allows for dynamic, intelligent interaction with your Odoo instance, unlocking a new layer of functionality.
Why Choose FastAPI for Odoo Integration?
FastAPI has rapidly become a favorite for building high-performance APIs, and it’s an excellent choice for a robust FastAPI Odoo MCP Integration. Here’s why:
- Blazing Fast Performance: Built on Starlette for web parts and Pydantic for data handling, FastAPI offers exceptional speed, crucial for real-time AI interactions.
- Asynchronous Capabilities: Its native support for
async/awaitmakes it perfect for handling multiple concurrent requests, such as simultaneous user queries or background tasks involving LLMs and Odoo. - Automatic Documentation: FastAPI automatically generates interactive API documentation (Swagger UI/OpenAPI), simplifying development and collaboration.
- Robust Data Validation: Pydantic ensures data integrity, reducing bugs and making your API more reliable.
- Type Hinting: Leveraging Python type hints improves code readability, maintainability, and allows for better IDE support.
- Modern Python Standard: It embraces modern Python features, making development a pleasure for Python developers.
These advantages make FastAPI an ideal foundation for managing the communication flow between a frontend application, the LLM, and your Odoo system within the FastAPI Odoo MCP Integration.
Setting the Stage: Your Powerful FastAPI Odoo MCP Integration Architecture
The architecture for our FastAPI Odoo MCP Integration involves several key components working in harmony:
- Frontend (Next.js): Provides the user interface for sending natural language queries and displaying responses.
- Backend (FastAPI): Acts as the central hub, handling WebSocket connections, relaying user prompts to the LLM, managing MCP tools, and returning responses to the frontend.
- LLM (e.g., Anthropic Claude): The brain that interprets natural language, decides which Odoo tool to use, and formats the final response.
- Odoo Instance: Your running Odoo ERP system, containing the business data you want to query.
- Odoo MCP Server: A specialized Python script that runs commands to interact directly with your Odoo instance using its API. It exposes Odoo functionalities as “tools” for the LLM.
This setup allows for a highly interactive and intelligent system where users can communicate with Odoo as if they were talking to a human expert.
Step-by-Step Tutorial: Implementing FastAPI Odoo MCP Integration
This section will guide you through the practical implementation of your FastAPI Odoo MCP Integration. We’ll focus on the backend logic and the Odoo MCP server, touching upon the frontend interaction.
Step 1: Project Setup & Prerequisites
Before diving into the code, ensure you have the following in place:
- Docker & Docker Compose: For containerized development and running Odoo and FastAPI.
- Python 3.9+: The language for FastAPI and the Odoo MCP server.
- Node.js & npm/yarn: For the Next.js frontend (if you plan to follow the frontend setup).
- A running Odoo instance: This can be a local Docker instance or a cloud-hosted one. Ensure you have the database name, username, and password.
- An LLM API Key: For this tutorial, we’ll use Anthropic’s API key. Set it in an
.envfile for security. - Project Structure:
my_mcp_project/ ├── backend/ │ ├── main.py │ ├── .env │ ├── requirements.txt │ ├── your_module/ │ │ ├── __init__.py │ │ ├── llm_client.py │ │ ├── integration_flow.py │ │ └── msp_client_manager.py ├── odoo_mcp_server/ │ ├── odoo_mcp.py │ ├── odoo.conf (example Odoo config for connection) │ └── msp_service_config.json ├── frontend/ (Next.js app, optional for this backend-focused tutorial) │ └── ... ├── docker-compose.yml
docker-compose.yml (Example for FastAPI and Odoo):
version: '3.8'
services:
odoo:
image: odoo:16.0
container_name: odoo_app
ports:
- "8069:8069"
environment:
- PASSWORD=odoo_password # Replace with a strong password
- ODOO_DB=odoo_db_name # Replace with your Odoo database name
volumes:
- odoo_data:/var/lib/odoo
- odoo_config:/etc/odoo
networks:
- mcp_network
fastapi_backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: fastapi_mcp_backend
ports:
- "8000:8000"
volumes:
- ./backend:/app
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
# Odoo credentials can be passed dynamically or via environment for the MCP server
networks:
- mcp_network
depends_on:
- odoo
networks:
mcp_network:
driver: bridge
volumes:
odoo_data:
odoo_config:
backend/Dockerfile:
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
backend/requirements.txt:
fastapi
uvicorn
python-dotenv
anthropic
pydantic
# Add odoorpc if you intend to directly connect from FastAPI, though we'll use a separate MCP server
backend/.env:
ANTHROPIC_API_KEY="your_anthropic_api_key_here"
Step 2: Backend (FastAPI) Foundation for MCP
This section covers the core FastAPI components for handling requests, interacting with the LLM, and managing MCP tools.
backend/main.py:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from your_module.integration_flow import IntegrationFlow
from your_module.msp_client_manager import MSPClientManager
import asyncio
import os
app = FastAPI()
# Initialize MSPClientManager globally or via dependency injection
# Initially, it might load default tools or an empty configuration
msp_client_manager = MSPClientManager()
# This is the main WebSocket endpoint for client interaction
@app.websocket("/agent/ws")
async def agent_websocket_endpoint(websocket: WebSocket):
await websocket.accept()
print("WebSocket connected for agent.")
try:
while True:
# Receive data from the frontend client
data = await websocket.receive_json()
print(f"Received data from frontend: {data}")
# Extract dynamic Odoo credentials and tool configuration
odoo_credentials = {
"odoo_url": data.get("odoo_url", os.getenv("ODOO_URL", "http://odoo:8069")),
"odoo_db": data.get("odoo_db", os.getenv("ODOO_DB", "odoo_db_name")),
"odoo_user": data.get("odoo_user", os.getenv("ODOO_USER", "admin")),
"odoo_pass": data.get("odoo_pass", os.getenv("ODOO_PASS", "odoo_password"))
}
tool_config = data.get('tool_config')
if tool_config:
# Dynamically load or update the Odoo tool in the manager
msp_client_manager.load_tool_config(tool_config, odoo_credentials)
# Process the incoming prompt using the IntegrationFlow
prompt = data.get("prompt")
if prompt:
flow = IntegrationFlow(msp_client_manager) # Pass the manager to flow
response_content = await flow.execute_prompt(prompt, odoo_credentials)
await websocket.send_json({"response": response_content})
else:
await websocket.send_json({"response": "Please provide a prompt."})
except WebSocketDisconnect:
print("WebSocket disconnected.")
except Exception as e:
print(f"WebSocket Error: {e}")
await websocket.send_json({"error": str(e)})
finally:
await websocket.close()
# For a simple health check or initial setup endpoint (optional)
@app.get("/")
async def read_root():
return {"message": "FastAPI Odoo MCP Integration Backend is running!"}
backend/your_module/llm_client.py:
This client handles communication with your chosen LLM (Anthropic in this case).
import anthropic
from pydantic import BaseSettings
import os
from dotenv import load_dotenv
load_dotenv() # Load environment variables
class Settings(BaseSettings):
anthropic_api_key: str = os.getenv("ANTHROPIC_API_KEY")
class Config:
env_file = ".env"
env_file_encoding = 'utf-8'
settings = Settings()
class AntropicClient:
def __init__(self):
if not settings.anthropic_api_key:
raise ValueError("ANTHROPIC_API_KEY not found in environment variables.")
self.client = anthropic.Anthropic(api_key=settings.anthropic_api_key)
async def generate_response(self, prompt: str, tools: list = None):
"""
Generates a response from the Anthropic LLM, potentially using tools.
The actual tool calling logic will be handled by the MCP server.
Here, we simulate sending tools for LLM awareness.
"""
messages = [{"role": "user", "content": prompt}]
# In a real tool-using scenario, the LLM would indicate tool calls.
# For this example, we're just passing the prompt.
# If actual tool definitions are needed for the LLM to understand capabilities:
# tool_specs = [...] # Define tool schemas here
try:
response = await self.client.messages.create(
model="claude-3-opus-20240229", # Or your preferred Claude model
max_tokens=1000,
messages=messages,
# tools=tool_specs # Uncomment and define if the LLM needs to see tool definitions
)
return response.content[0].text # Extracting text from the content block
except anthropic.APIError as e:
print(f"Anthropic API Error: {e}")
return f"An error occurred with the AI: {e}"
except Exception as e:
print(f"General LLM Client Error: {e}")
return f"An unexpected error occurred: {e}"
backend/your_module/msp_client_manager.py:
This manager handles loading and managing your MCP server configurations. It’s crucial for dynamic tool integration in our FastAPI Odoo MCP Integration.
import json
import subprocess
import os
import asyncio
class MSPClientManager:
def __init__(self, config_file="odoo_mcp_server/msp_service_config.json"):
self.config_file = config_file
self.servers = {} # Stores active MCP server processes and their config
self.sessions = {} # Could store active connections to tools if they expose an API
self._load_initial_config() # Load any default tools defined in a file
def _load_initial_config(self):
"""Loads initial MCP server configurations from a JSON file."""
if os.path.exists(self.config_file):
try:
with open(self.config_file, "r") as f:
initial_config = json.load(f)
for server_config in initial_config.get("servers", []):
# For initial load, credentials might be static or default
self.load_tool_config(server_config, {}) # Empty credentials for now
print(f"Loaded initial MCP configurations from {self.config_file}")
except Exception as e:
print(f"Error loading initial MCP config file {self.config_file}: {e}")
else:
print(f"No initial MCP config file found at {self.config_file}. Starting empty.")
def load_tool_config(self, tool_config: dict, odoo_credentials: dict):
"""
Loads or updates an MCP tool configuration dynamically.
This will start the MCP server process if it's not already running.
"""
server_name = tool_config.get('name')
if not server_name:
print("Error: Tool configuration missing 'name'.")
return
# Prevent re-initializing if already loaded with the same config
if server_name in self.servers and self.servers[server_name]['config'] == tool_config:
print(f"Tool '{server_name}' already loaded with current configuration.")
return
command = tool_config.get('command')
args = tool_config.get('args', [])
if not command:
print(f"Error: Tool '{server_name}' configuration missing 'command'.")
return
# Prepare environment variables for the subprocess
env = os.environ.copy()
env.update({
"ODOO_URL": odoo_credentials.get("odoo_url", ""),
"ODOO_DB": odoo_credentials.get("odoo_db", ""),
"ODOO_USER": odoo_credentials.get("odoo_user", ""),
"ODOO_PASS": odoo_credentials.get("odoo_pass", "")
})
try:
# Kill existing process if re-loading
if server_name in self.servers and 'process' in self.servers[server_name]:
self.servers[server_name]['process'].kill()
print(f"Killed existing process for tool '{server_name}'.")
# Start the new MCP server as a subprocess
# We assume 'command' points to a Python script like 'odoo_mcp.py'
full_command = ["python", os.path.join(os.getcwd(), 'odoo_mcp_server', command)] + args
print(f"Starting MCP server for '{server_name}' with command: {full_command}")
# Using Popen to run in background, non-blocking
process = subprocess.Popen(
full_command,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True # Decode stdout/stderr as text
)
self.servers[server_name] = {
'process': process,
'config': tool_config,
'status': 'running'
}
print(f"Successfully started MCP server for '{server_name}'.")
# You might want to add a small delay or a health check here
# to ensure the server is ready before queries are sent.
except FileNotFoundError:
print(f"Error: Command '{command}' not found. Make sure the script exists and is executable.")
self.servers[server_name] = {'status': 'failed', 'error': 'Command not found'}
except Exception as e:
print(f"Failed to start MCP server for '{server_name}': {e}")
self.servers[server_name] = {'status': 'failed', 'error': str(e)}
async def get_tool_output(self, server_name: str, prompt: str):
"""
Simulates getting output from an MCP server.
In a real scenario, this would involve IPC (e.g., HTTP request to the MCP server,
or reading from its stdout/stderr if it's designed to print responses).
For simplicity, we'll try to read from stdout if the process is still alive.
"""
server_info = self.servers.get(server_name)
if not server_info or server_info['status'] != 'running':
return f"Error: MCP server '{server_name}' is not running or not configured."
process = server_info['process']
# This is a highly simplified simulation.
# Real MCP communication would be more sophisticated (e.g., dedicated API endpoint on MCP server).
# Here, we're just reading whatever the subprocess might have printed after starting.
# To truly "interact" with a running MCP server based on a prompt, the MCP server itself
# would need to expose an API (e.g., HTTP endpoint) that FastAPI calls.
# For demonstration, let's assume the MCP server prints a response to stdout
# This part needs significant re-architecture for true two-way communication.
# The current MCP implementation often uses LLM tool-calling directly,
# where the LLM decides *what command to run* on a pre-defined tool.
# The 'odoo_mcp.py' script would need an API or a way to receive commands.
# Let's pivot to a more realistic model based on the video:
# The LLM generates a tool call (e.g., execute_method, search_contacts).
# The IntegrationFlow intercepts this and *then* executes the corresponding Odoo action.
# The MSPClientManager's primary role would be to *know* what tools are available
# and how to *execute* them (e.g., by making a direct odoorpc call, or running a script).
# For the video's context, the `odoo_mcp.py` effectively IS the tool,
# and FastAPI runs it and captures output or handles Odoo API calls itself.
# For now, we'll return a placeholder or an error to indicate this needs refinement.
return f"MCP server '{server_name}' started. Actual interaction logic (based on prompt) needs to be implemented by calling the Odoo API directly or via a sophisticated MCP server API."
def get_available_tools_for_llm(self) -> list:
"""Returns a list of tool definitions for the LLM to understand."""
# This is where you'd define the schema for Odoo tools,
# e.g., 'execute_method', 'search_contacts', 'get_invoices'.
# The LLM uses these definitions to decide which tool to call.
odoo_tool_schema = {
"type": "function",
"function": {
"name": "execute_odoo_method",
"description": "Executes a method on an Odoo model and returns the result.",
"parameters": {
"type": "object",
"properties": {
"model": {
"type": "string",
"description": "The Odoo model name (e.g., 'res.partner', 'account.move')."
},
"method": {
"type": "string",
"description": "The method to call on the model (e.g., 'search_count', 'read', 'create')."
},
"args": {
"type": "array",
"items": {"type": "string"},
"description": "List of arguments for the method (e.g., domain for search, fields for read)."
},
"kwargs": {
"type": "object",
"additionalProperties": {"type": "string"},
"description": "Dictionary of keyword arguments for the method."
}
},
"required": ["model", "method"]
}
}
}
# You can add more specific tools like 'get_contacts_by_country', 'get_sales_invoices_by_month'
# The video showed specific tools like 'buscar_empleados', 'consultar_vacaciones'.
# These would be defined here.
return [odoo_tool_schema] # Return list of available tool schemas
backend/your_module/integration_flow.py:
This class orchestrates the overall flow, connecting the LLM with the available MCP tools.
from your_module.llm_client import AntropicClient
from your_module.msp_client_manager import MSPClientManager
import json
import odoorpc # Used directly here for Odoo interaction based on LLM's tool call
class IntegrationFlow:
def __init__(self, msp_client_manager: MSPClientManager):
self.llm_client = AntropicClient()
self.msp_manager = msp_client_manager
self.odoo_connection = None # Will be set dynamically
async def _connect_to_odoo(self, odoo_credentials: dict):
"""Establishes a connection to Odoo."""
url = odoo_credentials["odoo_url"]
db = odoo_credentials["odoo_db"]
user = odoo_credentials["odoo_user"]
password = odoo_credentials["odoo_pass"]
if not all([url, db, user, password]):
raise ValueError("Missing Odoo credentials for connection.")
try:
# Extract host and port from URL
from urllib.parse import urlparse
parsed_url = urlparse(url)
host = parsed_url.hostname
port = parsed_url.port if parsed_url.port else 8069 # Default Odoo port
odoo = odoorpc.ODOO(host, port=port)
odoo.login(db, user, password)
self.odoo_connection = odoo
print(f"Successfully connected to Odoo DB: {db}")
except Exception as e:
print(f"Error connecting to Odoo: {e}")
raise
async def _execute_odoo_method(self, model: str, method: str, args: list = None, kwargs: dict = None):
"""
Executes a method on an Odoo model using the established connection.
This is the actual "tool" logic the LLM will call.
"""
if not self.odoo_connection:
raise ValueError("Odoo connection not established.")
try:
print(f"Executing Odoo method: {model}.{method} with args: {args}, kwargs: {kwargs}")
result = self.odoo_connection.execute(model, method, *(args or []), **(kwargs or {}))
print(f"Odoo method result: {result}")
return result
except Exception as e:
print(f"Error executing Odoo method {model}.{method}: {e}")
return f"Error executing Odoo method: {e}"
async def execute_prompt(self, prompt: str, odoo_credentials: dict):
"""
Processes a user prompt, potentially invoking Odoo tools.
"""
await self._connect_to_odoo(odoo_credentials) # Ensure Odoo is connected
# 1. Get tool definitions from MSP Manager
available_tools = self.msp_manager.get_available_tools_for_llm()
# 2. Send prompt and tool definitions to LLM
# The LLM will decide if it needs to call a tool
messages = [{"role": "user", "content": prompt}]
# Call Anthropic API with tool definitions (if the model supports it and we define them)
# Note: Anthropic's `messages.create` method supports tool definitions.
try:
llm_response = await self.llm_client.client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
messages=messages,
tools=available_tools # Pass the tool schemas to the LLM
)
# Check for tool calls from the LLM
if llm_response.stop_reason == "tool_use":
tool_call = llm_response.content[0] # Assuming one tool call
tool_name = tool_call.name
tool_input = tool_call.input
if tool_name == "execute_odoo_method":
odoo_result = await self._execute_odoo_method(
model=tool_input.get("model"),
method=tool_input.get("method"),
args=tool_input.get("args", []),
kwargs=tool_input.get("kwargs", {})
)
# Send the tool output back to the LLM for a natural language summary
tool_result_message = {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_call.id,
"content": str(odoo_result) # Convert result to string for LLM
}
]
}
messages.append(llm_response.content[0]) # Add the LLM's tool call to history
messages.append(tool_result_message) # Add tool result to history
final_llm_response = await self.llm_client.client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
messages=messages
)
return final_llm_response.content[0].text
else:
return f"LLM requested an unknown tool: {tool_name}"
else:
# LLM responded directly without a tool call
return llm_response.content[0].text
except Exception as e:
print(f"Error during prompt execution: {e}")
return f"An error occurred during processing: {e}"
Step 3: Crafting the Odoo MCP Server
The Odoo MCP server (odoo_mcp.py) is the workhorse for interacting with Odoo. In our enhanced FastAPI Odoo MCP Integration, it won’t necessarily be a long-running external API, but rather a Python script or a set of functions that the FastAPI IntegrationFlow directly uses or invokes. For the context given, the MCP server is treated as a component that gets executed, and its tools are discovered.
Let’s adapt odoo_mcp.py to be callable and leverage the odoorpc library, as the video implies direct Odoo interaction based on LLM suggestions. We will not have odoo_mcp.py as a separate, independently running server with its own API for simplicity, but rather as a module that the IntegrationFlow class utilizes to perform Odoo operations. The msp_service_config.json will describe the “Odoo tool” and point to odoo_mcp.py conceptually.
odoo_mcp_server/odoo_mcp.py:
This script would define the actual Odoo-specific functions. In a more advanced MCP setup, this might be a full-fledged service. For our current FastAPI Odoo MCP Integration, the core logic for executing Odoo methods is primarily handled within IntegrationFlow using odoorpc directly, based on the tool calls suggested by the LLM.
However, if you want odoo_mcp.py to be a command-line tool that MSPClientManager starts, it might look like this:
# odoo_mcp_server/odoo_mcp.py
import odoorpc
import argparse
import os
import json
def connect_to_odoo(url, db, user, password):
"""Establishes an Odoo connection."""
try:
from urllib.parse import urlparse
parsed_url = urlparse(url)
host = parsed_url.hostname
port = parsed_url.port if parsed_url.port else 8069
odoo = odoorpc.ODOO(host, port=port)
odoo.login(db, user, password)
return odoo
except Exception as e:
print(f"ERROR_CONNECTING_ODOO: {e}", file=os.stderr)
return None
def main():
parser = argparse.ArgumentParser(description="Odoo MCP Server/Tool. Executes Odoo commands.")
parser.add_argument("--action", required=True, help="The action to perform (e.g., 'get_contact_count', 'execute_method').")
parser.add_argument("--model", help="Odoo model name (e.g., 'res.partner').")
parser.add_argument("--method", help="Method to call on the Odoo model (e.g., 'search_count').")
parser.add_argument("--args", help="JSON string of arguments for the method (e.g., '[]', '[[\"country_id\", \"=\", 1]]').")
parser.add_argument("--kwargs", help="JSON string of keyword arguments for the method.")
args = parser.parse_args()
# Get credentials from environment variables set by MSPClientManager
odoo_url = os.getenv("ODOO_URL")
odoo_db = os.getenv("ODOO_DB")
odoo_user = os.getenv("ODOO_USER")
odoo_pass = os.getenv("ODOO_PASS")
if not all([odoo_url, odoo_db, odoo_user, odoo_pass]):
print("ERROR_CREDENTIALS_MISSING: Odoo credentials environment variables are not set.", file=os.stderr)
exit(1)
odoo_conn = connect_to_odoo(odoo_url, odoo_db, odoo_user, odoo_pass)
if not odoo_conn:
exit(1)
try:
if args.action == "execute_method":
model = args.model
method = args.method
method_args = json.loads(args.args) if args.args else []
method_kwargs = json.loads(args.kwargs) if args.kwargs else {}
if not model or not method:
print("ERROR_INVALID_ARGS: 'model' and 'method' are required for 'execute_method'.", file=os.stderr)
exit(1)
result = odoo_conn.execute(model, method, *method_args, **method_kwargs)
print(json.dumps({"result": result})) # Output result as JSON to stdout
elif args.action == "get_contact_count":
count = odoo_conn.execute('res.partner', 'search_count', [])
print(json.dumps({"result": count}))
# Add more specific actions here as needed
else:
print(f"ERROR_UNKNOWN_ACTION: Unknown action '{args.action}'.", file=os.stderr)
exit(1)
except Exception as e:
print(f"ERROR_EXECUTION_FAILED: {e}", file=os.stderr)
exit(1)
if __name__ == "__main__":
main()
odoo_mcp_server/msp_service_config.json:
This file describes the Odoo tool to the MSPClientManager.
{
"servers": [
{
"name": "Odoo_MCP_Tool",
"command": "odoo_mcp.py",
"args": []
}
]
}
Important Note: The current IntegrationFlow directly uses odoorpc based on LLM’s tool_use detection. The odoo_mcp.py script above is a more general example of how an MCP server could be structured as an external executable script that MSPClientManager starts and then interacts with (e.g., by parsing its stdout). For our FastAPI Odoo MCP Integration, the IntegrationFlow‘s direct odoorpc calls within the _execute_odoo_method are more aligned with the video’s underlying mechanism of calling Odoo methods. The msp_client_manager primarily provides the LLM with the schema of what Odoo operations are possible.
Step 4: Dynamic Tool Loading & Credential Management
A crucial aspect of a flexible FastAPI Odoo MCP Integration is the ability to load tools and manage credentials dynamically, rather than hardcoding them.
Updates to backend/your_module/msp_client_manager.py (already incorporated in the provided code):
The load_tool_config method within MSPClientManager is designed for this. It takes a tool_config dictionary and odoo_credentials, starting a new subprocess for the MCP server. Environment variables are passed to this subprocess for secure credential handling.
Updates to backend/main.py (already incorporated):
The agent_websocket_endpoint function now extracts odoo_url, odoo_db, odoo_user, and odoo_pass from the incoming WebSocket data. It then passes these dynamically to msp_client_manager.load_tool_config and integration_flow.execute_prompt. This allows you to connect to different Odoo instances or use different user credentials without restarting the FastAPI backend.
This dynamic approach ensures that your FastAPI Odoo MCP Integration is adaptable and scalable, capable of serving various Odoo environments or user-specific needs.
Step 5: Frontend (Next.js) for Intuitive Interaction
The frontend is where users experience the magic of FastAPI Odoo MCP Integration. It provides an interface to send natural language prompts and visualize the AI’s responses.
frontend/pages/index.js (Simplified Example):
// frontend/pages/index.js
import React, { useState, useEffect, useRef } from 'react';
const HomePage = () => {
const [isOpen, setIsOpen] = useState(false);
const [socket, setSocket] = useState(null);
const [message, setMessage] = useState('');
const [responses, setResponses] = useState([]); // Array to store multiple responses
const [odooUrl, setOdooUrl] = useState('http://localhost:8069'); // Or your Odoo IP/hostname
const [odooDb, setOdooDb] = useState('odoo_db_name');
const [odooUser, setOdooUser] = useState('admin');
const [odooPass, setOdooPass] = useState('odoo_password');
const messageEndRef = useRef(null);
const scrollToBottom = () => {
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(scrollToBottom, [responses]); // Scroll to bottom on new response
useEffect(() => {
if (isOpen && !socket) { // Connect only if modal is open and socket not yet established
const backendWsUrl = process.env.NEXT_PUBLIC_BACKEND_WS_URL || 'ws://localhost:8000/agent/ws';
const newSocket = new WebSocket(backendWsUrl);
newSocket.onopen = () => {
console.log('WebSocket connected');
// Optionally, send initial tool config on connection
const initialToolConfig = {
name: "Odoo_MCP_Tool",
command: "odoo_mcp.py", // Points to the script in odoo_mcp_server
args: []
};
newSocket.send(JSON.stringify({ tool_config: initialToolConfig }));
};
newSocket.onmessage = (event) => {
const data = JSON.parse(event.data);
setResponses(prev => [...prev, data.response || data.error || "No response"]);
};
newSocket.onclose = () => {
console.log('WebSocket disconnected');
setSocket(null); // Reset socket on close
};
newSocket.onerror = (error) => {
console.error('WebSocket error:', error);
setResponses(prev => [...prev, `WebSocket Error: ${error.message || 'Check console.'}`]);
};
setSocket(newSocket);
return () => {
newSocket.close();
};
} else if (!isOpen && socket) { // Close socket if modal closes
socket.close();
}
}, [isOpen]); // Depend on isOpen state
const sendMessage = () => {
if (socket && message.trim()) {
const payload = {
prompt: message,
odoo_url: odooUrl,
odoo_db: odooDb,
odoo_user: odooUser,
odoo_pass: odooPass
};
socket.send(JSON.stringify(payload));
setResponses(prev => [...prev, `You: ${message}`]);
setMessage('');
}
};
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
sendMessage();
}
};
return (
<div style={{ fontFamily: 'Arial, sans-serif', maxWidth: '800px', margin: '20px auto', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h1 style={{ textAlign: 'center', color: '#333' }}>Odoo AI Chat</h1>
<div style={{ marginBottom: '20px', border: '1px solid #eee', padding: '15px', borderRadius: '5px', backgroundColor: '#f9f9f9' }}>
<h3 style={{ marginTop: '0', color: '#555' }}>Odoo Credentials</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<input type="text" placeholder="Odoo URL (e.g., http://localhost:8069)" value={odooUrl} onChange={(e) => setOdooUrl(e.target.value)} style={inputStyle} />
<input type="text" placeholder="Odoo Database Name" value={odooDb} onChange={(e) => setOdooDb(e.target.value)} style={inputStyle} />
<input type="text" placeholder="Odoo Username" value={odooUser} onChange={(e) => setOdooUser(e.target.value)} style={inputStyle} />
<input type="password" placeholder="Odoo Password" value={odooPass} onChange={(e) => setOdooPass(e.target.value)} style={inputStyle} />
</div>
<button onClick={() => setIsOpen(true)} style={buttonStyle}>
{isOpen ? 'Reconnect AI Chat' : 'Connect to AI Chat'}
</button>
</div>
{isOpen && (
<div style={{ border: '1px solid #ddd', borderRadius: '5px', padding: '15px', backgroundColor: '#fff' }}>
<h3 style={{ marginTop: '0', color: '#555' }}>Chat with Odoo AI</h3>
<div style={{ height: '300px', overflowY: 'auto', border: '1px solid #eee', padding: '10px', borderRadius: '5px', marginBottom: '10px', backgroundColor: '#fdfdfd' }}>
{responses.map((res, index) => (
<p key={index} style={{ margin: '5px 0', wordBreak: 'break-word', whiteSpace: 'pre-wrap' }}>
<strong>{res.startsWith('You:') ? '' : 'AI:'}</strong> {res}
</p>
))}
<div ref={messageEndRef} />
</div>
<div style={{ display: 'flex', gap: '10px' }}>
<input
type="text"
placeholder="Ask your Odoo questions..."
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
style={{ ...inputStyle, flexGrow: 1 }}
/>
<button onClick={sendMessage} style={buttonStyle}>Send</button>
</div>
</div>
)}
</div>
);
};
// Basic inline styles for demonstration
const inputStyle = {
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px',
fontSize: '1em'
};
const buttonStyle = {
padding: '10px 15px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '1em',
marginTop: '10px'
};
export default HomePage;
To run this Next.js frontend, you’ll need to create a frontend folder, initialize a Next.js app (npx create-next-app frontend), and place this code in pages/index.js. Also, add a .env.local file in the frontend directory:frontend/.env.local:
NEXT_PUBLIC_BACKEND_WS_URL=ws://localhost:8000/agent/ws
This frontend connects to your FastAPI backend via WebSocket, sends prompts along with dynamic Odoo credentials, and displays the AI-generated responses. It also includes an initial message to the backend to dynamically load the Odoo tool when the WebSocket connects, a key feature of the FastAPI Odoo MCP Integration.
Step 6: Testing Your Powerful Integration
With all components in place, it’s time to test your FastAPI Odoo MCP Integration.
- Start your Docker containers:
docker-compose up --buildEnsure both Odoo and FastAPI containers are running.
- Start your Next.js frontend:
cd frontend npm run dev - Open your browser: Navigate to
http://localhost:3000. - Enter Odoo Credentials: Provide your Odoo instance details (URL, DB, User, Pass).
- Connect and Query: Click “Connect to AI Chat” and start asking questions!
Example Queries and Expected Responses:
- “How many contacts are there?”
- Expected: “Consulting the contact total model using the execute method on the
res.partnermodel… 93.” (Or the actual number in your Odoo).
- Expected: “Consulting the contact total model using the execute method on the
- “How many contacts have an email address?”
- Expected: “21.” (Or the actual number).
- “How many leads are in CRM?”
- Expected: “0.” (Or the actual number).
- “Give me the total of sales invoices issued this month.”
- Expected: (A list of invoice totals or a summarized number, depending on LLM’s interpretation and Odoo data).
- “What is the capital of Norway?”
- Expected: “Oslo” (if your LLM is general-purpose and can answer, otherwise it might state it cannot find the information using its available tools).
- “How many clients are from Chile?”
- Expected: “25.” (Or the actual number, demonstrating filtering).
You’ll observe how the LLM processes your query, identifies the need for an Odoo tool call, executes the _execute_odoo_method via the IntegrationFlow, and then uses the Odoo result to formulate a human-readable response. This iterative process is the core power of FastAPI Odoo MCP Integration.
Step 7: Advanced Considerations and Future Enhancements
Your FastAPI Odoo MCP Integration is now functional, but the potential for expansion is vast:
- Microservices Architecture: Instead of one monolithic FastAPI backend, consider separate microservices for each MCP server (e.g., one for Odoo, one for a finance API, etc.). This enhances scalability and fault isolation.
- Other MCP Servers: The MCP concept isn’t limited to Odoo. Explore creating MCP servers for:
- External APIs: Integrate with tax services (e.g., Chile’s “Servicio Impuestos Internos”), payment gateways, or shipping providers.
- Browser Automation (Node.js/JavaScript): Leverage tools like Playwright or Puppeteer within an MCP server (potentially Node.js-based) to automate web interactions (e.g., retrieving data from external websites that lack an API).
- CRM Systems: Connect to Salesforce, HubSpot, or other CRM platforms.
- Enhanced Tool Definitions: Refine the tool schemas (
get_available_tools_for_llminmsp_client_manager.py) to give the LLM more precise control over Odoo operations, including creating, updating, or deleting records. - Workflow Automation: Integrate the natural language queries into automated workflows. For example, “Create a sales order for client X with product Y” could trigger an Odoo sales order creation.
- Streaming Responses: Implement server-sent events (SSE) or a more sophisticated WebSocket message streaming on the frontend to display LLM responses character-by-character, improving user experience for long answers.
- Error Handling and Robustness: Enhance error handling, logging, and retry mechanisms for both LLM and Odoo interactions.
- Security: Implement robust authentication and authorization for your FastAPI backend and ensure secure handling of API keys and Odoo credentials.
Benefits of This Integration
Embracing FastAPI Odoo MCP Integration brings a multitude of powerful benefits:
- Enhanced Accessibility: Users can interact with complex Odoo data using simple, natural language, democratizing data access.
- Increased Efficiency: Automate routine data queries and report generation, freeing up valuable time for strategic tasks.
- Improved Decision Making: Quickly retrieve accurate, context-aware information from Odoo, leading to faster and more informed business decisions.
- Reduced Development Overhead: Leverage AI to interpret user intent, reducing the need for custom report development for every new query.
- Scalability: FastAPI’s performance and the modular nature of MCP servers ensure your system can grow with your business needs.
- Innovation: Position your business at the forefront of AI-driven ERP interaction, creating new possibilities for automation and user experience.
Conclusion
The journey to building a functional FastAPI Odoo MCP Integration is a testament to the power of modern web frameworks, intelligent language models, and robust ERP systems. By following this comprehensive tutorial, you’ve laid the groundwork for a system that transforms how you interact with your Odoo data. You’ve seen how FastAPI provides a high-performance, asynchronous backbone, how MCP bridges the gap between natural language and Odoo actions, and how dynamic tool and credential management fosters flexibility.
This integration isn’t just about asking questions; it’s about unlocking a new paradigm of intelligent automation and user experience within your ERP. The potential for further development, from expanding your MCP tools to integrating other microservices, is immense. Start experimenting with your FastAPI Odoo MCP Integration today and discover how AI can revolutionize your business operations!
External Resources:
- FastAPI Official Documentation
- Odoo Official Website
- Anthropic AI
- odoorpc Library on PyPI
- Model Context Protocol (Conceptual Repository Example – MicroAgent) (Please note: The specific repo from the video context about Node.js was not linked, this is a general conceptual example)
Internal Links (Conceptual):
Discover more from teguhteja.id
Subscribe to get the latest posts sent to your email.

