Files
server-deploy/windows-dev-stack/godot-mcp-pro-v1.14.1/addons/godot_mcp/commands/android_commands.gd
Joywayer dd3eb24d0f refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 01:11:20 +08:00

193 lines
7.8 KiB
GDScript

@tool
extends "res://addons/godot_mcp/commands/base_command.gd"
func get_commands() -> Dictionary:
return {
"list_android_devices": _list_android_devices,
"get_android_preset_info": _get_android_preset_info,
"deploy_to_android": _deploy_to_android,
}
## Resolve adb path from editor settings or PATH fallback.
func _resolve_adb_path() -> String:
var editor_settings := get_editor().get_editor_settings()
# Godot exposes this under export/android/adb (may be stored as an absolute path).
var configured: String = ""
if editor_settings.has_setting("export/android/adb"):
configured = str(editor_settings.get_setting("export/android/adb"))
if not configured.is_empty() and FileAccess.file_exists(configured):
return configured
# Fallback: assume adb is on PATH. OS.execute will resolve it at call time.
return "adb"
func _run(cmd: String, args: PackedStringArray) -> Dictionary:
var output: Array = []
var exit_code := OS.execute(cmd, args, output, true)
var stdout := ""
if not output.is_empty():
stdout = str(output[0])
return {"exit_code": exit_code, "stdout": stdout}
## List devices visible to adb.
func _list_android_devices(_params: Dictionary) -> Dictionary:
var adb := _resolve_adb_path()
var result := _run(adb, PackedStringArray(["devices", "-l"]))
if result["exit_code"] != 0:
return error(-32000, "adb failed (exit %d). Install Android platform-tools or set Editor Settings > Export > Android > Adb." % result["exit_code"], {"adb_path": adb, "output": result["stdout"]})
# Parse `adb devices -l` output:
# List of devices attached
# R58M12345 device usb:3-1 product:foo model:Pixel_5 device:redfin
var devices: Array = []
var lines: PackedStringArray = str(result["stdout"]).split("\n")
for raw_line in lines:
var line: String = raw_line.strip_edges()
if line.is_empty() or line.begins_with("List of devices") or line.begins_with("* daemon"):
continue
var parts: PackedStringArray = line.split(" ", false)
if parts.size() < 2:
continue
var dev: Dictionary = {"serial": parts[0], "state": parts[1]}
for i in range(2, parts.size()):
var kv: String = parts[i]
var eq: int = kv.find(":")
if eq > 0:
dev[kv.substr(0, eq)] = kv.substr(eq + 1)
devices.append(dev)
return success({"devices": devices, "count": devices.size(), "adb_path": adb})
## Find an Android preset in export_presets.cfg. Returns the preset dict or null.
func _find_android_preset(preset_name: String, preset_index: int) -> Dictionary:
var presets_path := "res://export_presets.cfg"
if not FileAccess.file_exists(presets_path):
return {}
var cfg := ConfigFile.new()
if cfg.load(presets_path) != OK:
return {}
var idx := 0
while cfg.has_section("preset.%d" % idx):
var section := "preset.%d" % idx
var platform := str(cfg.get_value(section, "platform", ""))
var name := str(cfg.get_value(section, "name", ""))
var matches := false
if not preset_name.is_empty():
matches = (name == preset_name)
elif preset_index >= 0:
matches = (idx == preset_index)
else:
# No filter: pick the first Android preset.
matches = (platform == "Android")
if matches:
var options_section := "preset.%d.options" % idx
var package_name := ""
if cfg.has_section(options_section):
package_name = str(cfg.get_value(options_section, "package/unique_name", ""))
return {
"index": idx,
"name": name,
"platform": platform,
"runnable": bool(cfg.get_value(section, "runnable", false)),
"export_path": str(cfg.get_value(section, "export_path", "")),
"package_name": package_name,
}
idx += 1
return {}
## Read Android preset metadata (package name, export path, etc.)
func _get_android_preset_info(params: Dictionary) -> Dictionary:
var preset_name: String = optional_string(params, "preset_name", "")
var preset_index: int = optional_int(params, "preset_index", -1)
var preset := _find_android_preset(preset_name, preset_index)
if preset.is_empty():
return error_not_found("Android export preset", "Configure an Android preset in Project > Export first.")
if preset["platform"] != "Android":
return error(-32000, "Preset '%s' is not an Android preset (platform=%s)" % [preset["name"], preset["platform"]])
return success(preset)
## Export APK, install it on a device, then optionally launch the main activity.
func _deploy_to_android(params: Dictionary) -> Dictionary:
var preset_name: String = optional_string(params, "preset_name", "")
var preset_index: int = optional_int(params, "preset_index", -1)
var device_serial: String = optional_string(params, "device_serial", "")
var debug: bool = optional_bool(params, "debug", true)
var launch: bool = optional_bool(params, "launch", true)
var skip_export: bool = optional_bool(params, "skip_export", false)
var preset := _find_android_preset(preset_name, preset_index)
if preset.is_empty():
return error_not_found("Android export preset", "Configure an Android preset in Project > Export first.")
if preset["platform"] != "Android":
return error(-32000, "Preset '%s' is not an Android preset" % preset["name"])
var export_path_res: String = preset["export_path"]
if export_path_res.is_empty():
return error(-32000, "Export path not configured for preset '%s'" % preset["name"])
var export_path_abs: String = ProjectSettings.globalize_path(export_path_res) if export_path_res.begins_with("res://") else export_path_res
var steps: Array = []
# Step 1: Export APK via Godot CLI (unless caller already has an APK).
if not skip_export:
var godot_bin := OS.get_executable_path()
var project_dir := ProjectSettings.globalize_path("res://")
var export_flag := "--export-debug" if debug else "--export-release"
var export_args := PackedStringArray(["--headless", "--path", project_dir, export_flag, preset["name"], export_path_abs])
var export_result := _run(godot_bin, export_args)
steps.append({"step": "export", "command": godot_bin, "args": export_args, "exit_code": export_result["exit_code"]})
if export_result["exit_code"] != 0:
return error(-32000, "Godot export failed (exit %d). See stdout." % export_result["exit_code"], {"steps": steps, "stdout": export_result["stdout"]})
if not FileAccess.file_exists(export_path_abs):
return error(-32000, "APK not found at %s after export" % export_path_abs, {"steps": steps})
# Step 2: adb install -r
var adb := _resolve_adb_path()
var install_args := PackedStringArray()
if not device_serial.is_empty():
install_args.append("-s")
install_args.append(device_serial)
install_args.append("install")
install_args.append("-r")
install_args.append(export_path_abs)
var install_result := _run(adb, install_args)
steps.append({"step": "install", "command": adb, "args": install_args, "exit_code": install_result["exit_code"], "stdout": install_result["stdout"]})
if install_result["exit_code"] != 0:
return error(-32000, "adb install failed (exit %d)" % install_result["exit_code"], {"steps": steps})
# Step 3: adb shell am start (optional)
if launch:
var package_name: String = preset["package_name"]
if package_name.is_empty():
steps.append({"step": "launch", "skipped": true, "reason": "package_name not found in preset"})
else:
var launch_args := PackedStringArray()
if not device_serial.is_empty():
launch_args.append("-s")
launch_args.append(device_serial)
launch_args.append("shell")
launch_args.append("monkey")
launch_args.append("-p")
launch_args.append(package_name)
launch_args.append("-c")
launch_args.append("android.intent.category.LAUNCHER")
launch_args.append("1")
var launch_result := _run(adb, launch_args)
steps.append({"step": "launch", "command": adb, "args": launch_args, "exit_code": launch_result["exit_code"], "stdout": launch_result["stdout"]})
return success({
"preset": preset["name"],
"apk_path": export_path_abs,
"device": device_serial if not device_serial.is_empty() else "(default)",
"package_name": preset["package_name"],
"steps": steps,
})