# Directory Structure
```
├── .gitignore
├── README.md
├── stdio
│ ├── .gitignore
│ ├── .python-version
│ ├── pyproject.toml
│ ├── README.md
│ ├── src
│ │ └── minium_mcp_server
│ │ ├── __init__.py
│ │ └── server.py
│ └── uv.lock
└── webapi
├── .gitignore
├── .python-version
├── pyproject.toml
├── README.md
├── src
│ └── minium_mcp_server
│ ├── __init__.py
│ └── server.py
├── uv.lock
└── web.py
```
# Files
--------------------------------------------------------------------------------
/stdio/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.12
2 |
```
--------------------------------------------------------------------------------
/webapi/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.12
2 |
```
--------------------------------------------------------------------------------
/stdio/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 |
```
--------------------------------------------------------------------------------
/webapi/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | **/__pycache__/
3 | **/*.py[oc]
4 | **/build/
5 | **/dist/
6 | **/wheels/
7 | **/*.egg-info
8 | **/.DS_Store
9 |
10 | # Virtual environments
11 | **/.venv
12 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # minium-mcp-server
2 |
3 | npx @modelcontextprotocol/inspector uv --directory /Users/roy.yan/Documents/3.Roy/python/minium_mcp_server/webapi run minium-mcp-server
```
--------------------------------------------------------------------------------
/webapi/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # minium-mcp-server MCP server
2 |
3 | A MCP server project
4 |
5 | ## Components
6 |
7 | ### Resources
8 |
9 | The server implements a simple note storage system with:
10 | - Custom note:// URI scheme for accessing individual notes
11 | - Each note resource has a name, description and text/plain mimetype
12 |
13 | ### Prompts
14 |
15 | The server provides a single prompt:
16 | - summarize-notes: Creates summaries of all stored notes
17 | - Optional "style" argument to control detail level (brief/detailed)
18 | - Generates prompt combining all current notes with style preference
19 |
20 | ### Tools
21 |
22 | The server implements one tool:
23 | - add-note: Adds a new note to the server
24 | - Takes "name" and "content" as required string arguments
25 | - Updates server state and notifies clients of resource changes
26 |
27 | ## Configuration
28 |
29 | [TODO: Add configuration details specific to your implementation]
30 |
31 | ## Quickstart
32 |
33 | ### Install
34 |
35 | #### Claude Desktop
36 |
37 | On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
38 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
39 |
40 | <details>
41 | <summary>Development/Unpublished Servers Configuration</summary>
42 | ```
43 | "mcpServers": {
44 | "minium-mcp-server": {
45 | "command": "uv",
46 | "args": [
47 | "--directory",
48 | "--path--/minium-mcp-server/webapi",
49 | "run",
50 | "minium-mcp-server"
51 | ]
52 | }
53 | }
54 | ```
55 | </details>
56 |
57 | <details>
58 | <summary>Published Servers Configuration</summary>
59 | ```
60 | "mcpServers": {
61 | "minium-mcp-server": {
62 | "command": "uvx",
63 | "args": [
64 | "minium-mcp-server"
65 | ]
66 | }
67 | }
68 | ```
69 | </details>
70 |
71 | ## Development
72 |
73 | ### Building and Publishing
74 |
75 | To prepare the package for distribution:
76 |
77 | 1. Sync dependencies and update lockfile:
78 | ```bash
79 | uv sync
80 | ```
81 |
82 | 2. Build package distributions:
83 | ```bash
84 | uv build
85 | ```
86 |
87 | This will create source and wheel distributions in the `dist/` directory.
88 |
89 | 3. Publish to PyPI:
90 | ```bash
91 | uv publish
92 | ```
93 |
94 | Note: You'll need to set PyPI credentials via environment variables or command flags:
95 | - Token: `--token` or `UV_PUBLISH_TOKEN`
96 | - Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
97 |
98 | ### Debugging
99 |
100 | Since MCP servers run over stdio, debugging can be challenging. For the best debugging
101 | experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
102 |
103 |
104 | You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
105 |
106 | ```bash
107 | npx @modelcontextprotocol/inspector uv --directory --path--/minium-mcp-server/webapi run minium-mcp-server
108 | ```
109 |
110 |
111 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
```
--------------------------------------------------------------------------------
/stdio/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # minium-mcp-server MCP server
2 |
3 | A MCP server project
4 |
5 | ## Components
6 |
7 | ### Resources
8 |
9 | The server implements a simple note storage system with:
10 | - Custom note:// URI scheme for accessing individual notes
11 | - Each note resource has a name, description and text/plain mimetype
12 |
13 | ### Prompts
14 |
15 | The server provides a single prompt:
16 | - summarize-notes: Creates summaries of all stored notes
17 | - Optional "style" argument to control detail level (brief/detailed)
18 | - Generates prompt combining all current notes with style preference
19 |
20 | ### Tools
21 |
22 | The server implements one tool:
23 | - add-note: Adds a new note to the server
24 | - Takes "name" and "content" as required string arguments
25 | - Updates server state and notifies clients of resource changes
26 |
27 | ## Configuration
28 |
29 | [TODO: Add configuration details specific to your implementation]
30 |
31 | ## Quickstart
32 |
33 | ### Install
34 |
35 | #### Claude Desktop
36 |
37 | On MacOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
38 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
39 |
40 | <details>
41 | <summary>Development/Unpublished Servers Configuration</summary>
42 | ```
43 | "mcpServers": {
44 | "minium-mcp-server": {
45 | "command": "uv",
46 | "args": [
47 | "--directory",
48 | "--path--/minium_mcp_server/stdio",
49 | "run",
50 | "minium-mcp-server",
51 | "--path",
52 | "--project-path--"
53 | ]
54 | }
55 | }
56 | ```
57 | </details>
58 |
59 | <details>
60 | <summary>Published Servers Configuration</summary>
61 | ```
62 | "mcpServers": {
63 | "minium-mcp-server": {
64 | "command": "uvx",
65 | "args": [
66 | "minium-mcp-server"
67 | ]
68 | }
69 | }
70 | ```
71 | </details>
72 |
73 | ## Development
74 |
75 | ### Building and Publishing
76 |
77 | To prepare the package for distribution:
78 |
79 | 1. Sync dependencies and update lockfile:
80 | ```bash
81 | uv sync
82 | ```
83 |
84 | 2. Build package distributions:
85 | ```bash
86 | uv build
87 | ```
88 |
89 | This will create source and wheel distributions in the `dist/` directory.
90 |
91 | 3. Publish to PyPI:
92 | ```bash
93 | uv publish
94 | ```
95 |
96 | Note: You'll need to set PyPI credentials via environment variables or command flags:
97 | - Token: `--token` or `UV_PUBLISH_TOKEN`
98 | - Or username/password: `--username`/`UV_PUBLISH_USERNAME` and `--password`/`UV_PUBLISH_PASSWORD`
99 |
100 | ### Debugging
101 |
102 | Since MCP servers run over stdio, debugging can be challenging. For the best debugging
103 | experience, we strongly recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector).
104 |
105 |
106 | You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command:
107 |
108 | ```bash
109 | npx @modelcontextprotocol/inspector uv --directory --path--/minium_mcp_server/stdio run minium-mcp-server --path --project-path--
110 | ```
111 |
112 |
113 | Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
```
--------------------------------------------------------------------------------
/webapi/src/minium_mcp_server/__init__.py:
--------------------------------------------------------------------------------
```python
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']
```
--------------------------------------------------------------------------------
/stdio/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "minium-mcp-server"
3 | version = "0.1.0"
4 | description = "A MCP server project"
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [ "mcp>=1.4.1", "minium"]
8 | [[project.authors]]
9 | name = "roy.yan"
10 |
11 | [build-system]
12 | requires = [ "hatchling",]
13 | build-backend = "hatchling.build"
14 |
15 | [project.scripts]
16 | minium-mcp-server = "minium_mcp_server:main"
17 |
```
--------------------------------------------------------------------------------
/webapi/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "minium-mcp-server"
3 | version = "0.1.0"
4 | description = "A MCP server project"
5 | readme = "README.md"
6 | requires-python = ">=3.12"
7 | dependencies = [ "mcp>=1.4.1", "minium", "flask"]
8 | [[project.authors]]
9 | name = "roy.yan"
10 |
11 | [build-system]
12 | requires = [ "hatchling",]
13 | build-backend = "hatchling.build"
14 |
15 | [project.scripts]
16 | minium-mcp-server = "minium_mcp_server:main"
17 |
```
--------------------------------------------------------------------------------
/stdio/src/minium_mcp_server/__init__.py:
--------------------------------------------------------------------------------
```python
1 | from . import server
2 | import asyncio
3 | import argparse
4 |
5 | def main():
6 | """Main entry point for the package."""
7 | parser = argparse.ArgumentParser(description='Minium MCP Server')
8 | parser.add_argument('--path',
9 | default="./MiniProgram",
10 | help='Path to WeChat MiniProgram project')
11 |
12 | args = parser.parse_args()
13 | asyncio.run(server.main(args.path))
14 |
15 |
16 | # Optionally expose other important items at package level
17 | __all__ = ["main", "server"]
```
--------------------------------------------------------------------------------
/webapi/web.py:
--------------------------------------------------------------------------------
```python
1 | from flask import Flask, request, jsonify
2 | import json
3 | import os
4 | import sys
5 | import minium
6 | import base64
7 |
8 | app = Flask(__name__)
9 | print("Starting Minium MCP Web Server")
10 |
11 | mini = None
12 | project_path = ''
13 | HOST = '0.0.0.0'
14 | PORT = 9188
15 |
16 | def mini_log_added(message):
17 | """
18 | 小程序 log 监听回调函数
19 | 将小程序的 log 格式化然后保存起来
20 | :param message: {"type": "log|warn|error", "args": [str, ..., ]}
21 | :return:
22 | """
23 | print(f'console.log: {message}')
24 |
25 | @app.route('/api/command', methods=['POST'])
26 | def handle_command():
27 | global mini, project_path
28 | try:
29 | command = request.json
30 | arguments = command['arguments']
31 |
32 | print(f"COMMAND: {json.dumps(command)}")
33 |
34 | match command['name']:
35 | case "open":
36 | if sys.platform == 'darwin': # macOS
37 | dev_tool_path = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
38 | elif sys.platform == 'win32': # Windows
39 | dev_tool_path = 'C:/Program Files (x86)/Tencent/微信web开发者工具/cli.bat'
40 | else:
41 | raise Exception("Unsupported operating system")
42 |
43 | project_path = arguments['path']
44 | try:
45 | mini = minium.Minium({
46 | "project_path": project_path,
47 | "dev_tool_path": dev_tool_path,
48 | "debug_mode": "error",
49 | "audits": True,
50 | "autofix": True
51 | })
52 | mini.app.enable_log()
53 | mini.app.add_observer("App.logAdded", mini_log_added)
54 | except Exception as e:
55 | # 重试
56 | return jsonify({
57 | "status": "error",
58 | "message": str(e)
59 | })
60 |
61 | return jsonify({
62 | "status": "success",
63 | "message": "Opened"
64 | })
65 |
66 | case "get_system_info":
67 | return jsonify({
68 | "status": "success",
69 | "message": mini.get_system_info()
70 | })
71 |
72 | case "shutdown":
73 | mini.shutdown()
74 | return jsonify({
75 | "status": "success",
76 | "message": "Closed"
77 | })
78 |
79 | case "screen_shot":
80 | output_path = os.path.join(project_path, "screenshots/{}_screen_shot.png".format(mini.app.get_current_page().page_id))
81 | if not os.path.isdir(os.path.dirname(output_path)):
82 | os.makedirs(os.path.dirname(output_path))
83 | if os.path.isfile(output_path):
84 | os.remove(output_path)
85 | mini.app.screen_shot(output_path)
86 | # 获取截图
87 | with open(output_path, "rb") as f:
88 | image = f.read()
89 | # 返回base64编码的图片
90 | image_base64 = base64.b64encode(image).decode('utf-8')
91 | # 删除截图
92 | os.remove(output_path)
93 | return jsonify({
94 | "status": "success",
95 | "type": "image",
96 | "data": image_base64
97 | })
98 |
99 | case "get_all_pages_path_and_method":
100 | all_pages_path = mini.app.get_all_pages_path()
101 | with open(os.path.join(project_path, "app.json"), "r", encoding="utf-8") as file: # 建议指定 encoding
102 | app = json.load(file) # 解析 JSON 文件 → Python 字典/列表
103 | tabbar = app.get('tabBar').get('list')
104 |
105 | result = []
106 | for path in all_pages_path:
107 | if path in [item.get('pagePath') for item in tabbar]:
108 | result.append({
109 | "path": f"/{path}",
110 | "method": "minium_switch_tab"
111 | })
112 | else:
113 | result.append({
114 | "path": f"/{path}",
115 | "method": "minium_navigate_to"
116 | })
117 | return jsonify({
118 | "status": "success",
119 | "message": f"```json\n{json.dumps(result, indent=4, ensure_ascii=False)}```"
120 | })
121 |
122 | case "get_navigate_method_of_page":
123 | with open(os.path.join(project_path, "app.json"), "r", encoding="utf-8") as file: # 建议指定 encoding
124 | app = json.load(file) # 解析 JSON 文件 → Python 字典/列表
125 | tabbar = app.get('tabBar').get('list')
126 | if arguments["path"] in [item.get('pagePath') for item in tabbar]:
127 | return jsonify({
128 | "status": "success",
129 | "message": "minium_switch_tab"
130 | })
131 | else:
132 | return jsonify({
133 | "status": "success",
134 | "message": "minium_navigate_to"
135 | })
136 |
137 | case "go_home":
138 | page = mini.app.go_home()
139 | if page is not None:
140 | return jsonify({
141 | "status": "success",
142 | "message": "Successfully enter the home page"
143 | })
144 | else:
145 | return jsonify({
146 | "status": "error",
147 | "message": "Failed to enter the home page"
148 | })
149 |
150 | case "navigate_to":
151 | # 如果arguments中没有params,则默认为空字符串
152 | if arguments.get("params") is None:
153 | arguments["params"] = {}
154 | page = mini.app.navigate_to(arguments["path"], arguments["params"])
155 | if page is not None:
156 | return jsonify({
157 | "status": "success",
158 | "message": f"Successfully enter the {arguments['path']} page"
159 | })
160 | else:
161 | return jsonify({
162 | "status": "error",
163 | "message": f"Failed to enter the {arguments['path']} page"
164 | })
165 |
166 | case "navigate_back":
167 | page = mini.app.navigate_back()
168 | if page is not None:
169 | return jsonify({
170 | "status": "success",
171 | "message": "Successful return to the previous page"
172 | })
173 | else:
174 | return jsonify({
175 | "status": "error",
176 | "message": f"Failed to return to the previous page"
177 | })
178 |
179 | case "switch_tab":
180 | page = mini.app.switch_tab(arguments["path"])
181 | if page is not None:
182 | return jsonify({
183 | "status": "success",
184 | "message": "Successfully switch tab"
185 | })
186 | else:
187 | return jsonify({
188 | "status": "error",
189 | "message": "Failed to switch tab"
190 | })
191 |
192 | case "redirect_to":
193 | if arguments.get("params") is None:
194 | arguments["params"] = {}
195 | page = mini.app.redirect_to(arguments["path"], arguments["params"])
196 | if page is not None:
197 | return jsonify({
198 | "status": "success",
199 | "message": "Successfully redirect to"
200 | })
201 | else:
202 | return jsonify({
203 | "status": "error",
204 | "message": "Failed to redirect to"
205 | })
206 |
207 | # case "evaluate":
208 | # msg_id = mini.app.evaluate(arguments["code"], arguments["params"], sync=False)
209 | # result = mini.app.get_async_response(msg_id, 5)
210 | # return jsonify({
211 | # "status": "success",
212 | # "message": f"Evaluate, Result: {result}"
213 | # })
214 |
215 | case "call_method":
216 | if arguments.get("params") is None:
217 | arguments["params"] = {}
218 | page = mini.app.get_current_page()
219 | result = page.call_method(arguments["method"], arguments["params"])
220 | return jsonify({
221 | "status": "success",
222 | "message": f"Call method, Result: {result}"
223 | })
224 |
225 | case "page_scroll_to":
226 | page = mini.app.get_current_page()
227 | page.scroll_to(arguments["top"], arguments["duration"])
228 | return jsonify({
229 | "status": "success",
230 | "message": f"Page scroll to, Top: {arguments['top']}, Duration: {arguments['duration']}"
231 | })
232 |
233 | case "page_get_wxml":
234 | page = mini.app.get_current_page()
235 | wxml = page.wxml
236 | # 分离wxml和css
237 | # 查询最后一个tag的位置
238 | last_tag_index = wxml.rfind("</")
239 | # 分割wxml和css
240 | wxml_content = wxml[:last_tag_index]
241 | css_content = wxml[last_tag_index:]
242 | first_tag_index = css_content.find(">")
243 | wxml_content += css_content[:first_tag_index+1]
244 | css_content = css_content[first_tag_index+1:]
245 |
246 | # 格式化输出
247 |
248 | return jsonify({
249 | "status": "success",
250 | "message": f"```xml\n{wxml_content}```"
251 | })
252 |
253 | case "page_get_css":
254 | page = mini.app.get_current_page()
255 | wxml = page.wxml
256 | # 分离wxml和css
257 | # 查询最后一个tag的位置
258 | last_tag_index = wxml.rfind("</")
259 | # 分割wxml和css
260 | wxml_content = wxml[:last_tag_index]
261 | css_content = wxml[last_tag_index:]
262 | first_tag_index = css_content.find(">")
263 | wxml_content += css_content[:first_tag_index+1]
264 | css_content = css_content[first_tag_index+1:]
265 |
266 | # 格式化输出
267 |
268 | return jsonify({
269 | "status": "success",
270 | "message": f"```css\n{css_content}```"
271 | })
272 |
273 | case "page_get_data":
274 | page = mini.app.get_current_page()
275 | return jsonify({
276 | "status": "success",
277 | "message": f"```json\n{page.data}```"
278 | })
279 |
280 | case "page_set_data":
281 | page = mini.app.get_current_page()
282 | data = page.data
283 | data[arguments['key']] = json.load(arguments['value'])
284 | return jsonify({
285 | "status": "success",
286 | "message": f"```json\n{page.data}```"
287 | })
288 |
289 | case "tap":
290 | page = mini.app.get_current_page()
291 | el = page.get_element(arguments["selector"])
292 | el.tap()
293 | return jsonify({
294 | "status": "success",
295 | "message": "Tapped"
296 | })
297 |
298 | case "long_press":
299 | page = mini.app.get_current_page()
300 | el = page.get_element(arguments["selector"])
301 | el.long_press()
302 | return jsonify({
303 | "status": "success",
304 | "message": "Long pressed"
305 | })
306 |
307 | case "move":
308 | page = mini.app.get_current_page()
309 | el = page.get_element(arguments["selector"])
310 | el.move(arguments["left"], arguments["top"])
311 | return jsonify({
312 | "status": "success",
313 | "message": f"Moved to, Top: {arguments['top']}, Left: {arguments['left']}"
314 | })
315 |
316 | case "input":
317 | page = mini.app.get_current_page()
318 | el = page.get_element(arguments["selector"])
319 | el.input(arguments["text"])
320 | return jsonify({
321 | "status": "success",
322 | "message": f"Input, Text: {arguments['text']}"
323 | })
324 |
325 | case "switch":
326 | page = mini.app.get_current_page()
327 | el = page.get_element(arguments["selector"])
328 | el.switch()
329 | return jsonify({
330 | "status": "success",
331 | "message": "Switched"
332 | })
333 |
334 | case "slide_to":
335 | page = mini.app.get_current_page()
336 | el = page.get_element(arguments["selector"])
337 | el.slide_to(arguments["value"])
338 | return jsonify({
339 | "status": "success",
340 | "message": f"Slided to, Value: {arguments['value']}"
341 | })
342 |
343 | case "pick":
344 | page = mini.app.get_current_page()
345 | el = page.get_element(arguments["selector"])
346 | el.pick(arguments["option"])
347 | return jsonify({
348 | "status": "success",
349 | "message": f"Picked, Option: {arguments['option']}"
350 | })
351 |
352 | case _:
353 | return jsonify({
354 | "status": "error",
355 | "message": f"Unknown command: {command['name']}"
356 | })
357 |
358 | except Exception as e:
359 | return jsonify({
360 | "status": "error",
361 | "message": str(e)
362 | })
363 |
364 | if __name__ == "__main__":
365 | app.run(host=HOST, port=PORT)
366 |
```
--------------------------------------------------------------------------------
/webapi/src/minium_mcp_server/server.py:
--------------------------------------------------------------------------------
```python
1 | import os
2 | import sys
3 | import logging
4 | import json
5 | import asyncio
6 | import requests
7 | from mcp.server import NotificationOptions, Server
8 | from mcp.server.models import InitializationOptions
9 |
10 | from mcp import types
11 | from typing import Any
12 | import mcp.server.stdio
13 |
14 | HOST = 'http://127.0.0.1'
15 | # HOST = 'http://192.168.3.42'
16 | PORT = 9188
17 |
18 | # reconfigure UnicodeEncodeError prone default (i.e. windows-1252) to utf-8
19 | if sys.platform == "win32" and os.environ.get('PYTHONIOENCODING') is None:
20 | sys.stdin.reconfigure(encoding="utf-8")
21 | sys.stdout.reconfigure(encoding="utf-8")
22 | sys.stderr.reconfigure(encoding="utf-8")
23 |
24 | logger = logging.getLogger('minium-mcp-server')
25 | print("Starting Minium MCP Server")
26 |
27 | async def main():
28 | server = Server("minium-mcp-server")
29 |
30 | @server.list_tools()
31 | async def handle_list_tools() -> list[types.Tool]:
32 | """List available tools"""
33 | return [
34 | types.Tool(
35 | name="minium_open",
36 | description="Open a project",
37 | inputSchema={
38 | "type": "object",
39 | "properties": {
40 | "path": {"type": "string", "description": "Project path"},
41 | },
42 | "required": ["path"],
43 | }
44 | ),
45 | types.Tool(
46 | name="minium_get_system_info",
47 | description="Get system info",
48 | inputSchema={
49 | "type": "object",
50 | "properties": {},
51 | "required": [],
52 | }
53 | ),
54 | types.Tool(
55 | name="minium_shutdown",
56 | description="Shutdown the developer tool",
57 | inputSchema={
58 | "type": "object",
59 | "properties": {},
60 | "required": [],
61 | }
62 | ),
63 | types.Tool(
64 | name="minium_screen_shot",
65 | description="Take a screenshot of the current page",
66 | inputSchema={
67 | "type": "object",
68 | "properties": {},
69 | "required": [],
70 | },
71 | ),
72 | types.Tool(
73 | name="minium_get_all_pages_path_and_method",
74 | description="Get paths of all pages and the method used to navigate to them",
75 | inputSchema={
76 | "type": "object",
77 | "properties": {},
78 | "required": [],
79 | }
80 | ),
81 | types.Tool(
82 | name="minium_get_navigate_method_of_page",
83 | description="Get the method used to navigate to a page",
84 | inputSchema={
85 | "type": "object",
86 | "properties": {
87 | "path": {"type": "string", "description": "Page path"},
88 | },
89 | "required": ["path"],
90 | }
91 | ),
92 | types.Tool(
93 | name="minium_go_home",
94 | description="Go to the home page",
95 | inputSchema={
96 | "type": "object",
97 | "properties": {},
98 | "required": [],
99 | }
100 | ),
101 | types.Tool(
102 | name="minium_navigate_to",
103 | description="Navigate to a page. Please get path of all pages before using this tool.",
104 | inputSchema={
105 | "type": "object",
106 | "properties": {
107 | "path": {"type": "string", "description": "Page path"},
108 | "params": {"type": "object", "description": "Query parameters"},
109 | },
110 | "required": ["path"],
111 | }
112 | ),
113 | types.Tool(
114 | name="minium_navigate_back",
115 | description="Navigate back to the previous page",
116 | inputSchema={
117 | "type": "object",
118 | "properties": {},
119 | "required": [],
120 | }
121 | ),
122 | types.Tool(
123 | name="minium_switch_tab",
124 | description="Switch to a tab. Please get path of all pages before using this tool.",
125 | inputSchema={
126 | "type": "object",
127 | "properties": {
128 | "path": {"type": "string", "description": "Page path"},
129 | },
130 | "required": ["path"],
131 | }
132 | ),
133 | types.Tool(
134 | name="minium_redirect_to",
135 | description="Redirect to a page",
136 | inputSchema={
137 | "type": "object",
138 | "properties": {
139 | "path": {"type": "string", "description": "Page path"},
140 | "params": {"type": "object", "description": "Query parameters"},
141 | },
142 | "required": ["path"],
143 | }
144 | ),
145 | types.Tool(
146 | name="minium_relaunch",
147 | description="Close all pages and open a new one",
148 | inputSchema={
149 | "type": "object",
150 | "properties": {
151 | "path": {"type": "string", "description": "Page path"},
152 | "params": {"type": "object", "description": "Query parameters"},
153 | },
154 | "required": ["path"],
155 | }
156 | ),
157 | # types.Tool(
158 | # name="evaluate",
159 | # description="Evaluate a JavaScript(es5) code",
160 | # inputSchema={
161 | # "type": "object",
162 | # "properties": {
163 | # "code": {"type": "string", "description": "Script code"},
164 | # "params": {"type": "object", "description": "Script parameters"},
165 | # },
166 | # "required": ["code", "params"],
167 | # }
168 | # ),
169 | types.Tool(
170 | name="minium_call_method",
171 | description="Call a method of page",
172 | inputSchema={
173 | "type": "object",
174 | "properties": {
175 | "method": {"type": "string", "description": "Method name"},
176 | "params": {"type": "object", "description": "Method parameters"},
177 | },
178 | "required": ["method", "params"],
179 | }
180 | ),
181 | types.Tool(
182 | name="minium_page_scroll_to",
183 | description="Scroll to the specified position of an page",
184 | inputSchema={
185 | "type": "object",
186 | "properties": {
187 | "top": {"type": "number", "description": "Scroll to the top"},
188 | "duration": {"type": "number", "description": "Scroll duration"},
189 | },
190 | "required": ["top", "duration"],
191 | }
192 | ),
193 | types.Tool(
194 | name="page_get_wxml",
195 | description="Get Dom structure of an page",
196 | inputSchema={
197 | "type": "object",
198 | "properties": {},
199 | "required": [],
200 | }
201 | ),
202 | types.Tool(
203 | name="page_get_css",
204 | description="Get CSS structure of an page",
205 | inputSchema={
206 | "type": "object",
207 | "properties": {},
208 | "required": [],
209 | }
210 | ),
211 | types.Tool(
212 | name="minium_page_get_data",
213 | description="Get data of an page",
214 | inputSchema={
215 | "type": "object",
216 | "properties": {},
217 | "required": [],
218 | }
219 | ),
220 | types.Tool(
221 | name="minium_page_set_data",
222 | description="Set data of an page",
223 | inputSchema={
224 | "type": "object",
225 | "properties": {
226 | "key": {"type": "string", "description": "key of data"},
227 | "value": {"type": "string", "description": "value of data"},
228 | },
229 | "required": ["key", "value"],
230 | }
231 | ),
232 | types.Tool(
233 | name="minium_tap",
234 | description="Tap an element",
235 | inputSchema={
236 | "type": "object",
237 | "properties": {
238 | "selector": {
239 | "type": "string",
240 | "description": "CSS selector or XPath expression"
241 | },
242 | },
243 | "required": ["selector"],
244 | }
245 | ),
246 | types.Tool(
247 | name="minium_long_press",
248 | description="Long press an element",
249 | inputSchema={
250 | "type": "object",
251 | "properties": {
252 | "selector": {
253 | "type": "string",
254 | "description": "CSS selector or XPath expression"
255 | },
256 | },
257 | "required": ["selector"],
258 | }
259 | ),
260 | types.Tool(
261 | name="minium_move",
262 | description="Perform gestures on the element",
263 | inputSchema={
264 | "type": "object",
265 | "properties": {
266 | "selector": {
267 | "type": "string",
268 | "description": "CSS selector or XPath expression"
269 | },
270 | "top": {
271 | "type": "number",
272 | "description": "Move to the top coordinate"
273 | },
274 | "left": {
275 | "type": "number",
276 | "description": "Move to the left coordinate"
277 | },
278 | },
279 | "required": ["selector", "top", "left"],
280 | }
281 | ),
282 | types.Tool(
283 | name="minium_input",
284 | description="Input text to an element",
285 | inputSchema={
286 | "type": "object",
287 | "properties": {
288 | "selector": {
289 | "type": "string",
290 | "description": "CSS selector or XPath expression"
291 | },
292 | "text": {
293 | "type": "string",
294 | "description": "Text to input"
295 | },
296 | },
297 | "required": ["selector", "text"],
298 | }
299 | ),
300 | types.Tool(
301 | name="minium_switch",
302 | description="Change the switch status of an element",
303 | inputSchema={
304 | "type": "object",
305 | "properties": {
306 | "selector": {
307 | "type": "string",
308 | "description": "CSS selector or XPath expression"
309 | },
310 | },
311 | "required": ["selector"],
312 | }
313 | ),
314 | types.Tool(
315 | name="minium_slide_to",
316 | description="Slide to the specified position of an element",
317 | inputSchema={
318 | "type": "object",
319 | "properties": {
320 | "selector": {
321 | "type": "string",
322 | "description": "CSS selector or XPath expression"
323 | },
324 | "value": {
325 | "type": "number",
326 | "description": "Slide value"
327 | },
328 | },
329 | "required": ["selector", "value"],
330 | }
331 | ),
332 | types.Tool(
333 | name="minium_pick",
334 | description="Pick an option of an element",
335 | inputSchema={
336 | "type": "object",
337 | "properties": {
338 | "selector": {
339 | "type": "string",
340 | "description": "CSS selector or XPath expression"
341 | },
342 | "option": {
343 | "type": "string",
344 | "description": "Option value"
345 | },
346 | },
347 | "required": ["selector", "option"],
348 | }
349 | )
350 | ]
351 |
352 | @server.call_tool()
353 | async def handle_call_tool(
354 | name: str, arguments: dict[str, Any] | None
355 | ):
356 | """Handle tool execution requests"""
357 | print(f"Received call tool request: {name} with args: {arguments}")
358 |
359 | try:
360 | # Send HTTP request to web server
361 | response = requests.post(
362 | f"{HOST}:{PORT}/api/command",
363 | json={
364 | "name": name.replace("minium_", ""),
365 | "arguments": arguments or {}
366 | },
367 | timeout=600000
368 | )
369 |
370 | if response.status_code != 200:
371 | raise Exception(f"HTTP error: {response.status_code}")
372 |
373 | response_data = response.json()
374 | if response_data.get("status") == "error":
375 | raise Exception(response_data.get("message", "Unknown error"))
376 |
377 | if response_data.get("type") == "image":
378 | return [types.ImageContent(type="image", mimeType="image/png", data=response_data.get("data"))]
379 |
380 | return [types.TextContent(type="text", text=response_data.get("message"))]
381 |
382 | except Exception as e:
383 | logger.error(f"Error handling tool request: {str(e)}")
384 | raise
385 |
386 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
387 | print("Server running with stdio transport")
388 | await server.run(
389 | read_stream,
390 | write_stream,
391 | InitializationOptions(
392 | server_name="minium",
393 | server_version="0.1.0",
394 | capabilities=server.get_capabilities(
395 | notification_options=NotificationOptions(),
396 | experimental_capabilities={},
397 | ),
398 | ),
399 | )
```
--------------------------------------------------------------------------------
/stdio/src/minium_mcp_server/server.py:
--------------------------------------------------------------------------------
```python
1 | import os
2 | import sys
3 | import logging
4 | import minium
5 | import base64
6 | from mcp.server import NotificationOptions, Server
7 | from mcp.server.models import InitializationOptions
8 | from mcp import types
9 | from typing import Any
10 | import mcp.server.stdio
11 |
12 | # reconfigure UnicodeEncodeError prone default (i.e. windows-1252) to utf-8
13 | if sys.platform == "win32" and os.environ.get('PYTHONIOENCODING') is None:
14 | sys.stdin.reconfigure(encoding="utf-8")
15 | sys.stdout.reconfigure(encoding="utf-8")
16 | sys.stderr.reconfigure(encoding="utf-8")
17 |
18 | logger = logging.getLogger('minium-mcp-server')
19 | logger.info("Starting Minium MCP Server")
20 |
21 | async def main(project_path: str):
22 | server = Server("minium-mcp-server")
23 | # 根据操作系统设置开发者工具cli路径
24 | if sys.platform == 'darwin': # macOS
25 | dev_tool_path = '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
26 | elif sys.platform == 'win32': # Windows
27 | dev_tool_path = 'C:/Program Files (x86)/Tencent/微信web开发者工具/cli.bat'
28 | else:
29 | raise Exception("Unsupported operating system")
30 |
31 | mini = minium.Minium({
32 | "project_path": project_path, # 替换成你的【小程序项目目录地址】
33 | "dev_tool_path": dev_tool_path,
34 | "debug_mode": "error"
35 | })
36 | mini.app.enable_log()
37 |
38 | @server.list_tools()
39 | async def handle_list_tools() -> list[types.Tool]:
40 | """List available tools"""
41 | return [
42 | types.Tool(
43 | name="minium_get_system_info",
44 | description="Get system info",
45 | inputSchema={
46 | "type": "object",
47 | "properties": {},
48 | "required": [],
49 | }
50 | ),
51 | types.Tool(
52 | name="minium_shutdown",
53 | description="Shutdown the developer tool",
54 | inputSchema={
55 | "type": "object",
56 | "properties": {},
57 | "required": [],
58 | }
59 | ),
60 | types.Tool(
61 | name="minium_screen_shot",
62 | description="Take a screenshot of the current page",
63 | inputSchema={
64 | "type": "object",
65 | "properties": {},
66 | "required": [],
67 | },
68 | ),
69 | types.Tool(
70 | name="minium_get_all_pages_path_and_method",
71 | description="Get paths of all pages and navigate method of each page",
72 | inputSchema={
73 | "type": "object",
74 | "properties": {},
75 | "required": [],
76 | }
77 | ),
78 | types.Tool(
79 | name="minium_get_navigate_method_of_page",
80 | description="Get navigate method of a page",
81 | inputSchema={
82 | "type": "object",
83 | "properties": {
84 | "path": {"type": "string", "description": "Page path"},
85 | },
86 | "required": ["path"],
87 | }
88 | ),
89 | types.Tool(
90 | name="minium_go_home",
91 | description="Go to the home page",
92 | inputSchema={
93 | "type": "object",
94 | "properties": {},
95 | "required": [],
96 | }
97 | ),
98 | types.Tool(
99 | name="minium_navigate_to",
100 | description="Navigate to a page. Please get path of all pages before using this tool.",
101 | inputSchema={
102 | "type": "object",
103 | "properties": {
104 | "path": {"type": "string", "description": "Page path"},
105 | "query": {"type": "string", "description": "Query parameters"},
106 | },
107 | "required": ["path"],
108 | }
109 | ),
110 | types.Tool(
111 | name="minium_navigate_back",
112 | description="Navigate back to the previous page",
113 | inputSchema={
114 | "type": "object",
115 | "properties": {},
116 | "required": [],
117 | }
118 | ),
119 | types.Tool(
120 | name="minium_switch_tab",
121 | description="Switch to a tab. Please get path of all pages before using this tool.",
122 | inputSchema={
123 | "type": "object",
124 | "properties": {
125 | "path": {"type": "string", "description": "Page path"},
126 | },
127 | "required": ["path"],
128 | }
129 | ),
130 | types.Tool(
131 | name="minium_redirect_to",
132 | description="Redirect to a page",
133 | inputSchema={
134 | "type": "object",
135 | "properties": {
136 | "path": {"type": "string", "description": "Page path"},
137 | "query": {"type": "string", "description": "Query parameters"},
138 | },
139 | "required": ["path"],
140 | }
141 | ),
142 | types.Tool(
143 | name="minium_relaunch",
144 | description="Close all pages and open a new one",
145 | inputSchema={
146 | "type": "object",
147 | "properties": {
148 | "path": {"type": "string", "description": "Page path"},
149 | "query": {"type": "string", "description": "Query parameters"},
150 | },
151 | "required": ["path"],
152 | }
153 | ),
154 | # types.Tool(
155 | # name="evaluate",
156 | # description="Evaluate a JavaScript(es5) code",
157 | # inputSchema={
158 | # "type": "object",
159 | # "properties": {
160 | # "code": {"type": "string", "description": "Script code"},
161 | # "params": {"type": "string", "description": "Script parameters"},
162 | # },
163 | # "required": ["code", "params"],
164 | # }
165 | # ),
166 | types.Tool(
167 | name="minium_call_method",
168 | description="Call a method of page",
169 | inputSchema={
170 | "type": "object",
171 | "properties": {
172 | "method": {"type": "string", "description": "Method name"},
173 | "params": {"type": "object", "description": "Method parameters"},
174 | },
175 | "required": ["method", "params"],
176 | }
177 | ),
178 | types.Tool(
179 | name="minium_page_scroll_to",
180 | description="Scroll to the specified position of an page",
181 | inputSchema={
182 | "type": "object",
183 | "properties": {
184 | "top": {"type": "number", "description": "Scroll to the top"},
185 | "duration": {"type": "number", "description": "Scroll duration"},
186 | },
187 | "required": ["top", "duration"],
188 | }
189 | ),
190 | types.Tool(
191 | name="page_get_wxml",
192 | description="Get Dom structure of an page",
193 | inputSchema={
194 | "type": "object",
195 | "properties": {},
196 | "required": [],
197 | }
198 | ),
199 | types.Tool(
200 | name="minium_page_get_data",
201 | description="Get data of an page",
202 | inputSchema={
203 | "type": "object",
204 | "properties": {},
205 | "required": [],
206 | }
207 | ),
208 | types.Tool(
209 | name="minium_page_set_data",
210 | description="Set data of an page",
211 | inputSchema={
212 | "type": "object",
213 | "properties": {
214 | "key": {"type": "string", "description": "key of data"},
215 | "value": {"type": "any", "description": "value of data"},
216 | },
217 | "required": [],
218 | }
219 | ),
220 | types.Tool(
221 | name="minium_tap",
222 | description="Tap an element",
223 | inputSchema={
224 | "type": "object",
225 | "properties": {
226 | "selector": {
227 | "type": "string",
228 | "description": "CSS selector or XPath expression"
229 | },
230 | },
231 | "required": ["selector"],
232 | }
233 | ),
234 | types.Tool(
235 | name="minium_long_press",
236 | description="Long press an element",
237 | inputSchema={
238 | "type": "object",
239 | "properties": {
240 | "selector": {
241 | "type": "string",
242 | "description": "CSS selector or XPath expression"
243 | },
244 | },
245 | "required": ["selector"],
246 | }
247 | ),
248 | types.Tool(
249 | name="minium_move",
250 | description="Perform gestures on the element",
251 | inputSchema={
252 | "type": "object",
253 | "properties": {
254 | "selector": {
255 | "type": "string",
256 | "description": "CSS selector or XPath expression"
257 | },
258 | "top": {
259 | "type": "number",
260 | "description": "Move to the top coordinate"
261 | },
262 | "left": {
263 | "type": "number",
264 | "description": "Move to the left coordinate"
265 | },
266 | },
267 | "required": ["selector", "top", "left"],
268 | }
269 | ),
270 | types.Tool(
271 | name="minium_input",
272 | description="Input text to an element",
273 | inputSchema={
274 | "type": "object",
275 | "properties": {
276 | "selector": {
277 | "type": "string",
278 | "description": "CSS selector or XPath expression"
279 | },
280 | "text": {
281 | "type": "string",
282 | "description": "Text to input"
283 | },
284 | },
285 | "required": ["selector", "text"],
286 | }
287 | ),
288 | types.Tool(
289 | name="minium_switch",
290 | description="Change the switch status of an element",
291 | inputSchema={
292 | "type": "object",
293 | "properties": {
294 | "selector": {
295 | "type": "string",
296 | "description": "CSS selector or XPath expression"
297 | },
298 | },
299 | "required": ["selector"],
300 | }
301 | ),
302 | types.Tool(
303 | name="minium_slide_to",
304 | description="Slide to the specified position of an element",
305 | inputSchema={
306 | "type": "object",
307 | "properties": {
308 | "selector": {
309 | "type": "string",
310 | "description": "CSS selector or XPath expression"
311 | },
312 | "value": {
313 | "type": "number",
314 | "description": "Slide value"
315 | },
316 | },
317 | "required": ["selector", "value"],
318 | }
319 | ),
320 | types.Tool(
321 | name="minium_pick",
322 | description="Pick an option of an element",
323 | inputSchema={
324 | "type": "object",
325 | "properties": {
326 | "selector": {
327 | "type": "string",
328 | "description": "CSS selector or XPath expression"
329 | },
330 | "option": {
331 | "type": "string",
332 | "description": "Option value"
333 | },
334 | },
335 | "required": ["selector", "option"],
336 | }
337 | )
338 | ]
339 |
340 | @server.call_tool()
341 | async def handle_call_tool(
342 | name: str, arguments: dict[str, Any] | None
343 | ):
344 | """Handle tool execution requests"""
345 | logger.info(f"Received call tool request: {name} with args: {arguments}")
346 | try:
347 | match name.replace("minium_", ""):
348 | case "get_system_info":
349 | return [types.TextContent(type="text", text=f"Error: {mini.get_system_info()}")]
350 | case "shutdown":
351 | mini.shutdown()
352 | return [types.TextContent(type="text", text=f"Success: Shutdown")]
353 | case "screen_shot":
354 | output_path = os.path.join(project_path, "screenshots/{}_screen_shot.png".format(mini.app.get_current_page().page_id))
355 | if not os.path.isdir(os.path.dirname(output_path)):
356 | os.mkdir(os.path.dirname(output_path))
357 | if os.path.isfile(output_path):
358 | os.remove(output_path)
359 | mini.app.screen_shot(output_path)
360 | # 获取截图
361 | with open(output_path, "rb") as f:
362 | image = f.read()
363 | # 返回base64编码的图片
364 | image_base64 = base64.b64encode(image).decode('utf-8')
365 | # 删除截图
366 | os.remove(output_path)
367 | return [types.ImageContent(type="image", mimeType="image/png", data=image_base64)]
368 |
369 | case "get_all_pages_path_and_method":
370 | all_pages_path = mini.app.get_all_pages_path()
371 | with open(os.path.join(project_path, "app.json"), "r", encoding="utf-8") as file: # 建议指定 encoding
372 | app = json.load(file) # 解析 JSON 文件 → Python 字典/列表
373 | tabbar = app.get('tabBar').get('list')
374 | result = []
375 | for path in all_pages_path:
376 | if path in [item.get('pagePath') for item in tabbar]:
377 | result.append({
378 | "path": f"/{path}",
379 | "method": "switch_tab"
380 | })
381 | else:
382 | result.append({
383 | "path": f"/{path}",
384 | "method": "navigate_to"
385 | })
386 | return [types.TextContent(type="text", text=f"```json\n{json.dumps(result, indent=4, ensure_ascii=False)}```")]
387 | case "get_navigate_method_of_page":
388 | with open(os.path.join(project_path, "app.json"), "r", encoding="utf-8") as file: # 建议指定 encoding
389 | app = json.load(file) # 解析 JSON 文件 → Python 字典/列表
390 | tabbar = app.get('tabBar').get('list')
391 | print(tabbar)
392 | if arguments["path"] in [item.get('pagePath') for item in tabbar]:
393 | return [types.TextContent(type="text", text=f"Success: switch_tab")]
394 | else:
395 | return [types.TextContent(type="text", text=f"Success: navigate_to")]
396 |
397 | case "go_home":
398 | mini.app.go_home()
399 | return [types.TextContent(type="text", text=f"Success: Go home")]
400 | case "navigate_to":
401 | mini.app.navigate_to(arguments["path"], arguments["query"])
402 | return [types.TextContent(type="text", text=f"Success: Navigate to")]
403 | case "navigate_back":
404 | mini.app.navigate_back()
405 | return [types.TextContent(type="text", text=f"Success: Navigate back")]
406 | case "switch_tab":
407 | mini.app.switch_tab(arguments["path"])
408 | return [types.TextContent(type="text", text=f"Success: Switch tab")]
409 | case "redirect_to":
410 | mini.app.redirect_to(arguments["path"], arguments["query"])
411 | return [types.TextContent(type="text", text=f"Success: Redirect to")]
412 |
413 | case "evaluate":
414 | msg_id = mini.app.evaluate(arguments["code"], arguments["params"], sync=False)
415 | # 你可以做一些其他操作后, 再通过get_async_response方法获取前面注入代码的运行结果
416 | result = mini.app.get_async_response(msg_id, 5)
417 | return [types.TextContent(type="text", text=f"Success: Evaluate, Result: {result}")]
418 | case "call_method":
419 | page = mini.app.get_current_page()
420 | result = page.call_method(arguments["method"], arguments["params"])
421 | return [types.TextContent(type="text", text=f"Success: Call method, Result: {result}")]
422 | case "page_scroll_to":
423 | page = mini.app.get_current_page()
424 | page.scroll_to(arguments["top"], arguments["duration"])
425 | return [types.TextContent(type="text", text=f"Success: Page scroll to, Top: {arguments['top']}, Duration: {arguments['duration']}")]
426 | case "page_get_wxml":
427 | page = mini.app.get_current_page()
428 | wxml = page.wxml
429 | # 分离wxml和css
430 | # 查询最后一个tag的位置
431 | last_tag_index = wxml.rfind("</")
432 | # 分割wxml和css
433 | wxml_content = wxml[:last_tag_index]
434 | css_content = wxml[last_tag_index:]
435 | first_tag_index = css_content.find(">")
436 | wxml_content += css_content[:first_tag_index+1]
437 | css_content = css_content[first_tag_index+1:]
438 |
439 | return [types.TextContent(type="text", text=f"```xml\n{wxml_content}```\n\n```css\n{css_content}```")]
440 | case "page_get_data":
441 | page = mini.app.get_current_page()
442 | return [types.TextContent(type="text", text=f"```json\n{json.dumps(page.data, indent=4, ensure_ascii=False)}```")]
443 | case "page_set_data":
444 | page = mini.app.get_current_page()
445 | data = page.data
446 | data[arguments['key']] = arguments['value']
447 | return [types.TextContent(type="text", text=f"```json\n{json.dumps(page.data, indent=4, ensure_ascii=False)}```")]
448 |
449 | case "tap":
450 | page = mini.app.get_current_page()
451 | el = page.get_element(arguments["element"])
452 | el.tap()
453 | return [types.TextContent(type="text", text=f"Success: Tap")]
454 | case "long_press":
455 | page = mini.app.get_current_page()
456 | el = page.get_element(arguments["element"])
457 | el.long_press()
458 | return [types.TextContent(type="text", text=f"Success: Long press")]
459 | case "move":
460 | page = mini.app.get_current_page()
461 | el = page.get_element(arguments["element"])
462 | el.move(arguments["left"], arguments["top"])
463 | return [types.TextContent(type="text", text=f"Success: Move to, Top: {arguments['top']}, Left: {arguments['left']}")]
464 | case "input":
465 | page = mini.app.get_current_page()
466 | el = page.get_element(arguments["element"])
467 | el.input(arguments["text"])
468 | return [types.TextContent(type="text", text=f"Success: Input, Text: {arguments['text']}")]
469 | case "switch":
470 | page = mini.app.get_current_page()
471 | el = page.get_element(arguments["element"])
472 | el.switch()
473 | return [types.TextContent(type="text", text=f"Success: Switch")]
474 | case "slide_to":
475 | page = mini.app.get_current_page()
476 | el = page.get_element(arguments["element"])
477 | el.slide_to(arguments["value"])
478 | return [types.TextContent(type="text", text=f"Success: Slide to, Value: {arguments['value']}")]
479 | case "pick":
480 | page = mini.app.get_current_page()
481 | el = page.get_element(arguments["element"])
482 | el.pick(arguments["option"])
483 | return [types.TextContent(type="text", text=f"Success: Pick, Option: {arguments['option']}")]
484 | case _:
485 | raise ValueError(f"Unknown tool: {name}")
486 | except Exception as e:
487 | logger.error(f"Error executing tool: {e}")
488 | return [types.TextContent(type="text", text=f"Error: {str(e)}")]
489 |
490 | async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
491 | logger.info("Server running with stdio transport")
492 | await server.run(
493 | read_stream,
494 | write_stream,
495 | InitializationOptions(
496 | server_name="minium",
497 | server_version="0.1.0",
498 | capabilities=server.get_capabilities(
499 | notification_options=NotificationOptions(),
500 | experimental_capabilities={},
501 | ),
502 | ),
503 | )
504 |
```