From 38909865632eccd9b776cd3de4fe8ef2a163caf4 Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 30 Mar 2026 19:17:56 -0500 Subject: [PATCH] mcp transport --- MCP_TRANSPORT.md | 281 +++++++++++++++++++++++++++++++++++++++++++++ server/odoo_mcp.py | 10 +- 2 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 MCP_TRANSPORT.md diff --git a/MCP_TRANSPORT.md b/MCP_TRANSPORT.md new file mode 100644 index 0000000..34bfdfb --- /dev/null +++ b/MCP_TRANSPORT.md @@ -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. diff --git a/server/odoo_mcp.py b/server/odoo_mcp.py index 0b45b04..28dc4c6 100644 --- a/server/odoo_mcp.py +++ b/server/odoo_mcp.py @@ -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()