uiautomator2-mcp-server 0.1.1__py3-none-any.whl → 0.1.2__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.
u2mcp/__main__.py CHANGED
@@ -1,55 +1,77 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  import logging
5
- from typing import Annotated, Any, Awaitable
4
+ import re
5
+ import secrets
6
+ from typing import Annotated, Any, Literal
6
7
 
7
8
  import typer
8
9
 
9
- logging.basicConfig(
10
- level=logging.INFO,
11
- format="[%(asctime)s] %(levelname)s %(name)s - %(message)s",
12
- handlers=[logging.StreamHandler()],
13
- force=True,
14
- )
15
-
16
- logging.getLogger("mcp.server").setLevel(logging.WARNING)
17
- logging.getLogger("sse_starlette").setLevel(logging.WARNING)
18
- logging.getLogger("docket").setLevel(logging.WARNING)
19
- logging.getLogger("fakeredis").setLevel(logging.WARNING)
20
-
21
10
 
22
11
  def run(
23
- http: Annotated[bool, typer.Option("--http", "-h", help="Run mcp server in streamable http mode")] = False,
24
- stdio: Annotated[bool, typer.Option("--stdio", "-s", help="Run mcp server in stdio mode")] = False,
25
- host: Annotated[str | None, typer.Option("--host", "-H", show_default=False, help="Host address for http mode")] = None,
26
- port: Annotated[int | None, typer.Option("--port", "-p", show_default=False, help="Port number for http mode")] = None,
27
- log_level: Annotated[str | None, typer.Option("--log-level", "-l", help="Log level")] = None,
12
+ transport: Annotated[
13
+ Literal["http", "stdio"], typer.Argument(help="Run mcp server on streamable-http http or stdio transport")
14
+ ] = "stdio",
15
+ host: Annotated[
16
+ str, typer.Option("--host", "-H", show_default=False, help="Host address of streamable-http transport")
17
+ ] = "127.0.0.1",
18
+ port: Annotated[
19
+ int, typer.Option("--port", "-p", show_default=False, help="Port number of streamable-http transport")
20
+ ] = 8000,
21
+ json_response: Annotated[bool, typer.Option("--json-response", "-j", help="Whether to use JSON response format")] = True,
22
+ log_level: Annotated[
23
+ Literal["debug", "info", "warning", "error", "critical"], typer.Option("--log-level", "-l", help="Log level")
24
+ ] = "info",
25
+ no_token: Annotated[
26
+ bool,
27
+ typer.Option(
28
+ "--no-token",
29
+ help="Disable authentication bearer token verification of streamable-http transport. If not set, a token will be generated randomly.",
30
+ ),
31
+ ] = False,
32
+ token: Annotated[
33
+ str | None,
34
+ typer.Option("--token", "-t", help="Explicit set token of streamable-http authentication"),
35
+ ] = None,
28
36
  ):
29
37
  """Run uiautomator2 mcp server"""
30
- if not http and not stdio:
31
- typer.Abort("Please specify one of ‘--http’ or ‘--stdio’")
38
+ logging.basicConfig(
39
+ level=log_level.upper(),
40
+ format="[%(asctime)s] %(levelname)s %(name)s - %(message)s",
41
+ handlers=[logging.StreamHandler()],
42
+ force=True,
43
+ )
44
+
45
+ logging.getLogger("mcp.server").setLevel(logging.WARNING)
46
+ logging.getLogger("sse_starlette").setLevel(logging.WARNING)
47
+ logging.getLogger("docket").setLevel(logging.WARNING)
48
+ logging.getLogger("fakeredis").setLevel(logging.WARNING)
32
49
 
33
50
  from . import tools as _
34
- from .mcp import mcp
51
+ from .mcp import mcp, update_params
52
+
53
+ transport_kwargs: dict[str, Any] = {"json_response": json_response}
35
54
 
36
- awaitables: list[Awaitable] = []
55
+ update_params(transport=transport)
56
+
57
+ if transport == "http":
58
+ if token:
59
+ token = token.strip()
60
+ if not re.match(r"^[a-zA-Z0-9\-_.~!$&'()*+,;=:@]{8,64}$", token):
61
+ raise typer.BadParameter("Token must be 8-64 characters long and can only contain URL-safe characters")
62
+ elif not no_token:
63
+ token = secrets.token_urlsafe()
64
+ if token:
65
+ update_params(token=token, host=host, port=port)
37
66
 
38
- if http:
39
- transport_kwargs: dict[str, Any] = {}
40
67
  if host:
41
68
  transport_kwargs["host"] = host
42
69
  if port:
43
70
  transport_kwargs["port"] = port
44
- awaitables.append(mcp.run_http_async(transport="streamable-http", **transport_kwargs, log_level=log_level))
45
-
46
- if stdio:
47
- awaitables.append(mcp.run_stdio_async(log_level=log_level))
48
-
49
- async def _run():
50
- await asyncio.gather(*awaitables)
51
71
 
52
- asyncio.run(_run())
72
+ mcp.run(transport="streamable-http", **transport_kwargs, log_level=log_level)
73
+ else:
74
+ mcp.run(log_level=log_level)
53
75
 
54
76
 
55
77
  def main():
u2mcp/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.1'
32
- __version_tuple__ = version_tuple = (0, 1, 1)
31
+ __version__ = version = '0.1.2'
32
+ __version_tuple__ = version_tuple = (0, 1, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
u2mcp/mcp.py CHANGED
@@ -11,8 +11,51 @@ Before performing operations on a device, you need to initialize it using the in
11
11
  All operations require a device serial number to identify the target device.
12
12
  """
13
13
 
14
+ from contextlib import asynccontextmanager
15
+ from textwrap import dedent
16
+ from typing import Any
17
+
14
18
  from fastmcp import FastMCP
19
+ from fastmcp.server.auth import AccessToken, AuthProvider
20
+ from rich.console import Console
21
+ from rich.markdown import Markdown
15
22
 
16
23
  __all__ = ["mcp"]
17
24
 
18
- mcp = FastMCP(name="uiautomator2", instructions=__doc__)
25
+ _params: dict[str, Any] = {}
26
+
27
+
28
+ def update_params(**kwargs):
29
+ global _params
30
+ _params.update(kwargs)
31
+
32
+
33
+ @asynccontextmanager
34
+ async def _lifespan(instance: FastMCP):
35
+ if _params.get("transport") == "http" and (token := _params.get("token")):
36
+ content = dedent(f"""
37
+ ------
38
+
39
+ **Server configured with authentication token. Connect using this token in the Authorization header:**
40
+
41
+ `Authorization: Bearer {token}`
42
+
43
+ ------
44
+ """).strip()
45
+ Console().print(Markdown(content))
46
+
47
+ yield
48
+
49
+
50
+ class _SimpleTokenAuthProvider(AuthProvider):
51
+ _scopes = ["mcp:tools"]
52
+
53
+ async def verify_token(self, token: str) -> AccessToken | None:
54
+ if server_token := _params.get("token"):
55
+ if token == server_token:
56
+ return AccessToken(token=token, client_id="user", scopes=self._scopes)
57
+ return None
58
+ return AccessToken(token=token, client_id="user", scopes=self._scopes)
59
+
60
+
61
+ mcp = FastMCP(name="uiautomator2", instructions=__doc__, lifespan=_lifespan, auth=_SimpleTokenAuthProvider())
u2mcp/tools/device.py CHANGED
@@ -10,8 +10,7 @@ from typing import Any, Literal
10
10
 
11
11
  import uiautomator2 as u2
12
12
  from adbutils import adb
13
- from fastmcp.dependencies import CurrentContext
14
- from fastmcp.server.context import Context
13
+ from fastmcp.server.dependencies import get_context
15
14
  from fastmcp.utilities.logging import get_logger
16
15
  from PIL.Image import Image
17
16
 
@@ -62,7 +61,7 @@ async def device_list() -> list[dict[str, Any]]:
62
61
 
63
62
 
64
63
  @mcp.tool("init")
65
- async def init(serial: str = "", ctx: Context = CurrentContext()):
64
+ async def init(serial: str = ""):
66
65
  """Install essential resources to device.
67
66
 
68
67
  Important:
@@ -111,6 +110,8 @@ async def init(serial: str = "", ctx: Context = CurrentContext()):
111
110
 
112
111
  logger.info("read uiautomator2 init command stdio")
113
112
 
113
+ ctx = get_context()
114
+
114
115
  while True:
115
116
  tag, line = await output_queue.get()
116
117
 
@@ -1,25 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uiautomator2-mcp-server
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: uiautomator2 mcp server
5
- Author: tanbro
6
- License: GPL-3.0-or-later
5
+ Author-email: tanbro <tanbro@163.com>
6
+ License-Expression: GPL-3.0-or-later
7
7
  Project-URL: homepage, https://github.com/tanbro/uiautomator2-mcp-server
8
8
  Project-URL: documentation, https://github.com/tanbro/uiautomator2-mcp-server/blob/main/README.md
9
9
  Project-URL: repository, https://github.com/tanbro/uiautomator2-mcp-server.git
10
10
  Project-URL: issues, https://github.com/tanbro/uiautomator2-mcp-server/issues
11
11
  Project-URL: changelog, https://github.com/tanbro/uiautomator2-mcp-server/blob/main/CHANGELOG.md
12
+ Keywords: uiautomator2,mcp,fastmcp,adb,android,adbutils
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
12
19
  Requires-Python: >=3.11
13
20
  Description-Content-Type: text/markdown
14
21
  License-File: LICENSE
15
- Requires-Dist: fastmcp<3.0,>=2.10
22
+ Requires-Dist: fastmcp<3.0,>=2.11.0
16
23
  Requires-Dist: uiautomator2<4.0,>=3.5
17
24
  Dynamic: license-file
18
25
 
19
26
  # uiautomator2-mcp-server
20
27
 
21
- ![GitHub Tag](https://img.shields.io/github/v/tag/tanbro/uiautomator2-mcp-server)
22
- ![PyPI - Version](https://img.shields.io/pypi/v/uiautomator2-mcp-server)
28
+ [![GitHub Tag](https://img.shields.io/github/v/tag/tanbro/uiautomator2-mcp-server)](https://github.com/tanbro/uiautomator2-mcp-server)
29
+ [![PyPI - Version](https://img.shields.io/pypi/v/uiautomator2-mcp-server)](https://pypi.org/project/uiautomator2-mcp-server/)
23
30
 
24
31
  A MCP (Model Context Protocol) server that provides tools for controlling and interacting with Android devices using uiautomator2. This server allows you to perform various operations on Android devices such as connecting to devices, taking screenshots, getting device information, accessing UI hierarchy, tap on screens, and more.
25
32
 
@@ -36,8 +43,8 @@ A MCP (Model Context Protocol) server that provides tools for controlling and in
36
43
  ## Requirements
37
44
 
38
45
  - Python >= 3.11
39
- - Android device with ADB access
40
- - Android device with uiautomator2 resources installed
46
+ - adb executable in your PATH
47
+ - Android device connected in debug mode
41
48
 
42
49
  ## Installation
43
50
 
@@ -54,10 +61,10 @@ The server can be run in different transport modes:
54
61
 
55
62
  ```bash
56
63
  # Run in streamable HTTP mode
57
- u2mcp --http --host 0.0.0.0 --port 8000
64
+ u2mcp --host 0.0.0.0 --port 8000 --no-token http
58
65
 
59
66
  # Run in stdio mode
60
- u2mcp --stdio
67
+ u2mcp stdio
61
68
  ```
62
69
 
63
70
  ### Using the Tools
@@ -0,0 +1,16 @@
1
+ u2mcp/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
2
+ u2mcp/__init__.py,sha256=xsioVkbaN9sg-us_kNMeH-DojD8xM-h6Nlhde6uRHCQ,104
3
+ u2mcp/__main__.py,sha256=CBZpaNarWiWkg-g-BCfwFxaMdP4EqG5ckHtc-ZMMwcE,2813
4
+ u2mcp/_version.py,sha256=UPabfftDNarXNRt7p9IXx9_aYhCVscDiY35GxOcfT8s,738
5
+ u2mcp/mcp.py,sha256=jP87QaNePDt3LGzdZnlI5W8r8P-I0NrH3Tyj336ZQO8,2017
6
+ u2mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ u2mcp/tools/__init__.py,sha256=tKfsoF6tYQSapfGC1ybEcU4S77cipyfO5tYR6u0eAlI,66
8
+ u2mcp/tools/action.py,sha256=A9dCED1NiKOJpw9lBewOQ9YQvfGOkK8bEL1L9FLjCQg,5088
9
+ u2mcp/tools/app.py,sha256=b-cOdDtjSKC8KeUK5TTJCPRXtzvwSH74P02Cm3mS-ck,6809
10
+ u2mcp/tools/device.py,sha256=herrj5IQ1jEav2W6K4i7UidJ7eQsg-WJCiHJD1qYKVA,9196
11
+ uiautomator2_mcp_server-0.1.2.dist-info/licenses/LICENSE,sha256=ea2KuPdrBJ0Dhw1ncbc7siZDKG4cz2z-_8cSQiu4n1g,33122
12
+ uiautomator2_mcp_server-0.1.2.dist-info/METADATA,sha256=9BxLeh6VjVBCtTx4S6w4YMcl2Nu7aFv8lfUJZpbk44s,4251
13
+ uiautomator2_mcp_server-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ uiautomator2_mcp_server-0.1.2.dist-info/entry_points.txt,sha256=y7lg94G2U5_VgrDtyY8-Ne4ZClFtHo6eAs3RnShuDSI,92
15
+ uiautomator2_mcp_server-0.1.2.dist-info/top_level.txt,sha256=elTNF05b2GS8jjS4Haa3ESnG6xbfRjpyoSRsivT0Uks,6
16
+ uiautomator2_mcp_server-0.1.2.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- u2mcp/.gitignore,sha256=mr9Izcwvjgv215xjRKhWEZ7vsyrKWhMqvWjSLHRYDjk,13
2
- u2mcp/__init__.py,sha256=xsioVkbaN9sg-us_kNMeH-DojD8xM-h6Nlhde6uRHCQ,104
3
- u2mcp/__main__.py,sha256=I5ON-vrBr8rGWblondFcP2HhuQKhDPLn1y40YYjCWng,1943
4
- u2mcp/_version.py,sha256=qS3Rb8IU851vIlLXMW4dseE1Fx3Ssytyif_1QTy34YM,738
5
- u2mcp/mcp.py,sha256=JSNkfVviZqeh-Io48STjRuI9jJYcw7eo-o80z6zOSQs,739
6
- u2mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- u2mcp/tools/__init__.py,sha256=tKfsoF6tYQSapfGC1ybEcU4S77cipyfO5tYR6u0eAlI,66
8
- u2mcp/tools/action.py,sha256=A9dCED1NiKOJpw9lBewOQ9YQvfGOkK8bEL1L9FLjCQg,5088
9
- u2mcp/tools/app.py,sha256=b-cOdDtjSKC8KeUK5TTJCPRXtzvwSH74P02Cm3mS-ck,6809
10
- u2mcp/tools/device.py,sha256=yN1mWMJeIh6cMQA3m5HE3VqAkaIJOyfNIuNgWn-xoSU,9242
11
- uiautomator2_mcp_server-0.1.1.dist-info/licenses/LICENSE,sha256=ea2KuPdrBJ0Dhw1ncbc7siZDKG4cz2z-_8cSQiu4n1g,33122
12
- uiautomator2_mcp_server-0.1.1.dist-info/METADATA,sha256=yw6XCj9zVj2jS108gHaCCZtQl3bOptakor8NpYO-tEg,3761
13
- uiautomator2_mcp_server-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- uiautomator2_mcp_server-0.1.1.dist-info/entry_points.txt,sha256=y7lg94G2U5_VgrDtyY8-Ne4ZClFtHo6eAs3RnShuDSI,92
15
- uiautomator2_mcp_server-0.1.1.dist-info/top_level.txt,sha256=elTNF05b2GS8jjS4Haa3ESnG6xbfRjpyoSRsivT0Uks,6
16
- uiautomator2_mcp_server-0.1.1.dist-info/RECORD,,