@reconcrap/boss-recommend-mcp 1.2.2 → 1.2.4

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/src/index.js CHANGED
@@ -4,7 +4,11 @@ import { spawn } from "node:child_process";
4
4
  import { createRequire } from "node:module";
5
5
  import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
- import { runRecommendPipeline } from "./pipeline.js";
7
+ import {
8
+ getFeaturedCalibrationResolution,
9
+ runRecommendCalibration
10
+ } from "./adapters.js";
11
+ import { runRecommendPipeline } from "./pipeline.js";
8
12
  import {
9
13
  RUN_MODE_ASYNC,
10
14
  RUN_STAGE_PREFLIGHT,
@@ -28,11 +32,13 @@ import {
28
32
  const require = createRequire(import.meta.url);
29
33
  const { version: SERVER_VERSION } = require("../package.json");
30
34
 
31
- const TOOL_START_RUN = "start_recommend_pipeline_run";
32
- const TOOL_GET_RUN = "get_recommend_pipeline_run";
33
- const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
34
- const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
35
- const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
35
+ const TOOL_START_RUN = "start_recommend_pipeline_run";
36
+ const TOOL_GET_RUN = "get_recommend_pipeline_run";
37
+ const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
38
+ const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
39
+ const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
40
+ const TOOL_RUN_FEATURED_CALIBRATION = "run_featured_calibration";
41
+ const TOOL_GET_FEATURED_CALIBRATION_STATUS = "get_featured_calibration_status";
36
42
 
37
43
  const SERVER_NAME = "boss-recommend-mcp";
38
44
  const FRAMING_UNKNOWN = "unknown";
@@ -141,7 +147,7 @@ function createJsonRpcError(id, code, message) {
141
147
  };
142
148
  }
143
149
 
144
- function createRunInputSchema() {
150
+ function createRunInputSchema() {
145
151
  return {
146
152
  type: "object",
147
153
  properties: {
@@ -290,10 +296,33 @@ function createRunInputSchema() {
290
296
  required: ["instruction"],
291
297
  additionalProperties: false
292
298
  };
293
- }
294
-
295
- function createToolsSchema() {
296
- return [
299
+ }
300
+
301
+ function createRunFeaturedCalibrationInputSchema() {
302
+ return {
303
+ type: "object",
304
+ properties: {
305
+ port: {
306
+ type: "integer",
307
+ minimum: 1,
308
+ description: "可选,Boss Chrome 远程调试端口(默认读取配置或 9222)"
309
+ },
310
+ timeout_ms: {
311
+ type: "integer",
312
+ minimum: 1000,
313
+ description: "可选,等待收藏点击的超时时间(毫秒)"
314
+ },
315
+ output: {
316
+ type: "string",
317
+ description: "可选,校准文件输出路径(默认 favorite-calibration.json)"
318
+ }
319
+ },
320
+ additionalProperties: false
321
+ };
322
+ }
323
+
324
+ function createToolsSchema() {
325
+ return [
297
326
  {
298
327
  name: TOOL_START_RUN,
299
328
  description: "异步启动 Boss 推荐页流水线(含同步门禁预检);只有在前置确认与页面就绪通过后才返回 run_id。",
@@ -335,20 +364,34 @@ function createToolsSchema() {
335
364
  additionalProperties: false
336
365
  }
337
366
  },
338
- {
339
- name: TOOL_RESUME_RUN,
340
- description: "继续指定 run_id 的 paused 流水线;沿用原 CSV 与 checkpoint 续跑。",
341
- inputSchema: {
367
+ {
368
+ name: TOOL_RESUME_RUN,
369
+ description: "继续指定 run_id 的 paused 流水线;沿用原 CSV 与 checkpoint 续跑。",
370
+ inputSchema: {
342
371
  type: "object",
343
372
  properties: {
344
373
  run_id: { type: "string" }
345
374
  },
346
- required: ["run_id"],
347
- additionalProperties: false
348
- }
349
- }
350
- ];
351
- }
375
+ required: ["run_id"],
376
+ additionalProperties: false
377
+ }
378
+ },
379
+ {
380
+ name: TOOL_RUN_FEATURED_CALIBRATION,
381
+ description: "手动执行精选页收藏按钮校准。执行前请先在 Boss 推荐页切换到精选 tab 并打开任意候选人详情页。",
382
+ inputSchema: createRunFeaturedCalibrationInputSchema()
383
+ },
384
+ {
385
+ name: TOOL_GET_FEATURED_CALIBRATION_STATUS,
386
+ description: "查询精选页收藏校准文件与校准脚本可用性。",
387
+ inputSchema: {
388
+ type: "object",
389
+ properties: {},
390
+ additionalProperties: false
391
+ }
392
+ }
393
+ ];
394
+ }
352
395
 
353
396
  function createToolResultResponse(id, payload, isError = false) {
354
397
  return {
@@ -367,7 +410,7 @@ function createToolResultResponse(id, payload, isError = false) {
367
410
  };
368
411
  }
369
412
 
370
- function validateRunArgs(args) {
413
+ function validateRunArgs(args) {
371
414
  if (!args || typeof args !== "object") {
372
415
  return "arguments must be an object";
373
416
  }
@@ -375,9 +418,37 @@ function validateRunArgs(args) {
375
418
  return "instruction is required and must be a string";
376
419
  }
377
420
  return null;
378
- }
379
-
380
- function getLastOutputLine(text) {
421
+ }
422
+
423
+ function validateRunFeaturedCalibrationArgs(args) {
424
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
425
+ return "arguments must be an object";
426
+ }
427
+
428
+ if (Object.prototype.hasOwnProperty.call(args, "port")) {
429
+ const port = Number.parseInt(String(args.port), 10);
430
+ if (!Number.isFinite(port) || port <= 0) {
431
+ return "port must be a positive integer";
432
+ }
433
+ }
434
+
435
+ if (Object.prototype.hasOwnProperty.call(args, "timeout_ms")) {
436
+ const timeoutMs = Number.parseInt(String(args.timeout_ms), 10);
437
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1000) {
438
+ return "timeout_ms must be an integer >= 1000";
439
+ }
440
+ }
441
+
442
+ if (Object.prototype.hasOwnProperty.call(args, "output")) {
443
+ if (typeof args.output !== "string" || !normalizeText(args.output)) {
444
+ return "output must be a non-empty string when provided";
445
+ }
446
+ }
447
+
448
+ return null;
449
+ }
450
+
451
+ function getLastOutputLine(text) {
381
452
  const lines = String(text || "")
382
453
  .split(/\r?\n/)
383
454
  .map((line) => normalizeText(line))
@@ -1219,8 +1290,57 @@ function handleResumeRunTool(args) {
1219
1290
  message: "已恢复 Recommend 流水线(detached)。默认不自动轮询;如需进度请按需调用 get_recommend_pipeline_run。"
1220
1291
  };
1221
1292
  }
1222
-
1223
- async function handleRequest(message, workspaceRoot) {
1293
+
1294
+ function handleGetFeaturedCalibrationStatusTool(workspaceRoot) {
1295
+ const resolution = getFeaturedCalibrationResolution(workspaceRoot);
1296
+ return {
1297
+ status: "CALIBRATION_STATUS",
1298
+ ready: resolution.calibration_usable === true,
1299
+ calibration_path: resolution.calibration_path,
1300
+ calibration_exists: resolution.calibration_exists,
1301
+ calibration_usable: resolution.calibration_usable,
1302
+ calibration_script_path: resolution.calibration_script_path,
1303
+ message: resolution.calibration_usable
1304
+ ? "精选页收藏校准文件可用。"
1305
+ : "精选页收藏校准文件不存在或无效。"
1306
+ };
1307
+ }
1308
+
1309
+ async function handleRunFeaturedCalibrationTool({ workspaceRoot, args }) {
1310
+ const result = await runRecommendCalibration(workspaceRoot, {
1311
+ port: args.port,
1312
+ timeoutMs: args.timeout_ms,
1313
+ output: args.output
1314
+ });
1315
+
1316
+ if (!result?.ok) {
1317
+ return {
1318
+ status: "FAILED",
1319
+ error: {
1320
+ code: result?.error?.code || "CALIBRATION_REQUIRED",
1321
+ message: result?.error?.message || "精选页收藏校准失败,请在推荐页精选 tab 打开候选人详情后点击收藏按钮再重试。",
1322
+ retryable: true
1323
+ },
1324
+ calibration_path: result?.calibration_path || null,
1325
+ calibration_script_path: result?.calibration_script_path || null,
1326
+ debug_port: result?.debug_port || null,
1327
+ diagnostics: {
1328
+ stdout_last_line: getLastOutputLine(result?.stdout),
1329
+ stderr_last_line: getLastOutputLine(result?.stderr)
1330
+ }
1331
+ };
1332
+ }
1333
+
1334
+ return {
1335
+ status: "CALIBRATED",
1336
+ message: "精选页收藏按钮校准完成,可重新执行 start_recommend_pipeline_run。",
1337
+ calibration_path: result.calibration_path,
1338
+ calibration_script_path: result.calibration_script_path,
1339
+ debug_port: result.debug_port
1340
+ };
1341
+ }
1342
+
1343
+ async function handleRequest(message, workspaceRoot) {
1224
1344
  if (!message || message.jsonrpc !== "2.0") {
1225
1345
  return createJsonRpcError(null, -32600, "Invalid JSON-RPC request");
1226
1346
  }
@@ -1262,34 +1382,45 @@ async function handleRequest(message, workspaceRoot) {
1262
1382
  const toolName = params?.name;
1263
1383
  const args = params?.arguments || {};
1264
1384
 
1265
- if (toolName === TOOL_START_RUN) {
1266
- const inputError = validateRunArgs(args);
1267
- if (inputError) {
1268
- return createJsonRpcError(id, -32602, inputError);
1269
- }
1270
- }
1271
-
1272
- if ([TOOL_GET_RUN, TOOL_CANCEL_RUN, TOOL_PAUSE_RUN, TOOL_RESUME_RUN].includes(toolName)) {
1273
- if (!args || typeof args.run_id !== "string" || !normalizeText(args.run_id)) {
1274
- return createJsonRpcError(id, -32602, "run_id is required and must be a string");
1275
- }
1385
+ if (toolName === TOOL_START_RUN) {
1386
+ const inputError = validateRunArgs(args);
1387
+ if (inputError) {
1388
+ return createJsonRpcError(id, -32602, inputError);
1389
+ }
1390
+ }
1391
+
1392
+ if (toolName === TOOL_RUN_FEATURED_CALIBRATION) {
1393
+ const inputError = validateRunFeaturedCalibrationArgs(args);
1394
+ if (inputError) {
1395
+ return createJsonRpcError(id, -32602, inputError);
1396
+ }
1397
+ }
1398
+
1399
+ if ([TOOL_GET_RUN, TOOL_CANCEL_RUN, TOOL_PAUSE_RUN, TOOL_RESUME_RUN].includes(toolName)) {
1400
+ if (!args || typeof args.run_id !== "string" || !normalizeText(args.run_id)) {
1401
+ return createJsonRpcError(id, -32602, "run_id is required and must be a string");
1402
+ }
1276
1403
  }
1277
1404
 
1278
1405
  try {
1279
1406
  let payload;
1280
- if (toolName === TOOL_START_RUN) {
1281
- payload = await handleStartRunTool({ workspaceRoot, args });
1282
- } else if (toolName === TOOL_GET_RUN) {
1283
- payload = handleGetRunTool(args);
1284
- } else if (toolName === TOOL_CANCEL_RUN) {
1285
- payload = handleCancelRunTool(args);
1286
- } else if (toolName === TOOL_PAUSE_RUN) {
1287
- payload = handlePauseRunTool(args);
1288
- } else if (toolName === TOOL_RESUME_RUN) {
1289
- payload = handleResumeRunTool(args);
1290
- } else {
1291
- return createJsonRpcError(id, -32602, `Unknown tool: ${toolName || ""}`);
1292
- }
1407
+ if (toolName === TOOL_START_RUN) {
1408
+ payload = await handleStartRunTool({ workspaceRoot, args });
1409
+ } else if (toolName === TOOL_GET_RUN) {
1410
+ payload = handleGetRunTool(args);
1411
+ } else if (toolName === TOOL_CANCEL_RUN) {
1412
+ payload = handleCancelRunTool(args);
1413
+ } else if (toolName === TOOL_PAUSE_RUN) {
1414
+ payload = handlePauseRunTool(args);
1415
+ } else if (toolName === TOOL_RESUME_RUN) {
1416
+ payload = handleResumeRunTool(args);
1417
+ } else if (toolName === TOOL_GET_FEATURED_CALIBRATION_STATUS) {
1418
+ payload = handleGetFeaturedCalibrationStatusTool(workspaceRoot);
1419
+ } else if (toolName === TOOL_RUN_FEATURED_CALIBRATION) {
1420
+ payload = await handleRunFeaturedCalibrationTool({ workspaceRoot, args });
1421
+ } else {
1422
+ return createJsonRpcError(id, -32602, `Unknown tool: ${toolName || ""}`);
1423
+ }
1293
1424
  const isError = payload?.status === "FAILED";
1294
1425
  return createToolResultResponse(id, payload, isError);
1295
1426
  } catch (error) {