frappe

Frappe MCP

Community frappe
Updated

Frappe MCP allows Frappe apps to function as MCP servers

Frappe MCP

Frappe MCP allows your Frappe Framework app to function as a Streamable HTTP MCPserver.

# 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 yourserver is an ASGI server.

Frappe Framework is not async, it makes use of Werkzeug, a WSGI server, and soa from scratch implementation was needed.

[!IMPORTANT]

Frappe MCP is in a highly experimental state, there will be bugs, breakingchanges and large updates. Mostly without notice.

On GitHub, click the Index button on the top right to view the index.

Installation

Using PIP:

pip install frappe-mcp

Using UV:

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 SSEwill be added as needed.

Auth

If you are using a version of the Framework having the OAuth2 updates(frappe#33188) then using FrappeMCP with it should be pretty straight forward. You can view this video to checkout 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 anOAuth Client on your Framework instance for the MCP client. You can check thedocs for this.

Documentation

Frappe MCP is fairly straightforward to use. Most of the MCP specific heavylifting is handled for you.

Basic Usage

To use frappe-mcp you first create an instance of the mcp object:

# 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 MCPendpoints for instance to group functionality.

Register tools with @mcp.tool

You use the instaniated object i.e. mcp to register tools:

# 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 aboveallows Frappe MCP to extract the inputSchema for the tool without any additionalconfiguration.

If needed, you can manually provide the inputSchema and other meta data like annotations.Check the Tools section for more details.

Register endpoint using @mcp.register

You use the instantiated object to mark a function as the entry point to yourMCP server, i.e. the function endpoint will be where your MCP server is servedfrom.

# 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 forthe method (docs).

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 whereyour tools are located.

If your tools are in the same file, or have been imported globally, you canleave 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:

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:

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 annotationsdefined by the MCP spec(reference).

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 toolusing mcp.add_tool.

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 definition that describes a tool's parameters.

The following tool:

@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:

{
  "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 thedecorator.

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 ofavailable tools, and integrates with a WSGI server (like Frappe Framework) toexpose MCP functionality.

In a Frappe application, you would typically create a single instance of thisclass and use the @mcp.register() decorator on an API endpoint. Tools can beadded using the @mcp.tool() decorator.

For use in other Werkzeug-based servers, you can use the mcp.handle() methoddirectly.

mcp.register decorator

This decorator is used in Frappe applications to designate a function as theentry point for MCP requests. It wraps the function with the necessary logic tohandle JSON-RPC messages, including initializing the tool registry and routingrequests 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:

# 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 awerkzeug.Response. It's the core request handling logic.

This method can be used to integrate the MCP server into any Werkzeug-based applicationi.e. even if you're not using Frappe Framework, you can use this to handle MCPendpoints 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.

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:

# 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 officialinspector tool to verify ifyour MCP endpoints are being served correctly.

inspector

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 youcan use it to test out your MCP server.

[!NOTE]

You may skip the final step by setting the allow_guests flag, i.e:

@mcp.register(allow_guests=True)
def handle_mcp(): ...

This bypasses auth, so make sure you don't do this in production.

MCP Server · Populars

MCP Server · New

    frappe

    Frappe MCP

    Frappe MCP allows Frappe apps to function as MCP servers

    Community frappe
    clay-good

    openlore

    openlore provides persistent architectural memory for AI coding agents by turning codebases into queryable knowledge graphs featuring static analysis, living specs, automated drift detection, and graph-native MCP tools to eliminate context decay and drastically slash orientation token costs.

    Community clay-good
    WJZ-P

    Gemini Skill

    gemini drawing MCP & skill through browser, can be used in openclaw or any agent that supports MCP. Gemini画图 MCP和sill,支持龙虾或任何agent使用٩(๑>◡<๑)۶

    Community WJZ-P
    btopn

    OpenInsider MCP

    MCP server that exposes live insider trading data to any LLM.

    Community btopn
    cygnusb

    coros-mcp

    MCP server for AI assistants to read and manage Coros fitness data: sleep, HRV, daily metrics, activities, and structured workouts via the unofficial Coros API

    Community cygnusb