Skip to main content
Glama
vaulcent

EasyTouch MCP

by vaulcent

EasyTouch MCP

EasyTouch MCP V1 is a public, multi-tenant, read-only gateway from MCP-compatible AI clients to each customer's own ERPNext/Frappe site. One deployment serves many customers without putting ERP credentials in tool arguments, model context, source code, or per-customer environment variables.

Security and architecture

MCP client -> OAuth authorization server -> Bearer token
    -> EasyTouch MCP resource server (/mcp)
    -> issuer + subject + tenant claim
    -> exact PostgreSQL tenant/user connection lookup
    -> decrypt ERP secret for this request only
    -> HTTPS Frappe REST call with token key:secret
    -> permission-filtered response

The MCP server is an OAuth 2.1 resource server. It does not implement login or issue tokens. The pluggable OAuthVerifier uses RFC 7662 introspection and checks token activity, expiry, required scope, audience, subject, and the configured tenant claim. The official SDK publishes RFC 9728 protected-resource metadata for /mcp.

Customer connections are unique by (tenant_id, user_id). PostgreSQL stores the ERP URL and API key plus a Fernet-encrypted API secret. The platform ENCRYPTION_KEY is the only decryption key. It should come from a secret manager and be backed up separately; losing it makes stored credentials unrecoverable. Rotation requires decrypting with the old key and re-encrypting with the new key in a controlled job.

Outbound ERP traffic permits only:

  • GET /api/resource/{DocType}

  • GET /api/resource/{DocType}/{name}

  • GET /api/method/frappe.client.get_count

  • GET /api/method/frappe.desk.form.load.getdoctype

  • GET /api/method/frappe.desk.query_report.run

  • GET /api/method/frappe.auth.get_logged_user

No generic write tool or write method exists. Redirects are disabled. ERP hostnames are normalized, DNS-resolved and rejected if any address is non-global in production; localhost, private, link-local, loopback, multicast, reserved and unspecified targets are blocked. Re-checks happen before every ERP request. Deploy behind an HTTPS reverse proxy with egress firewall rules as an additional SSRF control.

Frappe receives the customer's own Authorization: token API_KEY:API_SECRET, so Frappe's roles, user permissions, document sharing, field-level permissions, and report permissions remain authoritative. EasyTouch does not use ignore_permissions.

The built-in limiter is deliberately a single-process structure. Replace it with a Redis-backed implementation before scaling to multiple replicas. Audit records never contain filters, results, tokens, API keys, or secrets.

Related MCP server: ERPNext MCP Server

Tools

The six tools are list_documents, get_document, search_documents, count_documents, get_doctype_meta, and run_report. Limits, names, fields, filters, ordering, URL components, response size, and report inputs are validated. search_text V1 searches the document name; use explicit filters for other fields.

Install and run locally

Python 3.11+ is required (the container uses 3.12).

cp .env.example .env
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e '.[test]'
docker compose up -d postgres
python -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'

Put the generated value in .env as ENCRYPTION_KEY. For local auth, generate a random DEV_ACCESS_TOKEN (for example openssl rand -hex 32). It is accepted only when APP_ENV is not production. Then migrate and run:

alembic upgrade head
uvicorn app.main:app --host 127.0.0.1 --port 8000

Health check:

curl -sS http://127.0.0.1:8000/health

Connect an ERPNext customer

In production, obtain an EasyTouch access token from the configured OAuth provider. Its token/introspection response must contain sub, the tenant_id claim (configurable), the easytouch:read scope, and an audience for MCP_PUBLIC_URL. Submit the ERP credentials directly to the protected onboarding API; this response never includes the secret:

curl -sS http://127.0.0.1:8000/api/v1/connections/erpnext \
  -H "Authorization: Bearer $EASYTOUCH_ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"erp_url":"https://erp.customer.com/desk","api_key":"API_KEY","api_secret":"API_SECRET"}'

The service validates the credentials with GET /api/method/frappe.auth.get_logged_user, then encrypts and saves them. Never put credentials in shell history in production; the customer portal should send this request over HTTPS.

For direct real-ERP local testing without inserting a database connection, set DEV_ERP_URL, DEV_ERP_API_KEY, and DEV_ERP_API_SECRET. This bypass exists only outside production and only for the configured development tenant/user.

Test

pytest -q

To verify authenticated transport and tool discovery with the included official-SDK smoke client:

python scripts/smoke_mcp.py http://127.0.0.1:8000/mcp --token "$EASYTOUCH_ACCESS_TOKEN"

Or use the MCP Inspector:

npx @modelcontextprotocol/inspector http://127.0.0.1:8000/mcp \
  --header "Authorization: Bearer $EASYTOUCH_ACCESS_TOKEN"

An MCP-compatible client uses https://mcp.easytouch.so/mcp. It discovers the authorization server from https://mcp.easytouch.so/.well-known/oauth-protected-resource/mcp, completes OAuth with that provider, and sends the resulting bearer token on every MCP HTTP request.

Production deployment

  1. Provision PostgreSQL with TLS, backups, restricted network access and a least-privilege database user.

  2. Configure APP_ENV=production, a secret-manager-backed ENCRYPTION_KEY, real OAuth introspection credentials, MCP_PUBLIC_URL=https://mcp.easytouch.so/mcp, and exact ALLOWED_HOSTS/TRUSTED_ORIGINS.

  3. Run alembic upgrade head as a release task.

  4. Build and run the non-root Docker image behind a TLS reverse proxy. Restrict outbound port 443 and add network-level private-range denial.

  5. Replace the in-memory rate limiter with shared Redis before adding replicas. Send sanitized application logs and database audit events to the platform SIEM.

The production OAuth provider still needs to be selected and configured. It must support OAuth 2.1 client flows used by MCP clients, RFC 8414 metadata, RFC 9728 resource discovery, PKCE, resource indicators/audience binding, and the current Client ID Metadata Document flow. Dynamic Client Registration is legacy compatibility in the 2026-07-28 MCP revision. The MCP service itself intentionally remains only the resource server, as recommended by the current SDK.

Official sources verified for this implementation

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that enables LLMs to interact with ERPNext/Frappe sites for document CRUD, search, reports, workflows, and analytics, respecting user permissions and logging all actions.
    312
    AGPL 3.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables ERPNext management, file operations, read-only database access, and ERPNext API integration through a standardized MCP server.
    4
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A comprehensive MCP server for ERPNext providing generic, doctype-agnostic access to any ERPNext document type with robust permission controls, audit logging, and enterprise-grade security.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to interact with ERPNext data and functionality through the Model Context Protocol, including document CRUD, report running, and API method calls.
    MIT