mcp transport
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
# MCP Transport Mode — Usage Guide
|
||||
|
||||
This document describes the proposed dual-transport capability for `server/odoo_mcp.py`.
|
||||
No code changes have been made yet. Review this guide to evaluate viability before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The server currently runs in **stdio** mode exclusively — it is launched as a subprocess by
|
||||
Claude Code and communicates over stdin/stdout. The proposed change adds an **HTTP** mode that
|
||||
allows the same server, with the same tools and zero logic changes, to be reached by any
|
||||
HTTP-capable client independently of Claude.
|
||||
|
||||
The change is controlled by a single environment variable: `MCP_TRANSPORT`.
|
||||
|
||||
---
|
||||
|
||||
## How the Two Modes Work
|
||||
|
||||
### stdio (current default — no change)
|
||||
|
||||
Claude Code reads `.mcp.json` and spawns the server as a subprocess. The server blocks on
|
||||
stdin, waiting for JSON-RPC messages from Claude. All 30+ Odoo tools are available to Claude
|
||||
as native function calls.
|
||||
|
||||
```
|
||||
Claude Code → spawns process → odoo_mcp.py (stdio) → XML-RPC → mpmedia.odoo.com
|
||||
```
|
||||
|
||||
This mode is **not affected** by the transport change. Claude Code does not set
|
||||
`MCP_TRANSPORT`, so it always gets stdio.
|
||||
|
||||
### streamable-http (new, opt-in)
|
||||
|
||||
When `MCP_TRANSPORT=http` is set, the server binds to a TCP port and accepts MCP
|
||||
JSON-RPC over HTTP instead of stdin/stdout. Any MCP-compatible client or HTTP client
|
||||
can call it directly.
|
||||
|
||||
```
|
||||
HTTP client → POST /mcp → odoo_mcp.py (HTTP) → XML-RPC → mpmedia.odoo.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Code Change (proposed)
|
||||
|
||||
Replace the last two lines of `server/odoo_mcp.py`:
|
||||
|
||||
```python
|
||||
# Before (line 570-571):
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
|
||||
# After:
|
||||
if __name__ == "__main__":
|
||||
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
||||
if transport == "http":
|
||||
mcp.run(
|
||||
transport="streamable-http",
|
||||
host=os.environ.get("MCP_HOST", "0.0.0.0"),
|
||||
port=int(os.environ.get("MCP_PORT", "8080")),
|
||||
)
|
||||
else:
|
||||
mcp.run()
|
||||
```
|
||||
|
||||
No other files change. No tools change. No helper functions change.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `MCP_TRANSPORT` | `stdio` | Set to `http` to enable HTTP mode |
|
||||
| `MCP_HOST` | `0.0.0.0` | Bind address in HTTP mode |
|
||||
| `MCP_PORT` | `8080` | Bind port in HTTP mode |
|
||||
| `ODOO_URL` | `https://mpmedia.odoo.com` | Odoo instance URL |
|
||||
| `ODOO_DB` | `mpmedia-odoo-sh-main-13285275` | Odoo database name |
|
||||
| `ODOO_USERNAME` | `bgilliom@mpmedia.tv` | Odoo login |
|
||||
| `ODOO_API_KEY` | *(empty)* | Odoo API key — required |
|
||||
|
||||
---
|
||||
|
||||
## Usage Scenarios
|
||||
|
||||
### 1. Claude Code — unchanged
|
||||
|
||||
`.mcp.json` does not set `MCP_TRANSPORT`, so nothing changes. Claude Code continues to
|
||||
spawn the server as a stdio subprocess exactly as today.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"odoo-mpm": {
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "server/odoo_mcp.py"],
|
||||
"env": {
|
||||
"ODOO_URL": "https://mpmedia.odoo.com",
|
||||
"ODOO_DB": "mpmedia-odoo-sh-main-13285275",
|
||||
"ODOO_USERNAME": "bgilliom@mpmedia.tv",
|
||||
"ODOO_API_KEY": "your-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Run HTTP server manually (local development)
|
||||
|
||||
```bash
|
||||
ODOO_API_KEY=your-api-key MCP_TRANSPORT=http uv run --with mcp[cli] server/odoo_mcp.py
|
||||
```
|
||||
|
||||
Server starts on `http://localhost:8080`. Use a different port if needed:
|
||||
|
||||
```bash
|
||||
ODOO_API_KEY=your-api-key MCP_TRANSPORT=http MCP_PORT=9000 uv run --with mcp[cli] server/odoo_mcp.py
|
||||
```
|
||||
|
||||
### 3. Cursor or other MCP-compatible AI tools
|
||||
|
||||
Add to Cursor's MCP config (`~/.cursor/mcp.json` or workspace `.cursor/mcp.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"odoo-mpm": {
|
||||
"url": "http://localhost:8080/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The server must already be running in HTTP mode before Cursor connects.
|
||||
|
||||
### 4. n8n automation
|
||||
|
||||
In n8n, use the **MCP Client Tool** node (or HTTP Request node) pointed at:
|
||||
|
||||
```
|
||||
http://your-server:8080/mcp
|
||||
```
|
||||
|
||||
Example workflow: trigger on a schedule → call `search_helpdesk_tickets` with `stage=open`
|
||||
→ parse results → send Slack notification for unassigned tickets.
|
||||
|
||||
### 5. Custom Python script (no MCP at all)
|
||||
|
||||
The Odoo client functions (`_search_read`, `_create`, `_write`, etc.) can be imported
|
||||
directly. MCP is not involved:
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, "path/to/odoo-plugin-creation")
|
||||
|
||||
import os
|
||||
os.environ["ODOO_API_KEY"] = "your-api-key"
|
||||
|
||||
from server.odoo_mcp import _search_read, _create
|
||||
|
||||
# Get all open helpdesk tickets
|
||||
tickets = _search_read(
|
||||
"helpdesk.ticket",
|
||||
[["stage_id.name", "=", "New"]],
|
||||
["id", "name", "partner_id", "create_date"],
|
||||
limit=50
|
||||
)
|
||||
|
||||
for t in tickets:
|
||||
print(t["name"])
|
||||
```
|
||||
|
||||
### 6. Windows Service / always-on background process
|
||||
|
||||
To run the HTTP server persistently on Windows:
|
||||
|
||||
```powershell
|
||||
# Using NSSM (Non-Sucking Service Manager)
|
||||
nssm install odoo-mcp "uv"
|
||||
nssm set odoo-mcp AppParameters "run --with mcp[cli] C:\path\to\server\odoo_mcp.py"
|
||||
nssm set odoo-mcp AppEnvironmentExtra "MCP_TRANSPORT=http" "MCP_PORT=8080" "ODOO_API_KEY=your-api-key"
|
||||
nssm start odoo-mcp
|
||||
```
|
||||
|
||||
Or as a simple background process for development:
|
||||
|
||||
```powershell
|
||||
$env:MCP_TRANSPORT = "http"
|
||||
$env:ODOO_API_KEY = "your-api-key"
|
||||
Start-Process uv -ArgumentList "run --with mcp[cli] server/odoo_mcp.py" -WindowStyle Hidden
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Tools in HTTP Mode
|
||||
|
||||
All 30 tools are available identically in both modes:
|
||||
|
||||
| Module | Tools |
|
||||
|---|---|
|
||||
| Products | `search_products`, `get_product`, `get_product_stock` |
|
||||
| Knowledge | `search_knowledge_articles`, `get_knowledge_article`, `create_knowledge_article`, `update_knowledge_article` |
|
||||
| Contacts | `search_contacts`, `get_contact`, `create_contact` |
|
||||
| Sales | `search_sales_orders`, `get_sales_order`, `create_sales_order` |
|
||||
| CRM | `search_crm_leads`, `get_crm_lead`, `create_crm_lead`, `update_crm_lead`, `list_crm_stages` |
|
||||
| Project | `list_projects`, `get_project`, `search_tasks`, `get_task`, `create_task`, `update_task`, `list_task_stages` |
|
||||
| Helpdesk | `search_helpdesk_tickets`, `get_helpdesk_ticket`, `create_helpdesk_ticket`, `update_helpdesk_ticket`, `list_helpdesk_teams` |
|
||||
| Purchase | `search_purchase_orders`, `get_purchase_order` |
|
||||
| Inventory | `search_inventory`, `get_stock_moves`, `list_internal_locations` |
|
||||
| Employees | `search_employees`, `get_employee`, `list_departments` |
|
||||
| Utility | `odoo_search`, `odoo_get_record` |
|
||||
|
||||
---
|
||||
|
||||
## Calling Tools over HTTP
|
||||
|
||||
FastMCP's streamable-http transport uses the MCP JSON-RPC protocol over HTTP POST.
|
||||
|
||||
**Endpoint:** `POST http://localhost:8080/mcp`
|
||||
|
||||
**Example — call `search_tasks`:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/mcp \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "search_tasks",
|
||||
"arguments": {
|
||||
"query": "website redesign",
|
||||
"limit": 10
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
**Example — list all tools:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/mcp \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
In HTTP mode the server is network-accessible. Before exposing it beyond localhost:
|
||||
|
||||
- **Do not expose port 8080 to the internet** without authentication in front of it.
|
||||
The MCP protocol itself has no built-in auth layer.
|
||||
- For internal network use: bind to `MCP_HOST=127.0.0.1` (localhost only) or a specific
|
||||
internal interface.
|
||||
- For external access: put nginx or Caddy in front with Basic Auth or mTLS.
|
||||
- The Odoo API key grants the same permissions as the Odoo user — anyone who can reach
|
||||
the server can read and write Odoo data.
|
||||
|
||||
---
|
||||
|
||||
## What Does Not Change
|
||||
|
||||
- `server/odoo_mcp.py` tool logic — zero changes
|
||||
- `.mcp.json` — zero changes
|
||||
- `skills/odoo/SKILL.md` — zero changes
|
||||
- `.claude-plugin/plugin.json` — zero changes
|
||||
- Claude Code behavior — identical to today
|
||||
- All 30 Odoo tools — identical behavior in both modes
|
||||
|
||||
---
|
||||
|
||||
## Rollback
|
||||
|
||||
If HTTP mode causes any issue, simply stop the HTTP process. Claude Code is unaffected
|
||||
because it never sets `MCP_TRANSPORT`. There is nothing to revert in `.mcp.json` or
|
||||
any config file.
|
||||
+9
-1
@@ -568,4 +568,12 @@ def odoo_get_record(model: str, record_id: int) -> dict:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
transport = os.environ.get("MCP_TRANSPORT", "stdio")
|
||||
if transport == "http":
|
||||
mcp.run(
|
||||
transport="streamable-http",
|
||||
host=os.environ.get("MCP_HOST", "0.0.0.0"),
|
||||
port=int(os.environ.get("MCP_PORT", "8080")),
|
||||
)
|
||||
else:
|
||||
mcp.run()
|
||||
|
||||
Reference in New Issue
Block a user