@zltquant/zlt-mcp 0.4.2 → 0.5.0-beta.1

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
@@ -14,6 +14,12 @@ npx -y @zltquant/zlt-mcp list --json
14
14
  npx -y @zltquant/zlt-mcp call zlt-runtime-doctor --args-json '{}'
15
15
  ```
16
16
 
17
+ 业务调用支持 `call --output-json <新文件路径>`:完整 JSON 写入文件,stdout 返回含
18
+ `ok`、`tool`、`resultFile`、`cliProvenance`、精简 `data` 的回执。
19
+ 输出目录须已存在,已有文件会在执行工具前报错;每次使用新文件名。完整参数与大段数据从结果文件读取。
20
+ stderr 保留诊断,失败返回非零退出码。工具执行后若写文件失败,完整结果会输出到 stdout;先核对结果,不要自动重复执行。
21
+ 不传该参数时保持原有完整 stdout。
22
+
17
23
  其他支持原生 MCP 的客户端(Trae / Claude Code / Cursor 等)可以使用下面的配置:
18
24
 
19
25
  ```json
package/config.json CHANGED
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "name": "mcp-strategy-get-template-list",
24
- "description": "获取策略参数模板列表。创建策略前应先调用本工具,根据返回的模板列表选择 templateItemVal,再调用策略创建工具。",
24
+ "description": "获取平台策略参数模板列表。创建策略前应先调用本工具,只能从实时返回的条目取 val 作为 templateItemVal,不要编造或硬编码模板标识。用户明确指定的模板优先;用户只说「推荐策略并回测」等委托选择请求时,agent 可根据返回的名称、说明和可用标签选择合适模板,无需再要求用户选择。没有其他偏好时,优先选择实时返回的带平台推荐标签且适合的日线模板;没有返回推荐标记时,若列表中存在且适合用户需求,可选择「均线择时」作为后备;否则从当前列表选择其他合适模板并简要说明理由。随后调用 mcp-create-strategy-with-template 创建。",
25
25
  "inputSchema": {
26
26
  "type": "object",
27
27
  "properties": {
@@ -47,13 +47,13 @@
47
47
  },
48
48
  {
49
49
  "name": "mcp-create-strategy-with-template",
50
- "description": "基于用户选择的策略模板创建本地策略。先用 mcp-strategy-get-template-list 取模板列表让用户选,把所选模板的 val 填入 templateItemVal;templateVal 固定为 strategy。名称默认用「模板名称+日期时间(YYMMDD-HHMM)」,调用前允许用户修改或确认——ZLTAF 对重名策略会静默不创建并返回空结果,本工具已将其转为明确的 ErrNo 10051,遇到就改用唯一名称重试,**切勿改为复用既有同名策略**。\n\n返回含 strategyId 与 localPath,请直接使用,不要再自行查询策略列表按名称猜测。",
50
+ "description": "基于平台策略模板创建本地策略。先用 mcp-strategy-get-template-list 取实时列表,把所选条目的 val 填入 templateItemVal;templateVal 固定为 strategy。用户明确指定的模板优先;用户委托推荐时允许 agent 代选,无需再要求用户选择。名称默认用「模板名称+日期时间(YYMMDD-HHMM)」,允许用户修改;用户未指定名称时直接生成唯一名称——ZLTAF 对重名策略会静默不创建并返回空结果,本工具已将其转为明确的 ErrNo 10051,遇到就改用唯一名称重试,**切勿改为复用既有同名策略**。\n\n返回含 strategyId 与 localPath,请直接使用,不要再自行查询策略列表按名称猜测。",
51
51
  "inputSchema": {
52
52
  "type": "object",
53
53
  "properties": {
54
54
  "name": {
55
55
  "type": "string",
56
- "description": "策略名称。默认使用“模板名称+日期(YYMMDD)”生成,允许用户修改;调用本工具时必须传入最终确认后的名称。"
56
+ "description": "策略名称。默认使用“模板名称+日期时间(YYMMDD-HHMM)”生成唯一名称,允许用户修改;用户未指定名称时由 agent 直接生成,调用时传入该名称。"
57
57
  },
58
58
  "templateVal": {
59
59
  "type": "string",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "templateItemVal": {
67
67
  "type": "string",
68
- "description": "用户选择的模板 val,来自 mcp-strategy-get-template-list 返回的模板条目。调用前必须让用户选择模板。"
68
+ "description": "用户选择或委托 agent 代选的模板 val,必须来自 mcp-strategy-get-template-list 实时返回的模板条目;不要编造或硬编码。用户明确指定的模板优先。"
69
69
  },
70
70
  "userId": {
71
71
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zltquant/zlt-mcp",
3
- "version": "0.4.2",
3
+ "version": "0.5.0-beta.1",
4
4
  "description": "智量通 ZLT Quant MCP server(Windows x64;量化策略创建与本地回测)",
5
5
  "bin": {
6
6
  "zlt-mcp": "./zlt-mcp-proxy.bundle.cjs"
@@ -23302,14 +23302,14 @@ function shapeToolResult(tool, args, result, session) {
23302
23302
  session.activeBacktestRunIds.delete(args.runId);
23303
23303
  }
23304
23304
  const echoed = echoResolvedBacktestConfig(tool, args, result, session.backtestRunContexts);
23305
- const guarded = applyBacktestResultGuards(
23305
+ const guarded = annotateBacktestOutcome(tool, applyBacktestResultGuards(
23306
23306
  tool,
23307
23307
  args,
23308
23308
  echoed,
23309
23309
  session.backtestPollStates,
23310
23310
  session.backtestRunContexts,
23311
23311
  session.activeBacktestRunIds
23312
- );
23312
+ ));
23313
23313
  const activeChange = classifyActiveBacktestChange(tool, args, guarded);
23314
23314
  if (activeChange) {
23315
23315
  if (activeChange.active) session.activeBacktestRunIds.add(activeChange.runId);
@@ -23323,6 +23323,38 @@ function shapeToolResult(tool, args, result, session) {
23323
23323
  const distilled = distillFactorDetectReport(tool, args, diagnosed);
23324
23324
  return injectDebugRunId(tool, injectStrategyDocsHint(tool, distilled, session.docsPath));
23325
23325
  }
23326
+ function backtestStatusNumber(status) {
23327
+ return typeof status === "number" || typeof status === "string" && status.trim() !== "" ? Number(status) : NaN;
23328
+ }
23329
+ function annotateBacktestOutcome(tool, result) {
23330
+ if (tool.name !== "mcp-get-backtest-state") return result;
23331
+ try {
23332
+ const parsed = JSON.parse(result.content[0]?.text ?? "");
23333
+ if (!isRecord2(parsed) || !isRecord2(parsed.Data)) return result;
23334
+ const data = parsed.Data;
23335
+ const status = backtestStatusNumber(data.status);
23336
+ const reconciled = parsed.ErrNo === 10043 && data.reconciledFrom === "result.json";
23337
+ const backtestOutcome = reconciled ? "failed" : status === 0 || status === 1 ? "running" : status === 2 ? "failed" : status === 3 ? "cancelled" : status === 4 ? "terminated" : status === 5 ? "completed" : "unknown";
23338
+ const isTerminal2 = backtestOutcome !== "running" && backtestOutcome !== "unknown";
23339
+ const message = typeof data.error_msg === "string" ? data.error_msg.trim() : "";
23340
+ return {
23341
+ ...result,
23342
+ content: [{ type: "text", text: JSON.stringify({
23343
+ ...parsed,
23344
+ Data: {
23345
+ ...data,
23346
+ backtestOutcome,
23347
+ isTerminal: isTerminal2,
23348
+ evidenceSource: reconciled ? "current-attempt-result.json" : "ipc-state",
23349
+ nextAction: isTerminal2 ? "finalize-backtest" : backtestOutcome === "unknown" || data.waitStopped || data.pollingStopped ? "inspect-backtest-state" : "keep-waiting",
23350
+ ...backtestOutcome === "failed" && !reconciled && (!message || /异常(?:退出|终止)|unexpected.*exit|exited unexpectedly/i.test(message)) ? { failureCause: "unknown" } : {}
23351
+ }
23352
+ }) }]
23353
+ };
23354
+ } catch {
23355
+ return result;
23356
+ }
23357
+ }
23326
23358
  var ZLTAF_CONFIG_SEGMENTS = ["zltaf", "application-local.properties"];
23327
23359
  var FALLBACK_FACTOR_PROJECT_PATH = ["Users", "factors"];
23328
23360
  var factorProjectRootCache = /* @__PURE__ */ new Map();
@@ -24404,11 +24436,11 @@ function unreadableDownloadProgressError(uuid2) {
24404
24436
  );
24405
24437
  }
24406
24438
  function unreadableBacktestStateError(runId) {
24407
- return waitError(
24439
+ return annotateBacktestOutcome(BACKTEST_WAIT_POLL_IDENTITY, waitError(
24408
24440
  10054,
24409
24441
  "\u56DE\u6D4B\u72B6\u6001\u5E94\u7B54\u65E0\u6CD5\u89E3\u6790\uFF0C\u5DF2\u505C\u6B62\u7B49\u5F85\uFF1B\u8BF7\u7528 mcp-get-backtest-state \u590D\u67E5\u3002",
24410
24442
  { runId }
24411
- );
24443
+ ));
24412
24444
  }
24413
24445
  function factorWaitLane(executionId, session) {
24414
24446
  let staleCount = 0;
@@ -24557,7 +24589,7 @@ function backtestWaitLane(runId, session, codePath, resultBaseline, attemptStart
24557
24589
  return { verdict: "return", result: unreadableBacktestStateError(runId) };
24558
24590
  }
24559
24591
  const data = parsed.Data;
24560
- const status = Number(data.status);
24592
+ const status = backtestStatusNumber(data.status);
24561
24593
  if (!Number.isFinite(status)) {
24562
24594
  return { verdict: "return", result: unreadableBacktestStateError(runId) };
24563
24595
  }
@@ -24598,7 +24630,10 @@ function backtestWaitLane(runId, session, codePath, resultBaseline, attemptStart
24598
24630
  frozenCount = signature === lastSignature ? frozenCount + 1 : 0;
24599
24631
  lastSignature = signature;
24600
24632
  if (frozenCount >= WAIT_FROZEN_THRESHOLD) {
24601
- return { verdict: "return", result: frozenBacktestResult(parsed, data) };
24633
+ return { verdict: "return", result: annotateBacktestOutcome(
24634
+ BACKTEST_WAIT_POLL_IDENTITY,
24635
+ frozenBacktestResult(parsed, data)
24636
+ ) };
24602
24637
  }
24603
24638
  const progress = typeof data.progress === "number" && Number.isFinite(data.progress) ? data.progress : 0;
24604
24639
  return { verdict: "wait", progress };
@@ -24936,6 +24971,12 @@ function echoResolvedBacktestConfig(tool, args, result, contexts) {
24936
24971
  const echoedRunId = typeof parsed.Data.runId === "string" ? parsed.Data.runId.trim() : "";
24937
24972
  const runId = echoedRunId || (tool.name === "mcp-re-backtest" && typeof args.runId === "string" ? args.runId.trim() : "");
24938
24973
  const context = runId ? contexts.get(runId) : void 0;
24974
+ if (runId) echo.runId = runId;
24975
+ if (context?.codePath) {
24976
+ echo.codePath = context.codePath;
24977
+ echo.runDir = context.codePath;
24978
+ }
24979
+ if (context?.runParams) echo.runParams = { ...context.runParams };
24939
24980
  if (context?.attemptStartedAtMs) {
24940
24981
  echo.attemptFence = {
24941
24982
  attemptStartedAtMs: context.attemptStartedAtMs,
@@ -27501,7 +27542,7 @@ function runtimeRecoveryStopData(message, extra = {}, handler = "runtimeStart")
27501
27542
  // src/index.ts
27502
27543
  var CLI_NAME = "zlt-mcp";
27503
27544
  var CLI_FALLBACK_PACKAGE_NAME = "@zltquant/zlt-mcp";
27504
- var CLI_FALLBACK_VERSION = "0.4.2";
27545
+ var CLI_FALLBACK_VERSION = "0.5.0-beta.0";
27505
27546
  if (process.argv.length <= 2) {
27506
27547
  startMcpServer();
27507
27548
  } else {
@@ -27511,21 +27552,44 @@ if (process.argv.length <= 2) {
27511
27552
  if (kind !== "backtest") throw new Error(`Unsupported keepalive kind: ${kind}`);
27512
27553
  await keepMcpBacktestAlive(id, codePath);
27513
27554
  });
27514
- 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) => {
27555
+ 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) => {
27556
+ let outputFd;
27515
27557
  try {
27516
27558
  const args = parseMcpArgs(options.argsJson, options.argsFile);
27559
+ const timeoutMs = parseTimeoutOption(options.timeoutMs, "--timeout-ms");
27560
+ const maxTotalTimeoutMs = parseTimeoutOption(options.maxTotalTimeoutMs, "--max-total-timeout-ms");
27561
+ const resultFile = options.outputJson === void 0 ? void 0 : path14.resolve(options.outputJson);
27562
+ if (resultFile !== void 0) outputFd = fs13.openSync(resultFile, "wx");
27517
27563
  const result = await callMcpTool(tool, args, {
27518
- timeoutMs: parseTimeoutOption(options.timeoutMs, "--timeout-ms"),
27519
- maxTotalTimeoutMs: parseTimeoutOption(options.maxTotalTimeoutMs, "--max-total-timeout-ms"),
27564
+ timeoutMs,
27565
+ maxTotalTimeoutMs,
27520
27566
  showProgress: options.progress !== false
27521
27567
  });
27522
- process.stdout.write(JSON.stringify(withCliProvenance(result)) + "\n");
27568
+ const complete = withCliProvenance(result);
27569
+ const serialized = JSON.stringify(complete) + "\n";
27570
+ if (outputFd !== void 0 && resultFile !== void 0) {
27571
+ try {
27572
+ fs13.writeFileSync(outputFd, serialized, "utf-8");
27573
+ fs13.closeSync(outputFd);
27574
+ outputFd = void 0;
27575
+ } catch (error2) {
27576
+ process.stdout.write(serialized);
27577
+ throw new Error(`Tool returned a result, but saving ${resultFile} failed; complete result is on stdout. Do not repeat the tool automatically. ${String(error2)}`);
27578
+ }
27579
+ const receipt = compactCliReceipt(tool, complete, resultFile);
27580
+ process.stdout.write(JSON.stringify(receipt) + "\n");
27581
+ if (!receipt.ok) process.exitCode = 1;
27582
+ } else {
27583
+ process.stdout.write(serialized);
27584
+ }
27523
27585
  if (result.isError === true) process.exitCode = 1;
27524
27586
  } catch (error2) {
27525
27587
  const message = error2 instanceof Error ? error2.message : String(error2);
27526
27588
  process.stderr.write(`Error: ${message}
27527
27589
  `);
27528
27590
  process.exitCode = 1;
27591
+ } finally {
27592
+ if (outputFd !== void 0) fs13.closeSync(outputFd);
27529
27593
  }
27530
27594
  });
27531
27595
  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) => {
@@ -27619,6 +27683,50 @@ function resolveCliPackageProvenance() {
27619
27683
  function withCliProvenance(result) {
27620
27684
  return { ...result, cliProvenance: resolveCliPackageProvenance() };
27621
27685
  }
27686
+ function compactCliReceipt(tool, result, resultFile) {
27687
+ const payload = parseMcpResult(result);
27688
+ const code = payload?.ErrNo;
27689
+ const ok = result.isError !== true && (code === void 0 || code === null || Number(code) === 0);
27690
+ const source = payload?.Data;
27691
+ const data = {};
27692
+ if (source && typeof source === "object" && !Array.isArray(source)) {
27693
+ const fields = source;
27694
+ for (const key of [
27695
+ "runId",
27696
+ "backtestId",
27697
+ "strategyId",
27698
+ "localPath",
27699
+ "codePath",
27700
+ "runDir",
27701
+ "attemptFence",
27702
+ "status",
27703
+ "progress",
27704
+ "available",
27705
+ "runtimeReady",
27706
+ "backtestReady",
27707
+ "backtestOutcome",
27708
+ "isTerminal",
27709
+ "evidenceSource",
27710
+ "failureCause",
27711
+ "nextAction",
27712
+ "recommendedAction",
27713
+ "error_code",
27714
+ "error_msg"
27715
+ ]) {
27716
+ if (fields[key] !== void 0) {
27717
+ data[key] = key === "error_msg" && typeof fields[key] === "string" ? fields[key].slice(0, 512) : fields[key];
27718
+ }
27719
+ }
27720
+ }
27721
+ return {
27722
+ ok,
27723
+ tool,
27724
+ resultFile,
27725
+ cliProvenance: result.cliProvenance,
27726
+ data,
27727
+ ...!ok ? { error: { code: code ?? null, message: String(payload?.ErrMsg ?? "Tool reported an error; see resultFile").slice(0, 512) } } : {}
27728
+ };
27729
+ }
27622
27730
  function parseTimeoutOption(value, flag) {
27623
27731
  if (value === void 0) return void 0;
27624
27732
  const parsed = Number(value);