Synapse
Provides a permission-aware MCP server for ERPNext, enabling LLM clients to manage ERPNext documents (Sales Invoices, etc.) under the authenticated user's permissions, with OAuth and audit trails.
Provides a permission-aware MCP server for Frappe, allowing LLM clients to read and write Frappe site data under user permissions, with OAuth authentication and full audit logging.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Synapselist the last 10 sales invoices for customer Acme"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Synapse
Synapse is an MCP server for Frappe and ERPNext. It lets an AI client read and write a site over OAuth. The client acts as a real Frappe user and stays within that user's permissions. Every call is written to an audit log.
POST https://<your-site>/api/method/synapse.mcp.handle_mcpWhat makes it different
Many Frappe MCP servers run with full privileges and give the model raw SQL or document access with permissions turned off. That is fine for a personal sandbox. It is not safe on a business system. Synapse works the other way.
It never turns off permissions. Every tool runs as the signed in user. DocType permissions, User Permissions, share rules and submit or cancel rights all apply. Writes go through the normal insert, save, submit and cancel path, so validations, hooks and workflows run the same as they do in the desk.
It adds a second layer above permissions. "This user may edit Sales Invoices in the desk" and "an AI agent holding this user's token may edit Sales Invoices" are two different decisions. That second decision is the Synapse Profile.
It logs every call, including ones that are refused.
It has no external dependencies and creates no roles. A plain
bench install-appis the whole install.
Related MCP server: Frappe Assistant Core
Install
bench get-app https://github.com/dxbitz-technology/synapse
bench --site <your-site> install-app synapseCheck the current state of a site at any time:
bench --site <your-site> execute synapse.mcp_tools.check.reportIt prints what is set up and what is missing, in the order to fix it. A fresh install is fully closed. Nothing is reachable until you create a profile.
How access works
Access is granted by Synapse Profile records. A profile lists a set of roles and the DocTypes and actions those roles may use. A user's access is the sum of every enabled profile whose roles they hold.
With no matching profile, nothing is reachable.
A tick in a profile is a ceiling, not a grant. The user still needs the matching Frappe permission on the record. That is checked when the document is touched.
Full Access on a profile grants every action on every DocType and ignores the grid. The user's own Frappe permissions become the working limit. Use it only for a user whose Frappe permissions are already scoped the way you want.
Allow SQL on a profile turns on the raw SQL tool for its users. Read the SQL section first.
Two fixed rules sit above every profile and cannot be overridden:
Some DocTypes are never reachable. These hold tokens, credentials and the records that hand them out, plus Synapse's own settings, profiles and log. Reading them is how a read-only user could turn into a writer, or edit the gate that controls them.
Some DocTypes are read only and can never be written. These define the schema, the code and the permission model, for example DocType, Custom Field, Server Script, Custom DocPerm, Role and User. A user who could edit Custom DocPerm could grant themselves anything.
Synapse Settings also has a site wide Blocked DocTypes list. Use it to block something that a profile would otherwise allow.
Tools
Tool | Action needed |
| read |
| read |
| write |
| write |
| write |
| submit |
| cancel |
| delete |
| operate |
| a profile with Allow SQL (see below) |
The write tools fall into three bands, all under the write action:
Document level:
create_docmakes a document,update_docchanges fields on one (and replaces a whole child table if pointed at one),set_valuesets a single field.Row level:
add_childappends a row and returns its name and idx,set_child_valueedits one row (one or more fields, optionalexpect),set_child_rowsedits many rows atomically,delete_childremoves a row. Rows are addressed byrow.name, taken from a priorget_doc, never by position or content, so the log always names the row it touched. Each runs the parent's real save, so totals and tax recompute the same as in the desk.Field text:
replace_in_fieldchanges part of a long text field. It counts how many times the text appears and refuses unless that count matches the number you expected, so it cannot rewrite the wrong part.
For a child table, prefer the row-level tools. Use update_doc on a table
only to replace the whole thing on purpose. Reaching for update_doc to change
one price would silently discard every other row.
set_child_value, set_child_rows and delete_child take an optional expect
of current values. When given, the write is refused if the row no longer holds
those values, so an edit cannot land on a row that changed since it was read. A
submitted parent is refused, the same as a desk edit would be. run_operation
runs a document's own method (see below).
Dates come back in the format set in Synapse Settings, ISO by default. Writes accept ISO or DD-MM-YYYY, so a read then write round trip cannot swap the day and the month.
The operate action
run_operation runs a document's own method by name. This is the behaviour
behind a desk button, for example a Sales Invoice reposting its accounting
entries. Because it can run code, it has its own action, operate, which is
granted per DocType in a profile. That grant is what makes it safe to offer. A
profile has to say, for this DocType, that operations may run.
It still runs as the signed in user, under Frappe permissions, and every call is logged. Methods that already have their own tool (save, submit, cancel, delete and so on) are refused here, and so is anything private. So operate cannot be used to get around the other tools.
Custom tools
Any installed app can add its own tools to the Synapse endpoint, so an app can expose the specific jobs it knows how to do rather than only the generic document tools. It is all done through one hook. The app declares each tool as data and needs no import of synapse:
# in myapp/hooks.py
synapse_tools = [
{"method": "myapp.synapse_tools.open_tasks_for", "read_only": True},
]# in myapp/synapse_tools.py
import frappe
def open_tasks_for(project: str) -> dict:
"""Return the open tasks on a project.
The description and arguments the model sees come from this docstring and
the function signature.
"""
rows = frappe.get_list(
"Task",
filters={"project": project, "status": ["!=", "Completed"]},
fields=["name", "subject", "status"],
)
return {"project": project, "open_tasks": rows}An app that prefers to keep the flags next to the function can instead mark it
with @synapse.tool(read_only=True) and list the module path as a plain string
in the same hook (synapse_tools = ["myapp.synapse_tools"]). Both forms may be
mixed.
A custom tool runs the same way the built-in tools do. It runs as the signed in user, with Frappe permissions on, and every call is written to the Synapse Log. The app author is responsible for what the function does, so it should read and write through the normal Frappe document API and never with permissions off.
A registered tool is not reachable on its own. Two more things must be true:
Enable Custom Tools is ticked in Synapse Settings.
A Synapse Profile the caller holds lists the tool by its exact name, in the Custom Tools table.
This is the same explicit grant model as the rest of Synapse. Full Access does not include custom tools, because a custom tool can run any code its app wrote, so each one is granted by name. A tool whose name clashes with a built-in is refused at load, so an app can never replace a core tool. The readiness report lists every registered tool and whether a profile grants it:
bench --site <your-site> execute synapse.mcp_tools.check.reportModel provider
Synapse Settings has a Model Provider choice. Claude is the only provider that is wired up. The other options are placeholders. The setting records which model family the site presents Synapse for and does not change how tools run.
Setting it up
1. OAuth. Frappe 16 can publish OAuth server metadata and support dynamic client registration. This is what lets an MCP client connect without someone creating an OAuth Client record by hand. It is off by default. In OAuth Settings turn on Show Auth Server Metadata, Show Protected Resource Metadata and Enable Dynamic Client Registration. Synapse does not change these settings. They affect the whole site's OAuth behaviour.
A Frappe OAuth token is not limited to MCP. It authorises the whole /api
surface as that user, so scope the user accordingly.
2. Create a Synapse Profile. Add the roles the agent's user holds, then the DocTypes and actions those roles may use. Reading needs only a read tick.
3. Fill in Synapse Settings. Tick Enable Synapse Endpoint and Enable Read Tools. Reads work at this point. For writes, also tick Enable Write Tools. If that switch is off, the endpoint stays read only whatever a profile grants.
Connecting a client
claude mcp add --transport http mysite https://<your-site>/api/method/synapse.mcp.handle_mcpThen sign in. A browser opens on the site login page. Any MCP client that speaks Streamable HTTP with OAuth works the same way. In Claude Desktop it is Settings, Connectors, Add custom connector, with the same URL.
Raw SQL
run_sql_query does not use Frappe's permission system. A user in a profile with
Allow SQL can read every table on the site, whatever their DocType permissions
are. Grant it only to users who already have full database access.
It is off until Enable Read-Only SQL Tool is ticked in Synapse Settings and the
user holds a profile with Allow SQL. It does not use the profile's DocType
grants, because it never names a DocType. Prefer get_list and get_doc. Use
SQL only for a join or an aggregate they cannot express. If an agent keeps
reaching for SQL, the document tools are probably missing something it needs.
Two layers protect it:
A read only database user, enforced by MariaDB. A query that gets past the text filter still cannot write.
mcp_tools/guard.py. It checks the statement type, blocks comments, blocks more than one statement, blocks a list of keywords and tables, and caps the length. This is text matching, so treat it as a backup, not the main line of defence.
Set up the database user per site. As MariaDB root:
CREATE USER 'mcp_ro'@'localhost' IDENTIFIED BY '<STRONG_PASSWORD>';
GRANT SELECT ON `<DB_NAME>`.* TO 'mcp_ro'@'localhost';
REVOKE FILE ON *.* FROM 'mcp_ro'@'localhost';
FLUSH PRIVILEGES;Then in site_config.json (never in the repo):
{
"mcp_ro_db_user": "mcp_ro",
"mcp_ro_db_password": "<STRONG_PASSWORD>"
}Without those keys, the tool falls back to the site's normal read write connection and rolls back after every query. It works, but then the text guard is the only boundary. On hosted platforms where a second database user is not possible, that fallback is the only option. Decide before you enable SQL there.
Add more blocked tables per site with mcp_sql_blocked_tables in
site_config.json. MariaDB only. connection.py raises NotImplementedError on
other backends.
Audit
Every call writes a Synapse Log row: success, refusal or error. Each row records the tool, the user, how they signed in, the IP address, the document touched, the row counts and the timing. Writes also record the values sent and the before and after of each changed field. Calls that are refused before the tool runs are logged too.
If a tool looks blocked and there is no log row for it, the block is in the client, usually its own tool permission prompt. Check that first.
Log rows are written with their own commit after any rollback, so a failed or refused write still leaves a record. System Manager can read and report on the log but cannot create or edit rows from the desk. A daily job drops rows past the retention window. Untick Log Field Values if the data itself must not be copied into the log. Password fields are masked either way.
Tests
bench --site <your-site> run-tests --app synapseThe access model, the SQL guard, the tool schemas and the value conversion do not import anything from frappe, so they also run without a site:
python -m unittest discover -s apps/synapse -p 'test_mcp_*.py'Licence
GNU Affero General Public License v3.0 or later. See LICENSE.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
- odooOAuthcom.odooconsole
Odoo ERP for AI agents: hosted OAuth endpoint, gated writes, one endpoint for every instance.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Connect LLM tools to your Algolia account with user-scoped access for internal workflows.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables LLMs to interact with any ERPNext instance through comprehensive CRUD operations, advanced permissions, and a web chat interface.1MIT
- AlicenseNot gradedqualityAmaintenanceMCP 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.295AGPL 3.0
- AlicenseNot gradedqualityCmaintenanceEnables AI models to securely interact with Frappe Framework/ERPNext instances, supporting document CRUD, RPC methods, file management, workflows, reporting, and more via the Model Context Protocol.143ISC
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to interact with ERPNext data and functionality through the Model Context Protocol, including document CRUD, report running, and API method calls.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/erpuae/synapse'
If you have feedback or need assistance with the MCP directory API, please join our Discord server