harmony-build 0.2.0
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.
- package/README.md +265 -0
- package/dist/packages/arkanalyzer-mcp/src/index.d.ts +5 -0
- package/dist/packages/arkanalyzer-mcp/src/index.js +220 -0
- package/dist/packages/arkanalyzer-mcp/src/index.js.map +1 -0
- package/dist/packages/arkcheck-mcp/src/index.d.ts +5 -0
- package/dist/packages/arkcheck-mcp/src/index.js +160 -0
- package/dist/packages/arkcheck-mcp/src/index.js.map +1 -0
- package/dist/packages/arkcheck-mcp/src/rules.d.ts +15 -0
- package/dist/packages/arkcheck-mcp/src/rules.js +90 -0
- package/dist/packages/arkcheck-mcp/src/rules.js.map +1 -0
- package/dist/packages/dev-mcp/src/hvigor-bridge.d.ts +31 -0
- package/dist/packages/dev-mcp/src/hvigor-bridge.js +179 -0
- package/dist/packages/dev-mcp/src/hvigor-bridge.js.map +1 -0
- package/dist/packages/dev-mcp/src/index.d.ts +5 -0
- package/dist/packages/dev-mcp/src/index.js +256 -0
- package/dist/packages/dev-mcp/src/index.js.map +1 -0
- package/dist/packages/dev-mcp/src/log-stream.d.ts +56 -0
- package/dist/packages/dev-mcp/src/log-stream.js +147 -0
- package/dist/packages/dev-mcp/src/log-stream.js.map +1 -0
- package/dist/packages/harmony-knowledge-mcp/src/index.d.ts +5 -0
- package/dist/packages/harmony-knowledge-mcp/src/index.js +164 -0
- package/dist/packages/harmony-knowledge-mcp/src/index.js.map +1 -0
- package/dist/packages/hdc-mcp/src/hdc-runner.d.ts +32 -0
- package/dist/packages/hdc-mcp/src/hdc-runner.js +60 -0
- package/dist/packages/hdc-mcp/src/hdc-runner.js.map +1 -0
- package/dist/packages/hdc-mcp/src/index.d.ts +5 -0
- package/dist/packages/hdc-mcp/src/index.js +155 -0
- package/dist/packages/hdc-mcp/src/index.js.map +1 -0
- package/dist/packages/hvigor-mcp/src/index.d.ts +5 -0
- package/dist/packages/hvigor-mcp/src/index.js +300 -0
- package/dist/packages/hvigor-mcp/src/index.js.map +1 -0
- package/dist/packages/hvigor-mcp/src/log-parser.d.ts +28 -0
- package/dist/packages/hvigor-mcp/src/log-parser.js +65 -0
- package/dist/packages/hvigor-mcp/src/log-parser.js.map +1 -0
- package/dist/packages/mcp-common/src/entry.d.ts +1 -0
- package/dist/packages/mcp-common/src/entry.js +22 -0
- package/dist/packages/mcp-common/src/entry.js.map +1 -0
- package/dist/packages/mcp-common/src/errors.d.ts +13 -0
- package/dist/packages/mcp-common/src/errors.js +23 -0
- package/dist/packages/mcp-common/src/errors.js.map +1 -0
- package/dist/packages/mcp-common/src/index.d.ts +6 -0
- package/dist/packages/mcp-common/src/index.js +7 -0
- package/dist/packages/mcp-common/src/index.js.map +1 -0
- package/dist/packages/mcp-common/src/logger.d.ts +11 -0
- package/dist/packages/mcp-common/src/logger.js +32 -0
- package/dist/packages/mcp-common/src/logger.js.map +1 -0
- package/dist/packages/mcp-common/src/mcp-helpers.d.ts +41 -0
- package/dist/packages/mcp-common/src/mcp-helpers.js +94 -0
- package/dist/packages/mcp-common/src/mcp-helpers.js.map +1 -0
- package/dist/packages/mcp-common/src/safe-exec.d.ts +23 -0
- package/dist/packages/mcp-common/src/safe-exec.js +74 -0
- package/dist/packages/mcp-common/src/safe-exec.js.map +1 -0
- package/dist/packages/mcp-common/src/sandbox.d.ts +31 -0
- package/dist/packages/mcp-common/src/sandbox.js +96 -0
- package/dist/packages/mcp-common/src/sandbox.js.map +1 -0
- package/dist/packages/profiler-mcp/src/index.d.ts +5 -0
- package/dist/packages/profiler-mcp/src/index.js +148 -0
- package/dist/packages/profiler-mcp/src/index.js.map +1 -0
- package/dist/packages/profiler-mcp/src/trace-analyzer.d.ts +38 -0
- package/dist/packages/profiler-mcp/src/trace-analyzer.js +95 -0
- package/dist/packages/profiler-mcp/src/trace-analyzer.js.map +1 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.js +61 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/server/handlers.d.ts +11 -0
- package/dist/src/server/handlers.js +33 -0
- package/dist/src/server/handlers.js.map +1 -0
- package/dist/src/server/tools.d.ts +23 -0
- package/dist/src/server/tools.js +57 -0
- package/dist/src/server/tools.js.map +1 -0
- package/package.json +54 -0
- package//345/217/221/345/270/203/346/214/207/345/215/227.md +45 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 服务器通用脚手架:
|
|
3
|
+
* - registerTools(server, defs):声明式地一次性注册 ListTools / CallTool 两个处理器
|
|
4
|
+
* - zodToJsonSchema:把 zod 对象转成 MCP 需要的 JSON Schema(仅支持本项目用到的子集)
|
|
5
|
+
* - ok(data) / fail(err):MCP tool 响应的便捷构造器
|
|
6
|
+
*
|
|
7
|
+
* 该模块只依赖 zod 的类型;@modelcontextprotocol/sdk 由调用方注入,避免 common 强耦合。
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { toErrorPayload } from "./errors.js";
|
|
11
|
+
export function registerTools(server, deps, tools) {
|
|
12
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
13
|
+
server.setRequestHandler(deps.ListToolsRequestSchema, async () => ({
|
|
14
|
+
tools: tools.map((t) => ({
|
|
15
|
+
name: t.name,
|
|
16
|
+
description: t.description,
|
|
17
|
+
inputSchema: zodToJsonSchema(t.schema),
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
server.setRequestHandler(deps.CallToolRequestSchema, async (req) => {
|
|
21
|
+
const r = req;
|
|
22
|
+
const tool = byName.get(r.params.name);
|
|
23
|
+
if (!tool)
|
|
24
|
+
return fail({ code: "UNKNOWN_TOOL", message: `unknown tool: ${r.params.name}` });
|
|
25
|
+
try {
|
|
26
|
+
const parsed = tool.schema.parse(r.params.arguments ?? {});
|
|
27
|
+
const data = await tool.handler(parsed);
|
|
28
|
+
return ok(data);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return fail(toErrorPayload(err));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function ok(data) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function fail(payload) {
|
|
41
|
+
return {
|
|
42
|
+
isError: true,
|
|
43
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function zodToJsonSchema(schema) {
|
|
47
|
+
const shape = schema.shape;
|
|
48
|
+
const properties = {};
|
|
49
|
+
const required = [];
|
|
50
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
51
|
+
const meta = describeZod(value);
|
|
52
|
+
properties[key] = meta.schema;
|
|
53
|
+
if (meta.required)
|
|
54
|
+
required.push(key);
|
|
55
|
+
}
|
|
56
|
+
return { type: "object", properties, ...(required.length ? { required } : {}) };
|
|
57
|
+
}
|
|
58
|
+
function describeZod(t) {
|
|
59
|
+
const description = t._def.description;
|
|
60
|
+
let required = true;
|
|
61
|
+
let inner = t;
|
|
62
|
+
// 注意:unwrap 顺序无所谓,循环到稳定为止
|
|
63
|
+
// eslint-disable-next-line no-constant-condition
|
|
64
|
+
while (true) {
|
|
65
|
+
if (inner instanceof z.ZodDefault) {
|
|
66
|
+
required = false;
|
|
67
|
+
inner = inner._def.innerType;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (inner instanceof z.ZodOptional) {
|
|
71
|
+
required = false;
|
|
72
|
+
inner = inner._def.innerType;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
let schema;
|
|
78
|
+
if (inner instanceof z.ZodString)
|
|
79
|
+
schema = { type: "string" };
|
|
80
|
+
else if (inner instanceof z.ZodNumber)
|
|
81
|
+
schema = { type: "number" };
|
|
82
|
+
else if (inner instanceof z.ZodBoolean)
|
|
83
|
+
schema = { type: "boolean" };
|
|
84
|
+
else if (inner instanceof z.ZodEnum)
|
|
85
|
+
schema = { type: "string", enum: inner.options };
|
|
86
|
+
else if (inner instanceof z.ZodArray)
|
|
87
|
+
schema = { type: "array" };
|
|
88
|
+
else
|
|
89
|
+
schema = {};
|
|
90
|
+
if (description)
|
|
91
|
+
schema.description = description;
|
|
92
|
+
return { schema, required };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=mcp-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-helpers.js","sourceRoot":"","sources":["../../../../packages/mcp-common/src/mcp-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface RunOptions {
|
|
2
|
+
/** 工作目录,默认 process.cwd() */
|
|
3
|
+
cwd?: string;
|
|
4
|
+
/** 超时,毫秒;默认 60_000;0 表示不超时 */
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
/** 注入的环境变量(与 process.env 合并) */
|
|
7
|
+
env?: Record<string, string>;
|
|
8
|
+
/** stdout/stderr 单流最大字节数;超出截断;默认 1MB */
|
|
9
|
+
maxBytes?: number;
|
|
10
|
+
/** 标准输入文本,可选 */
|
|
11
|
+
stdin?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RunResult {
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
exitCode: number | null;
|
|
17
|
+
signal: NodeJS.Signals | null;
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function runCommand(command: string, args?: string[], options?: RunOptions): Promise<RunResult>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 安全的进程执行封装:
|
|
3
|
+
* - 不经过 shell(避免命令注入)
|
|
4
|
+
* - 强制 timeout
|
|
5
|
+
* - 截断超长输出
|
|
6
|
+
* - 命令白名单(外层调用方控制)
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { HarmonyMcpError } from "./errors.js";
|
|
10
|
+
const DEFAULT_TIMEOUT = 60_000;
|
|
11
|
+
const DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
12
|
+
export async function runCommand(command, args = [], options = {}) {
|
|
13
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT;
|
|
14
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
15
|
+
const started = Date.now();
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const child = spawn(command, args, {
|
|
18
|
+
cwd: options.cwd,
|
|
19
|
+
env: { ...process.env, ...(options.env ?? {}) },
|
|
20
|
+
shell: false,
|
|
21
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
22
|
+
});
|
|
23
|
+
let stdout = "";
|
|
24
|
+
let stderr = "";
|
|
25
|
+
let truncated = false;
|
|
26
|
+
let timer;
|
|
27
|
+
const onData = (which) => (chunk) => {
|
|
28
|
+
const cur = which === "stdout" ? stdout : stderr;
|
|
29
|
+
if (Buffer.byteLength(cur, "utf8") >= maxBytes) {
|
|
30
|
+
truncated = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const text = chunk.toString("utf8");
|
|
34
|
+
if (which === "stdout")
|
|
35
|
+
stdout += text;
|
|
36
|
+
else
|
|
37
|
+
stderr += text;
|
|
38
|
+
};
|
|
39
|
+
child.stdout.on("data", onData("stdout"));
|
|
40
|
+
child.stderr.on("data", onData("stderr"));
|
|
41
|
+
child.on("error", (err) => {
|
|
42
|
+
if (timer)
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
reject(new HarmonyMcpError("EXEC_SPAWN_FAILED", err.message, { command, args }));
|
|
45
|
+
});
|
|
46
|
+
child.on("close", (exitCode, signal) => {
|
|
47
|
+
if (timer)
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
resolve({
|
|
50
|
+
command,
|
|
51
|
+
args,
|
|
52
|
+
exitCode,
|
|
53
|
+
signal,
|
|
54
|
+
stdout,
|
|
55
|
+
stderr,
|
|
56
|
+
durationMs: Date.now() - started,
|
|
57
|
+
truncated,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
if (timeoutMs > 0) {
|
|
61
|
+
timer = setTimeout(() => {
|
|
62
|
+
child.kill("SIGKILL");
|
|
63
|
+
}, timeoutMs);
|
|
64
|
+
}
|
|
65
|
+
if (options.stdin !== undefined) {
|
|
66
|
+
child.stdin.write(options.stdin);
|
|
67
|
+
child.stdin.end();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
child.stdin.end();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=safe-exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-exec.js","sourceRoot":"","sources":["../../../../packages/mcp-common/src/safe-exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface SandboxOptions {
|
|
2
|
+
/** 允许的工作根目录列表(绝对路径) */
|
|
3
|
+
allowedRoots: string[];
|
|
4
|
+
/**
|
|
5
|
+
* v0.2: 允许 sandbox 在 resolve() 时自动把"看起来是鸿蒙工程根"的目录
|
|
6
|
+
* 加进白名单。默认 true。
|
|
7
|
+
*/
|
|
8
|
+
autoDetectProjectRoot?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 从任意 startPath(文件或目录)逐级向上找鸿蒙工程根。
|
|
12
|
+
* 找不到时返回 null。
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectProjectRoot(startPath: string): string | null;
|
|
15
|
+
export declare class PathSandbox {
|
|
16
|
+
private roots;
|
|
17
|
+
private readonly autoDetect;
|
|
18
|
+
constructor(opts: SandboxOptions);
|
|
19
|
+
/** 动态扩容:当 Agent 给出新工程目录时,可即时把它的工程根纳入白名单。 */
|
|
20
|
+
addRoot(rootPath: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* v0.2: 给"只读静态扫描"类工具用——
|
|
23
|
+
* 把传入路径自身(若是目录)或其所在工程根作为合法根注册进沙箱,
|
|
24
|
+
* 但 NOT 用于会触发 shell 命令的 hvigor 类工具。
|
|
25
|
+
* 返回最终生效的根目录绝对路径。
|
|
26
|
+
*/
|
|
27
|
+
ensureReadOnlyRoot(targetPath: string): string;
|
|
28
|
+
/** 把任意路径规范化为绝对路径,并校验其位于允许的根目录内。 */
|
|
29
|
+
resolve(targetPath: string): string;
|
|
30
|
+
private isInsideRoots;
|
|
31
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 路径沙箱:确保被操作的路径都在用户声明的工作区内,防止 Agent 误操作。
|
|
3
|
+
*
|
|
4
|
+
* v0.2 起:支持运行时自动从工具入参里的 project_dir / file_path
|
|
5
|
+
* 推断出鸿蒙工程根目录,并动态加入白名单 —— 这样用户切换工程时不必再改环境变量。
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import { HarmonyMcpError } from "./errors.js";
|
|
10
|
+
/** 用于判定一个目录是不是鸿蒙工程根的标志文件 */
|
|
11
|
+
const HARMONY_ROOT_MARKERS = [
|
|
12
|
+
"hvigorw",
|
|
13
|
+
"hvigorfile.ts",
|
|
14
|
+
"hvigorfile.js",
|
|
15
|
+
"build-profile.json5",
|
|
16
|
+
"oh-package.json5",
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* 从任意 startPath(文件或目录)逐级向上找鸿蒙工程根。
|
|
20
|
+
* 找不到时返回 null。
|
|
21
|
+
*/
|
|
22
|
+
export function detectProjectRoot(startPath) {
|
|
23
|
+
let cur = path.resolve(startPath);
|
|
24
|
+
// 如果起点是文件,转成它所在目录
|
|
25
|
+
try {
|
|
26
|
+
const st = fs.statSync(cur);
|
|
27
|
+
if (st.isFile())
|
|
28
|
+
cur = path.dirname(cur);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// 路径不存在也没关系,按目录处理
|
|
32
|
+
}
|
|
33
|
+
const root = path.parse(cur).root;
|
|
34
|
+
while (cur && cur !== root) {
|
|
35
|
+
for (const marker of HARMONY_ROOT_MARKERS) {
|
|
36
|
+
if (fs.existsSync(path.join(cur, marker))) {
|
|
37
|
+
return cur;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
cur = path.dirname(cur);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export class PathSandbox {
|
|
45
|
+
roots;
|
|
46
|
+
autoDetect;
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
this.autoDetect = opts.autoDetectProjectRoot !== false;
|
|
49
|
+
if (!opts.allowedRoots.length && !this.autoDetect) {
|
|
50
|
+
throw new HarmonyMcpError("SANDBOX_NO_ROOT", "PathSandbox requires at least one allowedRoot (or enable autoDetectProjectRoot)");
|
|
51
|
+
}
|
|
52
|
+
this.roots = opts.allowedRoots.map((p) => path.resolve(p));
|
|
53
|
+
}
|
|
54
|
+
/** 动态扩容:当 Agent 给出新工程目录时,可即时把它的工程根纳入白名单。 */
|
|
55
|
+
addRoot(rootPath) {
|
|
56
|
+
const abs = path.resolve(rootPath);
|
|
57
|
+
if (!this.roots.includes(abs)) {
|
|
58
|
+
this.roots.push(abs);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* v0.2: 给"只读静态扫描"类工具用——
|
|
63
|
+
* 把传入路径自身(若是目录)或其所在工程根作为合法根注册进沙箱,
|
|
64
|
+
* 但 NOT 用于会触发 shell 命令的 hvigor 类工具。
|
|
65
|
+
* 返回最终生效的根目录绝对路径。
|
|
66
|
+
*/
|
|
67
|
+
ensureReadOnlyRoot(targetPath) {
|
|
68
|
+
const abs = path.resolve(targetPath);
|
|
69
|
+
if (this.isInsideRoots(abs))
|
|
70
|
+
return abs;
|
|
71
|
+
const detected = this.autoDetect ? detectProjectRoot(abs) : null;
|
|
72
|
+
const root = detected ?? abs;
|
|
73
|
+
this.addRoot(root);
|
|
74
|
+
return abs;
|
|
75
|
+
}
|
|
76
|
+
/** 把任意路径规范化为绝对路径,并校验其位于允许的根目录内。 */
|
|
77
|
+
resolve(targetPath) {
|
|
78
|
+
const abs = path.resolve(targetPath);
|
|
79
|
+
if (this.isInsideRoots(abs))
|
|
80
|
+
return abs;
|
|
81
|
+
// v0.2: 自动探测——如果 abs 落在某个鸿蒙工程目录下,则把该工程根加入白名单。
|
|
82
|
+
if (this.autoDetect) {
|
|
83
|
+
const detected = detectProjectRoot(abs);
|
|
84
|
+
if (detected) {
|
|
85
|
+
this.addRoot(detected);
|
|
86
|
+
if (this.isInsideRoots(abs))
|
|
87
|
+
return abs;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new HarmonyMcpError("SANDBOX_VIOLATION", `path ${abs} is outside allowed roots`, { roots: this.roots });
|
|
91
|
+
}
|
|
92
|
+
isInsideRoots(abs) {
|
|
93
|
+
return this.roots.some((root) => abs === root || abs.startsWith(root + path.sep));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../../packages/mcp-common/src/sandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* profiler-mcp: 鸿蒙性能工具的 MCP Server 化。
|
|
4
|
+
*
|
|
5
|
+
* 工具:
|
|
6
|
+
* - capture_trace 通过 hdc shell hitrace 抓 trace(指定时长与 tag)
|
|
7
|
+
* - analyze_trace 把 ftrace 文本解析为 Top 慢函数 / 掉帧 / 主线程长段
|
|
8
|
+
* - capture_and_analyze 上面两个的组合,一步出根因
|
|
9
|
+
* - cold_start_breakdown 冷启动场景的便捷封装(启动 ability + 抓 5s trace)
|
|
10
|
+
*
|
|
11
|
+
* 依赖:本机已安装 hdc 并能连接设备/模拟器。
|
|
12
|
+
*/
|
|
13
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import fs from "node:fs/promises";
|
|
18
|
+
import os from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { createLogger, HarmonyMcpError, isMainModule, registerTools, runCommand, } from "@harmony-mcp/common";
|
|
21
|
+
import { analyzeTrace } from "./trace-analyzer.js";
|
|
22
|
+
const logger = createLogger("profiler-mcp");
|
|
23
|
+
const HDC_BIN = process.env.HDC_BIN ?? "hdc";
|
|
24
|
+
const DEFAULT_TARGET = process.env.HDC_TARGET;
|
|
25
|
+
// 默认抓的 tag 集合(覆盖 ArkUI 渲染 / Ability / IPC / 网络)
|
|
26
|
+
const DEFAULT_TAGS = ["app", "ability", "graphic", "ohos", "ace", "render"];
|
|
27
|
+
const TargetField = z.string().min(1).optional().describe("设备序列号;不填使用默认设备");
|
|
28
|
+
const CaptureTraceSchema = z.object({
|
|
29
|
+
duration_seconds: z.number().int().positive().max(60).default(5)
|
|
30
|
+
.describe("抓取时长(秒)"),
|
|
31
|
+
tags: z.array(z.string()).optional()
|
|
32
|
+
.describe(`hitrace tag 列表;默认 ${DEFAULT_TAGS.join(",")}`),
|
|
33
|
+
buffer_kb: z.number().int().positive().default(8192)
|
|
34
|
+
.describe("环形缓冲大小 KB"),
|
|
35
|
+
output_path: z.string().optional()
|
|
36
|
+
.describe("本地保存路径;省略则保存到临时目录并返回路径"),
|
|
37
|
+
target: TargetField,
|
|
38
|
+
});
|
|
39
|
+
const AnalyzeTraceSchema = z.object({
|
|
40
|
+
trace_path: z.string().describe("本地 .ftrace 文件路径"),
|
|
41
|
+
top_n: z.number().int().positive().max(50).default(10),
|
|
42
|
+
});
|
|
43
|
+
const CaptureAndAnalyzeSchema = CaptureTraceSchema.extend({
|
|
44
|
+
top_n: z.number().int().positive().max(50).default(10),
|
|
45
|
+
});
|
|
46
|
+
const ColdStartSchema = z.object({
|
|
47
|
+
bundle_name: z.string(),
|
|
48
|
+
ability_name: z.string(),
|
|
49
|
+
duration_seconds: z.number().int().positive().max(20).default(5),
|
|
50
|
+
target: TargetField,
|
|
51
|
+
});
|
|
52
|
+
async function hdc(args, target, timeoutMs = 60_000) {
|
|
53
|
+
const finalTarget = target ?? DEFAULT_TARGET;
|
|
54
|
+
const finalArgs = finalTarget ? ["-t", finalTarget, ...args] : args;
|
|
55
|
+
return runCommand(HDC_BIN, finalArgs, { timeoutMs });
|
|
56
|
+
}
|
|
57
|
+
async function captureTrace(opts) {
|
|
58
|
+
const duration = opts.duration_seconds ?? 5;
|
|
59
|
+
const tags = opts.tags?.length ? opts.tags : DEFAULT_TAGS;
|
|
60
|
+
const remoteFile = `/data/local/tmp/mcp-trace-${Date.now()}.ftrace`;
|
|
61
|
+
const localFile = opts.output_path ?? path.join(os.tmpdir(), `mcp-trace-${Date.now()}.ftrace`);
|
|
62
|
+
// 1) 抓 trace(hitrace 阻塞 N 秒后自动结束)
|
|
63
|
+
const captureArgs = [
|
|
64
|
+
"shell",
|
|
65
|
+
"hitrace",
|
|
66
|
+
"-t",
|
|
67
|
+
String(duration),
|
|
68
|
+
"-b",
|
|
69
|
+
String(opts.buffer_kb ?? 8192),
|
|
70
|
+
"-o",
|
|
71
|
+
remoteFile,
|
|
72
|
+
...tags,
|
|
73
|
+
];
|
|
74
|
+
const cap = await hdc(captureArgs, opts.target, (duration + 15) * 1000);
|
|
75
|
+
if (cap.exitCode !== 0) {
|
|
76
|
+
throw new HarmonyMcpError("HITRACE_FAILED", cap.stderr || "hitrace failed", {
|
|
77
|
+
stdout: cap.stdout,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// 2) 拉回本地
|
|
81
|
+
const recv = await hdc(["file", "recv", remoteFile, localFile], opts.target, 60_000);
|
|
82
|
+
if (recv.exitCode !== 0) {
|
|
83
|
+
throw new HarmonyMcpError("TRACE_RECV_FAILED", recv.stderr || "file recv failed");
|
|
84
|
+
}
|
|
85
|
+
const stat = await fs.stat(localFile);
|
|
86
|
+
return { local_path: localFile, bytes: stat.size, tags, duration_seconds: duration };
|
|
87
|
+
}
|
|
88
|
+
export const NAMESPACE = "profiler";
|
|
89
|
+
export const tools = [
|
|
90
|
+
{
|
|
91
|
+
name: "capture_trace",
|
|
92
|
+
description: "通过 hitrace 抓取指定时长的 trace,并把文件拉回本地",
|
|
93
|
+
schema: CaptureTraceSchema,
|
|
94
|
+
handler: captureTrace,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "analyze_trace",
|
|
98
|
+
description: "解析本地 ftrace 文件,输出 Top 慢函数 / 主线程长段 / 疑似掉帧",
|
|
99
|
+
schema: AnalyzeTraceSchema,
|
|
100
|
+
handler: async (a) => {
|
|
101
|
+
const text = await fs.readFile(a.trace_path, "utf8");
|
|
102
|
+
return analyzeTrace(text, a.top_n);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "capture_and_analyze",
|
|
107
|
+
description: "抓 trace 并立刻分析,一步出根因(推荐 Agent 默认调用)",
|
|
108
|
+
schema: CaptureAndAnalyzeSchema,
|
|
109
|
+
handler: async (a) => {
|
|
110
|
+
const cap = await captureTrace(a);
|
|
111
|
+
const text = await fs.readFile(cap.local_path, "utf8");
|
|
112
|
+
const analysis = analyzeTrace(text, a.top_n);
|
|
113
|
+
return { capture: cap, analysis };
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "cold_start_breakdown",
|
|
118
|
+
description: "冷启动场景便捷封装:force-stop → start_ability → 抓 N 秒 trace → 分析",
|
|
119
|
+
schema: ColdStartSchema,
|
|
120
|
+
handler: async (a) => {
|
|
121
|
+
await hdc(["shell", "aa", "force-stop", a.bundle_name], a.target);
|
|
122
|
+
// 启动 + 抓 trace 并发,确保抓到启动期
|
|
123
|
+
const startPromise = hdc(["shell", "aa", "start", "-b", a.bundle_name, "-a", a.ability_name], a.target);
|
|
124
|
+
const cap = await captureTrace({
|
|
125
|
+
duration_seconds: a.duration_seconds,
|
|
126
|
+
buffer_kb: 8192,
|
|
127
|
+
target: a.target,
|
|
128
|
+
});
|
|
129
|
+
await startPromise;
|
|
130
|
+
const text = await fs.readFile(cap.local_path, "utf8");
|
|
131
|
+
const analysis = analyzeTrace(text, 10);
|
|
132
|
+
return { capture: cap, analysis };
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
async function main() {
|
|
137
|
+
const server = new Server({ name: "harmony-profiler-mcp", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
138
|
+
registerTools(server, { ListToolsRequestSchema, CallToolRequestSchema }, tools);
|
|
139
|
+
await server.connect(new StdioServerTransport());
|
|
140
|
+
logger.info("profiler-mcp server started");
|
|
141
|
+
}
|
|
142
|
+
if (isMainModule(import.meta.url)) {
|
|
143
|
+
main().catch((err) => {
|
|
144
|
+
logger.error("fatal", { err: String(err) });
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/profiler-mcp/src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 极简 hitrace/ftrace 文本分析器(first pass)。
|
|
3
|
+
*
|
|
4
|
+
* 输入:hitrace --trace_dump 的文本输出(ftrace 文本格式)
|
|
5
|
+
* 解析维度:
|
|
6
|
+
* - 用户态打点段(tracing_mark_write: B|... E|... ) —— 提取 (name, duration_us)
|
|
7
|
+
* - frame 段(H:RSRenderThread / H:UIRender / H:Frame:)—— 提取掉帧
|
|
8
|
+
*
|
|
9
|
+
* 这是工程化解析的最小子集;完整版需要对接华为 SmartPerf 解析库。
|
|
10
|
+
*/
|
|
11
|
+
export interface TraceSegment {
|
|
12
|
+
name: string;
|
|
13
|
+
tid: number;
|
|
14
|
+
pid: number;
|
|
15
|
+
startUs: number;
|
|
16
|
+
durationUs: number;
|
|
17
|
+
}
|
|
18
|
+
export interface TraceAnalysis {
|
|
19
|
+
total_segments: number;
|
|
20
|
+
duration_us: number;
|
|
21
|
+
top_slow: Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
duration_us: number;
|
|
24
|
+
tid: number;
|
|
25
|
+
pid: number;
|
|
26
|
+
}>;
|
|
27
|
+
long_main_thread: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
duration_us: number;
|
|
30
|
+
}>;
|
|
31
|
+
suspected_jank_frames: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
duration_us: number;
|
|
34
|
+
}>;
|
|
35
|
+
summary: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function parseTrace(text: string): TraceSegment[];
|
|
38
|
+
export declare function analyzeTrace(text: string, topN?: number): TraceAnalysis;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 极简 hitrace/ftrace 文本分析器(first pass)。
|
|
3
|
+
*
|
|
4
|
+
* 输入:hitrace --trace_dump 的文本输出(ftrace 文本格式)
|
|
5
|
+
* 解析维度:
|
|
6
|
+
* - 用户态打点段(tracing_mark_write: B|... E|... ) —— 提取 (name, duration_us)
|
|
7
|
+
* - frame 段(H:RSRenderThread / H:UIRender / H:Frame:)—— 提取掉帧
|
|
8
|
+
*
|
|
9
|
+
* 这是工程化解析的最小子集;完整版需要对接华为 SmartPerf 解析库。
|
|
10
|
+
*/
|
|
11
|
+
const ENTRY_LINE = /^\s*\S+-(\d+)\s+\(\s*(\d+)\)\s+\[\d+\]\s+\S+\s+(\d+\.\d+):\s+tracing_mark_write:\s+([BE])\|\d+\|(.+)$/;
|
|
12
|
+
export function parseTrace(text) {
|
|
13
|
+
const stack = new Map(); // per-tid 栈
|
|
14
|
+
const segs = [];
|
|
15
|
+
for (const line of text.split(/\r?\n/)) {
|
|
16
|
+
const m = ENTRY_LINE.exec(line);
|
|
17
|
+
if (!m)
|
|
18
|
+
continue;
|
|
19
|
+
const tid = Number(m[1]);
|
|
20
|
+
const pid = Number(m[2]);
|
|
21
|
+
const tsUs = Math.round(parseFloat(m[3]) * 1_000_000);
|
|
22
|
+
const phase = m[4];
|
|
23
|
+
const name = m[5].trim();
|
|
24
|
+
const s = stack.get(tid) ?? [];
|
|
25
|
+
if (phase === "B") {
|
|
26
|
+
s.push({ name, pid, tid, startUs: tsUs });
|
|
27
|
+
stack.set(tid, s);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// E
|
|
31
|
+
const open = s.pop();
|
|
32
|
+
if (open) {
|
|
33
|
+
segs.push({
|
|
34
|
+
name: open.name,
|
|
35
|
+
pid: open.pid,
|
|
36
|
+
tid: open.tid,
|
|
37
|
+
startUs: open.startUs,
|
|
38
|
+
durationUs: tsUs - open.startUs,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return segs;
|
|
44
|
+
}
|
|
45
|
+
export function analyzeTrace(text, topN = 10) {
|
|
46
|
+
const segs = parseTrace(text);
|
|
47
|
+
if (!segs.length) {
|
|
48
|
+
return {
|
|
49
|
+
total_segments: 0,
|
|
50
|
+
duration_us: 0,
|
|
51
|
+
top_slow: [],
|
|
52
|
+
long_main_thread: [],
|
|
53
|
+
suspected_jank_frames: [],
|
|
54
|
+
summary: "no segments parsed; check that the trace text contains tracing_mark_write lines",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const minTs = Math.min(...segs.map((s) => s.startUs));
|
|
58
|
+
const maxTs = Math.max(...segs.map((s) => s.startUs + s.durationUs));
|
|
59
|
+
const sortedByDur = [...segs].sort((a, b) => b.durationUs - a.durationUs);
|
|
60
|
+
const topSlow = sortedByDur.slice(0, topN).map((s) => ({
|
|
61
|
+
name: s.name,
|
|
62
|
+
duration_us: s.durationUs,
|
|
63
|
+
tid: s.tid,
|
|
64
|
+
pid: s.pid,
|
|
65
|
+
}));
|
|
66
|
+
// 主线程经验:tid == pid(首线程多为主线程)
|
|
67
|
+
const mainSegs = segs.filter((s) => s.tid === s.pid);
|
|
68
|
+
const longMain = mainSegs
|
|
69
|
+
.filter((s) => s.durationUs >= 16_000) // 单段 > 一帧 16ms
|
|
70
|
+
.sort((a, b) => b.durationUs - a.durationUs)
|
|
71
|
+
.slice(0, topN)
|
|
72
|
+
.map((s) => ({ name: s.name, duration_us: s.durationUs }));
|
|
73
|
+
// 帧段:常见命名前缀有 "H:RSRenderThread::DrawFrame" / "H:UIRender" / "Frame:"
|
|
74
|
+
const frameSegs = segs.filter((s) => /^(H:RSRenderThread|H:UIRender|Frame:|H:Render|H:ArkUI_DoFrame)/i.test(s.name));
|
|
75
|
+
const jank = frameSegs
|
|
76
|
+
.filter((s) => s.durationUs >= 16_667) // 60fps 阈值
|
|
77
|
+
.sort((a, b) => b.durationUs - a.durationUs)
|
|
78
|
+
.slice(0, topN)
|
|
79
|
+
.map((s) => ({ name: s.name, duration_us: s.durationUs }));
|
|
80
|
+
const summary = [
|
|
81
|
+
`parsed ${segs.length} segments over ${(maxTs - minTs) / 1000}ms`,
|
|
82
|
+
`top slow: ${topSlow[0]?.name ?? "n/a"} (${topSlow[0]?.duration_us ?? 0}us)`,
|
|
83
|
+
`long main-thread segs: ${longMain.length}`,
|
|
84
|
+
`suspected jank frames: ${jank.length}`,
|
|
85
|
+
].join("; ");
|
|
86
|
+
return {
|
|
87
|
+
total_segments: segs.length,
|
|
88
|
+
duration_us: maxTs - minTs,
|
|
89
|
+
top_slow: topSlow,
|
|
90
|
+
long_main_thread: longMain,
|
|
91
|
+
suspected_jank_frames: jank,
|
|
92
|
+
summary,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=trace-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-analyzer.js","sourceRoot":"","sources":["../../../../packages/profiler-mcp/src/trace-analyzer.ts"],"names":[],"mappings":"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"}
|