- 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
104 lines
3.2 KiB
Python
104 lines
3.2 KiB
Python
"""
|
|
Frappe MCP Server — entry point.
|
|
Modules can be individually enabled/disabled via ENABLED_MODULES in .env.
|
|
|
|
Transports:
|
|
(default) stdio — for local Claude Desktop use
|
|
--sse HTTP/SSE — for VPS hosting (requires starlette + uvicorn)
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
from mcp.server import Server
|
|
from mcp.server.stdio import stdio_server
|
|
from mcp.types import Tool, TextContent
|
|
|
|
from frappe_mcp.module_registry import get_enabled_tools, get_enabled_handlers
|
|
|
|
app = Server("frappe-mcp")
|
|
|
|
|
|
@app.list_tools()
|
|
async def list_tools() -> list[Tool]:
|
|
return get_enabled_tools()
|
|
|
|
|
|
@app.call_tool()
|
|
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
handlers = get_enabled_handlers()
|
|
handler = handlers.get(name)
|
|
if not handler:
|
|
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
|
try:
|
|
result = await handler(arguments)
|
|
return [TextContent(type="text", text=result)]
|
|
except Exception as e:
|
|
return [TextContent(type="text", text=f"Error: {e}")]
|
|
|
|
|
|
def main():
|
|
if "--test-connection" in sys.argv:
|
|
from frappe_mcp.healthcheck import run_health_check
|
|
ok = asyncio.run(run_health_check())
|
|
sys.exit(0 if ok else 1)
|
|
if "--list-modules" in sys.argv:
|
|
from frappe_mcp.module_registry import print_module_status
|
|
print_module_status()
|
|
sys.exit(0)
|
|
if "--sse" in sys.argv:
|
|
_run_sse()
|
|
return
|
|
asyncio.run(_run_stdio())
|
|
|
|
|
|
async def _run_stdio():
|
|
async with stdio_server() as (read_stream, write_stream):
|
|
await app.run(read_stream, write_stream, app.create_initialization_options())
|
|
|
|
|
|
def _run_sse():
|
|
try:
|
|
import uvicorn
|
|
from starlette.applications import Starlette
|
|
from starlette.middleware import Middleware
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
from starlette.responses import Response
|
|
from starlette.routing import Mount, Route
|
|
from mcp.server.sse import SseServerTransport
|
|
except ImportError:
|
|
print("SSE dependencies missing. Run: pip install 'frappe-mcp[sse]'", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
host = os.environ.get("MCP_HOST", "0.0.0.0")
|
|
port = int(os.environ.get("MCP_PORT", "8001"))
|
|
bearer_token = os.environ.get("MCP_BEARER_TOKEN", "")
|
|
|
|
sse = SseServerTransport("/messages/")
|
|
|
|
async def handle_sse(request: Request) -> Response:
|
|
if bearer_token:
|
|
auth = request.headers.get("Authorization", "")
|
|
if auth != f"Bearer {bearer_token}":
|
|
return Response("Unauthorized", status_code=401)
|
|
async with sse.connect_sse(
|
|
request.scope, request.receive, request._send
|
|
) as streams:
|
|
await app.run(streams[0], streams[1], app.create_initialization_options())
|
|
return Response()
|
|
|
|
starlette_app = Starlette(
|
|
routes=[
|
|
Route("/sse", endpoint=handle_sse),
|
|
Mount("/messages/", app=sse.handle_post_message),
|
|
]
|
|
)
|
|
|
|
print(f"Frappe MCP SSE server starting on {host}:{port}")
|
|
uvicorn.run(starlette_app, host=host, port=port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|