fix(mcp): auto-register tools on startup and support cross-platform setup for all major AI IDEs
- Fix missing mcp_server.tools import on server initialization so mobile_run_task, mobile_manage_task, mobile_get_device_state, and mobile_inspect_trace are automatically registered. - Inject PYTHONPATH in generated and installed MCP configurations to guarantee Python module resolution across Windows, macOS, and Linux regardless of IDE CWD. - Expand artemis mcp --install and --generate-config to support VS Code, Cline, Roo Code, Claude Code CLI (~/.claude.json), Cursor, Windsurf, Antigravity, and OpenClaw. - Enhance config file merging with robust exception handling and fallback initialization.
This commit is contained in:
1 parent
fc6ddad655
commit
9e5aee85c8
12 files changed
+202
-67
No files matched your search
@@ -94,7 +94,7 @@ git clone https://github.com/google/artemis.git && cd artemis
|
|||||||
start.bat
|
start.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 **Tip**: Opens `http://localhost:8000` in your default browser with a device connection wizard, live screen mirroring, prompt sandbox, and execution replays. You can also run directly from CLI: `artemis run "Open Settings, find Battery and tell me current level" --profile flash`.
|
> 💡 **Tip**: Opens `http://localhost:8000` in your default browser with a device connection wizard, live screen mirroring, prompt sandbox, and execution replays. You can also run directly from CLI: `uv run artemis run "Open Settings, find Battery and tell me current level" --profile flash`.
|
||||||
|
|
||||||
<a id="mcp-setup"></a>
|
<a id="mcp-setup"></a>
|
||||||
<a id="mcp"></a>
|
<a id="mcp"></a>
|
||||||
@@ -107,21 +107,22 @@ ARTEMIS includes a native **Model Context Protocol (MCP)** server. Connect your
|
|||||||
|
|
||||||
### 1. One-Click Auto Install (Recommended)
|
### 1. One-Click Auto Install (Recommended)
|
||||||
|
|
||||||
Run the built-in installer to automatically configure or update your AI IDE (automatically resolves your virtual environment Python and repository path):
|
Running `./start.sh` or `start.bat` will prompt you to configure MCP for detected IDEs (or you can install/update anytime later manually using the commands below):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Auto-install for Antigravity / Jetski:
|
# Auto-install for Antigravity / Jetski:
|
||||||
artemis mcp --install antigravity
|
uv run artemis mcp --install antigravity
|
||||||
|
|
||||||
# Or install for all detected AI IDEs (Antigravity, Cursor, Claude Desktop/Code, OpenClaw):
|
# Or install for all detected AI IDEs (Antigravity, Cursor, Claude Desktop/Code, OpenClaw):
|
||||||
artemis mcp --install all
|
uv run artemis mcp --install all
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 **Tip**: You can also configure MCP interactively during first-time setup via `artemis init`.
|
> 💡 **Tip**: You can also configure MCP interactively during first-time setup via `uv run artemis init`.
|
||||||
|
> **Pro Tip**: If you want to use the `artemis` command globally without `uv run` in any directory, run `uv tool install -e .` once in the project root.
|
||||||
|
|
||||||
### 2. Manual Configuration (Optional)
|
### 2. Manual Configuration (Optional)
|
||||||
|
|
||||||
If you prefer to configure manually, run `artemis mcp --generate-config antigravity` (or `all`) to output the exact JSON snippet, or paste the following into your IDE configuration (replace `/path/to/artemis` with your actual repo path and point `command` to your `.venv` Python executable):
|
If you prefer to configure manually, run `uv run artemis mcp --generate-config antigravity` (or `all`) to output the exact JSON snippet, or paste the following into your IDE configuration (replace `/path/to/artemis` with your actual repo path and point `command` to your `.venv` Python executable):
|
||||||
|
|
||||||
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
|
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
|
||||||
```json
|
```json
|
||||||
@@ -217,9 +218,9 @@ if __name__ == "__main__":
|
|||||||
<sub>💡 <b>Console Overview</b>: <b>① View Switcher</b> (Home / Workspace) · <b>② Model & Replay</b> (Flash/Pro status & video replay) · <b>③ Live Agent Stream</b> (Action perception, target coordinates & structured results) · <b>④ Prompt Dock</b> (Natural language dispatch) · <b>⑤ Task Queue & Dashboard</b> (Lifecycle & history)</sub>
|
<sub>💡 <b>Console Overview</b>: <b>① View Switcher</b> (Home / Workspace) · <b>② Model & Replay</b> (Flash/Pro status & video replay) · <b>③ Live Agent Stream</b> (Action perception, target coordinates & structured results) · <b>④ Prompt Dock</b> (Natural language dispatch) · <b>⑤ Task Queue & Dashboard</b> (Lifecycle & history)</sub>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
* 🖥️ **Web Visual Test Console (`artemis ui`)**: Real-time screen projection and interactive panel, supporting natural language test dispatch, live reasoning telemetry, action trajectories, and execution replay;
|
* 🖥️ **Web Visual Test Console (`uv run artemis ui`)**: Real-time screen projection and interactive panel, supporting natural language test dispatch, live reasoning telemetry, action trajectories, and execution replay;
|
||||||
* 🔌 **Native MCP Protocol (IDE Collaboration)**: Operates as a standard MCP server seamlessly integrating with **Antigravity, Claude Code, Windsurf**, etc., directly driving real devices inside the IDE to verify bugs and run test cases;
|
* 🔌 **Native MCP Protocol (IDE Collaboration)**: Operates as a standard MCP server seamlessly integrating with **Antigravity, Claude Code, Windsurf**, etc., directly driving real devices inside the IDE to verify bugs and run test cases;
|
||||||
* 💻 **Developer CLI (`artemis run`)**: Direct terminal execution for automated test cases, exploratory stability inspection, or AndroidWorld benchmarks with high-fidelity structured terminal output;
|
* 💻 **Developer CLI (`uv run artemis run`)**: Direct terminal execution for automated test cases, exploratory stability inspection, or AndroidWorld benchmarks with high-fidelity structured terminal output;
|
||||||
* 🐍 **Python SDK**: Integrates as a standard Python library into existing automated testing frameworks (e.g., pytest) or CI/CD pipelines with strongly typed Pydantic structured outputs and assertion support.
|
* 🐍 **Python SDK**: Integrates as a standard Python library into existing automated testing frameworks (e.g., pytest) or CI/CD pipelines with strongly typed Pydantic structured outputs and assertion support.
|
||||||
|
|
||||||
## 📊 Head-to-Head Comparison
|
## 📊 Head-to-Head Comparison
|
||||||
|
|||||||
+9
-8
@@ -96,7 +96,7 @@ git clone https://github.com/google/artemis.git && cd artemis
|
|||||||
start.bat
|
start.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 **提示**:启动后将自动在默认浏览器中打开 Web 控制台(`http://localhost:8000`),提供设备连接向导、实时投屏、任务演练与状态回放面板。你也可以通过命令行直接运行:`artemis run "打开系统设置,找到电池选项并告诉我当前电量" --profile flash`。
|
> 💡 **提示**:启动后将自动在默认浏览器中打开 Web 控制台(`http://localhost:8000`),提供设备连接向导、实时投屏、任务演练与状态回放面板。你也可以通过命令行直接运行:`uv run artemis run "打开系统设置,找到电池选项并告诉我当前电量" --profile flash`。
|
||||||
|
|
||||||
<a id="mcp-setup"></a>
|
<a id="mcp-setup"></a>
|
||||||
<a id="mcp"></a>
|
<a id="mcp"></a>
|
||||||
@@ -109,21 +109,22 @@ ARTEMIS 内置原生 **Model Context Protocol (MCP)** 服务。只需将以下
|
|||||||
|
|
||||||
### 1. 一键自动安装到 IDE(推荐)
|
### 1. 一键自动安装到 IDE(推荐)
|
||||||
|
|
||||||
运行内置安装命令,即可自动配置或更新你的 AI IDE(会自动解析并填写虚拟环境 Python 和项目的绝对路径):
|
运行 `./start.sh` 或 `start.bat` 启动脚本时会主动询问是否需要配置当前检测到的 AI IDE(支持跳过并在之后随时手动执行以下命令挂载):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 一键安装到 Antigravity / Jetski:
|
# 一键安装到 Antigravity / Jetski:
|
||||||
artemis mcp --install antigravity
|
uv run artemis mcp --install antigravity
|
||||||
|
|
||||||
# 或一键安装到所有检测到的 AI IDE(Antigravity、Cursor、Claude Desktop/Code、OpenClaw):
|
# 或一键安装到所有检测到的 AI IDE(Antigravity、Cursor、Claude Desktop/Code、OpenClaw):
|
||||||
artemis mcp --install all
|
uv run artemis mcp --install all
|
||||||
```
|
```
|
||||||
|
|
||||||
> 💡 **提示**:你也可以在首次运行 `artemis init` 配置向导时,交互式完成 IDE 的 MCP 自动挂载。
|
> 💡 **提示**:你也可以在首次运行 `uv run artemis init` 配置向导时,交互式完成 IDE 的 MCP 自动挂载。
|
||||||
|
> **进阶提示**:如果希望在任意目录下都不需要加 `uv run` 就能全局直接使用 `artemis` 命令,可在项目根目录下执行一次 `uv tool install -e .`。
|
||||||
|
|
||||||
### 2. 手动配置(可选)
|
### 2. 手动配置(可选)
|
||||||
|
|
||||||
如果你习惯手动复制配置,可运行 `artemis mcp --generate-config antigravity`(或 `all`)获取自动计算好路径的 JSON 代码段,也可以参考以下模板将 `command` 填写为项目下 `.venv` 虚拟环境中的 Python 绝对路径,并将 `/path/to/artemis` 替换为项目实际路径:
|
如果你习惯手动复制配置,可运行 `uv run artemis mcp --generate-config antigravity`(或 `all`)获取自动计算好路径的 JSON 代码段,也可以参考以下模板将 `command` 填写为项目下 `.venv` 虚拟环境中的 Python 绝对路径,并将 `/path/to/artemis` 替换为项目实际路径:
|
||||||
|
|
||||||
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
|
* **Antigravity** (`~/.gemini/jetski/mcp_config.json`):
|
||||||
```json
|
```json
|
||||||
@@ -219,9 +220,9 @@ if __name__ == "__main__":
|
|||||||
<sub>💡 <b>控制台功能布局</b>:<b>① 顶栏视图切换</b>(主页与工作区) · <b>② 运行模式与录屏回放</b>(Flash/Pro 状态与视频回放) · <b>③ 实时感知推理流</b>(动作分解、点击坐标与结构化总结) · <b>④ 自然语言下发胶囊</b>(自然语言驱动真机) · <b>⑤ 任务队列看板</b>(状态流转与历史回溯)</sub>
|
<sub>💡 <b>控制台功能布局</b>:<b>① 顶栏视图切换</b>(主页与工作区) · <b>② 运行模式与录屏回放</b>(Flash/Pro 状态与视频回放) · <b>③ 实时感知推理流</b>(动作分解、点击坐标与结构化总结) · <b>④ 自然语言下发胶囊</b>(自然语言驱动真机) · <b>⑤ 任务队列看板</b>(状态流转与历史回溯)</sub>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
* 🖥️ **Web 可视化测试控制台 (`artemis ui`)**:集成设备实时投屏与交互面板,支持通过自然语言下发测试用例,实时观测推理步骤、操作轨迹、截图留存与异常状态回放;
|
* 🖥️ **Web 可视化测试控制台 (`uv run artemis ui`)**:集成设备实时投屏与交互面板,支持通过自然语言下发测试用例,实时观测推理步骤、操作轨迹、截图留存与异常状态回放;
|
||||||
* 🔌 **MCP 协议集成 (AI IDE 协同)**:作为标准 MCP 服务器无缝接入 **Antigravity、Claude Code、Windsurf** 等开发环境,在 IDE 中直接驱动真机完成自动化测试与 Bug 复现验证;
|
* 🔌 **MCP 协议集成 (AI IDE 协同)**:作为标准 MCP 服务器无缝接入 **Antigravity、Claude Code、Windsurf** 等开发环境,在 IDE 中直接驱动真机完成自动化测试与 Bug 复现验证;
|
||||||
* 💻 **命令行工具 (`artemis run`)**:支持通过 CLI 直接执行自动化用例、稳定性巡检或 AndroidWorld 基准评测,提供高保真结构化终端输出;
|
* 💻 **命令行工具 (`uv run artemis run`)**:支持通过 CLI 直接执行自动化用例、稳定性巡检或 AndroidWorld 基准评测,提供高保真结构化终端输出;
|
||||||
* 🐍 **Python SDK**:作为标准 Python 库无缝集成至现有自动化测试框架(如 pytest)或 CI/CD 流水线,提供基于 Pydantic 的强类型结构化结果与断言支持。
|
* 🐍 **Python SDK**:作为标准 Python 库无缝集成至现有自动化测试框架(如 pytest)或 CI/CD 流水线,提供基于 Pydantic 的强类型结构化结果与断言支持。
|
||||||
|
|
||||||
## 📊 方案横向对比
|
## 📊 方案横向对比
|
||||||
|
|||||||
@@ -140,13 +140,21 @@ def init_command() -> None:
|
|||||||
console.print(" [2] Cursor")
|
console.print(" [2] Cursor")
|
||||||
console.print(" [3] Claude Desktop / Claude Code")
|
console.print(" [3] Claude Desktop / Claude Code")
|
||||||
console.print(" [4] Windsurf")
|
console.print(" [4] Windsurf")
|
||||||
console.print(" [5] All supported IDEs")
|
console.print(" [5] VS Code / Cline / Roo Code")
|
||||||
console.print(" [6] Skip for now")
|
console.print(" [6] All supported IDEs")
|
||||||
|
console.print(" [7] Skip for now")
|
||||||
mcp_choice = Prompt.ask(
|
mcp_choice = Prompt.ask(
|
||||||
"Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6"], default="1"
|
"Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6", "7"], default="1"
|
||||||
)
|
)
|
||||||
if mcp_choice != "6":
|
if mcp_choice != "7":
|
||||||
target_map = {"1": "antigravity", "2": "cursor", "3": "claude", "4": "windsurf", "5": "all"}
|
target_map = {
|
||||||
|
"1": "antigravity",
|
||||||
|
"2": "cursor",
|
||||||
|
"3": "claude",
|
||||||
|
"4": "windsurf",
|
||||||
|
"5": "vscode",
|
||||||
|
"6": "all",
|
||||||
|
}
|
||||||
target = target_map.get(mcp_choice, "antigravity")
|
target = target_map.get(mcp_choice, "antigravity")
|
||||||
try:
|
try:
|
||||||
from artemis.interfaces.cli.commands.mcp import install_mcp_config
|
from artemis.interfaces.cli.commands.mcp import install_mcp_config
|
||||||
@@ -167,11 +175,11 @@ def init_command() -> None:
|
|||||||
f"[bold green]🎉 Setup completed successfully![/bold green]\n\n"
|
f"[bold green]🎉 Setup completed successfully![/bold green]\n\n"
|
||||||
f"Configured [bold cyan]{provider_name}[/bold cyan] in [dim]{env_path}[/dim]\n\n"
|
f"Configured [bold cyan]{provider_name}[/bold cyan] in [dim]{env_path}[/dim]\n\n"
|
||||||
"Try running your first task:\n"
|
"Try running your first task:\n"
|
||||||
' [bold cyan]artemis run "Open YouTube and search for Lo-Fi Hip Hop"[/bold cyan]\n\n'
|
' [bold cyan]uv run artemis run "Open YouTube and search for Lo-Fi Hip Hop"[/bold cyan]\n\n'
|
||||||
"Or connect/update your AI IDE anytime with:\n"
|
"Or connect/update your AI IDE anytime with:\n"
|
||||||
" [bold cyan]artemis mcp --install all[/bold cyan]\n\n"
|
" [bold cyan]uv run artemis mcp --install all[/bold cyan]\n\n"
|
||||||
"Or run health diagnostics anytime with:\n"
|
"Or run health diagnostics anytime with:\n"
|
||||||
" [bold cyan]artemis doctor[/bold cyan]",
|
" [bold cyan]uv run artemis doctor[/bold cyan]",
|
||||||
title="☕ Ready to Go!",
|
title="☕ Ready to Go!",
|
||||||
expand=False,
|
expand=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import sys
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from mcp_server.base import mcp as agent_mcp
|
from mcp_server.base import mcp as agent_mcp
|
||||||
|
import mcp_server.tools # noqa: F401
|
||||||
from mcp_server.utils import env_utils
|
from mcp_server.utils import env_utils
|
||||||
from artemis.mcp.adb_server import mcp as adb_mcp
|
from artemis.mcp.adb_server import mcp as adb_mcp
|
||||||
from artemis.utils.logger import get_logger
|
from artemis.utils.logger import get_logger
|
||||||
@@ -32,6 +33,17 @@ logger = get_logger(__name__)
|
|||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_vscode_user_dir() -> Path:
|
||||||
|
"""Returns the platform-specific VS Code user configuration directory."""
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return Path.home() / "Library" / "Application Support" / "Code" / "User"
|
||||||
|
elif sys.platform == "win32":
|
||||||
|
appdata = os.environ.get("APPDATA") or str(Path.home() / "AppData" / "Roaming")
|
||||||
|
return Path(appdata) / "Code" / "User"
|
||||||
|
else:
|
||||||
|
return Path.home() / ".config" / "Code" / "User"
|
||||||
|
|
||||||
|
|
||||||
def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict:
|
def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict:
|
||||||
"""Generates MCP configuration dictionary for the specified client."""
|
"""Generates MCP configuration dictionary for the specified client."""
|
||||||
config_body = {
|
config_body = {
|
||||||
@@ -40,6 +52,7 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
|
|||||||
"cwd": project_root,
|
"cwd": project_root,
|
||||||
"env": {
|
"env": {
|
||||||
"PYTHONUNBUFFERED": "1",
|
"PYTHONUNBUFFERED": "1",
|
||||||
|
"PYTHONPATH": project_root,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,38 +70,44 @@ def _get_config_snippet(client: str, python_exe: str, project_root: str) -> dict
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elif client in ("cursor", "windsurf"):
|
|
||||||
return {"mcpServers": {"artemis": config_body}}
|
|
||||||
elif client in ("claude", "claude_code", "claude_desktop"):
|
|
||||||
return {"mcpServers": {"artemis": config_body}}
|
|
||||||
elif client == "openclaw":
|
elif client == "openclaw":
|
||||||
return {"plugins": {"artemis_mcp": {"enabled": True, "type": "mcp", "server": config_body}}}
|
return {"plugins": {"artemis_mcp": {"enabled": True, "type": "mcp", "server": config_body}}}
|
||||||
else: # generic
|
else: # cursor, windsurf, claude, vscode, cline, roo, generic
|
||||||
return {"mcpServers": {"artemis": config_body}}
|
return {"mcpServers": {"artemis": config_body}}
|
||||||
|
|
||||||
|
|
||||||
def _merge_json_file(file_path: Path, server_name: str, server_config: dict, key_name: str = "mcpServers") -> bool:
|
def _merge_json_file(file_path: Path, server_name: str, server_config: dict, key_name: str = "mcpServers") -> bool:
|
||||||
"""Merges server configuration into a target JSON file without overwriting existing servers."""
|
"""Merges server configuration into a target JSON file without overwriting existing servers."""
|
||||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
try:
|
||||||
data: dict = {}
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
if file_path.exists() and file_path.stat().st_size > 0:
|
data: dict = {}
|
||||||
try:
|
if file_path.exists() and file_path.stat().st_size > 0:
|
||||||
data = json.loads(file_path.read_text(encoding="utf-8"))
|
try:
|
||||||
except Exception:
|
data = json.loads(file_path.read_text(encoding="utf-8"))
|
||||||
data = {}
|
if not isinstance(data, dict):
|
||||||
|
data = {}
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
|
||||||
if key_name not in data or not isinstance(data.get(key_name), dict):
|
if key_name not in data or not isinstance(data.get(key_name), dict):
|
||||||
data[key_name] = {}
|
data[key_name] = {}
|
||||||
|
|
||||||
data[key_name][server_name] = server_config
|
data[key_name][server_name] = server_config
|
||||||
file_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
file_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||||
return True
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not update MCP config file {file_path}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[str]:
|
def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[str]:
|
||||||
"""Auto-installs/merges ARTEMIS MCP configuration into IDE config files."""
|
"""Auto-installs/merges ARTEMIS MCP configuration into IDE config files across any OS."""
|
||||||
installed_paths: list[str] = []
|
installed_paths: list[str] = []
|
||||||
targets = ["antigravity", "cursor", "claude", "windsurf", "openclaw"] if client == "all" else [client]
|
targets = (
|
||||||
|
["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw"]
|
||||||
|
if client == "all"
|
||||||
|
else [client]
|
||||||
|
)
|
||||||
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
snippet = _get_config_snippet(target, python_exe, project_root)
|
snippet = _get_config_snippet(target, python_exe, project_root)
|
||||||
@@ -96,9 +115,10 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[
|
|||||||
server_cfg = snippet["mcpServers"]["artemis"]
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
jetski_path = Path.home() / ".gemini" / "jetski" / "mcp_config.json"
|
jetski_path = Path.home() / ".gemini" / "jetski" / "mcp_config.json"
|
||||||
config_path = Path.home() / ".gemini" / "config" / "mcp_config.json"
|
config_path = Path.home() / ".gemini" / "config" / "mcp_config.json"
|
||||||
_merge_json_file(jetski_path, "artemis", server_cfg)
|
if _merge_json_file(jetski_path, "artemis", server_cfg):
|
||||||
_merge_json_file(config_path, "artemis", server_cfg)
|
installed_paths.append(str(jetski_path))
|
||||||
installed_paths.extend([str(jetski_path), str(config_path)])
|
if _merge_json_file(config_path, "artemis", server_cfg):
|
||||||
|
installed_paths.append(str(config_path))
|
||||||
elif target in ("claude", "claude_code", "claude_desktop"):
|
elif target in ("claude", "claude_code", "claude_desktop"):
|
||||||
server_cfg = snippet["mcpServers"]["artemis"]
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
@@ -108,23 +128,43 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[
|
|||||||
claude_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
|
claude_path = Path(appdata) / "Claude" / "claude_desktop_config.json"
|
||||||
else:
|
else:
|
||||||
claude_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
claude_path = Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
||||||
_merge_json_file(claude_path, "artemis", server_cfg)
|
if _merge_json_file(claude_path, "artemis", server_cfg):
|
||||||
installed_paths.append(str(claude_path))
|
installed_paths.append(str(claude_path))
|
||||||
|
|
||||||
|
# Also install to Claude Code CLI global config (~/.claude.json)
|
||||||
|
claude_code_path = Path.home() / ".claude.json"
|
||||||
|
if _merge_json_file(claude_code_path, "artemis", server_cfg):
|
||||||
|
installed_paths.append(str(claude_code_path))
|
||||||
elif target == "cursor":
|
elif target == "cursor":
|
||||||
server_cfg = snippet["mcpServers"]["artemis"]
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
cursor_path = Path.home() / ".cursor" / "mcp.json"
|
cursor_path = Path.home() / ".cursor" / "mcp.json"
|
||||||
_merge_json_file(cursor_path, "artemis", server_cfg)
|
if _merge_json_file(cursor_path, "artemis", server_cfg):
|
||||||
installed_paths.append(str(cursor_path))
|
installed_paths.append(str(cursor_path))
|
||||||
elif target == "windsurf":
|
elif target == "windsurf":
|
||||||
server_cfg = snippet["mcpServers"]["artemis"]
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
windsurf_path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
|
windsurf_path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
|
||||||
_merge_json_file(windsurf_path, "artemis", server_cfg)
|
if _merge_json_file(windsurf_path, "artemis", server_cfg):
|
||||||
installed_paths.append(str(windsurf_path))
|
installed_paths.append(str(windsurf_path))
|
||||||
|
elif target == "vscode":
|
||||||
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
|
vscode_path = _get_vscode_user_dir() / "mcp.json"
|
||||||
|
if _merge_json_file(vscode_path, "artemis", server_cfg):
|
||||||
|
installed_paths.append(str(vscode_path))
|
||||||
|
elif target == "cline":
|
||||||
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
|
cline_path = _get_vscode_user_dir() / "globalStorage" / "saoudrizwan.claude-dev" / "settings" / "cline_mcp_settings.json"
|
||||||
|
if _merge_json_file(cline_path, "artemis", server_cfg):
|
||||||
|
installed_paths.append(str(cline_path))
|
||||||
|
elif target in ("roo", "roo_code"):
|
||||||
|
server_cfg = snippet["mcpServers"]["artemis"]
|
||||||
|
roo_path = _get_vscode_user_dir() / "globalStorage" / "rooveterinaryinc.roo-cline" / "settings" / "cline_mcp_settings.json"
|
||||||
|
if _merge_json_file(roo_path, "artemis", server_cfg):
|
||||||
|
installed_paths.append(str(roo_path))
|
||||||
elif target == "openclaw":
|
elif target == "openclaw":
|
||||||
plugin_cfg = snippet["plugins"]["artemis_mcp"]
|
plugin_cfg = snippet["plugins"]["artemis_mcp"]
|
||||||
openclaw_path = Path.home() / ".openclaw" / "openclaw.json"
|
openclaw_path = Path.home() / ".openclaw" / "openclaw.json"
|
||||||
_merge_json_file(openclaw_path, "artemis_mcp", plugin_cfg, key_name="plugins")
|
if _merge_json_file(openclaw_path, "artemis_mcp", plugin_cfg, key_name="plugins"):
|
||||||
installed_paths.append(str(openclaw_path))
|
installed_paths.append(str(openclaw_path))
|
||||||
|
|
||||||
return installed_paths
|
return installed_paths
|
||||||
|
|
||||||
@@ -165,7 +205,7 @@ def mcp_command(
|
|||||||
typer.Option(
|
typer.Option(
|
||||||
"--install",
|
"--install",
|
||||||
"-i",
|
"-i",
|
||||||
help="Auto-install and merge ARTEMIS MCP configuration into 'antigravity', 'claude', 'cursor', 'windsurf', 'openclaw', or 'all'.",
|
help="Auto-install and merge ARTEMIS MCP configuration into 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.",
|
||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
generate_config: Annotated[
|
generate_config: Annotated[
|
||||||
@@ -173,7 +213,7 @@ def mcp_command(
|
|||||||
typer.Option(
|
typer.Option(
|
||||||
"--generate-config",
|
"--generate-config",
|
||||||
"-g",
|
"-g",
|
||||||
help="Output ready-to-use MCP configuration JSON for 'antigravity', 'cursor', 'claude', 'windsurf', 'openclaw', or 'all'.",
|
help="Output ready-to-use MCP configuration JSON for 'antigravity', 'cursor', 'claude', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.",
|
||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -191,11 +231,15 @@ def mcp_command(
|
|||||||
"claude_desktop",
|
"claude_desktop",
|
||||||
"cursor",
|
"cursor",
|
||||||
"windsurf",
|
"windsurf",
|
||||||
|
"vscode",
|
||||||
|
"cline",
|
||||||
|
"roo",
|
||||||
|
"roo_code",
|
||||||
"openclaw",
|
"openclaw",
|
||||||
"all",
|
"all",
|
||||||
):
|
):
|
||||||
console.print(
|
console.print(
|
||||||
f"[bold red]Unsupported install target: '{install_config}'. Use 'antigravity', 'claude', 'cursor', 'windsurf', 'openclaw', or 'all'.[/bold red]"
|
f"[bold red]Unsupported install target: '{install_config}'. Use 'antigravity', 'claude', 'cursor', 'windsurf', 'vscode', 'cline', 'roo', 'openclaw', or 'all'.[/bold red]"
|
||||||
)
|
)
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
installed_paths = install_mcp_config(client, python_exe, project_root)
|
installed_paths = install_mcp_config(client, python_exe, project_root)
|
||||||
@@ -215,12 +259,21 @@ def mcp_command(
|
|||||||
"cursor (.cursor/mcp.json)": _get_config_snippet(
|
"cursor (.cursor/mcp.json)": _get_config_snippet(
|
||||||
"cursor", python_exe, project_root
|
"cursor", python_exe, project_root
|
||||||
),
|
),
|
||||||
"claude (claude_desktop_config.json)": _get_config_snippet(
|
"claude (claude_desktop_config.json & ~/.claude.json)": _get_config_snippet(
|
||||||
"claude", python_exe, project_root
|
"claude", python_exe, project_root
|
||||||
),
|
),
|
||||||
"windsurf (~/.codeium/windsurf/mcp_config.json)": _get_config_snippet(
|
"windsurf (~/.codeium/windsurf/mcp_config.json)": _get_config_snippet(
|
||||||
"windsurf", python_exe, project_root
|
"windsurf", python_exe, project_root
|
||||||
),
|
),
|
||||||
|
"vscode (mcp.json)": _get_config_snippet(
|
||||||
|
"vscode", python_exe, project_root
|
||||||
|
),
|
||||||
|
"cline (cline_mcp_settings.json)": _get_config_snippet(
|
||||||
|
"cline", python_exe, project_root
|
||||||
|
),
|
||||||
|
"roo (cline_mcp_settings.json)": _get_config_snippet(
|
||||||
|
"roo", python_exe, project_root
|
||||||
|
),
|
||||||
"openclaw (openclaw.json)": _get_config_snippet(
|
"openclaw (openclaw.json)": _get_config_snippet(
|
||||||
"openclaw", python_exe, project_root
|
"openclaw", python_exe, project_root
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ The included [`rules.md`](./rules.md) file contains the **Mobile Testing Mindset
|
|||||||
python -m mcp_server
|
python -m mcp_server
|
||||||
|
|
||||||
# Or via Artemis CLI
|
# Or via Artemis CLI
|
||||||
artemis mcp
|
uv run artemis mcp
|
||||||
```
|
```
|
||||||
|
|
||||||
### IDE Configuration
|
### IDE Configuration
|
||||||
@@ -70,10 +70,10 @@ artemis mcp
|
|||||||
#### One-Click Auto Install (Recommended)
|
#### One-Click Auto Install (Recommended)
|
||||||
Run the automated installer to detect and configure your IDE (Antigravity, Cursor, Claude Code/Desktop, OpenClaw):
|
Run the automated installer to detect and configure your IDE (Antigravity, Cursor, Claude Code/Desktop, OpenClaw):
|
||||||
```bash
|
```bash
|
||||||
artemis mcp --install all
|
uv run artemis mcp --install all
|
||||||
# Or install specifically for Antigravity:
|
# Or install specifically for Antigravity:
|
||||||
artemis mcp --install antigravity
|
uv run artemis mcp --install antigravity
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manual JSON Config Generation
|
#### Manual JSON Config Generation
|
||||||
Run `artemis mcp --generate-config antigravity` (or `all`) to output ready-to-use configuration JSON with resolved `.venv` Python and project paths.
|
Run `uv run artemis mcp --generate-config antigravity` (or `all`) to output ready-to-use configuration JSON with resolved `.venv` Python and project paths.
|
||||||
@@ -16,5 +16,6 @@
|
|||||||
|
|
||||||
from mcp_server.base import mcp
|
from mcp_server.base import mcp
|
||||||
from mcp_server.server import main
|
from mcp_server.server import main
|
||||||
|
import mcp_server.tools # noqa: F401
|
||||||
|
|
||||||
__all__ = ["mcp", "main"]
|
__all__ = ["mcp", "main"]
|
||||||
@@ -16,6 +16,13 @@
|
|||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
try:
|
||||||
|
from mcp.server.fastmcp.server import Settings as FastMCPSettings
|
||||||
|
|
||||||
|
FastMCPSettings.model_rebuild()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Define the shared FastMCP instance for external IDE and agent clients.
|
# Define the shared FastMCP instance for external IDE and agent clients.
|
||||||
mcp = FastMCP(
|
mcp = FastMCP(
|
||||||
"artemis",
|
"artemis",
|
||||||
@@ -27,3 +34,6 @@ mcp = FastMCP(
|
|||||||
"and mobile_inspect_trace to inspect detailed execution steps and visual action overlays."
|
"and mobile_inspect_trace to inspect detailed execution steps and visual action overlays."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Import tools so they are registered with the mcp instance upon module load
|
||||||
|
import mcp_server.tools # noqa: E402, F401
|
||||||
@@ -55,6 +55,7 @@ except Exception:
|
|||||||
from mcp_server.base import mcp
|
from mcp_server.base import mcp
|
||||||
|
|
||||||
# 2. Import tools to register them
|
# 2. Import tools to register them
|
||||||
|
import mcp_server.tools # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
def main(transport: str = "stdio", host: str = "127.0.0.1", port: int = 8001):
|
def main(transport: str = "stdio", host: str = "127.0.0.1", port: int = 8001):
|
||||||
|
|||||||
+24
-2
@@ -130,9 +130,31 @@ if ((-not (Test-Path $ShowcaseIndex)) -and (-not (Test-Path $ShowcaseIndexAlt1))
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 7. Launch unified Showcase UI & open browser
|
# 7. Optionally configure MCP for detected AI IDEs
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " 🔌 Would you like to configure ARTEMIS MCP for your AI IDEs?" -ForegroundColor Cyan
|
||||||
|
Write-Host " (Supported: Antigravity, Cursor, Claude Code/Desktop, OpenClaw, Windsurf)"
|
||||||
|
$installMcp = "Y"
|
||||||
|
if ([Console]::IsInputRedirected -eq $false) {
|
||||||
|
$response = Read-Host " Install MCP configuration now? [Y/n]"
|
||||||
|
if ($response -ne "") {
|
||||||
|
$installMcp = $response
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$installMcp = "N"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($installMcp -match "^[Yy]") {
|
||||||
|
Write-Host " ✔ Installing MCP server configuration..." -ForegroundColor Green
|
||||||
|
uv run artemis mcp --install all 2>$null | Out-Null
|
||||||
|
Write-Host " 💡 Tip: You can update or re-install anytime with: uv run artemis mcp --install all" -ForegroundColor Cyan
|
||||||
|
} else {
|
||||||
|
Write-Host " ⏭️ Skipped. You can install MCP anytime later with: uv run artemis mcp --install all" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# 8. Launch unified Showcase UI & open browser
|
||||||
Write-Host " 🚀 Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
|
Write-Host " 🚀 Launching Artemis Showcase UI & Admin Console..." -ForegroundColor Green
|
||||||
Write-Host " 💡 Tip: Connect to Antigravity / Claude Desktop anytime: artemis mcp --install all" -ForegroundColor Cyan
|
|
||||||
if ($NoOpen) {
|
if ($NoOpen) {
|
||||||
uv run artemis ui --port $Port --no-open
|
uv run artemis ui --port $Port --no-open
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -153,7 +153,26 @@ if [ ! -f "${SHOWCASE_INDEX}" ] && [ ! -f "${SHOWCASE_INDEX_ALT1}" ] && [ ! -f "
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 7. Launch unified Showcase UI & auto-open browser
|
# 7. Optionally configure MCP for detected AI IDEs
|
||||||
|
echo ""
|
||||||
|
echo -e " ${CYAN}🔌 Would you like to configure ARTEMIS MCP for your AI IDEs?${NC}"
|
||||||
|
echo -e " (Supported: Antigravity, Cursor, Claude Code/Desktop, OpenClaw, Windsurf)"
|
||||||
|
if [ -t 0 ]; then
|
||||||
|
read -r -p " Install MCP configuration now? [Y/n]: " INSTALL_MCP
|
||||||
|
INSTALL_MCP=${INSTALL_MCP:-Y}
|
||||||
|
else
|
||||||
|
INSTALL_MCP="n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${INSTALL_MCP}" =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e " ${GREEN}✔ Installing MCP server configuration...${NC}"
|
||||||
|
uv run artemis mcp --install all >/dev/null 2>&1 || true
|
||||||
|
echo -e " ${CYAN}💡 Tip: You can update or re-install anytime with: ${BOLD}uv run artemis mcp --install all${NC}"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}⏭️ Skipped. You can install MCP anytime later with: ${BOLD}uv run artemis mcp --install all${NC}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 8. Launch unified Showcase UI & auto-open browser
|
||||||
echo -e " ${GREEN}🚀 Launching Artemis Showcase UI & Admin Console...${NC}"
|
echo -e " ${GREEN}🚀 Launching Artemis Showcase UI & Admin Console...${NC}"
|
||||||
echo -e " ${CYAN}💡 Tip: Connect to Antigravity / Claude Desktop anytime: ${BOLD}artemis mcp --install all${NC}"
|
|
||||||
exec uv run artemis ui --open "$@"
|
exec uv run artemis ui --open "$@"
|
||||||
@@ -111,3 +111,17 @@ async def test_mobile_inspect_trace_invalid_action():
|
|||||||
res = await mobile_inspect_trace(action="invalid_action", trace_id="trace-123")
|
res = await mobile_inspect_trace(action="invalid_action", trace_id="trace-123")
|
||||||
assert "error" in res
|
assert "error" in res
|
||||||
assert "not supported" in res["message"]
|
assert "not supported" in res["message"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mcp_server_auto_registers_tools():
|
||||||
|
"""Verify that importing mcp_server.base auto-registers all mobile tools without manual tools import."""
|
||||||
|
from mcp_server.base import mcp
|
||||||
|
tools = await mcp.list_tools()
|
||||||
|
tool_names = {t.name for t in tools}
|
||||||
|
assert {
|
||||||
|
"mobile_run_task",
|
||||||
|
"mobile_manage_task",
|
||||||
|
"mobile_get_device_state",
|
||||||
|
"mobile_inspect_trace",
|
||||||
|
}.issubset(tool_names)
|
||||||
@@ -90,13 +90,16 @@ def test_cli_mcp_generate_config_antigravity():
|
|||||||
|
|
||||||
|
|
||||||
def test_cli_mcp_generate_config_all():
|
def test_cli_mcp_generate_config_all():
|
||||||
"""Verify 'artemis mcp --generate-config all' includes antigravity, cursor, windsurf, and claude."""
|
"""Verify 'artemis mcp --generate-config all' includes antigravity, cursor, windsurf, claude, vscode, cline, and roo."""
|
||||||
result = runner.invoke(app, ["mcp", "--generate-config", "all"])
|
result = runner.invoke(app, ["mcp", "--generate-config", "all"])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "antigravity" in result.output
|
assert "antigravity" in result.output
|
||||||
assert "cursor" in result.output
|
assert "cursor" in result.output
|
||||||
assert "windsurf" in result.output
|
assert "windsurf" in result.output
|
||||||
assert "claude" in result.output
|
assert "claude" in result.output
|
||||||
|
assert "vscode" in result.output
|
||||||
|
assert "cline" in result.output
|
||||||
|
assert "roo" in result.output
|
||||||
|
|
||||||
|
|
||||||
def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
|
def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
|
||||||
@@ -111,6 +114,7 @@ def test_cli_mcp_install_antigravity(tmp_path, monkeypatch):
|
|||||||
data = json.loads(jetski_file.read_text())
|
data = json.loads(jetski_file.read_text())
|
||||||
assert "artemis" in data["mcpServers"]
|
assert "artemis" in data["mcpServers"]
|
||||||
assert "mobile_run_task" in data["mcpServers"]["artemis"]["tools"]
|
assert "mobile_run_task" in data["mcpServers"]["artemis"]["tools"]
|
||||||
|
assert "PYTHONPATH" in data["mcpServers"]["artemis"]["env"]
|
||||||
|
|
||||||
|
|
||||||
def test_cli_mcp_install_all(tmp_path, monkeypatch):
|
def test_cli_mcp_install_all(tmp_path, monkeypatch):
|
||||||
@@ -122,5 +126,6 @@ def test_cli_mcp_install_all(tmp_path, monkeypatch):
|
|||||||
assert (tmp_path / ".cursor" / "mcp.json").exists()
|
assert (tmp_path / ".cursor" / "mcp.json").exists()
|
||||||
assert (tmp_path / ".codeium" / "windsurf" / "mcp_config.json").exists()
|
assert (tmp_path / ".codeium" / "windsurf" / "mcp_config.json").exists()
|
||||||
assert (tmp_path / ".openclaw" / "openclaw.json").exists()
|
assert (tmp_path / ".openclaw" / "openclaw.json").exists()
|
||||||
|
assert (tmp_path / ".claude.json").exists()
|
||||||
|
|
||||||
|
|
||||||
Reference in new issue
Block a user