@cjsqbn/resume-screening-mcp 0.1.2 → 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.
Files changed (2) hide show
  1. package/index.js +68 -167
  2. package/package.json +5 -1
package/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+
3
7
  const SERVER_NAME = "resume-screening-mcp";
4
- const SERVER_VERSION = "0.1.0";
8
+ const SERVER_VERSION = "0.2.0";
5
9
 
6
10
  const DEFAULT_CONFIG = {
7
11
  trust_threshold: 60,
@@ -531,179 +535,76 @@ function generateReport({ screening_result }) {
531
535
  return { markdown: lines.join("\n") };
532
536
  }
533
537
 
534
- const TOOLS = [
535
- {
536
- name: "parse_jd",
537
- description: "从岗位 JD 文本中提取工作年限、技能、行业、职责和加分项要求。",
538
- inputSchema: {
539
- type: "object",
540
- properties: {
541
- job_description: { type: "string", description: "岗位 JD 原文。" },
542
- },
543
- required: ["job_description"],
544
- },
545
- },
546
- {
547
- name: "analyze_candidate",
548
- description: "分析单个候选人,输出结构化简历信息、可信度、匹配度、综合得分和面试追问。",
549
- inputSchema: {
550
- type: "object",
551
- properties: {
552
- resume: {
553
- type: "object",
554
- properties: {
555
- file_name: { type: "string" },
556
- text: { type: "string" },
557
- },
558
- required: ["text"],
559
- },
560
- job_description: { type: "string" },
561
- config: { type: "object" },
562
- },
563
- required: ["resume"],
564
- },
565
- },
566
- {
567
- name: "screen_resumes",
568
- description: "批量分析多份简历和岗位 JD,输出淘汰名单、晋级排序榜、疑点追问和 Markdown 报告。",
569
- inputSchema: {
570
- type: "object",
571
- properties: {
572
- resumes: {
573
- type: "array",
574
- items: {
575
- type: "object",
576
- properties: {
577
- file_name: { type: "string" },
578
- text: { type: "string" },
579
- },
580
- required: ["text"],
581
- },
582
- },
583
- job_description: { type: "string" },
584
- config: {
585
- type: "object",
586
- properties: {
587
- trust_threshold: { type: "number" },
588
- match_weight: { type: "number" },
589
- competitiveness_weight: { type: "number" },
590
- },
591
- },
592
- },
593
- required: ["resumes", "job_description"],
594
- },
595
- },
596
- {
597
- name: "generate_report",
598
- description: "把 screen_resumes 的结构化结果转换为中文 Markdown 报告。",
599
- inputSchema: {
600
- type: "object",
601
- properties: {
602
- screening_result: { type: "object" },
538
+ function asTextResult(value) {
539
+ return {
540
+ content: [
541
+ {
542
+ type: "text",
543
+ text: JSON.stringify(value, null, 2),
603
544
  },
604
- required: ["screening_result"],
605
- },
606
- },
607
- ];
608
-
609
- function callTool(name, args) {
610
- if (name === "parse_jd") return parseJd(args || {});
611
- if (name === "analyze_candidate") return analyzeCandidate(args || {});
612
- if (name === "screen_resumes") return screenResumes(args || {});
613
- if (name === "generate_report") return generateReport(args || {});
614
- throw new Error(`未知工具:${name}`);
545
+ ],
546
+ };
615
547
  }
616
548
 
617
- let inputBuffer = Buffer.alloc(0);
618
- const keepAlive = setInterval(() => {}, 1 << 30);
619
-
620
- process.stdin.on("data", (chunk) => {
621
- inputBuffer = Buffer.concat([inputBuffer, chunk]);
622
- processMessages();
549
+ const resumeSchema = z.object({
550
+ file_name: z.string().optional(),
551
+ name: z.string().optional(),
552
+ text: z.string().min(1, "resume.text 不能为空"),
623
553
  });
624
554
 
625
- process.stdin.on("error", () => process.exit(1));
626
- process.stdin.resume();
627
-
628
- function processMessages() {
629
- while (true) {
630
- const headerEnd = inputBuffer.indexOf("\r\n\r\n");
631
- if (headerEnd === -1) return;
632
-
633
- const header = inputBuffer.slice(0, headerEnd).toString("utf8");
634
- const match = header.match(/Content-Length:\s*(\d+)/i);
635
- if (!match) {
636
- inputBuffer = inputBuffer.slice(headerEnd + 4);
637
- continue;
638
- }
639
-
640
- const length = Number(match[1]);
641
- const messageStart = headerEnd + 4;
642
- const messageEnd = messageStart + length;
643
- if (inputBuffer.length < messageEnd) return;
644
-
645
- const raw = inputBuffer.slice(messageStart, messageEnd).toString("utf8");
646
- inputBuffer = inputBuffer.slice(messageEnd);
647
-
648
- try {
649
- handleMessage(JSON.parse(raw));
650
- } catch (error) {
651
- sendError(null, -32700, `解析 MCP 消息失败:${error.message}`);
652
- }
653
- }
654
- }
655
-
656
- function handleMessage(message) {
657
- if (!message || typeof message !== "object") return;
658
- if (message.id === undefined || message.id === null) return;
659
-
660
- try {
661
- if (message.method === "initialize") {
662
- sendResult(message.id, {
663
- protocolVersion: message.params?.protocolVersion || "2024-11-05",
664
- capabilities: { tools: {} },
665
- serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
666
- });
667
- return;
668
- }
669
-
670
- if (message.method === "tools/list") {
671
- sendResult(message.id, { tools: TOOLS });
672
- return;
673
- }
555
+ const configSchema = z.object({
556
+ trust_threshold: z.number().optional(),
557
+ high_trust_threshold: z.number().optional(),
558
+ final_score_priority_threshold: z.number().optional(),
559
+ final_score_interview_threshold: z.number().optional(),
560
+ match_weight: z.number().optional(),
561
+ competitiveness_weight: z.number().optional(),
562
+ }).passthrough().optional();
563
+
564
+ const server = new McpServer({
565
+ name: SERVER_NAME,
566
+ version: SERVER_VERSION,
567
+ });
674
568
 
675
- if (message.method === "tools/call") {
676
- const toolName = message.params?.name;
677
- const args = message.params?.arguments || {};
678
- const result = callTool(toolName, args);
679
- sendResult(message.id, {
680
- content: [
681
- {
682
- type: "text",
683
- text: JSON.stringify(result, null, 2),
684
- },
685
- ],
686
- structuredContent: result,
687
- });
688
- return;
689
- }
569
+ server.tool(
570
+ "parse_jd",
571
+ "从岗位 JD 文本中提取工作年限、技能、行业、职责和加分项要求。",
572
+ {
573
+ job_description: z.string().min(1, "job_description 不能为空"),
574
+ },
575
+ async (args) => asTextResult(parseJd(args)),
576
+ );
690
577
 
691
- sendError(message.id, -32601, `不支持的方法:${message.method}`);
692
- } catch (error) {
693
- sendError(message.id, -32000, error.message || String(error));
694
- }
695
- }
578
+ server.tool(
579
+ "analyze_candidate",
580
+ "分析单个候选人,输出结构化简历信息、可信度、匹配度、综合得分和面试追问。",
581
+ {
582
+ resume: resumeSchema,
583
+ job_description: z.string().optional(),
584
+ config: configSchema,
585
+ },
586
+ async (args) => asTextResult(analyzeCandidate(args)),
587
+ );
696
588
 
697
- function sendResult(id, result) {
698
- writeMessage({ jsonrpc: "2.0", id, result });
699
- }
589
+ server.tool(
590
+ "screen_resumes",
591
+ "批量分析多份简历和岗位 JD,输出淘汰名单、晋级排序榜、疑点追问和 Markdown 报告。",
592
+ {
593
+ resumes: z.array(resumeSchema).min(1, "resumes 必须是非空数组"),
594
+ job_description: z.string().min(1, "job_description 不能为空"),
595
+ config: configSchema,
596
+ },
597
+ async (args) => asTextResult(screenResumes(args)),
598
+ );
700
599
 
701
- function sendError(id, code, message) {
702
- writeMessage({ jsonrpc: "2.0", id, error: { code, message } });
703
- }
600
+ server.tool(
601
+ "generate_report",
602
+ "把 screen_resumes 的结构化结果转换为中文 Markdown 报告。",
603
+ {
604
+ screening_result: z.record(z.string(), z.any()),
605
+ },
606
+ async (args) => asTextResult(generateReport(args)),
607
+ );
704
608
 
705
- function writeMessage(payload) {
706
- const json = JSON.stringify(payload);
707
- const bytes = Buffer.byteLength(json, "utf8");
708
- process.stdout.write(`Content-Length: ${bytes}\r\n\r\n${json}`);
709
- }
609
+ const transport = new StdioServerTransport();
610
+ await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjsqbn/resume-screening-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "MCP service for resume screening, JD matching, trust checking, scoring and candidate ranking.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,6 +20,10 @@
20
20
  "engines": {
21
21
  "node": ">=18"
22
22
  },
23
+ "dependencies": {
24
+ "@modelcontextprotocol/sdk": "^1.18.1",
25
+ "zod": "^3.25.76"
26
+ },
23
27
  "publishConfig": {
24
28
  "access": "public"
25
29
  },