Skip to main content
Glama

Frappe MCP Bridge

Lets Claude Code and Codex (or any MCP client) work with a Frappe / ERPNext site over the Model Context Protocol. They can read records, run reports, correct data, import rows and run patches. Every call is gated by MCP Bridge Settings and recorded in MCP Bridge Log.

It works with Frappe v15 and v16. The app ships switched off and read only, with only the read capability ticked.

How it connects

There are two ways to connect. Both go through the same gate and write to the same log.

Native HTTP (recommended)

Local stdio bridge

What runs on your machine

Nothing

A small Python process from mcp_server/

URL

https://<site>/api/method/frappe_mcp_bridge.api.mcp.serve

Same site, via frappe_mcp_bridge.api.mcp.execute

Sign-in

Each person's own Frappe login (OAuth), or Authorization: token <api_key>:<api_secret>

The API key pair, in a .env

Extra lock

–

FRAPPE_MCP_READ_ONLY=true refuses writes locally

The native endpoint is stateless Streamable HTTP: each POST carries its own credentials and gets plain JSON back.

1. Install on the site

bench get-app https://github.com/<you>/frappe_mcp_bridge
bench --site <site> install-app frappe_mcp_bridge
bench --site <site> migrate

2. Choose how people sign in

OAuth (recommended). Tick Allow OAuth Sign-In in MCP Bridge Settings and save. Each person then connects with their own Frappe account: the client opens this site's login page, they sign in and press Allow, and every call runs as them, so the log shows who did what. There are no keys to hand out, and access is revoked by deleting their OAuth Bearer Token. This works for Claude Code, Codex, Claude Desktop and claude.ai (the last two need the site on https).

What saving that checkbox does:

  • v16: switches on the discovery and client-registration options in Frappe's own OAuth Settings.

  • v15: Frappe has no discovery of its own, so the app serves those documents itself.

On both, client registration goes through this app so that the http://localhost redirect Claude Code and Codex use is accepted. Frappe v16's own registration refuses it outside developer mode.

API keys. A single shared user and key pair, set up as below. This is what the stdio bridge uses. Untick Allow API Key Sign-In once everyone is on OAuth.

  1. Create a user (e.g. mcp@yourcompany.com) with only the roles it needs. MCP can never exceed that user's own Frappe permissions.

  2. Open MCP Bridge Settings (or the MCP Bridge workspace):

    • Set MCP User, add one of that user's roles to Allowed Roles (the default is System Manager), and save.

    • Click Generate API Keys. The secret is shown once, together with ready-to-paste Claude Code and Codex config.

    • Tick Enable MCP Access. Leave Read Only Mode on until you want writes, and then use Allow Writes For… (see below).

With shell access you can use bench commands instead:

bench --site <site> mcp-bridge-keys --user mcp@yourcompany.com   # prints keys + client snippets
bench --site <site> mcp-bridge-enable                             # on, read only
bench --site <site> mcp-bridge-enable --allow-writes --minutes 30 # on, writes for 30 minutes
bench --site <site> mcp-bridge-enable --allow-writes              # on, writes with no time limit
bench --site <site> mcp-bridge-disable                            # off
bench --site <site> mcp-bridge-status                             # what is allowed + recent calls

3. Connect a client

With OAuth. Add the URL with no credentials. The client signs in on first use.

# Claude Code: then run /mcp, pick the server and choose Authenticate
claude mcp add --transport http prod https://erp.example.com/api/method/frappe_mcp_bridge.api.mcp.serve
# Codex, ~/.codex/config.toml: then run `codex mcp login prod`
[mcp_servers.prod]
url = "https://erp.example.com/api/method/frappe_mcp_bridge.api.mcp.serve"
  • claude.ai / Claude Desktop: Settings → Connectors → Add custom connector, with the same URL.

  • Redirect port changes: Frappe matches redirect URIs exactly. If a client later comes back on a different localhost port and the Allow step fails, clear its authentication (/mcp → Clear authentication in Claude Code) so it registers again.

With an API key, Claude Code:

claude mcp add --transport http prod \
  https://erp.example.com/api/method/frappe_mcp_bridge.api.mcp.serve \
  --header "Authorization: token <api_key>:<api_secret>"

Or commit a .mcp.json that reads the secret from your environment:

{
  "mcpServers": {
    "prod": {
      "type": "http",
      "url": "https://erp.example.com/api/method/frappe_mcp_bridge.api.mcp.serve",
      "headers": { "Authorization": "token ${FRAPPE_MCP_KEY}:${FRAPPE_MCP_SECRET}" }
    }
  }
}

Codex with an API key (~/.codex/config.toml). Set the header value with export FRAPPE_MCP_AUTH='token <api_key>:<api_secret>'.

[mcp_servers.prod]
url = "https://erp.example.com/api/method/frappe_mcp_bridge.api.mcp.serve"
env_http_headers = { "Authorization" = "FRAPPE_MCP_AUTH" }

Local stdio bridge: see mcp_server/README.md. Use it when you want the extra client-side read-only lock, or a client that only speaks stdio.

Once connected, ask Claude to call site_ping. It reports the user, the roles, and whether MCP is enabled and read only.

Safety model

Every call must pass all of these checks:

Lock

Where

What it does

Frappe roles

the signed-in user

The floor. MCP never exceeds what that user can do in the desk.

Enable MCP Access

MCP Bridge Settings

Master switch. Off means every tool call is refused.

Sign-in methods

MCP Bridge Settings

Allow OAuth Sign-In and Allow API Key Sign-In, each switchable on its own. Turning one off refuses its tokens immediately.

Read Only Mode

MCP Bridge Settings

Refuses every writing tool, whatever else is ticked.

Write window

MCP Bridge Settings

Allow Writes For… turns Read Only Mode off for 15 minutes to 8 hours. Writes are refused the moment it ends, and the form ticks Read Only Mode back on within 5 minutes.

Sensitive fields

MCP Bridge Settings

Masked values come back as ••• (see below).

Capabilities

MCP Bridge Settings

read, write, submit, delete, import, patch, sql, admin, script, files, backup. Only read is on by default.

Allowed Roles / IPs

MCP Bridge Settings

The caller must hold an allowed role, and optionally come from an allowed IP or CIDR range.

Doctype allow/block lists

MCP Bridge Settings

User, Role, DocPerm, Server Script, System Settings, this app's own settings and other permission-carrying doctypes are always refused.

Limits

MCP Bridge Settings

Rows per read, documents per write batch, calls per hour.

  • Dry run. Every writing tool takes dry_run. The work really runs inside a savepoint and is rolled back, so the errors are the ones a live call would hit. Default To Dry Run makes that the default. run_patch is the exception: a patch commits as it goes, so its dry run only reports whether it would run and whether the module imports.

  • Audit. Every call, including refusals, is logged in MCP Bridge Log: tool, user, client, IP, duration, outcome and (optionally) payloads. Secrets in payloads are redacted. Old rows are cleared daily according to Log Retention.

Sensitive fields

Everything a tool returns goes to an AI model. List fields that must never do so under Sensitive Fields: a document type plus fieldname, or just a fieldname for every document type (e.g. salary, bank_ac_no, passport_number).

  • Masked in every result. Their values come back as ••• in documents, child tables, lists, CSV exports, report rows, SQL results and before/after change reports.

  • Indirect reveals are refused. Calls that would reveal a value some other way are refused: filtering, sorting or grouping on a masked field, using it inside an expression or alias, or naming it in SQL.

  • describe_site lists the masked field names, so Claude can explain a ••• instead of guessing.

  • run_server_script is not covered. A script can read anything its user can, so keep Allow Server Script off on a site with masked fields.

  • Server files and backups are not covered. Masking applies to documents, not to logs, source files or the database dump a backup holds.

Server files

Tick Allow Server Files to let Claude read the code and logs on the server, and export your apps. It is off by default, and only a System Manager can use it, whatever Allowed Roles says.

  • Reach. Everything under the bench directory (apps/, sites/, logs/, config/) and nothing outside it. A symlink cannot lead out.

  • Always refused. site_config.json and common_site_config.json, .env files, private keys and certificates (*.pem, *.key), Redis ACLs, .git, bench backups, and each site's private/ folder. .env.example is allowed.

  • Export. export_apps zips every installed app that Frappe does not maintain itself (anything whose publisher is not Frappe Technologies), or the apps you name. The zip leaves out .git, node_modules, virtualenvs and caches. It returns a download link that works in a browser or with curl for 60 minutes without signing in, so treat the link as a password. Switching off MCP access or Allow Server Files stops every open link, the link respects Allowed IP Addresses, and each download is logged in MCP Bridge Log. The zips are deleted within an hour of their link expiring.

Backups

Tick Allow Backups to have Claude take a backup of the site on the server and hand you a download link, so you can restore a copy of production on your own machine. It is off by default, and only a System Manager can use it, whatever Allowed Roles says. After updating the app, run bench --site <site> migrate so the checkbox appears.

"Back up production and restore it here as shop.localhost."

  1. create_backup starts Frappe's own backup in the background, the same code as bench backup and the scheduled backup, for the database or, with with_files, the public and private files too. A small site answers within the call; for a large one list_backups with wait_seconds=30 follows it.

  2. list_backups shows every backup on the server however it was made, scheduled ones included, with sizes and the free disk space.

  3. get_backup_links returns one link per file, valid for 30 minutes, with the restore steps. Claude fetches them with curl to your machine and, once you name the local site, runs bench restore on it. Restoring replaces that site's database.

  • A link is a password. Anyone holding it can download the file for 30 minutes without signing in, so Claude shows it only to you. It stops working when MCP access or Allow Backups is switched off, it respects Allowed IP Addresses, and every download is logged in MCP Bridge Log with the address that fetched it.

  • Backups are not masked. A backup holds every record, including the fields listed under Sensitive Fields and every user's password hash. Keep it on an encrypted disk and delete it when you are done.

  • The site config is never offered. Frappe leaves a copy of site_config.json, with the database password and encryption key, beside every backup. The app never lists or serves it. So Password fields and two-factor secrets on the restored copy cannot be decrypted, which also stops the copy from using the server's mail, payment and other integrations. If you need them, copy encryption_key from the server's site_config.json into the local one yourself.

  • Before the restored copy runs: bench --site <site> set-config mute_emails 1 and bench --site <site> disable-scheduler. The database holds the server's unsent emails and integration settings.

  • Disk. A backup is refused when the free space is less than the estimated size plus 1 GB.

  • Retention. Like any Frappe backup, starting one removes this site's backup files older than 23 hours (keep_backups_for_hours in site_config.json).

  • It needs a worker. The backup runs on the long queue. If it stays queued, no worker is running on the server.

  • Encrypted and partial backups. If the site encrypts its backups (System Settings), restore with --encryption-key; the key is not part of the download. A partial backup, from a backup entry in site_config.json, is restored with bench partial-restore.

Tools

  • Orientation: site_ping, site_capabilities, describe_site, list_doctypes, get_doctype_schema, list_reports

  • Read: get_document, get_single, list_documents, count_documents, export_records, run_report, run_sql (SELECT only), get_mcp_logs, get_error_logs

  • Write: create_document, update_document, bulk_update_documents, import_records, submit_document, cancel_document, amend_document, delete_document, rename_document

  • Maintenance: list_patches (one app or all), get_patch_log, run_patch, clear_cache, reload_doctype, force_set_values, run_scheduled_job, run_server_script

  • Server files: list_server_files, read_server_file, search_server_files, export_apps

  • Backups: create_backup, list_backups, get_backup_links

Troubleshooting

Symptom

Cause

401 with resource_metadata

No credentials. Either add the API key header or let the client sign in with OAuth.

OAuth sign-in is off / API key sign-in is off

That sign-in method is unticked in MCP Bridge Settings.

The write window ended at …

The Allow Writes For time ran out. Open a new window.

… uses masked fields

The call would reveal a sensitive field. Ask without filtering or sorting on it.

MCP access is disabled

Enable MCP Access is off.

… holds none of the roles allowed for MCP access

Give the MCP user one of the Allowed Roles, or add its role to that list.

Read Only Mode is on

Untick it, or the tool is meant to be refused.

… is not ticked in MCP Bridge Settings

That capability is off. The message names the exact checkbox.

… can never be reached through MCP

The doctype is on the built-in block list.

… holds credentials or private files

The file is on the built-in block list for server files.

This download link is invalid or has expired

The link's time is up, or the capability behind it was switched off. Run export_apps or get_backup_links again.

Not enough free disk space for a backup

The server's disk cannot hold the backup plus the 1 GB margin. Free some space or leave out the files.

A backup stays queued

No long queue worker is running on the server.

Development

Adding a tool takes three steps. The gate needs no change.

  1. Add a handler in frappe_mcp_bridge/mcp/handlers/ with @tool(name, capability, writes=…, doctype_param=…).

  2. Add its long description to frappe_mcp_bridge/mcp/descriptions.py.

  3. Add a thin wrapper in mcp_server/src/frappe_mcp_bridge_client/tools/.

The native endpoint builds each tool's input schema from the handler's signature and type hints.

Tests. The suite runs against both Frappe versions. The tests swap in an in-memory settings doc and suppress commits, so they leave nothing behind on the site.

bench --site <site> set-config allow_tests true
bench --site <site> run-tests --app frappe_mcp_bridge

GitHub Actions (.github/workflows/ci.yml) runs ruff and the suite on Frappe v15 (Python 3.11) and v16 (Python 3.14) for every push and pull request.

License

mit

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    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
    A
    quality
    A
    maintenance
    Connects MCP clients to Frappe/ERPNext sites via REST API, enabling document CRUD, search, and remote method calls.
    7
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server for the Frappe Cloud control plane that lets agents manage sites, bench groups, servers, backups, deploys, logs, analytics, and billing. It provides read, write, and optional destructive tools for operating Frappe Cloud hosting infrastructure.
    MIT