mseep-dify-mcp-server 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: mseep-dify-mcp-server
3
+ Version: 0.1.1
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: httpx>=0.28.1
7
+ Requires-Dist: mcp>=1.1.2
8
+ Requires-Dist: omegaconf>=2.3.0
9
+ Requires-Dist: pip>=24.3.1
10
+ Requires-Dist: python-dotenv>=1.0.1
11
+ Requires-Dist: requests
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Model Context Protocol (MCP) Server for dify workflows
15
+ A simple implementation of an MCP server for using [dify](https://github.com/langgenius/dify). It achieves the invocation of the Dify workflow by calling the tools of MCP.
16
+ ## 📰 News
17
+ * [2025/4/15] zNow supports directly using environment variables to pass `base_url` and `app_sks`, making it more convenient to use with cloud-hosted platforms.
18
+
19
+
20
+ ## 🔨Installation
21
+ The server can be installed via [Smithery](https://smithery.ai/server/dify-mcp-server) or manually.
22
+
23
+ ### Step1: prepare config.yaml or enviroments
24
+ You can configure the server using either environment variables or a `config.yaml` file.
25
+
26
+ #### Method 1: Using Environment Variables (Recommended for Cloud Platforms)
27
+
28
+ Set the following environment variables:
29
+
30
+ ```shell
31
+ export DIFY_BASE_URL="https://cloud.dify.ai/v1"
32
+ export DIFY_APP_SKS="app-sk1,app-sk2" # Comma-separated list of your Dify App SKs
33
+ ```
34
+
35
+ * `DIFY_BASE_URL`: The base URL for your Dify API.
36
+ * `DIFY_APP_SKS`: A comma-separated list of your Dify App Secret Keys (SKs). Each SK typically corresponds to a different Dify workflow you want to make available via MCP.
37
+
38
+ #### Method 2: Using `config.yaml`
39
+
40
+ Create a `config.yaml` file to store your Dify base URL and App SKs.
41
+
42
+ Example `config.yaml`:
43
+
44
+ ```yaml
45
+ dify_base_url: "https://cloud.dify.ai/v1"
46
+ dify_app_sks:
47
+ - "app-sk1" # SK for workflow 1
48
+ - "app-sk2" # SK for workflow 2
49
+ # Add more SKs as needed
50
+ ```
51
+
52
+ * `dify_base_url`: The base URL for your Dify API.
53
+ * `dify_app_sks`: A list of your Dify App Secret Keys (SKs). Each SK typically corresponds to a different Dify workflow.
54
+
55
+ You can create this file quickly using the following command (adjust the path and values as needed):
56
+
57
+ ```bash
58
+ # Create a directory if it doesn't exist
59
+ mkdir -p ~/.config/dify-mcp-server
60
+
61
+ # Create the config file
62
+ cat > ~/.config/dify-mcp-server/config.yaml <<EOF
63
+ dify_base_url: "https://cloud.dify.ai/v1"
64
+ dify_app_sks:
65
+ - "app-your-sk-1"
66
+ - "app-your-sk-2"
67
+ EOF
68
+
69
+ echo "Configuration file created at ~/.config/dify-mcp-server/config.yaml"
70
+ ```
71
+
72
+ When running the server (as shown in Step 2), you will need to provide the path to this `config.yaml` file via the `CONFIG_PATH` environment variable if you choose this method.
73
+
74
+ ### Step2: Installation on your client
75
+ ❓ If you haven't installed uv or uvx yet, you can do it quickly with the following command:
76
+ ```
77
+ curl -Ls https://astral.sh/uv/install.sh | sh
78
+ ```
79
+
80
+ #### ✅ Method 1: Use uvx (no need to clone code, recommended)
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "dify-mcp-server": {
86
+ "command": "uvx",
87
+ "args": [
88
+ "--from","git+https://github.com/YanxingLiu/dify-mcp-server","dify_mcp_server"
89
+ ],
90
+ "env": {
91
+ "DIFY_BASE_URL": "https://cloud.dify.ai/v1",
92
+ "DIFY_APP_SKS": "app-sk1,app-sk2",
93
+ }
94
+ }
95
+ }
96
+ }
97
+ ```
98
+ or
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "dify-mcp-server": {
103
+ "command": "uvx",
104
+ "args": [
105
+ "--from","git+https://github.com/YanxingLiu/dify-mcp-server","dify_mcp_server"
106
+ ],
107
+ "env": {
108
+ "CONFIG_PATH": "/Users/lyx/Downloads/config.yaml"
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ #### ✅ Method 2: Use uv (local clone + uv start)
116
+
117
+ You can also run the dify mcp server manually in your clients. The config of client should like the following format:
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "mcp-server-rag-web-browser": {
122
+ "command": "uv",
123
+ "args": [
124
+ "--directory", "${DIFY_MCP_SERVER_PATH}",
125
+ "run", "dify_mcp_server"
126
+ ],
127
+ "env": {
128
+ "CONFIG_PATH": "$CONFIG_PATH"
129
+ }
130
+ }
131
+ }
132
+ }
133
+ ```
134
+ or
135
+ ```json
136
+ {
137
+ "mcpServers": {
138
+ "mcp-server-rag-web-browser": {
139
+ "command": "uv",
140
+ "args": [
141
+ "--directory", "${DIFY_MCP_SERVER_PATH}",
142
+ "run", "dify_mcp_server"
143
+ ],
144
+ "env": {
145
+ "CONFIG_PATH": "$CONFIG_PATH"
146
+ }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+ Example config:
152
+ ```json
153
+ {
154
+ "mcpServers": {
155
+ "dify-mcp-server": {
156
+ "command": "uv",
157
+ "args": [
158
+ "--directory", "/Users/lyx/Downloads/dify-mcp-server",
159
+ "run", "dify_mcp_server"
160
+ ],
161
+ "env": {
162
+ "DIFY_BASE_URL": "https://cloud.dify.ai/v1",
163
+ "DIFY_APP_SKS": "app-sk1,app-sk2",
164
+ }
165
+ }
166
+ }
167
+ }
168
+ ```
169
+ ### Enjoy it
170
+ At last, you can use dify tools in any client who supports mcp.
@@ -0,0 +1,6 @@
1
+ src/dify_mcp_server/__init__.py,sha256=kTuRDcmoAa4al59WMbwXx_TjJhMqmpzCMFCZ981BL1M,213
2
+ src/dify_mcp_server/server.py,sha256=pTdMPTmvK1uO6Ki062bVSp-VQ9cBbFO2WtMSfowGgzU,8966
3
+ mseep_dify_mcp_server-0.1.1.dist-info/METADATA,sha256=Oa23r-DEa2TddtWfAR45XxjzlXKJ7qMv6LTfPzo5EBQ,4519
4
+ mseep_dify_mcp_server-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ mseep_dify_mcp_server-0.1.1.dist-info/entry_points.txt,sha256=YughlZESvmM9v4WMtY4GAUzFWY-udx_CBKQUa3WXxeU,57
6
+ mseep_dify_mcp_server-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dify_mcp_server = dify_mcp_server:main
@@ -0,0 +1,9 @@
1
+ from . import server
2
+ import asyncio
3
+
4
+ def main():
5
+ """Main entry point for the package."""
6
+ asyncio.run(server.main())
7
+
8
+ # Optionally expose other important items at package level
9
+ __all__ = ['main', 'server']
@@ -0,0 +1,281 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ from abc import ABC
5
+
6
+ import mcp.server.stdio
7
+ import mcp.types as types
8
+ import requests
9
+ from mcp.server import NotificationOptions, Server
10
+ from mcp.server.models import InitializationOptions
11
+ from omegaconf import OmegaConf
12
+
13
+
14
+ def get_app_info():
15
+ config_path = os.getenv("CONFIG_PATH")
16
+ base_url = os.getenv("DIFY_BASE_URL")
17
+ dify_app_sks = os.getenv("DIFY_APP_SKS")
18
+ if config_path is not None:
19
+ print(f"Loading config from {config_path}")
20
+ config = OmegaConf.load(config_path)
21
+ dify_base_url = config.get('dify_base_url', "https://api.dify.ai/v1")
22
+ dify_app_sks = config.get('dify_app_sks', [])
23
+ return dify_base_url, dify_app_sks
24
+ elif base_url is not None and dify_app_sks is not None:
25
+ print(f"Loading config from env variables")
26
+ dify_base_url = base_url
27
+ dify_app_sks = dify_app_sks.split(",")
28
+ return dify_base_url, dify_app_sks
29
+
30
+ class DifyAPI(ABC):
31
+ def __init__(self,
32
+ base_url: str,
33
+ dify_app_sks: list,
34
+ user="default_user"):
35
+ # dify configs
36
+ self.dify_base_url = base_url
37
+ self.dify_app_sks = dify_app_sks
38
+ self.user = user
39
+
40
+ # dify app infos
41
+ dify_app_infos = []
42
+ dify_app_params = []
43
+ dify_app_metas = []
44
+ for key in self.dify_app_sks:
45
+ dify_app_infos.append(self.get_app_info(key))
46
+ dify_app_params.append(self.get_app_parameters(key))
47
+ dify_app_metas.append(self.get_app_meta(key))
48
+ self.dify_app_infos = dify_app_infos
49
+ self.dify_app_params = dify_app_params
50
+ self.dify_app_metas = dify_app_metas
51
+ self.dify_app_names = [x['name'] for x in dify_app_infos]
52
+
53
+ def chat_message(
54
+ self,
55
+ api_key,
56
+ inputs={},
57
+ response_mode="streaming",
58
+ conversation_id=None,
59
+ user="default_user",
60
+ files=None,):
61
+ url = f"{self.dify_base_url}/workflows/run"
62
+ headers = {
63
+ "Authorization": f"Bearer {api_key}",
64
+ "Content-Type": "application/json"
65
+ }
66
+ data = {
67
+ "inputs": inputs,
68
+ "response_mode": response_mode,
69
+ "user": user,
70
+ }
71
+ if conversation_id:
72
+ data["conversation_id"] = conversation_id
73
+ if files:
74
+ files_data = []
75
+ for file_info in files:
76
+ file_path = file_info.get('path')
77
+ transfer_method = file_info.get('transfer_method')
78
+ if transfer_method == 'local_file':
79
+ files_data.append(('file', open(file_path, 'rb')))
80
+ elif transfer_method == 'remote_url':
81
+ pass
82
+ response = requests.post(
83
+ url, headers=headers, data=data, files=files_data, stream=response_mode == "streaming")
84
+ else:
85
+ response = requests.post(
86
+ url, headers=headers, json=data, stream=response_mode == "streaming")
87
+ response.raise_for_status()
88
+ if response_mode == "streaming":
89
+ for line in response.iter_lines():
90
+ if line:
91
+ if line.startswith(b'data:'):
92
+ try:
93
+ json_data = json.loads(line[5:].decode('utf-8'))
94
+ yield json_data
95
+ except json.JSONDecodeError:
96
+ print(f"Error decoding JSON: {line}")
97
+ else:
98
+ return response.json()
99
+
100
+ def upload_file(
101
+ self,
102
+ api_key,
103
+ file_path,
104
+ user="default_user"):
105
+
106
+ url = f"{self.dify_base_url}/files/upload"
107
+ headers = {
108
+ "Authorization": f"Bearer {api_key}"
109
+ }
110
+ files = {
111
+ "file": open(file_path, "rb")
112
+ }
113
+ data = {
114
+ "user": user
115
+ }
116
+ response = requests.post(url, headers=headers, files=files, data=data)
117
+ response.raise_for_status()
118
+ return response.json()
119
+
120
+ def stop_response(
121
+ self,
122
+ api_key,
123
+ task_id,
124
+ user="default_user"):
125
+
126
+ url = f"{self.dify_base_url}/chat-messages/{task_id}/stop"
127
+ headers = {
128
+ "Authorization": f"Bearer {api_key}",
129
+ "Content-Type": "application/json"
130
+ }
131
+ data = {
132
+ "user": user
133
+ }
134
+ response = requests.post(url, headers=headers, json=data)
135
+ response.raise_for_status()
136
+ return response.json()
137
+
138
+ def get_app_info(
139
+ self,
140
+ api_key,
141
+ user="default_user"):
142
+
143
+ url = f"{self.dify_base_url}/info"
144
+ headers = {
145
+ "Authorization": f"Bearer {api_key}"
146
+ }
147
+ params = {
148
+ "user": user
149
+ }
150
+ response = requests.get(url, headers=headers, params=params)
151
+ response.raise_for_status()
152
+ return response.json()
153
+
154
+ def get_app_parameters(
155
+ self,
156
+ api_key,
157
+ user="default_user"):
158
+ url = f"{self.dify_base_url}/parameters"
159
+ headers = {
160
+ "Authorization": f"Bearer {api_key}"
161
+ }
162
+ params = {
163
+ "user": user
164
+ }
165
+ response = requests.get(url, headers=headers, params=params)
166
+ response.raise_for_status()
167
+ return response.json()
168
+
169
+ def get_app_meta(
170
+ self,
171
+ api_key,
172
+ user="default_user"):
173
+ url = f"{self.dify_base_url}/meta"
174
+ headers = {
175
+ "Authorization": f"Bearer {api_key}"
176
+ }
177
+ params = {
178
+ "user": user
179
+ }
180
+ response = requests.get(url, headers=headers, params=params)
181
+ response.raise_for_status()
182
+ return response.json()
183
+
184
+
185
+ base_url, dify_app_sks = get_app_info()
186
+ server = Server("dify_mcp_server")
187
+ dify_api = DifyAPI(base_url, dify_app_sks)
188
+
189
+
190
+ @server.list_tools()
191
+ async def handle_list_tools() -> list[types.Tool]:
192
+ """
193
+ List available tools.
194
+ Each tool specifies its arguments using JSON Schema validation.
195
+ """
196
+ tools = []
197
+ tool_names = dify_api.dify_app_names
198
+ tool_infos = dify_api.dify_app_infos
199
+ tool_params = dify_api.dify_app_params
200
+ tool_num = len(tool_names)
201
+ for i in range(tool_num):
202
+ # 0. load app info for each tool
203
+ app_info = tool_infos[i]
204
+ # 1. load app param for each tool
205
+ inputSchema = dict(
206
+ type="object",
207
+ properties={},
208
+ required=[],
209
+ )
210
+ app_param = tool_params[i]
211
+ property_num = len(app_param['user_input_form'])
212
+ if property_num > 0:
213
+ for j in range(property_num):
214
+ param = app_param['user_input_form'][j]
215
+ # TODO: Add readme about strange dify user input param format
216
+ param_type = list(param.keys())[0]
217
+ param_info = param[param_type]
218
+ property_name = param_info['variable']
219
+ inputSchema["properties"][property_name] = dict(
220
+ type=param_type,
221
+ description=param_info['label'],
222
+ )
223
+ if param_info['required']:
224
+ inputSchema['required'].append(property_name)
225
+
226
+ tools.append(
227
+ types.Tool(
228
+ name=app_info['name'],
229
+ description=app_info['description'],
230
+ inputSchema=inputSchema,
231
+ )
232
+ )
233
+ return tools
234
+
235
+
236
+ @server.call_tool()
237
+ async def handle_call_tool(
238
+ name: str, arguments: dict | None
239
+ ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
240
+ tool_names = dify_api.dify_app_names
241
+ if name in tool_names:
242
+ tool_idx = tool_names.index(name)
243
+ tool_sk = dify_api.dify_app_sks[tool_idx]
244
+ responses = dify_api.chat_message(
245
+ tool_sk,
246
+ arguments,
247
+ )
248
+ for res in responses:
249
+ if res['event'] == 'workflow_finished':
250
+ outputs = res['data']['outputs']
251
+ mcp_out = []
252
+ for _, v in outputs.items():
253
+ mcp_out.append(
254
+ types.TextContent(
255
+ type='text',
256
+ text=v
257
+ )
258
+ )
259
+ return mcp_out
260
+ else:
261
+ raise ValueError(f"Unknown tool: {name}")
262
+
263
+
264
+ async def main():
265
+ # Run the server using stdin/stdout streams
266
+ async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
267
+ await server.run(
268
+ read_stream,
269
+ write_stream,
270
+ InitializationOptions(
271
+ server_name="dify_mcp_server",
272
+ server_version="0.1.0",
273
+ capabilities=server.get_capabilities(
274
+ notification_options=NotificationOptions(),
275
+ experimental_capabilities={},
276
+ ),
277
+ ),
278
+ )
279
+
280
+ if __name__ == "__main__":
281
+ asyncio.run(main())