- 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
177 lines
6.4 KiB
Python
177 lines
6.4 KiB
Python
"""
|
|
Level 6 — Reporting and analytics tools.
|
|
Aggregate queries, dashboard data, PDF rendering.
|
|
"""
|
|
|
|
import json
|
|
from mcp.types import Tool
|
|
from frappe_mcp.client.frappe_api import FrappeClient
|
|
|
|
|
|
def tools() -> list[Tool]:
|
|
return [
|
|
Tool(
|
|
name="frappe_aggregate_documents",
|
|
description=(
|
|
"Aggregate document data: sum, avg, min, max, or count grouped by a field. "
|
|
"e.g. total sales per customer, count of issues per status."
|
|
),
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["doctype", "function", "field"],
|
|
"properties": {
|
|
"doctype": {"type": "string"},
|
|
"function": {
|
|
"type": "string",
|
|
"enum": ["sum", "avg", "min", "max", "count"],
|
|
},
|
|
"field": {"type": "string", "description": "Field to aggregate e.g. 'grand_total'"},
|
|
"group_by": {"type": "string", "description": "Field to group by e.g. 'customer'"},
|
|
"filters": {"type": "array"},
|
|
"limit": {"type": "integer", "default": 50},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_get_dashboard_data",
|
|
description=(
|
|
"Pull data for a Dashboard Chart — returns the chart's data points "
|
|
"for display as a summary or KPI."
|
|
),
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["chart_name"],
|
|
"properties": {
|
|
"chart_name": {"type": "string"},
|
|
"filters": {"type": "object"},
|
|
"refresh": {"type": "boolean", "default": False},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_render_document_pdf",
|
|
description=(
|
|
"Generate a PDF for a document using a Print Format. "
|
|
"Returns the PDF download URL on the Frappe server."
|
|
),
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["doctype", "name"],
|
|
"properties": {
|
|
"doctype": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"print_format": {"type": "string", "description": "Print Format name (optional, uses default if empty)"},
|
|
"letterhead": {"type": "string"},
|
|
"language": {"type": "string", "default": "en"},
|
|
},
|
|
},
|
|
),
|
|
Tool(
|
|
name="frappe_get_number_card_value",
|
|
description="Get the current value of a Number Card (single KPI metric).",
|
|
inputSchema={
|
|
"type": "object",
|
|
"required": ["card_name"],
|
|
"properties": {
|
|
"card_name": {"type": "string"},
|
|
"filters": {"type": "object"},
|
|
},
|
|
},
|
|
),
|
|
]
|
|
|
|
|
|
def handlers() -> dict:
|
|
return {
|
|
"frappe_aggregate_documents": _aggregate_documents,
|
|
"frappe_get_dashboard_data": _get_dashboard_data,
|
|
"frappe_render_document_pdf": _render_document_pdf,
|
|
"frappe_get_number_card_value": _get_number_card_value,
|
|
}
|
|
|
|
|
|
async def _aggregate_documents(args: dict) -> str:
|
|
client = FrappeClient()
|
|
func = args["function"].upper()
|
|
field = args["field"]
|
|
group_by = args.get("group_by")
|
|
filters = args.get("filters")
|
|
|
|
if group_by:
|
|
sql = f"SELECT `{group_by}`, {func}(`{field}`) as value FROM `tab{args['doctype']}`"
|
|
conditions = []
|
|
if filters and isinstance(filters, list):
|
|
for f in filters:
|
|
if len(f) >= 3:
|
|
conditions.append(f"`{f[0]}` {f[1]} '{f[2]}'")
|
|
if conditions:
|
|
sql += " WHERE " + " AND ".join(conditions)
|
|
sql += f" GROUP BY `{group_by}` ORDER BY value DESC LIMIT {args.get('limit', 50)}"
|
|
result = await client.call_method("frappe.client.get_list", doctype="__query__", query=sql)
|
|
else:
|
|
sql = f"SELECT {func}(`{field}`) as value FROM `tab{args['doctype']}`"
|
|
conditions = []
|
|
if filters and isinstance(filters, list):
|
|
for f in filters:
|
|
if len(f) >= 3:
|
|
conditions.append(f"`{f[0]}` {f[1]} '{f[2]}'")
|
|
if conditions:
|
|
sql += " WHERE " + " AND ".join(conditions)
|
|
result = await client.call_method(
|
|
"frappe.desk.query_report.run",
|
|
report_name="__inline__",
|
|
filters={},
|
|
)
|
|
# Fallback: use get_list with aggregation via API
|
|
result = await client.call_method(
|
|
"frappe.client.get_value",
|
|
doctype=args["doctype"],
|
|
filters=filters,
|
|
fieldname=f"{func.lower()}({field})",
|
|
)
|
|
return json.dumps({"function": func, "field": field, "group_by": group_by, "result": result}, indent=2)
|
|
|
|
|
|
async def _get_dashboard_data(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.call_method(
|
|
"frappe.desk.doctype.dashboard_chart.dashboard_chart.get",
|
|
chart_name=args["chart_name"],
|
|
filters=json.dumps(args.get("filters", {})),
|
|
refresh=args.get("refresh", False),
|
|
)
|
|
return json.dumps(result, indent=2)
|
|
|
|
|
|
async def _render_document_pdf(args: dict) -> str:
|
|
client = FrappeClient()
|
|
params = {
|
|
"doctype": args["doctype"],
|
|
"name": args["name"],
|
|
"print_format": args.get("print_format", ""),
|
|
"letterhead": args.get("letterhead", ""),
|
|
"lang": args.get("language", "en"),
|
|
}
|
|
pdf_url = (
|
|
f"{client.base_url}/api/method/frappe.utils.pdf.download_pdf"
|
|
f"?doctype={params['doctype']}&name={params['name']}"
|
|
f"&format={params['print_format']}&lang={params['lang']}"
|
|
)
|
|
return json.dumps({
|
|
"pdf_url": pdf_url,
|
|
"doctype": args["doctype"],
|
|
"name": args["name"],
|
|
"print_format": args.get("print_format", "default"),
|
|
"note": "Open pdf_url in browser or download directly from Frappe server",
|
|
}, indent=2)
|
|
|
|
|
|
async def _get_number_card_value(args: dict) -> str:
|
|
client = FrappeClient()
|
|
result = await client.call_method(
|
|
"frappe.desk.doctype.number_card.number_card.get_result",
|
|
doc={"name": args["card_name"]},
|
|
filters=json.dumps(args.get("filters", {})),
|
|
)
|
|
return json.dumps({"card": args["card_name"], "value": result}, indent=2)
|