Content
# Frappe MCP
Frappe MCP allows your Frappe Framework app to function as a [Streamable HTTP MCP
server](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http).
```python
# app/app/mcp.py
import frappe_mcp
mcp = frappe_mcp.MCP("todo-mcp")
@mcp.tool()
def fetch_todos(username: str): ...
@mcp.tool()
def mark_done(name: str): ...
# MCP endpoint at: http://<BASE_URL>/api/method/app.mcp.handle_mcp
@mcp.register()
def handle_mcp(): ...
```
> [!NOTE]
>
> **Why not use the official Python SDK?**
>
> The official Python SDK only supports async Python, i.e. it assumes that your
> server is an ASGI server.
>
> Frappe Framework is not async, it makes use of Werkzeug, a WSGI server, and so
> a from scratch implementation was needed.
> [!IMPORTANT]
>
> Frappe MCP is in a highly experimental state, there will be bugs, breaking
> changes and large updates. Mostly without notice.
_On GitHub, click the Index button on the top right to view the index._
## Installation
Using PIP:
```bash
pip install frappe-mcp
```
Using UV:
```bash
uv add frappe-mcp
```
## Limitations
Frappe MCP is yet in its infancy, as of now it **only supports** Tools.
Remaining server features such as resources, prompts, tool streaming using SSE
will be added as needed.
## Auth
If you are using a version of the Framework having the OAuth2 updates
([frappe#33188](https://github.com/frappe/frappe/pull/33188)) then using Frappe
MCP with it should be pretty straight forward. You can view this video to check
out how to set up Auth on the MCP Inspector.
https://github.com/user-attachments/assets/a1783a36-7bea-4361-8c7b-bdbb9789877b
If your version does not contain these updates, you will have to register an
OAuth Client on your Framework instance for the MCP client. You can check the
[docs](https://docs.frappe.io/framework/oauth2) for this.
## Documentation
Frappe MCP is fairly straightforward to use. Most of the MCP specific heavy
lifting is handled for you.
### Basic Usage
To use `frappe-mcp` you first create an instance of the `mcp` object:
```python
# app/app/mcp.py (same dir as hooks.py)
import frappe_mcp
mcp = frappe_mcp.MCP("your-app-mcp")
```
Each instance of an `MCP` object can be used to register a single MCP endpoint.
You can create multiple of these objects if you need to serve multiple MCP
endpoints for instance to group functionality.
#### Register tools with `@mcp.tool`
You use the instaniated object i.e. `mcp` to register tools:
```python
# app/app/tools/tools.py
from app.mcp import mcp
@mcp.tool()
def tool_name(a: int, b: str):
"""Description of what the tool does
Args:
a: Description for arg `a`.
b: Description for arg `b`.
"""
... # tool body
return value
```
> [!TIP]
>
> Using Google style docstrings and type annotations like in the example above
> allows Frappe MCP to extract the `inputSchema` for the tool without any additional
> configuration.
If needed, you can manually provide the `inputSchema` and other meta data like annotations.
Check the [Tools](#tools) section for more details.
#### Register endpoint using `@mcp.register`
You use the instantiated object to mark a function as the _entry point_ to your
MCP server, i.e. the function endpoint will be where your MCP server is served
from.
```python
# app/app/mcp.py
@mcp.register()
def handle_mcp():
import app.tools.tools # ensures that your tools are registered
```
Once this is done, your MCP server should be serving at the REST endpoint for
the method ([docs](https://docs.frappe.io/framework/user/en/api/rest#remote-method-calls)).
In this case the endpoint when running locally would be:
```
http://<SITE_NAME:PORT>/api/method/app.mcp.handle_mcp
```
> [!WARNING]
>
> The function body's **only purpose is to import files containing your tools**.
> If this is not done your tools will not be loaded as Frappe MCP does not know where
> your tools are located.
>
> If your tools are in the same file, or have been imported globally, you can
> leave the function body empty.
### Tools
You can register tools, in the following ways:
1. Using the `@mcp.tool` decorator
2. Using the `mcp.add_tool` method
#### `@mcp.tool` decorator
The `@mcp.tool` decorator registers a function as a tool that can be used by an LLM.
The decorator accepts the following optional arguments:
- `name` (optional `str`): The name of the tool. If not provided, the function's `__name__` will be used.
- `description` (optional `str`): A description of what the tool does. If not provided, it will be extracted from the function's docstring.
- `input_schema` (optional `dict`): The JSON schema for the tool's input. If not provided, it will be inferred from the function's signature and docstring.
- `use_entire_docstring` (optional `bool`): If `True`, the entire docstring will be used as the tool's description. Otherwise, only the first section is used (i.e. no `Args`). Defaults to `False`.
- `annotations` (optional `dict`): Additional context about the tool, such as validation information or examples of how to use it. This should be a dictionary conforming to the `ToolAnnotations` `TypedDict` structure.
**Example:**
```python
from frappe_mcp import ToolAnnotations, MCP
mcp = MCP()
annotations = ToolAnnotations(
title="Get Current Weather",
readOnlyHint=True,
)
@mcp.tool(annotations=annotations)
def get_current_weather(location: str, unit: str = "celsius"):
'''Get the current weather in a given location.'''
# ... implementation ...
```
#### `mcp.add_tool` method
The `mcp.add_tool` method allows manually defining a tool, serving as an alternative to the `@mcp.tool` decorator.
It takes a `Tool` object as an arg.
**Example:**
```python
from frappe_mcp import Tool, MCP
mcp = MCP()
def get_current_weather(location: str, unit: str = "celsius"):
'''Get the current weather in a given location.'''
# ... implementation ...
# Create a tool object
weather_tool = Tool(
name="get_current_weather",
description="...",
input_schema={'type':'object', 'properties':{ ... }},
output_schema=None,
annotations=None,
fn=get_current_weather,
)
# Add the tool to the MCP instance
mcp.add_tool(weather_tool)
```
#### Tool Annotations
The `ToolAnnotations` can be used to provide additional tool annotations
defined by the MCP spec
([reference](https://modelcontextprotocol.io/docs/concepts/tools#tool-annotations)).
```python
class ToolAnnotations(TypedDict, total=False):
title: str | None
readOnlyHint: bool | None
destructiveHint: bool | None
idempotentHint: bool | None
openWorldHint: bool | None
```
#### Tool Definition
The `Tool` object that is used when manually defining and registering a tool
using `mcp.add_tool`.
```python
class Tool(TypedDict):
name: str
description: str
input_schema: dict[str, Any]
output_schema: dict[str, Any] | None
annotations: ToolAnnotations | None
fn: Callable
```
#### Input Schema
Input schema refers to the [JSON Schema](https://json-schema.org/understanding-json-schema/reference/type) definition that describes a tool's parameters.
The following tool:
```python
@mcp.tool()
def tool_name(a: int, b: str = "default"):
"""Description of what the tool does
Args:
a: Description for arg `a`.
b: Description for arg `b`.
"""
... # tool body
return value
```
will have this input schema:
```json
{
"type": "object",
"properties": {
"a": {
"type": "integer",
"description": "Description for arg `a`."
},
"b": {
"type": "string",
"description": "Description for arg `b`."
}
},
"required": ["a"]
}
```
This input schema is generated from the tool body automatically when using the
decorator.
### MCP
The `MCP` class is the main class for creating an MCP server.
This class orchestrates the handling of JSON-RPC requests, manages a registry of
available tools, and integrates with a WSGI server (like Frappe Framework) to
expose MCP functionality.
In a Frappe application, you would typically create a single instance of this
class and use the `@mcp.register()` decorator on an API endpoint. Tools can be
added using the `@mcp.tool()` decorator.
For use in other Werkzeug-based servers, you can use the `mcp.handle()` method
directly.
#### `mcp.register` decorator
This decorator is used in Frappe applications to designate a function as the
entry point for MCP requests. It wraps the function with the necessary logic to
handle JSON-RPC messages, including initializing the tool registry and routing
requests to the appropriate handlers.
The decorator accepts the following optional arguments:
- `allow_guest` (optional `bool`): If `True`, allows unauthenticated access to the endpoint. Defaults to `False`.
- `xss_safe` (optional `bool`): If `True`, response will not be sanitized for XSS. Defaults to `False`.
**Example:**
```python
# In app/mcp.py
from frappe_mcp import MCP
mcp = MCP(name="my-mcp-server")
@mcp.register()
def handle_mcp():
'''The entry point for MCP requests.'''
# This function body is executed before request handling.
# It's a good place to import modules that register tools.
import app.tools
```
#### `mcp.handle` method
This method directly processes a `werkzeug.Request` and returns a
`werkzeug.Response`. It's the core request handling logic.
This method can be used to integrate the MCP server into **any Werkzeug-based application**
i.e. even if you're not using Frappe Framework, you can use this to handle MCP
endpoints in your server.
It accepts the following arguments:
- `request`: The `werkzeug.Request` object containing the MCP request.
- `response`: A `werkzeug.Response` object to be populated with the MCP response.
It returns the populated `werkzeug.Response` object.
## CLI
Frappe MCP comes with a handy CLI tool to help you verify that your MCP server is set up correctly.
<img width="436" alt="check" src="https://github.com/user-attachments/assets/a8e1481a-5388-4976-9728-404677381a07" />
Its `check` command inspects your Frappe apps to ensure that `frappe_mcp` is being used correctly. This is also the default command, so you can run it with `frappe-mcp` or `frappe-mcp check`.
It performs the following checks:
- Verifies that it's running within a Frappe environment.
- Finds all apps that are potentially using `frappe_mcp`.
- For each app, it discovers MCP handlers.
- It then checks the handlers and their tools for correctness.
**Options:**
- `--app`, `-a`': Check only a specific app.
- `--verbose`, `-v`: Show detailed information such as the input schema.
**Usage:**
```bash
# After installing frappe-mcp and using it in your app
# In your frappe bench dir so that you can use the cli
source ./env/bin/activate
# Check all apps that might be using Frappe MCP
frappe-mcp
# Check specific app with verbose output
frappe-mcp check --app app_name --verbose
```
## Testing against Inspector
You can use the official
[inspector](https://github.com/modelcontextprotocol/inspector) tool to verify if
your MCP endpoints are being served correctly.

Make sure to:
1. Set **Transport** to **Streamable HTTP**.
2. Set **URL** to your MCP endpoint (you can use the CLI command `frappe-mcp check` to get it).
3. Navigate to Auth Settings then click on **Quick OAuth Flow**
After this you'll be prompted to login and authorize the client after which you
can use it to test out your MCP server.
> [!NOTE]
>
> You may skip the final step by setting the `allow_guests` flag, i.e:
>
> ```python
> @mcp.register(allow_guests=True)
> def handle_mcp(): ...
> ```
>
> **This bypasses auth, so make sure you don't do this in production.**
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
firecrawl
Firecrawl MCP Server enables web scraping, crawling, and content extraction.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers