JSON Resume MCP Server

Official
  • src
import OpenAI from "openai"; import { Resume } from "./types.js"; import { CodebaseAnalysisResult } from "./codebase.js"; import { resumeUpdateSchema, ResumeUpdate } from "./schemas.js"; import { z } from "zod"; export class OpenAIService { private client: OpenAI; constructor(apiKey: string) { if (!apiKey) { throw new Error("OpenAI API key is required"); } this.client = new OpenAI({ apiKey }); } /** * Generate a new project and skills based on codebase analysis */ async generateResumeEnhancement( codebaseAnalysis: CodebaseAnalysisResult ): Promise<ResumeUpdate> { try { console.error("Preparing OpenAI API call for resume enhancement..."); // Call OpenAI API with function calling const response = await this.client.chat.completions.create({ model: "gpt-4", messages: [ { role: "system", content: "You are a professional technical resume writer that creates high-quality JSON Resume compatible project entries and skills based on codebase analysis. Focus on capturing the essence and significance of the project, not just technical details.\n\n" + "Guidelines:\n" + "- Begin by clearly explaining what the project DOES and its PURPOSE - this is the most important part\n" + "- Focus on the problem it solves and value it provides to users or stakeholders\n" + "- Then mention key technical achievements and architectural decisions\n" + "- Create professional, substantive descriptions that highlight business value\n" + "- Avoid trivial details like file counts or minor technologies\n" + "- For dates, use YYYY-MM-DD format and ensure they are realistic (not in the future)\n" + "- For ongoing projects, omit the endDate field entirely\n" + "- Only include skills that are substantive and resume-worthy\n" + "- Group skills by category when possible\n" + "- Prioritize quality over quantity in skills and descriptions", }, { role: "user", content: `Based on this codebase analysis, generate a single project entry and relevant skills for a resume that focuses first on WHAT the project does and WHY it matters, then how it was implemented: ${codebaseAnalysis.readmeContent ? `README.md Content:\n${codebaseAnalysis.readmeContent}\n\n` : ''} Codebase Analysis:\n${JSON.stringify(codebaseAnalysis, null, 2)}` }, ], functions: [ { name: "create_resume_update", description: "Create a new project entry and skills based on codebase analysis", parameters: { type: "object", properties: { newProject: { type: "object", properties: { name: { type: "string", description: "Professional project name" }, startDate: { type: "string", description: "Project start date in YYYY-MM-DD or YYYY-MM format (must be a realistic date, not in the future)" }, endDate: { type: "string", description: "Project end date in YYYY-MM-DD or YYYY-MM format. OMIT THIS FIELD ENTIRELY for ongoing projects - do not use 'Present' or future dates." }, description: { type: "string", description: "Professional project description that STARTS by clearly explaining what the project does and why it matters. Begin with its purpose and function, then mention key technologies and implementation details. (60-100 words recommended)" }, highlights: { type: "array", items: { type: "string" }, description: "Bullet points highlighting key achievements and technologies that demonstrate significant impact" }, url: { type: "string", description: "Project URL" }, roles: { type: "array", items: { type: "string" }, description: "Roles held during the project" }, entity: { type: "string", description: "Organization name associated with the project" }, type: { type: "string", description: "Type of project (application, library, etc.)" } }, required: ["name", "startDate", "description"] }, newSkills: { type: "array", description: "Only include substantive, resume-worthy skills that demonstrate significant expertise", items: { type: "object", properties: { name: { type: "string", description: "Professional skill name" }, level: { type: "string", description: "Skill proficiency level" }, keywords: { type: "array", items: { type: "string" }, description: "Related keywords for this skill" }, category: { type: "string", description: "Skill category for grouping (e.g., 'Programming Languages', 'Frameworks', 'Tools')" } }, required: ["name"] } }, changes: { type: "array", items: { type: "string" }, description: "Summary of changes made to the resume" } }, required: ["newProject", "newSkills", "changes"] } } ], function_call: { name: "create_resume_update" } }); console.error("Received response from OpenAI API"); const functionCall = response.choices[0]?.message?.function_call; if (!functionCall?.arguments) { console.error("Error: No function call arguments in OpenAI response"); throw new Error("No function call arguments received from OpenAI"); } // Parse and validate the response console.error("Parsing and validating OpenAI response..."); try { const result = JSON.parse(functionCall.arguments); const validated = resumeUpdateSchema.parse(result); console.error("Successfully validated schema"); return validated; } catch (parseError) { console.error("Error parsing OpenAI response:", parseError instanceof SyntaxError ? "JSON parse error" : parseError instanceof z.ZodError ? "Schema validation error" : "Unknown error" ); console.error("Raw function call arguments:", functionCall.arguments.substring(0, 200) + "..."); throw parseError; } } catch (error) { if (error instanceof z.ZodError) { console.error("Schema validation error:", error.errors); } else if (error instanceof SyntaxError) { console.error("JSON parsing error:", error.message); } else if (error instanceof Error) { console.error("OpenAI API error:", error.message); console.error("Error details:", error); } else { console.error("Unknown error:", error); } throw error; } } /** * Add only new project and skills to a resume without modifying any existing content */ async enhanceResume(resume: Resume, update: ResumeUpdate): Promise<Resume> { const result = JSON.parse(JSON.stringify(resume)) as Resume; // Store the _gistId separately so we can add it back later const gistId = (result as any)._gistId; // Add the new project if it doesn't already exist const existingProjects = new Set( (result.projects || []).map(project => project.name.toLowerCase()) ); if (update.newProject && !existingProjects.has(update.newProject.name.toLowerCase())) { result.projects = [...(result.projects || []), update.newProject]; } // Add new skills if they don't already exist const existingSkills = new Set( (result.skills || []).map(skill => typeof skill === 'string' ? skill.toLowerCase() : skill.name.toLowerCase() ) ); const newSkills = update.newSkills.filter(skill => !existingSkills.has(skill.name.toLowerCase()) ); if (newSkills.length > 0) { result.skills = [...(result.skills || []), ...newSkills]; } // Add back the _gistId if it existed if (gistId) { (result as any)._gistId = gistId; } return result; } /** * Generate a summary of updates made to the resume */ async generateUpdateSummary(changes: string[]): Promise<string> { try { console.error("Generating update summary..."); // Call OpenAI API to generate a summary const response = await this.client.chat.completions.create({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a helpful assistant that creates concise summaries of resume updates." }, { role: "user", content: `Create a brief, professional summary of these changes made to a resume:\n${changes.join('\n')}` } ], max_tokens: 150 }); const summary = response.choices[0]?.message?.content || "Resume updated with new project details and skills."; return summary; } catch (error) { console.error("Error generating update summary:", error); return "Resume updated with new project details and skills."; } } }