@zltquant/zlt-mcp 0.4.1 → 0.5.0-beta.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 CHANGED
@@ -7,7 +7,20 @@
7
7
 
8
8
  ## 接入
9
9
 
10
- 把下面的配置加进你的 MCP 客户端(Trae / WorkBuddy / Claude Code / Cursor 等):
10
+ WorkBuddy 使用配套 Skill,并由 Skill 调用 npm CLI 完成任务,无需配置 MCP 连接器:
11
+
12
+ ```powershell
13
+ npx -y @zltquant/zlt-mcp list --json
14
+ npx -y @zltquant/zlt-mcp call zlt-runtime-doctor --args-json '{}'
15
+ ```
16
+
17
+ 业务调用支持 `call --output-json <新文件路径>`:完整 JSON 写入文件,stdout 返回含
18
+ `ok`、`tool`、`resultFile`、`cliProvenance`、精简 `data` 的回执。
19
+ 输出目录须已存在,已有文件会在执行工具前报错;每次使用新文件名。完整参数与大段数据从结果文件读取。
20
+ stderr 保留诊断,失败返回非零退出码。工具执行后若写文件失败,完整结果会输出到 stdout;先核对结果,不要自动重复执行。
21
+ 不传该参数时保持原有完整 stdout。
22
+
23
+ 其他支持原生 MCP 的客户端(Trae / Claude Code / Cursor 等)可以使用下面的配置:
11
24
 
12
25
  ```json
13
26
  {
@@ -20,17 +33,10 @@
20
33
  }
21
34
  ```
22
35
 
23
- WorkBuddy 也可以不配置 MCP,直接由 Skill 调用 CLI:
24
-
25
- ```powershell
26
- npx -y @zltquant/zlt-mcp list --json
27
- npx -y @zltquant/zlt-mcp call mcp-get-strategy-list --args-json '{"status":0,"treeId":0}'
28
- ```
29
-
30
36
  首次使用前请从 https://api.zltquant.com/login 获取 Key,并由用户自己创建
31
37
  `%USERPROFILE%\.zltquant\credentials.json`(只填 `apiKey`)。
32
38
 
33
- 也可以固定安装后用 node 直接启动(技能市场的 zlt-mcp-setup 技能走这条路):
39
+ 其他客户端无法使用 npx 时,也可以固定安装后用 node 启动 MCP 服务:
34
40
 
35
41
  ```powershell
36
42
  npm i @zltquant/zlt-mcp --prefix "$env:USERPROFILE\.zlt-quant" --registry https://registry.npmmirror.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zltquant/zlt-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0-beta.0",
4
4
  "description": "智量通 ZLT Quant MCP server(Windows x64;量化策略创建与本地回测)",
5
5
  "bin": {
6
6
  "zlt-mcp": "./zlt-mcp-proxy.bundle.cjs"
@@ -18374,8 +18374,21 @@ function parsePackageList(value) {
18374
18374
 
18375
18375
  // ../zlt-runtime-core/dist/registry.js
18376
18376
  var import_child_process = require("child_process");
18377
- var import_util2 = require("util");
18378
- var execAsync = (0, import_util2.promisify)(import_child_process.exec);
18377
+ var POWERSHELL_EXE = "powershell.exe";
18378
+ var REGISTRY_SUBKEY_ENV = "ZLT_REGISTRY_SUBKEY_B64";
18379
+ var REGISTRY_VALUE_NAME_ENV = "ZLT_REGISTRY_VALUE_NAME_B64";
18380
+ var READ_REGISTRY_VALUE_SCRIPT = [
18381
+ "$subKey = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:ZLT_REGISTRY_SUBKEY_B64))",
18382
+ "$valueName = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:ZLT_REGISTRY_VALUE_NAME_B64))",
18383
+ "$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($subKey)",
18384
+ "if ($null -eq $key) { exit 1 }",
18385
+ "try {",
18386
+ " if ($key.GetValueKind($valueName) -ne [Microsoft.Win32.RegistryValueKind]::String) { exit 1 }",
18387
+ " $value = $key.GetValue($valueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)",
18388
+ " if ($null -eq $value) { exit 1 }",
18389
+ " [Console]::Out.Write([string]$value)",
18390
+ "} finally { $key.Dispose() }"
18391
+ ].join("\n");
18379
18392
  function buildAssistantRegistryKey(registryKeyName) {
18380
18393
  return `HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${registryKeyName}`;
18381
18394
  }
@@ -18393,26 +18406,45 @@ function getRegistryKeyNames(config2) {
18393
18406
  }
18394
18407
  return names;
18395
18408
  }
18396
- function parseRegistryStringValue(stdout, valueName) {
18397
- const escapedValueName = valueName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18398
- const match = stdout.match(new RegExp(`${escapedValueName}\\s+REG_SZ\\s+(.+)`, "i"));
18399
- return match?.[1]?.trim() || "";
18409
+ function buildPowerShellRegistryEnvironment(registryKey, valueName) {
18410
+ const subKey = registryKey.replace(/^HKLM\\/i, "");
18411
+ return {
18412
+ ...process.env,
18413
+ [REGISTRY_SUBKEY_ENV]: Buffer.from(subKey, "utf-8").toString("base64"),
18414
+ [REGISTRY_VALUE_NAME_ENV]: Buffer.from(valueName, "utf-8").toString("base64")
18415
+ };
18400
18416
  }
18401
- async function readAssistantRegistryStringValue(config2, valueName, suppressStderr = true) {
18417
+ function buildPowerShellRegistryArgs() {
18418
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", READ_REGISTRY_VALUE_SCRIPT];
18419
+ }
18420
+ function readRegistryValueWithPowerShell(registryKey, valueName) {
18421
+ return new Promise((resolve8, reject) => {
18422
+ (0, import_child_process.execFile)(POWERSHELL_EXE, buildPowerShellRegistryArgs(), {
18423
+ encoding: "utf-8",
18424
+ env: buildPowerShellRegistryEnvironment(registryKey, valueName),
18425
+ windowsHide: true
18426
+ }, (error2, stdout) => {
18427
+ if (error2)
18428
+ reject(error2);
18429
+ else
18430
+ resolve8(stdout.trim());
18431
+ });
18432
+ });
18433
+ }
18434
+ async function readAssistantRegistryStringValue(config2, valueName, _suppressStderr = true) {
18402
18435
  const registryKeyNames = getRegistryKeyNames(config2);
18403
18436
  const registryKey = buildAssistantRegistryKey(registryKeyNames[0]);
18404
18437
  if (process.platform !== "win32")
18405
18438
  return { registryKey, valueName };
18406
- const stderrRedirect = suppressStderr ? " 2>nul" : "";
18407
18439
  for (const registryKeyName of registryKeyNames) {
18408
18440
  const candidateRegistryKey = buildAssistantRegistryKey(registryKeyName);
18409
18441
  try {
18410
- const { stdout } = await execAsync(`reg query "${candidateRegistryKey}" /v ${valueName}${stderrRedirect}`);
18442
+ const stdout = await readRegistryValueWithPowerShell(candidateRegistryKey, valueName);
18411
18443
  return {
18412
18444
  registryKey: candidateRegistryKey,
18413
18445
  valueName,
18414
18446
  stdout,
18415
- value: parseRegistryStringValue(stdout, valueName)
18447
+ value: stdout
18416
18448
  };
18417
18449
  } catch {
18418
18450
  }
@@ -24904,6 +24936,12 @@ function echoResolvedBacktestConfig(tool, args, result, contexts) {
24904
24936
  const echoedRunId = typeof parsed.Data.runId === "string" ? parsed.Data.runId.trim() : "";
24905
24937
  const runId = echoedRunId || (tool.name === "mcp-re-backtest" && typeof args.runId === "string" ? args.runId.trim() : "");
24906
24938
  const context = runId ? contexts.get(runId) : void 0;
24939
+ if (runId) echo.runId = runId;
24940
+ if (context?.codePath) {
24941
+ echo.codePath = context.codePath;
24942
+ echo.runDir = context.codePath;
24943
+ }
24944
+ if (context?.runParams) echo.runParams = { ...context.runParams };
24907
24945
  if (context?.attemptStartedAtMs) {
24908
24946
  echo.attemptFence = {
24909
24947
  attemptStartedAtMs: context.attemptStartedAtMs,
@@ -27468,7 +27506,8 @@ function runtimeRecoveryStopData(message, extra = {}, handler = "runtimeStart")
27468
27506
 
27469
27507
  // src/index.ts
27470
27508
  var CLI_NAME = "zlt-mcp";
27471
- var CLI_FALLBACK_VERSION = "0.4.0";
27509
+ var CLI_FALLBACK_PACKAGE_NAME = "@zltquant/zlt-mcp";
27510
+ var CLI_FALLBACK_VERSION = "0.5.0-beta.0";
27472
27511
  if (process.argv.length <= 2) {
27473
27512
  startMcpServer();
27474
27513
  } else {
@@ -27478,28 +27517,51 @@ if (process.argv.length <= 2) {
27478
27517
  if (kind !== "backtest") throw new Error(`Unsupported keepalive kind: ${kind}`);
27479
27518
  await keepMcpBacktestAlive(id, codePath);
27480
27519
  });
27481
- program2.command("call <tool>").description("Call any MCP tool through the bundled MCP server").option("--args-json <json>", "JSON object containing tool arguments").option("--args-file <path>", "Read the JSON argument object from a file").option("--timeout-ms <ms>", "MCP request inactivity timeout in milliseconds").option("--max-total-timeout-ms <ms>", "Optional hard upper bound for the request").option("--no-progress", "Suppress progress diagnostics on stderr").action(async (tool, options) => {
27520
+ program2.command("call <tool>").description("Call any MCP tool through the bundled MCP server").option("--args-json <json>", "JSON object containing tool arguments").option("--args-file <path>", "Read the JSON argument object from a file").option("--output-json <path>", "Save the complete result to a new JSON file and print a compact receipt").option("--timeout-ms <ms>", "MCP request inactivity timeout in milliseconds").option("--max-total-timeout-ms <ms>", "Optional hard upper bound for the request").option("--no-progress", "Suppress progress diagnostics on stderr").action(async (tool, options) => {
27521
+ let outputFd;
27482
27522
  try {
27483
27523
  const args = parseMcpArgs(options.argsJson, options.argsFile);
27524
+ const timeoutMs = parseTimeoutOption(options.timeoutMs, "--timeout-ms");
27525
+ const maxTotalTimeoutMs = parseTimeoutOption(options.maxTotalTimeoutMs, "--max-total-timeout-ms");
27526
+ const resultFile = options.outputJson === void 0 ? void 0 : path14.resolve(options.outputJson);
27527
+ if (resultFile !== void 0) outputFd = fs13.openSync(resultFile, "wx");
27484
27528
  const result = await callMcpTool(tool, args, {
27485
- timeoutMs: parseTimeoutOption(options.timeoutMs, "--timeout-ms"),
27486
- maxTotalTimeoutMs: parseTimeoutOption(options.maxTotalTimeoutMs, "--max-total-timeout-ms"),
27529
+ timeoutMs,
27530
+ maxTotalTimeoutMs,
27487
27531
  showProgress: options.progress !== false
27488
27532
  });
27489
- process.stdout.write(JSON.stringify(result) + "\n");
27533
+ const complete = withCliProvenance(result);
27534
+ const serialized = JSON.stringify(complete) + "\n";
27535
+ if (outputFd !== void 0 && resultFile !== void 0) {
27536
+ try {
27537
+ fs13.writeFileSync(outputFd, serialized, "utf-8");
27538
+ fs13.closeSync(outputFd);
27539
+ outputFd = void 0;
27540
+ } catch (error2) {
27541
+ process.stdout.write(serialized);
27542
+ throw new Error(`Tool returned a result, but saving ${resultFile} failed; complete result is on stdout. Do not repeat the tool automatically. ${String(error2)}`);
27543
+ }
27544
+ const receipt = compactCliReceipt(tool, complete, resultFile);
27545
+ process.stdout.write(JSON.stringify(receipt) + "\n");
27546
+ if (!receipt.ok) process.exitCode = 1;
27547
+ } else {
27548
+ process.stdout.write(serialized);
27549
+ }
27490
27550
  if (result.isError === true) process.exitCode = 1;
27491
27551
  } catch (error2) {
27492
27552
  const message = error2 instanceof Error ? error2.message : String(error2);
27493
27553
  process.stderr.write(`Error: ${message}
27494
27554
  `);
27495
27555
  process.exitCode = 1;
27556
+ } finally {
27557
+ if (outputFd !== void 0) fs13.closeSync(outputFd);
27496
27558
  }
27497
27559
  });
27498
27560
  program2.command("list").description("List MCP tools exposed by the bundled server").option("--json", "Emit the complete tools/list response as JSON").action(async (options) => {
27499
27561
  try {
27500
27562
  const result = await listMcpTools();
27501
27563
  if (options.json) {
27502
- process.stdout.write(JSON.stringify(result) + "\n");
27564
+ process.stdout.write(JSON.stringify(withCliProvenance(result)) + "\n");
27503
27565
  return;
27504
27566
  }
27505
27567
  const tools = Array.isArray(result.tools) ? result.tools : [];
@@ -27564,13 +27626,16 @@ if (process.argv.length <= 2) {
27564
27626
  });
27565
27627
  }
27566
27628
  function resolveCliVersion() {
27629
+ return resolveCliPackageProvenance().packageVersion;
27630
+ }
27631
+ function resolveCliPackageProvenance() {
27567
27632
  let current = path14.dirname(path14.resolve(process.argv[1] || process.cwd()));
27568
27633
  for (let depth = 0; depth < 6; depth += 1) {
27569
27634
  const manifestPath = path14.join(current, "package.json");
27570
27635
  try {
27571
27636
  const manifest = JSON.parse(fs13.readFileSync(manifestPath, "utf-8"));
27572
27637
  if ((manifest.name === "@zltquant/zlt-mcp" || manifest.name === "zlt-mcp-proxy") && typeof manifest.version === "string") {
27573
- return manifest.version;
27638
+ return { packageName: manifest.name, packageVersion: manifest.version };
27574
27639
  }
27575
27640
  } catch {
27576
27641
  }
@@ -27578,7 +27643,50 @@ function resolveCliVersion() {
27578
27643
  if (parent === current) break;
27579
27644
  current = parent;
27580
27645
  }
27581
- return CLI_FALLBACK_VERSION;
27646
+ return { packageName: CLI_FALLBACK_PACKAGE_NAME, packageVersion: CLI_FALLBACK_VERSION };
27647
+ }
27648
+ function withCliProvenance(result) {
27649
+ return { ...result, cliProvenance: resolveCliPackageProvenance() };
27650
+ }
27651
+ function compactCliReceipt(tool, result, resultFile) {
27652
+ const payload = parseMcpResult(result);
27653
+ const code = payload?.ErrNo;
27654
+ const ok = result.isError !== true && (code === void 0 || code === null || Number(code) === 0);
27655
+ const source = payload?.Data;
27656
+ const data = {};
27657
+ if (source && typeof source === "object" && !Array.isArray(source)) {
27658
+ const fields = source;
27659
+ for (const key of [
27660
+ "runId",
27661
+ "backtestId",
27662
+ "strategyId",
27663
+ "localPath",
27664
+ "codePath",
27665
+ "runDir",
27666
+ "attemptFence",
27667
+ "status",
27668
+ "progress",
27669
+ "available",
27670
+ "runtimeReady",
27671
+ "backtestReady",
27672
+ "nextAction",
27673
+ "recommendedAction",
27674
+ "error_code",
27675
+ "error_msg"
27676
+ ]) {
27677
+ if (fields[key] !== void 0) {
27678
+ data[key] = key === "error_msg" && typeof fields[key] === "string" ? fields[key].slice(0, 512) : fields[key];
27679
+ }
27680
+ }
27681
+ }
27682
+ return {
27683
+ ok,
27684
+ tool,
27685
+ resultFile,
27686
+ cliProvenance: result.cliProvenance,
27687
+ data,
27688
+ ...!ok ? { error: { code: code ?? null, message: String(payload?.ErrMsg ?? "Tool reported an error; see resultFile").slice(0, 512) } } : {}
27689
+ };
27582
27690
  }
27583
27691
  function parseTimeoutOption(value, flag) {
27584
27692
  if (value === void 0) return void 0;