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 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "odoo-mpm", "name": "odoo-mpm",
"version": "0.1.2", "version": "0.2.0",
"description": "Connects Claude to MPM's Odoo instance (mpmedia.odoo.com) — Products, Knowledge, Contacts, Sales, CRM, Project, Helpdesk, Purchase, Inventory, and Employees.", "description": "Read-only connection to MPM's Odoo instance (mpmedia.odoo.com) — Products, Knowledge, Contacts, Sales, CRM, Project, Helpdesk, Purchase, Inventory, and Employees.",
"author": { "name": "Message Point Media" }, "author": { "name": "Message Point Media" },
"keywords": ["odoo", "erp", "mpm", "project", "crm", "helpdesk"] "keywords": ["odoo", "erp", "mpm", "project", "crm", "helpdesk"]
} }
+2 -123
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Odoo MCP Server for MPM Odoo MCP Server for MPM (read-only)
Connects to mpmedia.odoo.com via XML-RPC and exposes tools for Connects to mpmedia.odoo.com via XML-RPC and exposes read-only tools for
Products, Knowledge, Contacts, Sales, CRM, Project, Helpdesk, Products, Knowledge, Contacts, Sales, CRM, Project, Helpdesk,
Purchase, Inventory, Employees, and Knowledge Templates. 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") api_key = ODOO_API_KEY or _keychain_get("odoo_api_key")
return username, 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(): def _connect():
global _uid, _models, _resolved_api_key global _uid, _models, _resolved_api_key
if _uid is not None: if _uid is not None:
@@ -132,11 +126,6 @@ def _read(model, ids, fields=None):
kw["fields"] = fields kw["fields"] = fields
return _call(model, "read", [ids], kw) 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 ─────────────────────────────────────────────────────────────── # ── FastMCP App ───────────────────────────────────────────────────────────────
mcp = FastMCP("Odoo MPM") mcp = FastMCP("Odoo MPM")
@@ -274,26 +263,6 @@ def get_knowledge_article(article_id: int) -> dict:
) )
return article 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() @mcp.tool()
def search_knowledge_templates(query: str = "", category: str = "", limit: int = 50) -> list: def search_knowledge_templates(query: str = "", category: str = "", limit: int = 50) -> list:
"""Search Knowledge Base article templates. """Search Knowledge Base article templates.
@@ -375,18 +344,6 @@ def get_contact(contact_id: int) -> dict:
"website", "comment", "category_id", "child_ids", "user_id"]) "website", "comment", "category_id", "child_ids", "user_id"])
return r[0] if r else {} 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 order["lines"] = lines
return order 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"]) "activity_ids", "date_conversion"])
return r[0] if r else {} 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() @mcp.tool()
def list_crm_stages() -> list: def list_crm_stages() -> list:
"""List all CRM pipeline stages with their IDs and names.""" """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"]) "child_ids", "depend_on_ids", "planned_hours", "effective_hours"])
return r[0] if r else {} 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() @mcp.tool()
def list_task_stages(project_id: int = None) -> list: def list_task_stages(project_id: int = None) -> list:
"""List task stages. Optionally filter by project.""" """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"]) "date_last_stage_update", "kanban_state", "tag_ids"])
return r[0] if r else {} 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() @mcp.tool()
def list_helpdesk_teams() -> list: def list_helpdesk_teams() -> list:
"""List all helpdesk teams.""" """List all helpdesk teams."""