diff --git a/.env b/.env index 4127d6e..71c6752 100644 --- a/.env +++ b/.env @@ -1,47 +1,51 @@ -# Frappe instance URL (your VPS / Docker host) -# If using a domain: https://erp.yourdomain.com -# If using Docker with port mapping: http://YOUR_VPS_IP:8000 -FRAPPE_URL=http://147.93.40.215:10009/ +MCP_HOST=0.0.0.0 +MCP_PORT=16000 +MCP_BEARER_TOKEN=MetaFrappe@2026 -# API credentials — generate in Frappe: Settings > My Account > API Access -FRAPPE_API_KEY=2650fa15dc9393f -FRAPPE_API_SECRET=766ad5af8577685 +# # Frappe instance URL (your VPS / Docker host) +# # If using a domain: https://erp.yourdomain.com +# # If using Docker with port mapping: http://YOUR_VPS_IP:8000 +# FRAPPE_URL=http://147.93.40.215:10009/ -# For multi-site Docker setups (optional) — the site name e.g. "site1.localhost" -FRAPPE_SITE_NAME= +# # API credentials — generate in Frappe: Settings > My Account > API Access +# FRAPPE_API_KEY=2650fa15dc9393f +# FRAPPE_API_SECRET=766ad5af8577685 -# Set to true to block all write/delete operations (safe read-only mode) -READ_ONLY_MODE=false +# # For multi-site Docker setups (optional) — the site name e.g. "site1.localhost" +# FRAPPE_SITE_NAME= -# HTTP timeout in seconds for API calls -REQUEST_TIMEOUT=30 +# # Set to true to block all write/delete operations (safe read-only mode) +# READ_ONLY_MODE=false -ENABLED_MODULES=foundation,document_inspect,analytics +# # HTTP timeout in seconds for API calls +# REQUEST_TIMEOUT=30 -# ── Module on/off switches ─────────────────────────────────────────────────── -# Uncomment a line to disable that module (all others stay ON) -# MODULE_FOUNDATION=false -# MODULE_DOCTYPES=false -# MODULE_DOCUMENTS=false -# MODULE_DOCUMENT_INSPECT=false -# MODULE_DOCUMENT_LIFECYCLE=false -# MODULE_CUSTOM_FIELDS=false -# MODULE_SCRIPTS=false -# MODULE_WORKFLOW_TOOLS=false -# MODULE_ANALYTICS=false -# MODULE_REPORTS=false -# MODULE_PRINT_FORMATS=false -# MODULE_BULK_OPS=false -# MODULE_BUSINESS_ACTIONS=false -# MODULE_USERS=false -# MODULE_ADMIN=false -# MODULE_PROPERTY_SETTERS=false -# MODULE_NAMING_SERIES=false -# MODULE_FILES=false -# MODULE_ACTIVITY=false -# MODULE_DASHBOARDS=false -# MODULE_WEBHOOKS=false -# MODULE_EMAIL_TEMPLATES=false -# MODULE_TRANSLATIONS=false -# MODULE_SCHEDULER=false -# MODULE_GOVERNANCE=false +# ENABLED_MODULES=foundation,document_inspect,analytics + +# # ── Module on/off switches ─────────────────────────────────────────────────── +# # Uncomment a line to disable that module (all others stay ON) +# # MODULE_FOUNDATION=false +# # MODULE_DOCTYPES=false +# # MODULE_DOCUMENTS=false +# # MODULE_DOCUMENT_INSPECT=false +# # MODULE_DOCUMENT_LIFECYCLE=false +# # MODULE_CUSTOM_FIELDS=false +# # MODULE_SCRIPTS=false +# # MODULE_WORKFLOW_TOOLS=false +# # MODULE_ANALYTICS=false +# # MODULE_REPORTS=false +# # MODULE_PRINT_FORMATS=false +# # MODULE_BULK_OPS=false +# # MODULE_BUSINESS_ACTIONS=false +# # MODULE_USERS=false +# # MODULE_ADMIN=false +# # MODULE_PROPERTY_SETTERS=false +# # MODULE_NAMING_SERIES=false +# # MODULE_FILES=false +# # MODULE_ACTIVITY=false +# # MODULE_DASHBOARDS=false +# # MODULE_WEBHOOKS=false +# # MODULE_EMAIL_TEMPLATES=false +# # MODULE_TRANSLATIONS=false +# # MODULE_SCHEDULER=false +# # MODULE_GOVERNANCE=false diff --git a/CONNECT.md b/CONNECT.md new file mode 100644 index 0000000..3b0ad33 --- /dev/null +++ b/CONNECT.md @@ -0,0 +1,277 @@ +# Frappe MCP — Connection Guide + +**Server:** `https://mcp-frappe.metatronhost.com/sse` +**Server Token:** `MetaFrappe@2026` + +You also need your own Frappe/ERPNext credentials to pass along with each connection: + +| Header | Value | +|---|---| +| `Authorization` | `Bearer MetaFrappe@2026` | +| `X-Frappe-URL` | Your ERPNext URL e.g. `https://erp.yourcompany.com` | +| `X-Frappe-API-Key` | Your Frappe API Key | +| `X-Frappe-API-Secret` | Your Frappe API Secret | +| `X-Frappe-Site-Name` | *(optional)* Only for multi-site Docker setups | + +> **How to get your Frappe API Key & Secret:** +> Login to ERPNext → top-right avatar → **My Profile** → **API Access** → **Generate Keys** + +--- + +## 1. Claude Desktop + +Edit your Claude Desktop config file: + +- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +- **Linux:** `~/.config/Claude/claude_desktop_config.json` + +```json +{ + "mcpServers": { + "frappe": { + "url": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +Restart Claude Desktop after saving. You should see **frappe** appear in the tools panel (hammer icon). + +--- + +## 2. Claude Code (CLI) + +### Option A — Project-level (only this project) + +Create `.mcp.json` in your project root: + +```json +{ + "mcpServers": { + "frappe": { + "url": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +### Option B — Global (available in all projects) + +```bash +claude mcp add frappe \ + --transport sse \ + --url "https://mcp-frappe.metatronhost.com/sse" \ + --header "Authorization: Bearer MetaFrappe@2026" \ + --header "X-Frappe-URL: https://erp.yourcompany.com" \ + --header "X-Frappe-API-Key: your_api_key" \ + --header "X-Frappe-API-Secret: your_api_secret" +``` + +Verify it's connected: +```bash +claude mcp list +``` + +--- + +## 3. Cursor + +Open **Cursor Settings** → **MCP** → **Add new MCP server** + +Or edit `~/.cursor/mcp.json` (create if it doesn't exist): + +```json +{ + "mcpServers": { + "frappe": { + "url": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +Restart Cursor. The Frappe tools will appear in Cursor's AI chat context. + +--- + +## 4. VS Code (GitHub Copilot — MCP extension) + +Install the **MCP extension** from VS Code marketplace first. + +Then edit VS Code `settings.json` (`Ctrl+Shift+P` → *Open User Settings JSON*): + +```json +{ + "mcp.servers": { + "frappe": { + "url": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +Or via **workspace** `.vscode/settings.json` (per-project): + +```json +{ + "mcp.servers": { + "frappe": { + "url": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +--- + +## 5. Windsurf (Codeium) + +Edit `~/.codeium/windsurf/mcp_config.json`: + +```json +{ + "mcpServers": { + "frappe": { + "serverUrl": "https://mcp-frappe.metatronhost.com/sse", + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } +} +``` + +--- + +## 6. Continue.dev + +Edit `~/.continue/config.json`: + +```json +{ + "mcpServers": [ + { + "name": "frappe", + "transport": { + "type": "sse", + "url": "https://mcp-frappe.metatronhost.com/sse", + "requestOptions": { + "headers": { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret" + } + } + } + } + ] +} +``` + +--- + +## 7. Any Custom App (OpenAI / Anthropic SDK) + +Use any MCP client library and connect via SSE: + +```python +import httpx + +headers = { + "Authorization": "Bearer MetaFrappe@2026", + "X-Frappe-URL": "https://erp.yourcompany.com", + "X-Frappe-API-Key": "your_api_key", + "X-Frappe-API-Secret": "your_api_secret", +} + +# SSE endpoint +response = httpx.get( + "https://mcp-frappe.metatronhost.com/sse", + headers=headers, + timeout=None +) +``` + +--- + +## Verifying the Connection + +You can test the server is reachable from a terminal: + +```bash +curl -N \ + -H "Authorization: Bearer MetaFrappe@2026" \ + -H "X-Frappe-URL: https://erp.yourcompany.com" \ + -H "X-Frappe-API-Key: your_api_key" \ + -H "X-Frappe-API-Secret: your_api_secret" \ + https://mcp-frappe.metatronhost.com/sse +``` + +Expected: an SSE stream starting with `event: endpoint` data. + +A `401` means wrong bearer token. +A `400` means missing Frappe credential headers. +A `200` stream means you're connected. + +--- + +## Available Tools (150+) + +Once connected, the AI has access to tools across these categories: + +| Category | Examples | +|---|---| +| Documents | create, read, update, delete, submit, cancel | +| DocTypes | list, inspect schema, create custom DocTypes | +| Search & Inspect | search, timeline, linked docs, count | +| Workflows | list transitions, execute approval, get state | +| Analytics | aggregate, dashboard data, PDF render, KPI | +| Bulk Operations | batch create/update/delete/submit | +| ERPNext Business | Sales, Purchase, Stock, HR, Support shortcuts | +| Users & Roles | create users, assign roles, set permissions | +| Admin | cache, system settings, scheduled jobs | +| Governance | dry-run, risk score, audit log, rollback | + +--- + +## Security Notes + +- The bearer token `MetaFrappe@2026` controls access to the MCP server itself. +- Your Frappe API Key/Secret controls what you can do inside ERPNext — scoped to your user's permissions. +- All traffic is encrypted via HTTPS. +- Never share your Frappe API Secret publicly. diff --git a/frappe_mcp/tools/admin.py b/frappe_mcp/tools/admin.py index 425acad..fc15a92 100644 --- a/frappe_mcp/tools/admin.py +++ b/frappe_mcp/tools/admin.py @@ -66,6 +66,7 @@ def tools() -> list[Tool]: "values": { "type": "array", "description": "Parameterized values for %s placeholders", + "items": {}, }, }, }, diff --git a/frappe_mcp/tools/analytics.py b/frappe_mcp/tools/analytics.py index c63513b..1b5a9cd 100644 --- a/frappe_mcp/tools/analytics.py +++ b/frappe_mcp/tools/analytics.py @@ -27,7 +27,7 @@ def tools() -> list[Tool]: }, "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"}, + "filters": {"type": "array", "items": {}}, "limit": {"type": "integer", "default": 50}, }, }, diff --git a/frappe_mcp/tools/doctypes.py b/frappe_mcp/tools/doctypes.py index e166720..0a41dbf 100644 --- a/frappe_mcp/tools/doctypes.py +++ b/frappe_mcp/tools/doctypes.py @@ -66,6 +66,7 @@ def tools() -> list[Tool]: }, "permissions": { "type": "array", + "items": {"type": "object"}, "description": "List of permission rows. Defaults to System Manager full access.", }, }, diff --git a/frappe_mcp/tools/document_inspect.py b/frappe_mcp/tools/document_inspect.py index cdeb42c..afbd007 100644 --- a/frappe_mcp/tools/document_inspect.py +++ b/frappe_mcp/tools/document_inspect.py @@ -22,7 +22,7 @@ def tools() -> list[Tool]: "properties": { "doctype": {"type": "string"}, "query": {"type": "string", "description": "Search text"}, - "filters": {"type": "array", "description": "Additional filter conditions"}, + "filters": {"type": "array", "items": {}, "description": "Additional filter conditions"}, "fields": {"type": "array", "items": {"type": "string"}}, "limit": {"type": "integer", "default": 20}, "order_by": {"type": "string", "default": "modified desc"}, @@ -104,7 +104,7 @@ def tools() -> list[Tool]: "required": ["doctype"], "properties": { "doctype": {"type": "string"}, - "filters": {"type": "array"}, + "filters": {"type": "array", "items": {}}, }, }, ), diff --git a/frappe_mcp/tools/documents.py b/frappe_mcp/tools/documents.py index 5150f7c..e542d5a 100644 --- a/frappe_mcp/tools/documents.py +++ b/frappe_mcp/tools/documents.py @@ -23,6 +23,7 @@ def tools() -> list[Tool]: }, "filters": { "type": "array", + "items": {}, "description": "Filter list e.g. [[\"status\", \"=\", \"Open\"]]", }, "limit": {"type": "integer", "default": 20},