read only update

This commit is contained in:
2026-04-29 16:07:57 -05:00
parent f0db0711c9
commit 948be0f6ae
2 changed files with 4 additions and 125 deletions
+2 -123
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
Odoo MCP Server for MPM
Connects to mpmedia.odoo.com via XML-RPC and exposes tools for
Odoo MCP Server for MPM (read-only)
Connects to mpmedia.odoo.com via XML-RPC and exposes read-only tools for
Products, Knowledge, Contacts, Sales, CRM, Project, Helpdesk,
Purchase, Inventory, Employees, and Knowledge Templates.
"""
@@ -82,12 +82,6 @@ def _get_credentials() -> tuple[str, str]:
api_key = ODOO_API_KEY or _keychain_get("odoo_api_key")
return username, api_key
def _get_credentials() -> tuple[str, str]:
"""Resolve username and API key: env vars take priority, then macOS Keychain."""
username = ODOO_USERNAME or _keychain_get("odoo_username")
api_key = ODOO_API_KEY or _keychain_get("odoo_api_key")
return username, api_key
def _connect():
global _uid, _models, _resolved_api_key
if _uid is not None:
@@ -132,11 +126,6 @@ def _read(model, ids, fields=None):
kw["fields"] = fields
return _call(model, "read", [ids], kw)
def _create(model, vals):
return _call(model, "create", [vals])
def _write(model, ids, vals):
return _call(model, "write", [[ids] if isinstance(ids, int) else ids, vals])
# ── FastMCP App ───────────────────────────────────────────────────────────────
mcp = FastMCP("Odoo MPM")
@@ -274,26 +263,6 @@ def get_knowledge_article(article_id: int) -> dict:
)
return article
@mcp.tool()
def create_knowledge_article(name: str, body: str, parent_id: int = None) -> int:
"""Create a new Knowledge article. body is HTML. Returns new article ID."""
vals = {"name": name, "body": body}
if parent_id:
vals["parent_id"] = parent_id
return _create("knowledge.article", vals)
@mcp.tool()
def update_knowledge_article(article_id: int, name: str = "", body: str = "") -> bool:
"""Update a Knowledge article's title and/or body (HTML)."""
vals = {}
if name:
vals["name"] = name
if body:
vals["body"] = body
if not vals:
return False
return _write("knowledge.article", article_id, vals)
@mcp.tool()
def search_knowledge_templates(query: str = "", category: str = "", limit: int = 50) -> list:
"""Search Knowledge Base article templates.
@@ -375,18 +344,6 @@ def get_contact(contact_id: int) -> dict:
"website", "comment", "category_id", "child_ids", "user_id"])
return r[0] if r else {}
@mcp.tool()
def create_contact(name: str, email: str = "", phone: str = "",
is_company: bool = False, parent_id: int = None,
street: str = "", city: str = "") -> int:
"""Create a new contact. Returns the new contact ID."""
vals = {"name": name, "is_company": is_company}
if email: vals["email"] = email
if phone: vals["phone"] = phone
if parent_id: vals["parent_id"] = parent_id
if street: vals["street"] = street
if city: vals["city"] = city
return _create("res.partner", vals)
# ════════════════════════════════════════════════════════════════════════════
@@ -425,19 +382,6 @@ def get_sales_order(order_id: int) -> dict:
order["lines"] = lines
return order
@mcp.tool()
def create_sales_order(partner_id: int, lines: list) -> int:
"""Create a sales order. lines is a list of dicts with keys:
product_id (int), product_uom_qty (float), price_unit (float).
Returns new order ID."""
order_lines = []
for l in lines:
order_lines.append((0, 0, {
"product_id": l["product_id"],
"product_uom_qty": l.get("product_uom_qty", 1),
"price_unit": l.get("price_unit", 0),
}))
return _create("sale.order", {"partner_id": partner_id, "order_line": order_lines})
# ════════════════════════════════════════════════════════════════════════════
@@ -470,28 +414,6 @@ def get_crm_lead(lead_id: int) -> dict:
"activity_ids", "date_conversion"])
return r[0] if r else {}
@mcp.tool()
def create_crm_lead(name: str, partner_id: int = None, expected_revenue: float = 0,
description: str = "", email: str = "", phone: str = "") -> int:
"""Create a new CRM opportunity. Returns new lead ID."""
vals = {"name": name, "type": "opportunity", "expected_revenue": expected_revenue}
if partner_id: vals["partner_id"] = partner_id
if description: vals["description"] = description
if email: vals["email_from"] = email
if phone: vals["phone"] = phone
return _create("crm.lead", vals)
@mcp.tool()
def update_crm_lead(lead_id: int, stage_id: int = None, probability: float = None,
expected_revenue: float = None, note: str = "") -> bool:
"""Update a CRM lead's stage, probability, revenue, or notes."""
vals = {}
if stage_id is not None: vals["stage_id"] = stage_id
if probability is not None: vals["probability"] = probability
if expected_revenue is not None: vals["expected_revenue"] = expected_revenue
if note: vals["description"] = note
return _write("crm.lead", lead_id, vals) if vals else False
@mcp.tool()
def list_crm_stages() -> list:
"""List all CRM pipeline stages with their IDs and names."""
@@ -543,29 +465,6 @@ def get_task(task_id: int) -> dict:
"child_ids", "depend_on_ids", "planned_hours", "effective_hours"])
return r[0] if r else {}
@mcp.tool()
def create_task(name: str, project_id: int, description: str = "",
date_deadline: str = "", user_ids: list = None) -> int:
"""Create a project task. date_deadline format: YYYY-MM-DD. Returns task ID."""
vals = {"name": name, "project_id": project_id}
if description: vals["description"] = description
if date_deadline: vals["date_deadline"] = date_deadline
if user_ids: vals["user_ids"] = [(6, 0, user_ids)]
return _create("project.task", vals)
@mcp.tool()
def update_task(task_id: int, name: str = "", stage_id: int = None,
description: str = "", date_deadline: str = "",
kanban_state: str = "") -> bool:
"""Update a task. kanban_state: normal, done, blocked."""
vals = {}
if name: vals["name"] = name
if stage_id: vals["stage_id"] = stage_id
if description: vals["description"] = description
if date_deadline: vals["date_deadline"] = date_deadline
if kanban_state: vals["kanban_state"] = kanban_state
return _write("project.task", task_id, vals) if vals else False
@mcp.tool()
def list_task_stages(project_id: int = None) -> list:
"""List task stages. Optionally filter by project."""
@@ -613,26 +512,6 @@ def get_helpdesk_ticket(ticket_id: int) -> dict:
"date_last_stage_update", "kanban_state", "tag_ids"])
return r[0] if r else {}
@mcp.tool()
def create_helpdesk_ticket(name: str, description: str = "",
partner_id: int = None, team_id: int = None) -> int:
"""Create a helpdesk ticket. Returns new ticket ID."""
vals = {"name": name}
if description: vals["description"] = description
if partner_id: vals["partner_id"] = partner_id
if team_id: vals["team_id"] = team_id
return _create("helpdesk.ticket", vals)
@mcp.tool()
def update_helpdesk_ticket(ticket_id: int, stage_id: int = None,
user_id: int = None, note: str = "") -> bool:
"""Update a helpdesk ticket's stage, assignee, or add a note."""
vals = {}
if stage_id: vals["stage_id"] = stage_id
if user_id: vals["user_id"] = user_id
if note: vals["description"] = note
return _write("helpdesk.ticket", ticket_id, vals) if vals else False
@mcp.tool()
def list_helpdesk_teams() -> list:
"""List all helpdesk teams."""