#!/usr/bin/env node /** * mcp.js — AutoX-MCP 桥的最小客户端 * * 桥地址:http://127.0.0.1:18789/mcp (JSON-RPC 2.0,streamable HTTP) * 只实现我们真正用到的几个工具,保持这一层薄且可读。 * 注意:该端点是 PC 本机的 loopback,**只能在 PC 上调用**, * 在 adb shell 里 curl 它查的是手机的 loopback,必然连不上。 */ 'use strict'; const ENDPOINT = process.env.TTA_MCP_ENDPOINT || 'http://127.0.0.1:18789/mcp'; const HEALTH_URL = process.env.TTA_MCP_HEALTH || 'http://127.0.0.1:18789/health'; let nextId = 1; async function rpc(method, params) { const body = { jsonrpc: '2.0', id: nextId++, method, params: params || {} }; const response = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (!response.ok) { throw new Error(`MCP HTTP ${response.status} ${response.statusText}`); } const payload = await response.json(); if (payload.error) { throw new Error(`MCP 错误:${JSON.stringify(payload.error)}`); } return payload.result; } /** 工具返回的 content[0].text 里再套一层 JSON,这里统一拆开 */ function unwrap(result) { if (!result || !Array.isArray(result.content) || result.content.length === 0) { return result; } const text = result.content[0].text; try { return JSON.parse(text); } catch (error) { return { raw: text }; } } async function callTool(name, args) { return unwrap(await rpc('tools/call', { name, arguments: args || {} })); } async function health() { const response = await fetch(HEALTH_URL); return response.json(); } async function listDevices() { return callTool('device_list', {}); } async function connect(serial) { const args = { mode: 'adb' }; if (serial) { args.serial = serial; } return callTool('device_connect', args); } /** * 在设备上运行一段脚本。 * 返回 {status:'ok'|'error'|'timeout'|'needs_confirmation', logs, error} * —— status 必须显式判断,不能只看 logs 里有没有内容。 */ async function runCode(code, options = {}) { const args = { code }; if (options.timeoutMs) { args.timeoutMs = options.timeoutMs; } if (options.confirmToken) { args.confirmToken = options.confirmToken; } if (options.deviceId) { args.deviceId = options.deviceId; } return callTool('device_run_code', args); } async function getLayout(args = {}) { return callTool('device_get_layout', args); } module.exports = { ENDPOINT, HEALTH_URL, rpc, callTool, health, listDevices, connect, runCode, getLayout };