#
tokens: 9436/50000 18/18 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── _assets
│   ├── add_endpoint.png
│   ├── add_tool_list.png
│   ├── cherry_studio_mcp_sse.png
│   ├── cherry_studio_mcp_streamable_http.png
│   ├── edit_tool.png
│   ├── icon.svg
│   ├── install_plugin_via_github.png
│   ├── mcp_sse_url.png
│   ├── mcp_streamable_http_url.png
│   ├── mcp_urls.png
│   └── save_endpoint.png
├── .difyignore
├── .env.example
├── .github
│   └── ISSUE_TEMPLATE
│       ├── bug_report.yml
│       ├── config.yml
│       └── feature_request.yml
├── .gitignore
├── endpoints
│   ├── mcp_get.py
│   ├── mcp_get.yaml
│   ├── mcp_post.py
│   ├── mcp_post.yaml
│   ├── messages.py
│   ├── messages.yaml
│   ├── sse.py
│   └── sse.yaml
├── group
│   └── mcp_compat_dify_tools.yaml
├── GUIDE.md
├── main.py
├── manifest.yaml
├── PRIVACY.md
├── README.md
└── requirements.txt
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
INSTALL_METHOD=remote
REMOTE_INSTALL_HOST=debug.dify.ai
REMOTE_INSTALL_PORT=5003
REMOTE_INSTALL_KEY=********-****-****-****-************

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Vscode
.vscode/

```

--------------------------------------------------------------------------------
/.difyignore:
--------------------------------------------------------------------------------

```
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Vscode
.vscode/

# Git
.git/
.gitignore

# Mac
.DS_Store

# Windows
Thumbs.db

```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
dify_plugin>=0.2.1,<0.3.0
```

--------------------------------------------------------------------------------
/endpoints/sse.yaml:
--------------------------------------------------------------------------------

```yaml
path: "/sse"
method: "GET"
extra:
  python:
    source: "endpoints/sse.py"

```

--------------------------------------------------------------------------------
/endpoints/mcp_get.yaml:
--------------------------------------------------------------------------------

```yaml
path: "/mcp"
method: "GET"
extra:
  python:
    source: "endpoints/mcp_get.py"

```

--------------------------------------------------------------------------------
/endpoints/mcp_post.yaml:
--------------------------------------------------------------------------------

```yaml
path: "/mcp"
method: "POST"
extra:
  python:
    source: "endpoints/mcp_post.py"

```

--------------------------------------------------------------------------------
/endpoints/messages.yaml:
--------------------------------------------------------------------------------

```yaml
path: "/messages/"
method: "POST"
extra:
  python:
    source: "endpoints/messages.py"

```

--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------

```python
from dify_plugin import Plugin, DifyPluginEnv

plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))

if __name__ == '__main__':
    plugin.run()

```

--------------------------------------------------------------------------------
/group/mcp_compat_dify_tools.yaml:
--------------------------------------------------------------------------------

```yaml
settings:
  - name: tools
    type: array[tools]
    required: true
    label:
      en_US: Tool list
      zh_Hans: 工具列表
endpoints:
  - endpoints/sse.yaml
  - endpoints/messages.yaml
  - endpoints/mcp_get.yaml
  - endpoints/mcp_post.yaml
```

--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------

```markdown
# Privacy Policy

This tool is designed with privacy in mind and does not collect any user data. We are committed to maintaining your privacy and ensuring your data remains secure.

## Data Collection

- **No Personal Information**: We do not collect, store, or process any personal information.
- **No Usage Data**: We do not track or monitor how you use the tool.
- **No Analytics**: We do not implement any analytics or tracking mechanisms.

## Third-Party Services

This tool does not integrate with or utilize any third-party services that might collect user data.

## Changes to Privacy Policy

If there are any changes to our privacy practices, we will update this document accordingly.

```

--------------------------------------------------------------------------------
/endpoints/mcp_get.py:
--------------------------------------------------------------------------------

```python
from typing import Mapping

from dify_plugin import Endpoint
from werkzeug import Request, Response


class McpGetEndpoint(Endpoint):
    def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
        """
        Streamable HTTP in Dify is a lightweight design,
        it only supported POST and don't support Server-Sent Events (SSE).
        """
        response = {
            "jsonrpc": "2.0",
            "id": None,
            "error": {
                "code": -32000,
                "message": "Not support make use of Server-Sent Events (SSE) to stream multiple server messages."
            },
        }

        return Response(response, status=405, content_type="application/json")

```

--------------------------------------------------------------------------------
/manifest.yaml:
--------------------------------------------------------------------------------

```yaml
version: 0.1.1
type: plugin
author: junjiem
name: mcp_compat_dify_tools
label:
  en_US: MCP Compatible Dify Tools
  zh_Hans: MCP Compatible Dify Tools
description:
  en_US: "Convert your Dify tools's API to MCP compatible API (Note: must dify 1.2.0+)"
  zh_Hans: 将您的Dify工具的API转换为MCP兼容API(注:必须 dify 1.2.0+)
icon: icon.svg
resource:
  memory: 268435456
  permission:
    endpoint:
      enabled: true
    tool:
      enabled: true
    storage:
      enabled: true
      size: 1048576
plugins:
  endpoints:
    - group/mcp_compat_dify_tools.yaml
meta:
  version: 0.0.1
  arch:
    - amd64
    - arm64
  runner:
    language: python
    version: "3.12"
    entrypoint: main
created_at: 2025-04-11T15:35:09.8828922+08:00
privacy: PRIVACY.md
verified: false

```

--------------------------------------------------------------------------------
/endpoints/sse.py:
--------------------------------------------------------------------------------

```python
import json
import time
import uuid
from typing import Mapping

from dify_plugin import Endpoint
from werkzeug import Request, Response


def create_sse_message(event, data):
    return f"event: {event}\ndata: {json.dumps(data) if isinstance(data, (dict, list)) else data}\n\n"


class SSEEndpoint(Endpoint):
    def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
        """
        Invokes the endpoint with the given request.
        """
        session_id = str(uuid.uuid4()).replace("-", "")

        def generate():
            endpoint = f"messages/?session_id={session_id}"
            yield create_sse_message("endpoint", endpoint)

            while True:
                if self.session.storage.exist(session_id):
                    message = self.session.storage.get(session_id)
                    message = message.decode()
                    self.session.storage.delete(session_id)
                    yield create_sse_message("message", message)
                time.sleep(0.5)

        return Response(generate(), status=200, content_type="text/event-stream")

```

--------------------------------------------------------------------------------
/_assets/icon.svg:
--------------------------------------------------------------------------------

```
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1744362742132" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8998"
     width="48" height="48">
    <path d="M171.84 477.184l296.192-296.128q39.808-39.808 96.064-39.808 56.32 0 96.128 39.808t39.808 96.064q0 7.488-0.704 14.72 6.848-0.64 13.888-0.64 57.152 0 97.536 40.32 40.32 40.448 40.32 97.536t-40.32 97.472l-269.696 269.696q-4.928 4.928 0 9.856l57.152 57.152a30.464 30.464 0 0 1-43.072 43.072l-57.152-57.152q-19.84-19.84-19.84-48t19.84-48l269.696-269.696q22.528-22.528 22.528-54.4t-22.528-54.4q-22.592-22.592-54.4-22.592-31.936 0-54.464 22.592l-17.28 17.216L435.2 598.336a30.464 30.464 0 0 1-43.008-0.064l-0.192-0.128a30.272 30.272 0 0 1 0.128-42.88L599.68 347.52l17.408-17.408q21.952-22.016 21.952-53.056t-21.952-52.992q-22.016-22.016-53.056-22.016t-52.992 22.016L214.912 520.256a30.464 30.464 0 0 1-43.072-43.072z m567.104-29.44L513.92 672.96q-39.808 39.808-96.128 39.808t-96.128-39.808q-39.808-39.808-39.808-96.128t39.808-96.128l225.088-225.024a30.464 30.464 0 0 1 43.008 43.008L364.8 523.712q-21.952 22.016-21.952 53.056t21.952 53.056q22.016 21.952 53.056 21.952t52.992-21.952l225.088-225.088a30.464 30.464 0 0 1 43.072 43.072z"
          p-id="8999" fill="#1296db"></path>
</svg>
```

--------------------------------------------------------------------------------
/GUIDE.md:
--------------------------------------------------------------------------------

```markdown
## User Guide of how to develop a Dify Plugin

Hi there, looks like you have already created a Plugin, now let's get you started with the development!

### Choose a Plugin type you want to develop

Before start, you need some basic knowledge about the Plugin types, Plugin supports to extend the following abilities in Dify:
- **Tool**: Tool Providers like Google Search, Stable Diffusion, etc. it can be used to perform a specific task.
- **Model**: Model Providers like OpenAI, Anthropic, etc. you can use their models to enhance the AI capabilities.
- **Endpoint**: Like Service API in Dify and Ingress in Kubernetes, you can extend a http service as an endpoint and control its logics using your own code.

Based on the ability you want to extend, we have divided the Plugin into three types: **Tool**, **Model**, and **Extension**.

- **Tool**: It's a tool provider, but not only limited to tools, you can implement an endpoint there, for example, you need both `Sending Message` and `Receiving Message` if you are building a Discord Bot, **Tool** and **Endpoint** are both required.
- **Model**: Just a model provider, extending others is not allowed.
- **Extension**: Other times, you may only need a simple http service to extend the functionalities, **Extension** is the right choice for you.

I believe you have chosen the right type for your Plugin while creating it, if not, you can change it later by modifying the `manifest.yaml` file.

### Manifest

Now you can edit the `manifest.yaml` file to describe your Plugin, here is the basic structure of it:

- version(version, required):Plugin's version
- type(type, required):Plugin's type, currently only supports `plugin`, future support `bundle`
- author(string, required):Author, it's the organization name in Marketplace and should also equals to the owner of the repository
- label(label, required):Multi-language name
- created_at(RFC3339, required):Creation time, Marketplace requires that the creation time must be less than the current time
- icon(asset, required):Icon path
- resource (object):Resources to be applied
  - memory (int64):Maximum memory usage, mainly related to resource application on SaaS for serverless, unit bytes
  - permission(object):Permission application
    - tool(object):Reverse call tool permission
      - enabled (bool)
    - model(object):Reverse call model permission
      - enabled(bool)
      - llm(bool)
      - text_embedding(bool)
      - rerank(bool)
      - tts(bool)
      - speech2text(bool)
      - moderation(bool)
    - node(object):Reverse call node permission
      - enabled(bool) 
    - endpoint(object):Allow to register endpoint permission
      - enabled(bool)
    - app(object):Reverse call app permission
      - enabled(bool)
    - storage(object):Apply for persistent storage permission
      - enabled(bool)
      - size(int64):Maximum allowed persistent memory, unit bytes
- plugins(object, required):Plugin extension specific ability yaml file list, absolute path in the plugin package, if you need to extend the model, you need to define a file like openai.yaml, and fill in the path here, and the file on the path must exist, otherwise the packaging will fail.
  - Format
    - tools(list[string]): Extended tool suppliers, as for the detailed format, please refer to [Tool Guide](https://docs.dify.ai/plugins/schema-definition/tool)
    - models(list[string]):Extended model suppliers, as for the detailed format, please refer to [Model Guide](https://docs.dify.ai/plugins/schema-definition/model)
    - endpoints(list[string]):Extended Endpoints suppliers, as for the detailed format, please refer to [Endpoint Guide](https://docs.dify.ai/plugins/schema-definition/endpoint)
  - Restrictions
    - Not allowed to extend both tools and models
    - Not allowed to have no extension
    - Not allowed to extend both models and endpoints
    - Currently only supports up to one supplier of each type of extension
- meta(object)
  - version(version, required):manifest format version, initial version 0.0.1
  - arch(list[string], required):Supported architectures, currently only supports amd64 arm64
  - runner(object, required):Runtime configuration
    - language(string):Currently only supports python
    - version(string):Language version, currently only supports 3.12
    - entrypoint(string):Program entry, in python it should be main

### Install Dependencies

- First of all, you need a Python 3.11+ environment, as our SDK requires that.
- Then, install the dependencies:
    ```bash
    pip install -r requirements.txt
    ```
- If you want to add more dependencies, you can add them to the `requirements.txt` file, once you have set the runner to python in the `manifest.yaml` file, `requirements.txt` will be automatically generated and used for packaging and deployment.

### Implement the Plugin

Now you can start to implement your Plugin, by following these examples, you can quickly understand how to implement your own Plugin:

- [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai): best practice for model provider
- [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google): a simple example for tool provider
- [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko): a funny example for endpoint group

### Test and Debug the Plugin

You may already noticed that a `.env.example` file in the root directory of your Plugin, just copy it to `.env` and fill in the corresponding values, there are some environment variables you need to set if you want to debug your Plugin locally.

- `INSTALL_METHOD`: Set this to `remote`, your plugin will connect to a Dify instance through the network.
- `REMOTE_INSTALL_HOST`: The host of your Dify instance, you can use our SaaS instance `https://debug.dify.ai`, or self-hosted Dify instance.
- `REMOTE_INSTALL_PORT`: The port of your Dify instance, default is 5003
- `REMOTE_INSTALL_KEY`: You should get your debugging key from the Dify instance you used, at the right top of the plugin management page, you can see a button with a `debug` icon, click it and you will get the key.

Run the following command to start your Plugin:

```bash
python -m main
```

Refresh the page of your Dify instance, you should be able to see your Plugin in the list now, but it will be marked as `debugging`, you can use it normally, but not recommended for production.

### Package the Plugin

After all, just package your Plugin by running the following command:

```bash
dify-plugin plugin package ./ROOT_DIRECTORY_OF_YOUR_PLUGIN
```

you will get a `plugin.difypkg` file, that's all, you can submit it to the Marketplace now, look forward to your Plugin being listed!


## User Privacy Policy

Please fill in the privacy policy of the plugin if you want to make it published on the Marketplace, refer to [PRIVACY.md](PRIVACY.md) for more details.
```

--------------------------------------------------------------------------------
/endpoints/messages.py:
--------------------------------------------------------------------------------

```python
import json
import logging
from typing import Mapping, cast, Any

from dify_plugin import Endpoint
from dify_plugin.entities import I18nObject
from dify_plugin.entities.tool import ToolParameter, ToolProviderType, ToolInvokeMessage, ToolDescription
from dify_plugin.interfaces.agent import ToolEntity, AgentToolIdentity
from pydantic import BaseModel
from werkzeug import Request, Response


class EndpointParams(BaseModel):
    tools: list[ToolEntity] | None


class MessageEndpoint(Endpoint):
    def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
        """
        Invokes the endpoint with the given request.
        """

        session_id = r.args.get('session_id')
        data = r.json
        method = data.get("method")

        print("===============tools==============")
        print(settings.get("tools"))

        if method == "initialize":
            response = {
                "jsonrpc": "2.0",
                "id": data.get("id"),
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {
                        "experimental": {},
                        "prompts": {"listChanged": False},
                        "resources": {
                            "subscribe": False,
                            "listChanged": False
                        },
                        "tools": {"listChanged": False}
                    },
                    "serverInfo": {
                        "name": "MCP Compatible Dify Tools",
                        "version": "1.0.0"
                    }
                }
            }

        elif method == "notifications/initialized":
            return Response("", status=202, content_type="application/json")

        elif method == "tools/list":
            try:
                tools: list[ToolEntity] = self._init_tools(settings.get("tools"))

                mcp_tools = self._init_mcp_tools(tools)

                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "result": {
                        "tools": mcp_tools
                    }
                }
            except Exception as e:
                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "error": {
                        "code": -32000,
                        "message": str(e)
                    }
                }
        elif method == "tools/call":
            try:
                tools: list[ToolEntity] = self._init_tools(settings.get("tools"))
                tool_instances = {tool.identity.name: tool for tool in tools} if tools else {}

                tool_name = data.get("params", {}).get("name")
                arguments = data.get("params", {}).get("arguments", {})

                tool_instance = tool_instances.get(tool_name)
                if tool_instance:
                    result = self._invoke_tool(tool_instance, arguments)
                else:
                    raise ValueError(f"Unknown tool: {tool_name}")

                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "result": {
                        "content": [{"type": "text", "text": result}],
                        "isError": False
                    }
                }
            except Exception as e:
                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "error": {
                        "code": -32000,
                        "message": str(e)
                    }
                }
        else:
            response = {
                "jsonrpc": "2.0",
                "id": data.get("id"),
                "error": {
                    "code": -32001,
                    "message": f"Unsupported method: {method}"
                }
            }

        self.session.storage.set(session_id, json.dumps(response).encode())
        return Response("", status=202, content_type="application/json")

    def _init_tools(self, tools_param_value) -> list[ToolEntity]:
        """
        init ToolEntity list
        """

        result: list[ToolEntity] = []

        value = cast(list[dict[str, Any]], tools_param_value)
        value = [tool for tool in value if tool.get("enabled", False)]

        for tool in value:
            tool_type = tool["type"]
            tool_name = tool["tool_name"]
            tool_label = tool["tool_label"]
            tool_description = tool.get("tool_description", None)
            extra_description = tool.get("extra", {}).get("description", None)
            provider_name = tool["provider_name"]
            schemas = tool.get("schemas", [])
            settings = tool.get("settings", {})

            identity = AgentToolIdentity(
                author="Dify",
                name=tool_name,
                label=I18nObject(en_US=tool_label),
                provider=provider_name,
            )

            llm_description = (
                extra_description
                if extra_description else tool_description
                if tool_description else tool_label
            )
            description = ToolDescription(
                human=I18nObject(en_US=llm_description),
                llm=llm_description,
            )

            provider_type = ToolProviderType.BUILT_IN
            if tool_type == "api":
                provider_type = ToolProviderType.API
            elif tool_type == "workflow":
                provider_type = ToolProviderType.WORKFLOW

            parameters = []
            for schema in schemas:
                parameters.append(ToolParameter(**schema))

            runtime_parameters = {}
            for parameter_name, parameter_value in settings.items():
                runtime_parameters[parameter_name] = parameter_value.get("value")

            tool_entity = ToolEntity(
                identity=identity,
                parameters=parameters,
                description=description,
                provider_type=provider_type,
                runtime_parameters=runtime_parameters,
            )

            result.append(tool_entity)

        return result

    def _init_mcp_tools(self, tools: list[ToolEntity] | None) -> list[dict]:
        """
        Init mcp tools
        """

        mcp_tools = []
        for tool in tools or []:
            try:
                mcp_tool = self._convert_tool_to_mcp_tool(tool)
            except Exception:
                logging.exception("Failed to convert Dify tool to MCP tool")
                continue

            mcp_tools.append(mcp_tool)

        return mcp_tools

    def _convert_tool_to_mcp_tool(self, tool: ToolEntity) -> dict:
        """
        convert tool to prompt message tool
        """
        mcp_tool = {
            "name": tool.identity.name,
            "description": tool.description.llm if tool.description else "",
            "inputSchema": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }

        parameters = tool.parameters
        for parameter in parameters:
            if parameter.form != ToolParameter.ToolParameterForm.LLM:
                continue

            parameter_type = parameter.type
            if parameter.type in {
                ToolParameter.ToolParameterType.FILE,
                ToolParameter.ToolParameterType.FILES,
            }:
                continue
            enum = []
            if parameter.type == ToolParameter.ToolParameterType.SELECT:
                enum = [option.value for option in parameter.options] if parameter.options else []

            mcp_tool["inputSchema"]["properties"][parameter.name] = {
                "type": parameter_type,
                "description": parameter.llm_description or "",
            }

            if len(enum) > 0:
                mcp_tool["inputSchema"]["properties"][parameter.name]["enum"] = enum

            if parameter.required:
                mcp_tool["inputSchema"]["required"].append(parameter.name)

        return mcp_tool

    def _invoke_tool(self, tool: ToolEntity, tool_call_args) -> str:
        """
        invoke tool
        """

        tool_invoke_responses = self.session.tool.invoke(
            provider_type=ToolProviderType(tool.provider_type),
            provider=tool.identity.provider,
            tool_name=tool.identity.name,
            parameters={**tool.runtime_parameters, **tool_call_args},
        )

        result = ""
        for response in tool_invoke_responses:
            if response.type == ToolInvokeMessage.MessageType.TEXT:
                result += cast(ToolInvokeMessage.TextMessage, response.message).text
            elif response.type == ToolInvokeMessage.MessageType.LINK:
                result += (
                        f"result link: {cast(ToolInvokeMessage.TextMessage, response.message).text}."
                        + " please tell user to check it."
                )
            elif response.type in {
                ToolInvokeMessage.MessageType.IMAGE_LINK,
                ToolInvokeMessage.MessageType.IMAGE,
            }:
                result += f"Not support message type: {response.type}."
            elif response.type == ToolInvokeMessage.MessageType.JSON:
                text = json.dumps(
                    cast(ToolInvokeMessage.JsonMessage, response.message).json_object,
                    ensure_ascii=False,
                )
                result += f"tool response: {text}."
            else:
                result += f"tool response: {response.message!r}."

        return result

```

--------------------------------------------------------------------------------
/endpoints/mcp_post.py:
--------------------------------------------------------------------------------

```python
import json
import logging
import uuid
from typing import Mapping, cast, Any

from dify_plugin import Endpoint
from dify_plugin.entities import I18nObject
from dify_plugin.entities.tool import ToolParameter, ToolProviderType, ToolInvokeMessage, ToolDescription
from dify_plugin.interfaces.agent import ToolEntity, AgentToolIdentity
from pydantic import BaseModel
from werkzeug import Request, Response


class EndpointParams(BaseModel):
    tools: list[ToolEntity] | None


class McpPostEndpoint(Endpoint):
    def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
        """
        The simplest MCP Streamable HTTP transport implementation.

        1. not validate the `Origin` header
        2. not authentication
        3. not valid session id
        4. not support Server-Sent Events (SSE)
        """

        session_id = r.args.get('session_id')
        data = r.json
        method = data.get("method")

        print("===============tools==============")
        print(settings.get("tools"))

        if method == "initialize":
            session_id = str(uuid.uuid4()).replace("-", "")
            response = {
                "jsonrpc": "2.0",
                "id": data.get("id"),
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {
                        "tools": {},
                    },
                    "serverInfo": {
                        "name": "MCP Compatible Dify Tools",
                        "version": "1.0.0"
                    },
                },
            }
            headers = {"mcp-session-id": session_id}
            return Response(
                json.dumps(response),
                status=200,
                content_type="application/json",
                headers=headers,
            )
        elif method == "notifications/initialized":
            return Response("", status=202, content_type="application/json")

        elif method == "tools/list":
            try:
                tools: list[ToolEntity] = self._init_tools(settings.get("tools"))

                mcp_tools = self._init_mcp_tools(tools)

                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "result": {
                        "tools": mcp_tools
                    }
                }
            except Exception as e:
                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "error": {
                        "code": -32000,
                        "message": str(e)
                    }
                }
        elif method == "tools/call":
            try:
                tools: list[ToolEntity] = self._init_tools(settings.get("tools"))
                tool_instances = {tool.identity.name: tool for tool in tools} if tools else {}

                tool_name = data.get("params", {}).get("name")
                arguments = data.get("params", {}).get("arguments", {})

                tool_instance = tool_instances.get(tool_name)
                if tool_instance:
                    result = self._invoke_tool(tool_instance, arguments)
                else:
                    raise ValueError(f"Unknown tool: {tool_name}")

                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "result": {
                        "content": [{"type": "text", "text": result}],
                        "isError": False
                    }
                }
            except Exception as e:
                response = {
                    "jsonrpc": "2.0",
                    "id": data.get("id"),
                    "error": {
                        "code": -32000,
                        "message": str(e)
                    }
                }
        else:
            response = {
                "jsonrpc": "2.0",
                "id": data.get("id"),
                "error": {
                    "code": -32001,
                    "message": f"Unsupported method: {method}"
                }
            }

        return Response(
            json.dumps(response), status=200, content_type="application/json"
        )

    def _init_tools(self, tools_param_value) -> list[ToolEntity]:
        """
        init ToolEntity list
        """

        result: list[ToolEntity] = []

        value = cast(list[dict[str, Any]], tools_param_value)
        value = [tool for tool in value if tool.get("enabled", False)]

        for tool in value:
            tool_type = tool["type"]
            tool_name = tool["tool_name"]
            tool_label = tool["tool_label"]
            tool_description = tool.get("tool_description", None)
            extra_description = tool.get("extra", {}).get("description", None)
            provider_name = tool["provider_name"]
            schemas = tool.get("schemas", [])
            settings = tool.get("settings", {})

            identity = AgentToolIdentity(
                author="Dify",
                name=tool_name,
                label=I18nObject(en_US=tool_label),
                provider=provider_name,
            )

            llm_description = (
                extra_description
                if extra_description else tool_description
                if tool_description else tool_label
            )
            description = ToolDescription(
                human=I18nObject(en_US=llm_description),
                llm=llm_description,
            )

            provider_type = ToolProviderType.BUILT_IN
            if tool_type == "api":
                provider_type = ToolProviderType.API
            elif tool_type == "workflow":
                provider_type = ToolProviderType.WORKFLOW

            parameters = []
            for schema in schemas:
                parameters.append(ToolParameter(**schema))

            runtime_parameters = {}
            for parameter_name, parameter_value in settings.items():
                runtime_parameters[parameter_name] = parameter_value.get("value")

            tool_entity = ToolEntity(
                identity=identity,
                parameters=parameters,
                description=description,
                provider_type=provider_type,
                runtime_parameters=runtime_parameters,
            )

            result.append(tool_entity)

        return result

    def _init_mcp_tools(self, tools: list[ToolEntity] | None) -> list[dict]:
        """
        Init mcp tools
        """

        mcp_tools = []
        for tool in tools or []:
            try:
                mcp_tool = self._convert_tool_to_mcp_tool(tool)
            except Exception:
                logging.exception("Failed to convert Dify tool to MCP tool")
                continue

            mcp_tools.append(mcp_tool)

        return mcp_tools

    def _convert_tool_to_mcp_tool(self, tool: ToolEntity) -> dict:
        """
        convert tool to prompt message tool
        """
        mcp_tool = {
            "name": tool.identity.name,
            "description": tool.description.llm if tool.description else "",
            "inputSchema": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }

        parameters = tool.parameters
        for parameter in parameters:
            if parameter.form != ToolParameter.ToolParameterForm.LLM:
                continue

            parameter_type = parameter.type
            if parameter.type in {
                ToolParameter.ToolParameterType.FILE,
                ToolParameter.ToolParameterType.FILES,
            }:
                continue
            enum = []
            if parameter.type == ToolParameter.ToolParameterType.SELECT:
                enum = [option.value for option in parameter.options] if parameter.options else []

            mcp_tool["inputSchema"]["properties"][parameter.name] = {
                "type": parameter_type,
                "description": parameter.llm_description or "",
            }

            if len(enum) > 0:
                mcp_tool["inputSchema"]["properties"][parameter.name]["enum"] = enum

            if parameter.required:
                mcp_tool["inputSchema"]["required"].append(parameter.name)

        return mcp_tool

    def _invoke_tool(self, tool: ToolEntity, tool_call_args) -> str:
        """
        invoke tool
        """

        tool_invoke_responses = self.session.tool.invoke(
            provider_type=ToolProviderType(tool.provider_type),
            provider=tool.identity.provider,
            tool_name=tool.identity.name,
            parameters={**tool.runtime_parameters, **tool_call_args},
        )

        result = ""
        for response in tool_invoke_responses:
            if response.type == ToolInvokeMessage.MessageType.TEXT:
                result += cast(ToolInvokeMessage.TextMessage, response.message).text
            elif response.type == ToolInvokeMessage.MessageType.LINK:
                result += (
                        f"result link: {cast(ToolInvokeMessage.TextMessage, response.message).text}."
                        + " please tell user to check it."
                )
            elif response.type in {
                ToolInvokeMessage.MessageType.IMAGE_LINK,
                ToolInvokeMessage.MessageType.IMAGE,
            }:
                result += f"Not support message type: {response.type}."
            elif response.type == ToolInvokeMessage.MessageType.JSON:
                text = json.dumps(
                    cast(ToolInvokeMessage.JsonMessage, response.message).json_object,
                    ensure_ascii=False,
                )
                result += f"tool response: {text}."
            else:
                result += f"tool response: {response.message!r}."

        return result

```