@cjsqbn/resume-screening-mcp 0.1.1 → 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 -165
  2. package/package.json +6 -2
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,177 +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
-
619
- process.stdin.on("data", (chunk) => {
620
- inputBuffer = Buffer.concat([inputBuffer, chunk]);
621
- 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 不能为空"),
622
553
  });
623
554
 
624
- process.stdin.on("error", () => process.exit(1));
625
-
626
- function processMessages() {
627
- while (true) {
628
- const headerEnd = inputBuffer.indexOf("\r\n\r\n");
629
- if (headerEnd === -1) return;
630
-
631
- const header = inputBuffer.slice(0, headerEnd).toString("utf8");
632
- const match = header.match(/Content-Length:\s*(\d+)/i);
633
- if (!match) {
634
- inputBuffer = inputBuffer.slice(headerEnd + 4);
635
- continue;
636
- }
637
-
638
- const length = Number(match[1]);
639
- const messageStart = headerEnd + 4;
640
- const messageEnd = messageStart + length;
641
- if (inputBuffer.length < messageEnd) return;
642
-
643
- const raw = inputBuffer.slice(messageStart, messageEnd).toString("utf8");
644
- inputBuffer = inputBuffer.slice(messageEnd);
645
-
646
- try {
647
- handleMessage(JSON.parse(raw));
648
- } catch (error) {
649
- sendError(null, -32700, `解析 MCP 消息失败:${error.message}`);
650
- }
651
- }
652
- }
653
-
654
- function handleMessage(message) {
655
- if (!message || typeof message !== "object") return;
656
- if (message.id === undefined || message.id === null) return;
657
-
658
- try {
659
- if (message.method === "initialize") {
660
- sendResult(message.id, {
661
- protocolVersion: message.params?.protocolVersion || "2024-11-05",
662
- capabilities: { tools: {} },
663
- serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
664
- });
665
- return;
666
- }
667
-
668
- if (message.method === "tools/list") {
669
- sendResult(message.id, { tools: TOOLS });
670
- return;
671
- }
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
+ });
672
568
 
673
- if (message.method === "tools/call") {
674
- const toolName = message.params?.name;
675
- const args = message.params?.arguments || {};
676
- const result = callTool(toolName, args);
677
- sendResult(message.id, {
678
- content: [
679
- {
680
- type: "text",
681
- text: JSON.stringify(result, null, 2),
682
- },
683
- ],
684
- structuredContent: result,
685
- });
686
- return;
687
- }
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
+ );
688
577
 
689
- sendError(message.id, -32601, `不支持的方法:${message.method}`);
690
- } catch (error) {
691
- sendError(message.id, -32000, error.message || String(error));
692
- }
693
- }
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
+ );
694
588
 
695
- function sendResult(id, result) {
696
- writeMessage({ jsonrpc: "2.0", id, result });
697
- }
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
+ );
698
599
 
699
- function sendError(id, code, message) {
700
- writeMessage({ jsonrpc: "2.0", id, error: { code, message } });
701
- }
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
+ );
702
608
 
703
- function writeMessage(payload) {
704
- const json = JSON.stringify(payload);
705
- const bytes = Buffer.byteLength(json, "utf8");
706
- process.stdout.write(`Content-Length: ${bytes}\r\n\r\n${json}`);
707
- }
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.1",
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,8 +20,12 @@
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
  },
26
30
  "license": "MIT"
27
- }
31
+ }