基于MCP协议构建智能求职助手:从架构设计到工程实践

发布时间:2026/5/27 8:13:21

基于MCP协议构建智能求职助手:从架构设计到工程实践 1. 项目概述一个为求职者量身定制的免费智能助手最近我的一位技术极客好友也是我最好的朋友完成了一个让我眼前一亮的项目他搭建了一个完全免费的MCP服务器专门用来辅助求职。如果你正在找工作或者未来有跳槽的打算这个工具可能会成为你的“秘密武器”。简单来说它就像一个24小时在线的、精通行业动态和招聘套路的私人求职顾问能帮你从海量信息中快速筛选出真正有价值的机会并优化你的求职策略。这个项目的核心是“MCP”架构。MCP即模型上下文协议你可以把它理解为一个标准化的“插件接口”。它允许像ChatGPT、Claude这类大型语言模型安全、规范地连接到外部数据源和工具。而我朋友搭建的这个服务器就是一个专门为求职场景设计的MCP服务端。它本身不直接生成内容而是为AI大脑比如Claude Desktop提供了一套强大的“求职专用工具箱”和“实时数据接口”。当你向AI提问时AI可以通过这个MCP服务器调用实时的招聘信息、分析职位要求、对比你的简历从而给出极其精准和个性化的建议。这解决了求职过程中的几个核心痛点信息过载每天刷几十个招聘网站、匹配低效海投简历石沉大海、准备不足不了解岗位真实要求和面试风向。传统的求职方式是手动、离散的而这个MCP服务器将整个过程自动化、智能化了。它适合任何正在积极求职的职场人无论是应届生还是资深人士尤其适合那些希望用技术提升求职效率、不想在信息筛选上浪费太多时间的朋友。接下来我就为你彻底拆解这个项目的设计思路、技术实现和具体用法。2. 核心设计思路为什么是MCP为什么免费在动手之前我朋友花了大量时间思考架构选型。为什么最终选择了MCP而不是做一个独立的Web应用或浏览器插件这背后有几个关键考量。2.1 技术架构选型的深层逻辑首先生态融合优于独立开发。一个独立的求职网站或App需要用户改变习惯单独打开使用。而MCP服务器是“寄生”在用户已经高频使用的AI助手如Claude Desktop中的。用户不需要离开自己熟悉的对话界面就能获得增强的求职能力。这种“无缝集成”的体验用户接受成本极低。其次能力复用与专注核心。像Claude、ChatGPT这样的模型已经具备了强大的自然语言理解和生成能力。我们不需要从头去造一个“AI大脑”只需要为这个大脑配上“专业的眼睛和手”——也就是实时数据获取和结构化分析能力。MCP协议完美定义了大脑与手脚之间的通信规范让我们可以专注于构建最擅长的领域工具。再者数据隐私与安全性。MCP协议设计之初就考虑了安全性。服务器运行在本地或用户可控的私有环境所有的求职数据简历内容、关注的职位、沟通记录都无需上传到第三方云端服务器进行处理极大地保护了用户的隐私。这对于包含大量个人敏感信息的求职活动来说是至关重要的优势。最后协议标准化带来的兼容性与未来性。MCP是一个开放协议这意味着今天为Claude Desktop开发的服务器未来也可能兼容其他支持MCP的AI平台。一次开发多处可用降低了长期维护成本也避免了被单一平台绑定的风险。2.2 功能边界与“免费”的可持续性这个项目定位非常清晰做一个纯粹的、增强信息获取与决策效率的工具而不是一个招聘平台。它不会替代LinkedIn、Boss直聘等平台而是作为它们的“智能导航仪”。它的核心功能围绕“信息”展开聚合、解析、分析和建议。关于“免费”这是项目的核心原则之一。实现方式主要是利用现有的、免费的公开数据接口和开源技术栈。服务器运行所需的计算资源很小可以轻松部署在免费的云函数平台如Vercel、Cloudflare Workers或甚至在一台树莓派上。数据源方面优先整合那些提供公开API或允许合理爬取的招聘信息聚合网站需要严格遵守其Robots协议。没有昂贵的商业数据采购成本。维护成本主要是朋友的个人时间投入。他将其视为一个开源项目相信来自社区的反馈和贡献最终能反哺项目使其更健壮形成良性循环。当然未来如果用户量巨大产生显著的云服务成本可能会考虑接受捐赠或提供增值服务如更快的数据更新频率、更深入的公司背调报告但核心的求职信息查询与分析功能将始终保持免费。3. 核心模块拆解这个服务器里到底有什么这个MCP服务器不是一个黑盒它由几个相互协作的模块组成。理解这些模块你就能明白它如何工作甚至可以根据自己的需求进行定制。3.1 数据聚合与清洗模块这是服务器的“眼睛”和“耳朵”。它的任务是从多个招聘渠道抓取或接收职位信息。我们并没有粗暴地全网爬取而是采取了更可持续的策略。主要数据源策略公开API接入优先寻找并提供RSS订阅源的招聘板块或者一些技术社区自带的职位API。这些是获取数据的“绿色通道”。聚合平台精选针对一些大型招聘平台我们只抓取其面向搜索引擎公开的、无需登录即可浏览的职位列表页和详情页。严格遵守robots.txt规则控制请求频率模拟正常用户访问避免对目标网站造成压力。用户自定义订阅允许用户通过服务器提交他们特别关注的公司招聘官网或特定职位的链接服务器会定期为其检查更新。数据清洗与标准化 原始职位描述是高度非结构化的文本。这个模块的核心任务是将它们转化为结构化的数据。我们使用了一系列自然语言处理技术命名实体识别自动提取公司名称、职位名称、工作地点、薪资范围如“20-40K”、学历要求、工作经验要求如“3-5年”等关键实体。关键词与技能标签化从职位描述中提取技术栈如“Python”, “React”, “Kubernetes”、软技能如“沟通能力”、“团队协作”和业务领域关键词如“金融科技”、“SaaS”。薪资标准化将五花八门的薪资表述如“面议”、“20-40K·15薪”、“年薪30-50万”统一折算为月度基准薪资便于比较。去重与合并同一职位可能在多个平台发布通过公司、职位、地点等核心信息进行模糊匹配去重。注意数据抓取必须合法合规。我们的代码中内置了严格的伦理检查包括请求间隔、尊重robots.txt、以及一个手动开关允许用户随时禁用特定数据源的抓取。我们坚决反对任何形式的恶意爬取。3.2 智能匹配与分析引擎这是服务器的“大脑”。它接收用户的个人资料如简历文本、技能清单、求职偏好和清洗后的职位数据进行计算和匹配。核心匹配算法 匹配并非简单的关键词堆砌。我们设计了一个加权评分模型包含以下几个维度技能匹配度将职位要求的技能与用户技能进行对比。核心技能通常出现在“任职要求”前列权重高加分技能权重低。完全匹配得分最高相关技能如要求“React”用户有“Vue”得分次之。经验匹配度分析职位要求的经验年限与用户工作年限的契合度。这是一个区间匹配并非完全相等。例如要求“3-5年”用户有4年经验就是完美匹配有6年经验也是高度匹配可能视为资深候选人有2年经验则匹配度较低。薪资期望匹配度将用户期望薪资与职位薪资范围进行对比。这是一个双向匹配职位薪资是否满足用户期望以及用户期望是否在职位预算的合理范围内。地点与工作模式匹配度远程、混合、现场办公以及具体城市都是重要的过滤和加权项。公司/行业偏好用户可以设置优先考虑的行业或公司列表匹配到的职位会获得额外加分。深度分析功能竞争力分析服务器可以模拟告诉你“根据当前市场数据对于这个‘高级后端开发工程师’职位你的技能匹配度超过了80%的潜在申请人但在‘云原生架构’经验上略有不足。建议在简历中强化你在容器化项目中的角色。”趋势洞察通过分析一段时间内收集的职位数据服务器可以反馈“过去一个月‘数据工程师’岗位中要求‘Apache Airflow’技能的占比上升了15%”从而提示你学习方向。JD解读帮你拆解一份冗长的职位描述提炼出真正的核心要求、团队情况从描述风格推断以及可能的“雷点”如频繁出现的“抗压能力强”、“快速迭代”可能暗示加班多。3.3 MCP协议接口层这是服务器的“嘴巴”和“手”负责与AI助手对话。它严格遵循MCP协议规范向外暴露一系列“工具”。暴露的关键工具search_jobs核心搜索工具。AI助手可以调用它并传递用户自然语言描述的查询如“帮我找上海、远程、薪资30K以上的Python后端工作”。服务器将自然语言转换为结构化查询参数执行搜索并返回结果。analyze_resume_fit简历匹配分析工具。用户可以将简历文本或关键信息通过AI助手传递给服务器。服务器会将其与某个特定职位ID进行深度匹配生成详细的匹配报告。get_market_insights市场洞察工具。AI助手可以询问“最近Java开发的市场需求怎么样”服务器返回基于近期数据的趋势分析。track_company公司追踪工具。用户可以添加心仪公司服务器会监控其招聘页面的新职位发布。通信流程示例 当你在Claude Desktop中输入“我想看看北京有没有机器学习相关的实习机会。”Claude理解你的意图决定调用此MCP服务器的search_jobs工具。Claude按照MCP协议格式构造一个包含查询参数地点“北京”职位关键词“机器学习”经验要求“实习”的请求发送给你的本地服务器。你的服务器收到请求数据聚合模块立即查询本地数据库或实时抓取如有需要智能匹配引擎快速过滤和排序。服务器将结果一份结构化的职位列表包含公司、职位、简介、匹配度评分按照MCP协议格式打包返回给Claude。Claude接收结果并用自然语言组织成一段友好的回复呈现给你“我找到了几个北京的机器学习实习机会A公司正在招聘算法实习要求Python和TensorFlowB研究院的实习岗侧重数据挖掘…… 这是根据当前信息匹配度排序的你需要我详细解读哪一个”4. 从零到一的搭建实操指南如果你有一定技术基础完全可以跟着步骤搭建自己的实例。以下是基于Node.js环境的一个简化版实现路径。4.1 基础环境搭建与项目初始化首先确保你的开发环境就绪。你需要安装Node.js建议18.x或以上版本和npm。然后我们初始化项目并安装核心依赖。# 创建一个新的项目目录 mkdir mcp-job-server cd mcp-job-server # 初始化npm项目 npm init -y # 安装核心依赖 npm install modelcontextprotocol/sdk fastify cheerio node-html-parser axios # 安装开发依赖用于代码检查和构建 npm install --save-dev typescript types/node tsxmodelcontextprotocol/sdk这是官方MCP SDK帮助我们快速构建符合协议的服务器。fastify一个高性能的Web框架用于构建服务器接收HTTP请求MCP over HTTP传输方式。cheerio与node-html-parser用于解析HTML页面从招聘网站抓取数据。axios用于发送HTTP请求获取网页或API数据。typescript我们使用TypeScript来开发以获得更好的类型安全和开发体验。接下来配置TypeScript。创建tsconfig.json文件{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }并更新package.json中的脚本部分scripts: { build: tsc, start: node dist/index.js, dev: tsx watch src/index.ts }4.2 MCP服务器骨架与工具定义现在在src目录下创建核心文件。我们先构建服务器的骨架并定义它对外提供的工具。src/index.ts- 服务器主入口import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; import Fastify from fastify; // 初始化MCP服务器 const server new Server( { name: job-search-assistant, version: 0.1.0, }, { capabilities: { tools: {}, // 声明我们支持工具 }, } ); // 定义我们提供的工具列表 const tools [ { name: search_jobs, description: 根据关键词、地点、薪资范围等条件搜索职位信息。, inputSchema: { type: object, properties: { keywords: { type: string, description: 职位关键词如“Python后端”、“产品经理” }, location: { type: string, description: 工作地点如“上海”、“远程” }, salaryExpectation: { type: string, description: 薪资期望如“20-30K” }, experience: { type: string, description: 经验要求如“应届”、“1-3年” }, }, required: [], }, }, { name: analyze_resume_fit, description: 分析一份简历与特定职位的匹配度。, inputSchema: { type: object, properties: { resumeText: { type: string, description: 简历文本内容 }, jobDescription: { type: string, description: 职位描述文本 }, }, required: [resumeText, jobDescription], }, }, ]; // 处理“列出工具”请求 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools: tools, }; }); // 处理“调用工具”请求 server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args } request.params; if (name search_jobs) { // 这里是实际搜索逻辑的入口 const { keywords, location } args as any; const results await performJobSearch(keywords, location); // 调用后端的搜索函数 return { content: [ { type: text, text: 找到${results.length}个相关职位\n results.map(job - ${job.title} ${job.company} (${job.location})).join(\n), }, ], }; } else if (name analyze_resume_fit) { const { resumeText, jobDescription } args as any; const analysis await analyzeFit(resumeText, jobDescription); // 调用匹配分析函数 return { content: [ { type: text, text: 匹配度分析完成\n技能匹配度${analysis.skillMatch}%\n经验匹配${analysis.expMatch}\n关键建议${analysis.suggestion}, }, ], }; } throw new Error(未知工具: ${name}); }); // 启动服务器使用stdio传输适用于Claude Desktop async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP求职服务器已启动stdio模式); } main().catch(console.error); // 以下是需要你后续实现的函数模拟 async function performJobSearch(keywords?: string, location?: string): Promiseany[] { // 这里将集成数据聚合模块 console.log(模拟搜索关键词${keywords}, 地点${location}); return [ { title: 后端开发工程师, company: 某科技公司, location: 上海 }, { title: 数据分析师, company: 某数据公司, location: 远程 }, ]; } async function analyzeFit(resumeText: string, jobDescription: string): Promiseany { // 这里将集成智能匹配引擎 console.log(分析简历与职位匹配度); return { skillMatch: 85, expMatch: 高度匹配, suggestion: 建议在简历中突出分布式系统项目经验。 }; }这个骨架代码已经定义了一个具备两个工具search_jobs和analyze_resume_fit的MCP服务器并能通过Claude Desktop的stdio方式连接。performJobSearch和analyzeFit函数目前只是模拟我们需要用真实的数据逻辑来填充它们。4.3 实现数据聚合模块我们在src目录下创建一个>import axios from axios; import * as cheerio from cheerio; import Parser from rss-parser; // 需要安装 npm install rss-parser interface JobListing { id: string; title: string; company: string; location: string; salary?: string; description: string; url: string; source: string; postedDate: Date; skills: string[]; // 提取的技能关键词 } export class DataAggregator { private parser: Parser; constructor() { this.parser new Parser(); } /** * 从预设的RSS源获取职位 */ async fetchFromRSSFeeds(): PromiseJobListing[] { const jobs: JobListing[] []; const rssFeeds [ https://example-tech-jobs.com/feed, // 示例源需替换为真实源 // 可以添加更多RSS源 ]; for (const feedUrl of rssFeeds) { try { const feed await this.parser.parseURL(feedUrl); for (const item of feed.items) { // 基础信息提取 const job: JobListing { id: rss_${Date.now()}_${Math.random()}, title: item.title || 未知职位, company: this.extractCompanyFromTitle(item.title), // 简单提取可优化 location: this.extractLocation(item.content || ), salary: this.extractSalary(item.content || ), description: item.contentSnippet || item.content || , url: item.link || , source: RSS, postedDate: item.pubDate ? new Date(item.pubDate) : new Date(), skills: this.extractSkills(item.content || ), }; jobs.push(job); } } catch (error) { console.error(抓取RSS源失败 ${feedUrl}:, error.message); // 优雅降级不影响其他源 } } return jobs; } /** * 从HTML页面抓取示例一个假设的招聘列表页 * 注意务必遵守目标网站的robots.txt并设置合理的请求间隔和User-Agent。 */ async scrapeFromJobBoard(url: string): PromiseJobListing[] { const jobs: JobListing[] []; try { // 1. 设置请求头模拟浏览器 const headers { User-Agent: MCP-Job-Server/1.0 (用于个人求职辅助研究如有问题请联系xxxemail.com), }; // 2. 延迟一下避免请求过快 await this.delay(2000); const response await axios.get(url, { headers }); const $ cheerio.load(response.data); // 3. 根据目标网站HTML结构解析 // 这里是一个示例选择器实际需要根据目标网站调整 $(.job-listing).each((index, element) { const title $(element).find(.job-title).text().trim(); const company $(element).find(.company-name).text().trim(); const location $(element).find(.location).text().trim(); const salary $(element).find(.salary).text().trim(); const description $(element).find(.description).text().trim(); const jobUrl $(element).find(a).attr(href) || ; if (title company) { jobs.push({ id: scrape_${Date.now()}_${index}, title, company, location, salary: salary || undefined, description, url: jobUrl.startsWith(http) ? jobUrl : new URL(jobUrl, url).href, source: Scraped, postedDate: new Date(), skills: this.extractSkills(description), }); } }); } catch (error) { console.error(抓取页面失败 ${url}:, error.message); } return jobs; } // --- 辅助函数 --- private extractCompanyFromTitle(title: string): string { // 简单实现假设格式为“职位名称 - 公司名称” const parts title.split( - ); return parts.length 1 ? parts[1] : 未知公司; } private extractLocation(text: string): string { const locationPatterns [/北京|上海|广州|深圳|杭州|成都|远程|异地/gi]; for (const pattern of locationPatterns) { const match text.match(pattern); if (match) return match[0]; } return 地点未知; } private extractSalary(text: string): string | undefined { const salaryPattern /(\d{1,3}[kK千]-?\d{0,3}[kK千]?|\d{1,6}\s*万)/; const match text.match(salaryPattern); return match ? match[0] : undefined; } private extractSkills(text: string): string[] { const skillKeywords [Python, Java, JavaScript, React, Vue, Node.js, Docker, Kubernetes, AWS, MySQL, Redis, 机器学习, 数据分析]; const foundSkills: string[] []; for (const skill of skillKeywords) { if (text.includes(skill)) { foundSkills.push(skill); } } return foundSkills; } private delay(ms: number): Promisevoid { return new Promise(resolve setTimeout(resolve, ms)); } }这个聚合器类提供了两种获取数据的方式通过RSS订阅更友好和通过HTML抓取需谨慎。在实际使用中你应该优先配置RSS源并仅为那些没有API/RSS但允许爬取的网站编写特定的抓取解析器。4.4 实现智能匹配引擎接下来我们实现一个简化版的匹配引擎。创建一个src/matching-engine.ts文件。src/matching-engine.ts- 简化版匹配引擎import { JobListing } from ./data-aggregator; interface ResumeProfile { skills: string[]; experienceYears: number; expectedSalary?: number; // 月薪期望单位K preferredLocations: string[]; } interface MatchResult { score: number; // 综合匹配分 (0-100) breakdown: { skillScore: number; experienceScore: number; locationScore: number; salaryScore: number; }; suggestions: string[]; } export class MatchingEngine { /** * 计算单个职位与简历的匹配度 */ calculateMatch(job: JobListing, profile: ResumeProfile): MatchResult { const breakdown { skillScore: this.calculateSkillMatch(job.skills, profile.skills), experienceScore: this.calculateExperienceMatch(job.description, profile.experienceYears), locationScore: this.calculateLocationMatch(job.location, profile.preferredLocations), salaryScore: this.calculateSalaryMatch(job.salary, profile.expectedSalary), }; // 加权计算总分权重可根据实际情况调整 const totalScore Math.round( breakdown.skillScore * 0.4 breakdown.experienceScore * 0.3 breakdown.locationScore * 0.2 breakdown.salaryScore * 0.1 ); const suggestions this.generateSuggestions(job, profile, breakdown); return { score: totalScore, breakdown, suggestions, }; } /** * 批量匹配并排序 */ matchAndSortJobs(jobs: JobListing[], profile: ResumeProfile): Array{ job: JobListing; result: MatchResult } { const matchedJobs jobs.map(job ({ job, result: this.calculateMatch(job, profile), })); // 按匹配分数降序排序 return matchedJobs.sort((a, b) b.result.score - a.result.score); } // --- 各维度匹配算法 --- private calculateSkillMatch(jobSkills: string[], profileSkills: string[]): number { if (jobSkills.length 0) return 50; // 职位未明确要求技能给基准分 const matchedSkills jobSkills.filter(skill profileSkills.some(profileSkill profileSkill.toLowerCase().includes(skill.toLowerCase()) || skill.toLowerCase().includes(profileSkill.toLowerCase()) ) ); return (matchedSkills.length / jobSkills.length) * 100; } private calculateExperienceMatch(jobDescription: string, profileExpYears: number): number { // 简单正则匹配经验要求如“3-5年”、“1年以上” const expPattern /(\d)\s*[-~到]?\s*(\d)?\s*年/; const match jobDescription.match(expPattern); if (!match) return 70; // 未明确要求给一个中等偏上分数 const minExp parseInt(match[1], 10); const maxExp match[2] ? parseInt(match[2], 10) : minExp 5; // 如果只写“3年”假设上限为8年 if (profileExpYears minExp profileExpYears maxExp) { return 100; // 完美匹配区间 } else if (profileExpYears minExp) { // 每少一年扣15分最低20分 return Math.max(20, 100 - (minExp - profileExpYears) * 15); } else { // 每多一年扣5分最低40分资深候选人可能依然合适 return Math.max(40, 100 - (profileExpYears - maxExp) * 5); } } private calculateLocationMatch(jobLocation: string, preferredLocations: string[]): number { const jobLocLower jobLocation.toLowerCase(); if (jobLocLower.includes(远程)) return 100; // 远程工作匹配所有地点偏好 if (preferredLocations.some(loc jobLocLower.includes(loc.toLowerCase()))) { return 100; } return 0; } private calculateSalaryMatch(jobSalaryText?: string, expectedSalary?: number): number { if (!jobSalaryText || !expectedSalary) return 50; // 信息不全给基准分 // 简单解析薪资文本如“20-30K” const salaryPattern /(\d{1,3})\s*[-~到]?\s*(\d{1,3})?\s*[kK千]/; const match jobSalaryText.match(salaryPattern); if (!match) return 50; const minSalary parseInt(match[1], 10); const maxSalary match[2] ? parseInt(match[2], 10) : minSalary; const jobAvgSalary (minSalary maxSalary) / 2; // 期望薪资在职位薪资范围内得高分低于范围得中分高于范围得低分 if (expectedSalary minSalary expectedSalary maxSalary) return 100; if (expectedSalary minSalary) return Math.max(30, (expectedSalary / minSalary) * 100); // 期望过高 return Math.max(20, (maxSalary / expectedSalary) * 100); } private generateSuggestions(job: JobListing, profile: ResumeProfile, breakdown: MatchResult[breakdown]): string[] { const suggestions: string[] []; if (breakdown.skillScore 70) { const missingSkills job.skills.filter(js !profile.skills.some(ps ps.toLowerCase().includes(js.toLowerCase()))); if (missingSkills.length 0) { suggestions.push(职位要求的技能“${missingSkills.join(、)}”在你的简历中未突出显示建议补充相关项目经验或学习记录。); } } if (breakdown.experienceScore 60) { suggestions.push(该职位可能期望${this.extractExpFromJD(job.description)}年经验的候选人你的${profile.experienceYears}年经验略有差距可在求职信中强调你的快速学习能力和项目成果。); } if (breakdown.salaryScore 30) { suggestions.push(你的期望薪资可能显著高于该职位的预算需评估是否愿意调整期望或该职位是否有其他补偿优势。); } return suggestions; } private extractExpFromJD(text: string): string { const expPattern /(\d)\s*[-~到]?\s*(\d)?\s*年/; const match text.match(expPattern); return match ? (match[2] ? ${match[1]}-${match[2]} : ${match[1]}) : 若干; } }这个匹配引擎虽然简化但涵盖了核心逻辑多维度加权评分、各维度的具体计算规则以及基于差距的分析建议。在实际应用中你可以引入更复杂的NLP库如natural来提升技能提取和描述的语义理解能力。4.5 集成与运行最后我们需要将聚合器和匹配引擎集成到主服务器中并填充之前留空的performJobSearch和analyzeFit函数。更新src/index.ts中的相关部分// 在文件顶部引入新模块 import { DataAggregator } from ./data-aggregator.js; import { MatchingEngine } from ./matching-engine.js; // 在server初始化后创建聚合器和引擎实例 const aggregator new DataAggregator(); const matchingEngine new MatchingEngine(); // 模拟一个用户简历 profile (实际应从用户输入或配置获取) const mockUserProfile { skills: [Python, JavaScript, Node.js, Docker], experienceYears: 3, expectedSalary: 25, preferredLocations: [上海, 远程], }; // 替换原有的模拟函数 async function performJobSearch(keywords?: string, location?: string): Promiseany[] { console.log(开始搜索关键词${keywords}, 地点${location}); // 1. 从多个源获取数据 const jobsFromRSS await aggregator.fetchFromRSSFeeds(); // 注意实际抓取应谨慎这里仅作演示 // const jobsFromScrape await aggregator.scrapeFromJobBoard(https://example-jobs.com/list); let allJobs [...jobsFromRSS /*, ...jobsFromScrape*/]; // 2. 简单的前端过滤服务器端可做更复杂的 if (keywords) { const kwLower keywords.toLowerCase(); allJobs allJobs.filter(job job.title.toLowerCase().includes(kwLower) || job.description.toLowerCase().includes(kwLower) || job.skills.some(skill skill.toLowerCase().includes(kwLower)) ); } if (location) { const locLower location.toLowerCase(); allJobs allJobs.filter(job job.location.toLowerCase().includes(locLower)); } // 3. 使用匹配引擎排序 const sortedJobs matchingEngine.matchAndSortJobs(allJobs, mockUserProfile); // 4. 返回格式化结果取前10个 return sortedJobs.slice(0, 10).map(({ job, result }) ({ ...job, matchScore: result.score, suggestion: result.suggestions[0] || 匹配良好。, })); } async function analyzeFit(resumeText: string, jobDescription: string): Promiseany { console.log(开始分析简历匹配度); // 从简历文本中提取技能和年限这里简化处理实际应用需要更复杂的解析 const extractedSkills [Python, Node.js]; // 应通过NLP从resumeText提取 const extractedExp 3; // 应通过NLP从resumeText提取 const profile: ResumeProfile { skills: extractedSkills, experienceYears: extractedExp, expectedSalary: mockUserProfile.expectedSalary, preferredLocations: mockUserProfile.preferredLocations, }; // 从职位描述中提取技能 const jobSkills aggregator[extractSkills](jobDescription); // 调用聚合器内的技能提取方法 const mockJob: JobListing { id: analysis_job, title: 待分析职位, company: 待分析公司, location: 未知, description: jobDescription, url: , source: 用户输入, postedDate: new Date(), skills: jobSkills, }; const result matchingEngine.calculateMatch(mockJob, profile); return { skillMatch: Math.round(result.breakdown.skillScore), expMatch: result.breakdown.experienceScore 80 ? 高度匹配 : result.breakdown.experienceScore 60 ? 基本匹配 : 经验略有不足, suggestion: result.suggestions.join( ) || 简历与职位要求契合度较高。, }; }现在一个具备基本功能的MCP求职服务器就搭建完成了。你可以运行npm run dev启动开发服务器然后在Claude Desktop中配置连接这个本地服务器即可开始使用。5. 配置、使用与高级技巧5.1 在Claude Desktop中配置MCP服务器找到Claude Desktop配置在Mac上配置文件通常位于~/Library/Application Support/Claude/claude_desktop_config.json。在Windows上位于%APPDATA%\Claude\claude_desktop_config.json。编辑配置文件在配置文件中添加你的MCP服务器配置。假设你的服务器运行在本地的http://localhost:3000如果你实现了HTTP传输层或者使用stdio方式如上述代码所示。{ mcpServers: { job-search-assistant: { command: node, args: [/你的项目绝对路径/dist/index.js], env: { NODE_ENV: production } } } }如果使用HTTP配置会略有不同需要指定URL。重启Claude Desktop保存配置文件后完全重启Claude Desktop应用。验证连接重启后在Claude的对话中输入“你能用什么工具”它应该会列出search_jobs和analyze_resume_fit等工具表示连接成功。5.2 实际使用场景示例连接成功后你就可以像和普通Claude聊天一样使用增强的求职功能了场景一精准搜索你“帮我找一下深圳的、需要React和TypeScript经验的前端开发职位薪资最好在25K以上。”Claude调用search_jobs后“我找到了8个匹配度较高的职位。其中‘某大厂高级前端工程师’岗位匹配度92%要求3年以上React/TS经验薪资范围25-40K。‘某创业公司前端负责人’匹配度85%薪资30-50K但要求5年以上经验。这是完整列表你对哪个更感兴趣我可以进一步分析JD。”场景二简历优化你粘贴你的简历文本和某个心仪职位的JDClaude调用analyze_resume_fit后“分析完成。你的简历与目标职位整体匹配度为78%。优势在于项目经验与技术要求高度吻合。主要差距职位强调‘微服务架构设计’和‘性能优化度量’而你的简历中这两点描述较为简略。建议在‘XX项目’部分补充你设计的服务间通信方案和接口性能压测数据量化提升指标如QPS提升XX%延迟降低XX%。”场景三市场调研你“我想转行做数据产品经理现在市场行情怎么样需要补哪些技能”Claude可结合get_market_insights工具或基于已有数据分析“根据近期的职位数据‘数据产品经理’岗位数量环比增长15%。高频技能关键词依次是SQL出现率95%、数据分析85%、Python70%、A/B测试65%、Tableau/Power BI60%。薪资范围集中在25-50K。建议你优先巩固SQL和数据分析方法论并学习一个BI工具。”5.3 高级技巧与避坑指南数据源的维护是最大挑战招聘网站的页面结构经常变动。你的HTML解析器cheerio选择器很容易“失效”。最佳实践是优先使用API/RSS永远把寻找和维护稳定的官方数据接口放在第一位。封装解析器为每个数据源编写独立的解析类并做好错误处理和日志记录。当解析失败时能快速定位是哪个网站的结构变了。设置更新频率不要频繁抓取同一个网站。对于免费数据源建议每小时或每半天更新一次即可并在代码中设置明显的请求间隔和随机延迟。匹配算法的调优需要“数据喂养”最初的权重设置如技能占40%可能不准确。建议增加一个简单的反馈机制。在返回匹配结果时可以加一个“这个匹配度你觉得准吗”的反馈选项通过Claude交互收集用户反馈来微调权重。定期用一批“标准简历”和“标准职位”去测试匹配结果观察分数分布是否合理。本地部署与隐私最安全的方式是将整个服务器部署在你自己的电脑或家庭服务器上。这样所有的简历数据、搜索记录都完全留在本地。如果你使用云函数务必确保环境变量配置正确并且云服务商有良好的数据安全政策。处理“薪资面议”很多职位薪资写的是“面议”。我们的匹配引擎会给一个基准分50分。更好的策略是根据公司规模、职位级别和地点利用历史数据估算一个薪资范围让匹配更有参考性。让Claude发挥“软实力”分析MCP服务器提供硬数据职位列表、匹配分数而Claude可以在此基础上做“软分析”。例如你可以要求Claude“根据这个职位描述帮我草拟一段针对性的求职信开头”或者“这个岗位描述里提到‘跨部门沟通’我应该在面试中准备哪些例子” 将结构化数据与AI的创造性语言能力结合价值最大化。6. 常见问题与故障排除在实际搭建和使用过程中你可能会遇到以下问题Q1: Claude Desktop无法连接到我的MCP服务器提示“无法连接到服务器”或“命令执行失败”。检查配置路径确保claude_desktop_config.json中args指向的JavaScript文件路径是绝对路径并且是编译后的dist/index.js如果你用TypeScript。检查命令权限确保node命令在系统PATH中。可以在终端测试直接运行node /path/to/your/dist/index.js看是否报错。查看日志MCP服务器通过console.error输出的日志可以在Claude Desktop的开发者工具控制台或系统日志中查看里面常有具体错误信息。Q2: 工具调用成功了但返回的结果是空的或明显不对。数据源问题首先检查你的数据聚合模块。RSS源地址是否有效网页抓取的选择器是否因网站改版而失效在代码中添加详细的日志打印出抓取到的原始数据。网络问题确保你的服务器运行环境可以访问外网用于抓取数据。如果使用云函数检查其网络配置。过滤条件过严检查performJobSearch函数中的关键词和地点过滤逻辑是否因为大小写或空格导致误过滤。Q3: 匹配分数感觉不准确比如一个明显很匹配的职位分数很低。技能词库不匹配检查extractSkills函数中的skillKeywords列表。是否遗漏了该职位的关键技能如“Go”、“Rust”你需要根据目标行业不断维护和更新这个词库。描述文本解析不全有些职位信息可能在HTML的折叠区域或通过JavaScript加载。确保你的抓取逻辑能获取到完整的职位描述文本。权重不合理调整MatchingEngine中calculateMatch里的权重系数0.4, 0.3, 0.2, 0.1。如果你更看重地点可以提高locationScore的权重。Q4: 运行一段时间后被招聘网站封了IP。立即停止抓取这是最严重的信号。检查伦理设置回顾你的代码是否设置了足够的请求延迟如delay(2000)表示2秒User-Agent是否标识清晰、友好使用代理池高级对于需要大规模抓取的研究项目可以考虑使用轮换代理IP的服务但这会显著增加复杂性和成本。对于个人使用强烈建议仅依赖RSS和官方API或极其克制地抓取少量网站。Q5: 如何添加新的数据源在DataAggregator类中创建一个新方法例如fetchFromNewSource(url: string)。根据新数据源的类型API/HTML使用axios获取数据并用cheerio或JSON解析器提取字段。将提取的数据格式化为标准的JobListing接口。在fetchJobs这类总调度方法中调用你的新方法并将其结果合并到总列表中。务必先手动测试在浏览器中访问目标页面用开发者工具查看其网络请求和HTML结构确保你的解析逻辑稳定可靠。搭建这样一个工具的过程本身就是一个极佳的学习项目它涉及了全栈开发、数据抓取、简单NLP、算法设计和系统集成。即使最终不用于求职你在过程中获得的能力提升或许就是你下一份工作最好的“简历”。

相关新闻