| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- #!/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
- };
|