@uwa4d/openapi-mcp 0.2.0-beta.0 → 0.2.0-beta.10

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.
Files changed (46) hide show
  1. package/README.md +105 -9
  2. package/dist/annotate-dashboard.d.ts +48 -0
  3. package/dist/annotate-dashboard.js +413 -0
  4. package/dist/cli.js +29 -6
  5. package/dist/client.js +4 -13
  6. package/dist/composite/helpers.d.ts +19 -0
  7. package/dist/composite/helpers.js +90 -0
  8. package/dist/composite/index.d.ts +25 -0
  9. package/dist/composite/index.js +46 -0
  10. package/dist/composite/name-pattern.d.ts +8 -0
  11. package/dist/composite/name-pattern.js +25 -0
  12. package/dist/composite/overview-view.d.ts +33 -0
  13. package/dist/composite/overview-view.js +373 -0
  14. package/dist/composite/report-diagnosis.d.ts +2 -0
  15. package/dist/composite/report-diagnosis.js +347 -0
  16. package/dist/composite/route-overview.d.ts +72 -0
  17. package/dist/composite/route-overview.js +233 -0
  18. package/dist/composite/stack-agg.d.ts +77 -0
  19. package/dist/composite/stack-agg.js +409 -0
  20. package/dist/composite/stack-test-mode.d.ts +33 -0
  21. package/dist/composite/stack-test-mode.js +60 -0
  22. package/dist/composite/top-functions.d.ts +21 -0
  23. package/dist/composite/top-functions.js +178 -0
  24. package/dist/composite/top-resources.d.ts +12 -0
  25. package/dist/composite/top-resources.js +263 -0
  26. package/dist/composite/types.d.ts +25 -0
  27. package/dist/composite/types.js +2 -0
  28. package/dist/error-hints.d.ts +23 -0
  29. package/dist/error-hints.js +80 -0
  30. package/dist/indicator-dashboard-keys.d.ts +12 -0
  31. package/dist/indicator-dashboard-keys.js +50 -0
  32. package/dist/indicator-dashboard-keys.json +338 -0
  33. package/dist/presets.js +9 -0
  34. package/dist/server-instructions.d.ts +5 -0
  35. package/dist/server-instructions.js +17 -0
  36. package/dist/server.d.ts +6 -1
  37. package/dist/server.js +33 -5
  38. package/dist/tools.d.ts +17 -0
  39. package/dist/tools.js +399 -18
  40. package/dist/version-check.d.ts +14 -0
  41. package/dist/version-check.js +94 -0
  42. package/dist/version-guide.d.ts +19 -0
  43. package/dist/version-guide.js +223 -0
  44. package/package.json +2 -2
  45. package/spec/overrides.json +26 -0
  46. package/spec/uwa-openapi.json +495 -111
package/dist/cli.js CHANGED
@@ -2,9 +2,11 @@
2
2
  import { Command, Option } from 'commander';
3
3
  import { UwaClient } from './client.js';
4
4
  import { loadSpec } from './spec.js';
5
+ import { compositeFilterKeys, selectCompositeTools, COMPOSITE_TOOLS } from './composite/index.js';
5
6
  import { filterKeys, selectOperations } from './presets.js';
6
7
  import { DEFAULT_MAX_CHARS, DEFAULT_MAX_ROWS, toolName } from './tools.js';
7
8
  import { PACKAGE_VERSION, startStdio } from './server.js';
9
+ import { checkForUpdate, formatUpdateNotice, resolveUpdateChannel } from './version-check.js';
8
10
  const DEFAULT_BASE_URL = 'https://secure-api.uwa4d.com';
9
11
  const SANDBOX_BASE_URL = 'https://sandbox-api.uwa4d.com';
10
12
  function splitList(value) {
@@ -70,10 +72,10 @@ program
70
72
  .option('--spec <path>', '自定义 spec 文件路径')
71
73
  .action((opts) => {
72
74
  const spec = loadSpec(opts.spec);
73
- const selected = new Set(selectOperations(spec.operations, {
74
- tools: opts.tool ?? ['all'],
75
- engine: opts.engine,
76
- }).map((o) => o.id));
75
+ const toolFilter = opts.tool ?? ['all'];
76
+ const engine = opts.engine;
77
+ const selected = new Set(selectOperations(spec.operations, { tools: toolFilter, engine }).map((o) => o.id));
78
+ const selectedComposite = new Set(selectCompositeTools({ tools: toolFilter, engine }).map((t) => t.id));
77
79
  const byModule = new Map();
78
80
  for (const op of spec.operations) {
79
81
  const key = op.modules.join(',');
@@ -89,8 +91,19 @@ program
89
91
  console.log(`${mark} ${toolName(op.id, 'snake', '').padEnd(52)} ${op.method.padEnd(4)} ${engines.padEnd(7)} ${op.name}`);
90
92
  }
91
93
  }
92
- console.log(`\n ${spec.operations.length} 个工具,当前条件选中 ${selected.size} 个(* 标记)`);
93
- console.log(`可用过滤键:${[...new Set(spec.operations.flatMap(filterKeys))].sort().join(', ')}`);
94
+ console.log('\n## composite(手写复合工具)');
95
+ for (const t of COMPOSITE_TOOLS) {
96
+ const mark = selectedComposite.has(t.id) ? '*' : ' ';
97
+ const engines = t.engines.map((e) => (e === 'unity' ? 'U3D' : 'UE')).join('+');
98
+ console.log(`${mark} ${toolName(t.id, 'snake', '').padEnd(52)} CMP ${engines.padEnd(7)} ${t.title}`);
99
+ }
100
+ const total = spec.operations.length + COMPOSITE_TOOLS.length;
101
+ const selectedCount = selected.size + selectedComposite.size;
102
+ console.log(`\n共 ${total} 个工具(原子 ${spec.operations.length} + 复合 ${COMPOSITE_TOOLS.length}),当前条件选中 ${selectedCount} 个(* 标记)`);
103
+ const keys = [
104
+ ...new Set([...spec.operations.flatMap(filterKeys), ...compositeFilterKeys()]),
105
+ ].sort();
106
+ console.log(`可用过滤键:${keys.join(', ')}`);
94
107
  });
95
108
  program
96
109
  .command('check')
@@ -121,6 +134,16 @@ program
121
134
  console.log('\n凭证有效,接口连通。');
122
135
  if (typeof total === 'number')
123
136
  console.log(`最近 7 天有 ${total} 份报告。`);
137
+ console.log(`\nMCP 包版本:${PACKAGE_VERSION}(${resolveUpdateChannel(PACKAGE_VERSION)} 通道)`);
138
+ if (process.env['UWA_MCP_SKIP_UPDATE_CHECK'] !== '1') {
139
+ const update = await checkForUpdate(PACKAGE_VERSION);
140
+ if (update) {
141
+ console.log(formatUpdateNotice(update));
142
+ }
143
+ else {
144
+ console.log('已是最新同通道版本(或暂时无法连接 npm registry)。');
145
+ }
146
+ }
124
147
  console.log('\n可以按 README 配置 MCP 客户端了。');
125
148
  }
126
149
  catch (err) {
package/dist/client.js CHANGED
@@ -1,22 +1,12 @@
1
1
  import { gunzipSync } from 'node:zlib';
2
2
  import { authHeaders } from './auth.js';
3
+ import { resolveErrorHint } from './error-hints.js';
3
4
  function isEnvelope(v) {
4
5
  if (!v || typeof v !== 'object' || Array.isArray(v))
5
6
  return false;
6
7
  const o = v;
7
8
  return o['status'] === 'success' || o['status'] === 'failed';
8
9
  }
9
- /** 常见错误码的排查提示,直接给到模型,省掉一轮试错。 */
10
- const ERROR_HINTS = {
11
- 20001: '业务参数不合法,核对参数名、取值范围和必填项(注意批量接口的参数名多为复数,如 dataKeys)',
12
- 23508: '数据服务错误,常见原因是报告不适用该接口版本(如新报告调用了 1.0 接口,或旧报告调用了 2.0 接口),改用对应版本重试',
13
- 24050: '该账号未开通 Open API 权限,请联系 UWA 工作人员开通',
14
- 24052: '请求参数有误,请对照接口文档检查',
15
- 24054: 'AppId 不存在,检查 appId 是否正确、是否用错了环境(sandbox / 线上凭证不通用)',
16
- 24056: '签名错误,检查 appSecret 是否正确',
17
- 24057: '时间戳过期,本机时间与服务端偏差不能超过 20 分钟',
18
- 30001: '服务端错误,常见原因是用错了引擎对应的接口(如对 UE 报告调用了 Unity 专用接口)',
19
- };
20
10
  export class UwaApiError extends Error {
21
11
  code;
22
12
  rawMessage;
@@ -68,8 +58,9 @@ export class UwaClient {
68
58
  if (parsed.status === 'failed' || parsed.error) {
69
59
  const code = parsed.error?.code ?? -1;
70
60
  const raw = parsed.error?.data?.rawMessage ?? '';
71
- const hint = ERROR_HINTS[code];
72
- throw new UwaApiError(code, raw, `[${code}] ${parsed.error?.message ?? '请求失败'}${raw ? ` (${raw})` : ''}${hint ? `\n排查建议:${hint}` : ''}`);
61
+ const apiMessage = parsed.error?.message ?? '请求失败';
62
+ const hint = resolveErrorHint(code, raw, apiMessage);
63
+ throw new UwaApiError(code, raw, `[${code}] ${apiMessage}${raw ? ` (${raw})` : ''}${hint ? `\n排查建议:${hint}` : ''}`);
73
64
  }
74
65
  return parsed.data ?? {};
75
66
  }
@@ -0,0 +1,19 @@
1
+ import type { ToolResult } from '../tools.js';
2
+ export declare function isObj(v: unknown): v is Record<string, unknown>;
3
+ export declare function asText(value: unknown): string;
4
+ /** 把任意结果压进字符闸门,超限时保留元信息 + 提示。 */
5
+ export declare function jsonToolResult(data: unknown, maxChars: number): ToolResult;
6
+ export declare function errorResult(err: unknown): ToolResult;
7
+ /** 从 createDate 字符串取出 YYYY-MM-DD,无法解析则返回空。 */
8
+ export declare function dayOf(dateStr: string | undefined | null): string | null;
9
+ export declare function dayGte(day: string, cutoff: string): boolean;
10
+ export declare function dayGt(day: string, cutoff: string): boolean;
11
+ export declare function dayLt(day: string, cutoff: string): boolean;
12
+ export declare function dayLte(day: string, cutoff: string): boolean;
13
+ /** 宽松比较 SDK 版本号(按点分数字)。缺省或无法解析视为不满足。 */
14
+ export declare function sdkAtLeast(sdk: string | undefined | null, min: string): boolean;
15
+ export declare function requireReportKey(args: Record<string, unknown>): {
16
+ dataKey?: string;
17
+ recordId?: string;
18
+ };
19
+ export declare function topNOf(args: Record<string, unknown>, fallback?: number): number;
@@ -0,0 +1,90 @@
1
+ export function isObj(v) {
2
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
3
+ }
4
+ export function asText(value) {
5
+ if (typeof value === 'string')
6
+ return value;
7
+ if (value === undefined || value === null)
8
+ return '(无返回内容)';
9
+ return JSON.stringify(value, null, 2);
10
+ }
11
+ /** 把任意结果压进字符闸门,超限时保留元信息 + 提示。 */
12
+ export function jsonToolResult(data, maxChars) {
13
+ const text = asText(data);
14
+ if (maxChars <= 0 || text.length <= maxChars) {
15
+ return { content: [{ type: 'text', text }] };
16
+ }
17
+ return {
18
+ content: [
19
+ {
20
+ type: 'text',
21
+ text: asText({
22
+ _truncated: `返回超过 ${maxChars} 字符上限已截断。请缩小 topN / 查询范围,或启动时调大 --max-chars。`,
23
+ _returnedChars: maxChars,
24
+ }),
25
+ },
26
+ { type: 'text', text: text.slice(0, maxChars) },
27
+ ],
28
+ };
29
+ }
30
+ export function errorResult(err) {
31
+ const msg = err instanceof Error ? err.message : String(err);
32
+ return { content: [{ type: 'text', text: msg }], isError: true };
33
+ }
34
+ /** 从 createDate 字符串取出 YYYY-MM-DD,无法解析则返回空。 */
35
+ export function dayOf(dateStr) {
36
+ if (!dateStr)
37
+ return null;
38
+ const m = /(\d{4}-\d{2}-\d{2})/.exec(String(dateStr));
39
+ return m?.[1] ?? null;
40
+ }
41
+ export function dayGte(day, cutoff) {
42
+ return day >= cutoff;
43
+ }
44
+ export function dayGt(day, cutoff) {
45
+ return day > cutoff;
46
+ }
47
+ export function dayLt(day, cutoff) {
48
+ return day < cutoff;
49
+ }
50
+ export function dayLte(day, cutoff) {
51
+ return day <= cutoff;
52
+ }
53
+ /** 宽松比较 SDK 版本号(按点分数字)。缺省或无法解析视为不满足。 */
54
+ export function sdkAtLeast(sdk, min) {
55
+ if (!sdk)
56
+ return false;
57
+ const parse = (s) => s
58
+ .replace(/^v/i, '')
59
+ .split(/[^0-9]+/)
60
+ .filter(Boolean)
61
+ .map((n) => Number.parseInt(n, 10));
62
+ const a = parse(sdk);
63
+ const b = parse(min);
64
+ if (!a.length || a.some((n) => Number.isNaN(n)))
65
+ return false;
66
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
67
+ const x = a[i] ?? 0;
68
+ const y = b[i] ?? 0;
69
+ if (x > y)
70
+ return true;
71
+ if (x < y)
72
+ return false;
73
+ }
74
+ return true;
75
+ }
76
+ export function requireReportKey(args) {
77
+ const dataKey = typeof args['dataKey'] === 'string' && args['dataKey'] ? args['dataKey'] : undefined;
78
+ const recordId = args['recordId'] !== undefined && args['recordId'] !== null && args['recordId'] !== ''
79
+ ? String(args['recordId'])
80
+ : undefined;
81
+ if (!dataKey && !recordId)
82
+ throw new Error('必须提供 dataKey 或 recordId');
83
+ return { dataKey, recordId };
84
+ }
85
+ export function topNOf(args, fallback = 20) {
86
+ const n = typeof args['topN'] === 'number' ? args['topN'] : fallback;
87
+ if (!Number.isFinite(n) || n < 1)
88
+ return fallback;
89
+ return Math.min(Math.floor(n), 200);
90
+ }
@@ -0,0 +1,25 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Engine } from '../spec.js';
3
+ import { type NameCase } from '../tools.js';
4
+ import type { UwaClient } from '../client.js';
5
+ import type { CompositeTool } from './types.js';
6
+ /** 全部手写复合工具(不进 uwa-openapi.json)。 */
7
+ export declare const COMPOSITE_TOOLS: readonly CompositeTool[];
8
+ export declare const COMPOSITE_DEFAULT_IDS: readonly string[];
9
+ export interface SelectCompositeOptions {
10
+ tools?: string[];
11
+ engine?: Engine | 'all';
12
+ }
13
+ /**
14
+ * 与原子工具同一套 --tool / --engine 过滤语义。
15
+ * 未指定 --tool 时视为 preset.default(复合工具若声明了该键则选中)。
16
+ */
17
+ export declare function selectCompositeTools(opts: SelectCompositeOptions): CompositeTool[];
18
+ export declare function registerCompositeTools(server: McpServer, client: UwaClient, opts: {
19
+ tools: string[];
20
+ engine: Engine | 'all';
21
+ nameCase: NameCase;
22
+ namePrefix: string;
23
+ maxChars: number;
24
+ }): number;
25
+ export declare function compositeFilterKeys(): string[];
@@ -0,0 +1,46 @@
1
+ import { toolName } from '../tools.js';
2
+ import { topResourcesTool } from './top-resources.js';
3
+ import { topFunctionsTool } from './top-functions.js';
4
+ import { reportDiagnosisTool } from './report-diagnosis.js';
5
+ /** 全部手写复合工具(不进 uwa-openapi.json)。 */
6
+ export const COMPOSITE_TOOLS = [
7
+ topResourcesTool,
8
+ topFunctionsTool,
9
+ reportDiagnosisTool,
10
+ ];
11
+ export const COMPOSITE_DEFAULT_IDS = COMPOSITE_TOOLS.map((t) => t.id);
12
+ /**
13
+ * 与原子工具同一套 --tool / --engine 过滤语义。
14
+ * 未指定 --tool 时视为 preset.default(复合工具若声明了该键则选中)。
15
+ */
16
+ export function selectCompositeTools(opts) {
17
+ const wanted = new Set((opts.tools?.length ? opts.tools : ['preset.default']).map((t) => t.trim()).filter(Boolean));
18
+ const engine = opts.engine ?? 'all';
19
+ return COMPOSITE_TOOLS.filter((t) => {
20
+ if (engine !== 'all' && !t.engines.includes(engine))
21
+ return false;
22
+ const keys = new Set([t.id, 'preset.all', 'all', 'composite', ...t.filterKeys]);
23
+ return [...keys].some((k) => wanted.has(k));
24
+ });
25
+ }
26
+ export function registerCompositeTools(server, client, opts) {
27
+ const selected = selectCompositeTools({ tools: opts.tools, engine: opts.engine });
28
+ const ctx = {
29
+ client,
30
+ maxChars: opts.maxChars,
31
+ nameCase: opts.nameCase,
32
+ namePrefix: opts.namePrefix,
33
+ engine: opts.engine,
34
+ };
35
+ for (const tool of selected) {
36
+ server.registerTool(toolName(tool.id, opts.nameCase, opts.namePrefix), {
37
+ title: tool.title,
38
+ description: tool.description,
39
+ inputSchema: tool.inputSchema,
40
+ }, (async (args) => tool.handler(args ?? {}, ctx)));
41
+ }
42
+ return selected.length;
43
+ }
44
+ export function compositeFilterKeys() {
45
+ return [...new Set(COMPOSITE_TOOLS.flatMap((t) => [t.id, 'composite', ...t.filterKeys]))].sort();
46
+ }
@@ -0,0 +1,8 @@
1
+ /** 按函数名过滤(子串或 /regex/flags),供 top_functions 本地过滤 idmap 结果。 */
2
+ export declare function matchesNamePattern(name: string, pattern?: unknown): boolean;
3
+ export declare function filterRowsByNamePattern<T extends {
4
+ name: string;
5
+ }>(rows: T[], pattern: unknown): {
6
+ rows: T[];
7
+ matchedTotal: number;
8
+ };
@@ -0,0 +1,25 @@
1
+ /** 按函数名过滤(子串或 /regex/flags),供 top_functions 本地过滤 idmap 结果。 */
2
+ export function matchesNamePattern(name, pattern) {
3
+ if (pattern == null || (typeof pattern === 'string' && !pattern.trim()))
4
+ return true;
5
+ const p = String(pattern).trim();
6
+ if (p.length >= 2 && p.startsWith('/') && p.lastIndexOf('/') > 0) {
7
+ const last = p.lastIndexOf('/');
8
+ const body = p.slice(1, last);
9
+ const flags = p.slice(last + 1);
10
+ try {
11
+ return new RegExp(body, flags).test(name);
12
+ }
13
+ catch {
14
+ return name.toLowerCase().includes(p.toLowerCase());
15
+ }
16
+ }
17
+ return name.toLowerCase().includes(p.toLowerCase());
18
+ }
19
+ export function filterRowsByNamePattern(rows, pattern) {
20
+ if (pattern == null || (typeof pattern === 'string' && !pattern.trim())) {
21
+ return { rows, matchedTotal: rows.length };
22
+ }
23
+ const matched = rows.filter((r) => matchesNamePattern(r.name, pattern));
24
+ return { rows: matched, matchedTotal: matched.length };
25
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Overview 统计 v1 / v2 / UE 2.0 返回结构差异很大:
3
+ * - Unity v1:对象形 brief { fps_mean: [49] } + asset_stats
4
+ * - UE 2.0:数组形 brief [{ label, value }] + Resource 段
5
+ * - Unity v2(POST):categories[] + basicInfo{}
6
+ * 这里统一抽成复合工具能用的扁平视图。
7
+ */
8
+ export interface OverviewView {
9
+ /** 简报指标 key → 单值 */
10
+ brief: Record<string, unknown>;
11
+ /** 报告基本信息 label/key → 单值 */
12
+ summary: Record<string, unknown>;
13
+ /** 函数/卡顿相关条目 */
14
+ functions: {
15
+ name: string;
16
+ value: number;
17
+ unit?: string;
18
+ source: string;
19
+ label?: string;
20
+ }[];
21
+ /** 内存/资源相关条目(类型级) */
22
+ memory: {
23
+ name: string;
24
+ assetType: string;
25
+ memoryBytes: number;
26
+ _source: string;
27
+ }[];
28
+ /** 是否开启资源采集;无法判定时为 null */
29
+ hasResource: boolean | null;
30
+ shape: 'v1' | 'v2' | 'ue-v2' | 'unknown';
31
+ }
32
+ /** 把 unwrap 后的报告或原始 data 统一成 OverviewView。 */
33
+ export declare function viewOverview(data: unknown): OverviewView;