MCP-Frappe/frappe_mcp/tools/foundation.py
MOHAN 2ee93048e1 feat: Add tools for managing server scripts, client scripts, translations, assignment rules, user permissions, webhooks, API keys, and workflows
- Implemented server and client script management tools in `frappe_mcp/tools/scripts.py`
- Added translation and user permission management tools in `frappe_mcp/tools/translations.py`
- Created user and role management tools in `frappe_mcp/tools/users.py`
- Developed webhook and API key management tools in `frappe_mcp/tools/webhooks.py`
- Introduced workflow management tools in `frappe_mcp/tools/workflow_tools.py`
- Added `pyproject.toml` for project metadata and dependencies
2026-04-21 20:26:45 +05:30

145 lines
5.0 KiB
Python

"""
Level 1 — Foundation tools.
These are the absolute minimum. Without these, the AI is blind.
"""
import json
from mcp.types import Tool
from frappe_mcp.client.frappe_api import FrappeClient
def tools() -> list[Tool]:
return [
Tool(
name="frappe_ping",
description="Check whether Frappe is reachable. Returns version and status.",
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="frappe_get_session_info",
description=(
"Returns the current session: logged-in user, roles, site name, "
"enabled modules, and Frappe version. Call this first to understand context."
),
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="frappe_get_doctype_permissions",
description=(
"Returns what the current user can do on a DocType: "
"read, write, create, delete, submit, cancel, amend, print, email, export, import."
),
inputSchema={
"type": "object",
"required": ["doctype"],
"properties": {
"doctype": {"type": "string"},
},
},
),
Tool(
name="frappe_list_modules",
description=(
"List all Frappe/ERPNext modules available on the site "
"(Selling, Buying, Stock, Accounts, HR, CRM, Support, Custom, etc.)."
),
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="frappe_get_doctype_meta",
description=(
"Get comprehensive DocType schema: all fields with types, required flags, "
"options, child tables, link fields, and permission hints. "
"Essential before creating or editing documents."
),
inputSchema={
"type": "object",
"required": ["doctype"],
"properties": {
"doctype": {"type": "string"},
"include_permissions": {"type": "boolean", "default": True},
},
},
),
]
def handlers() -> dict:
return {
"frappe_ping": _ping,
"frappe_get_session_info": _get_session_info,
"frappe_get_doctype_permissions": _get_doctype_permissions,
"frappe_list_modules": _list_modules,
"frappe_get_doctype_meta": _get_doctype_meta,
}
async def _ping(args: dict) -> str:
client = FrappeClient()
try:
versions = await client.call_method("frappe.utils.change_log.get_versions")
frappe_ver = versions.get("frappe", {}).get("version", "unknown") if isinstance(versions, dict) else "unknown"
return json.dumps({"status": "ok", "frappe_version": frappe_ver, "url": client.base_url}, indent=2)
except Exception as e:
return json.dumps({"status": "unreachable", "error": str(e)}, indent=2)
async def _get_session_info(args: dict) -> str:
client = FrappeClient()
user = await client.call_method("frappe.auth.get_logged_user")
user_doc = await client.get_doc("User", user)
roles = [r["role"] for r in user_doc.get("roles", [])]
versions = await client.call_method("frappe.utils.change_log.get_versions")
modules_raw = await client.get_list(
"Module Def",
fields=["name", "app_name"],
limit=100,
)
return json.dumps({
"user": user,
"roles": roles,
"frappe_url": client.base_url,
"site_name": client.settings.frappe_site_name or "(default)",
"frappe_version": versions.get("frappe", {}).get("version") if isinstance(versions, dict) else None,
"installed_apps": list(versions.keys()) if isinstance(versions, dict) else [],
"modules": [m["name"] for m in (modules_raw if isinstance(modules_raw, list) else [])],
}, indent=2)
async def _get_doctype_permissions(args: dict) -> str:
client = FrappeClient()
result = await client.call_method(
"frappe.client.get_perm",
doctype=args["doctype"],
)
return json.dumps(result, indent=2)
async def _list_modules(args: dict) -> str:
client = FrappeClient()
result = await client.get_list(
"Module Def",
fields=["name", "app_name"],
limit=150,
order_by="name asc",
)
return json.dumps(result, indent=2)
async def _get_doctype_meta(args: dict) -> str:
client = FrappeClient()
meta = await client.call_method(
"frappe.desk.form.load.getdoctype",
doctype=args["doctype"],
with_parent=1,
cached_timestamp=None,
)
if args.get("include_permissions", True):
try:
perms = await client.call_method("frappe.client.get_perm", doctype=args["doctype"])
if isinstance(meta, dict):
meta["_permissions"] = perms
except Exception:
pass
return json.dumps(meta, indent=2)