JSON Resume MCP Server
Official
by jsonresume
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { GitHubService } from "./src/github.js";
import { OpenAIService } from "./src/openai.js";
import { Resume } from "./src/types.js";
import { CodebaseAnalyzer } from "./src/codebase.js";
import { ResumeEnhancer } from "./src/resume-enhancer.js";
const server = new Server(
{
name: "jsonresume-mcp",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
logging: {},
},
}
);
// Environment variables
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const GITHUB_USERNAME = process.env.GITHUB_USERNAME;
// Initialize services
let githubService: GitHubService;
let openaiService: OpenAIService;
let codebaseAnalyzer: CodebaseAnalyzer;
let resumeEnhancer: ResumeEnhancer;
try {
if (!GITHUB_TOKEN) {
throw new Error("GITHUB_TOKEN environment variable is required");
}
if (!OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY environment variable is required");
}
if (!GITHUB_USERNAME) {
throw new Error("GITHUB_USERNAME environment variable is required");
}
githubService = new GitHubService(GITHUB_TOKEN, GITHUB_USERNAME);
openaiService = new OpenAIService(OPENAI_API_KEY);
codebaseAnalyzer = new CodebaseAnalyzer(process.cwd());
resumeEnhancer = new ResumeEnhancer(openaiService);
console.error("Services initialized successfully");
} catch (error) {
console.error("Error initializing services:", error);
process.exit(1);
}
// Define MCP tools
const ANALYZE_CODEBASE_TOOL: Tool = {
name: "github_analyze_codebase",
description: "This is a tool from the github MCP server.\nAnalyzes the current codebase and returns information about technologies, languages, and recent commits",
inputSchema: {
type: "object",
properties: {
directory: {
type: "string",
description: "The directory to analyze. If not provided, uses current working directory.",
},
},
required: [],
},
};
const CHECK_RESUME_TOOL: Tool = {
name: "github_check_resume",
description: "This is a tool from the github MCP server.\nChecks if a GitHub user has a JSON Resume and returns its information",
inputSchema: {
type: "object",
properties: {},
required: [],
},
};
const ENHANCE_RESUME_WITH_PROJECT_TOOL: Tool = {
name: "github_enhance_resume_with_project",
description: "This is a tool from the github MCP server.\nEnhances a GitHub user's JSON Resume with information about their current project",
inputSchema: {
type: "object",
properties: {
directory: {
type: "string",
description: "The directory of the project to analyze. If not provided, uses current working directory.",
},
},
required: [],
},
};
const tools = [
ANALYZE_CODEBASE_TOOL,
CHECK_RESUME_TOOL,
ENHANCE_RESUME_WITH_PROJECT_TOOL,
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools,
}));
function doHello(name: string) {
return {
message: `Hello, ${name}!`,
};
}
async function analyzeCodebase(directory?: string) {
try {
console.error("Starting codebase analysis...");
// Create a new analyzer for the specified directory
const analyzer = directory ? new CodebaseAnalyzer(directory) : codebaseAnalyzer;
// Analyze the codebase
const analysis = await analyzer.analyze();
console.error("Codebase analysis completed");
return {
message: "Codebase analysis completed successfully",
analysis,
summary: analysis.summary
};
} catch (error) {
console.error("Error analyzing codebase:", error);
throw error;
}
}
async function checkResume() {
try {
console.error("Checking for existing resume...");
// Fetch the user's resume from GitHub gists
const resume = await githubService.getResumeFromGists();
if (!resume) {
return {
message: "No resume found",
exists: false,
resumeUrl: null
};
}
// Remove the _gistId property for cleaner output
const { _gistId, ...cleanResume } = resume;
return {
message: "Resume found",
exists: true,
resumeUrl: `https://registry.jsonresume.org/${GITHUB_USERNAME}`,
resume: cleanResume
};
} catch (error) {
console.error("Error checking resume:", error);
throw error;
}
}
async function enhanceResumeWithProject(directory?: string) {
try {
console.error("Starting resume enhancement with current project...");
// Step 1: Fetch the user's resume from GitHub gists
console.error("Fetching resume from GitHub gists...");
let resume = await githubService.getResumeFromGists();
if (!resume) {
// If no resume exists, create a sample one
console.error("No resume found, creating a sample resume...");
const userProfile = await githubService.getUserProfile();
resume = await githubService.createSampleResume();
console.error("Sample resume created successfully");
} else {
console.error("Existing resume found");
}
// Step 2: Analyze the current codebase
console.error("Analyzing current project...");
const analyzer = directory ? new CodebaseAnalyzer(directory) : codebaseAnalyzer;
const codebaseAnalysis = await analyzer.analyze();
// Step 3: Enhance the resume with the current project
console.error("Enhancing resume with current project...");
const { updatedResume, changes, summary, userMessage, resumeLink } = await resumeEnhancer.enhanceWithCurrentProject(
resume,
codebaseAnalysis,
GITHUB_USERNAME || ''
);
// Step 4: Update the resume on GitHub
console.error("Updating resume on GitHub...");
const finalResume = await githubService.updateResume(updatedResume);
return {
message: "Resume enhanced with current project successfully",
changes: changes,
summary,
userMessage,
resumeUrl: resumeLink || `https://registry.jsonresume.org/${GITHUB_USERNAME}`,
projectName: codebaseAnalysis.repoName,
warning: "⚠️ Note: Automatic resume updates might have modified your resume in ways that don't match your preferences. You can revert to a previous version through your GitHub Gist revision history if needed."
};
} catch (error) {
console.error("Error enhancing resume with project:", error);
throw error;
}
}
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "github_hello_tool") {
console.error("Hello tool", request.params.arguments);
const input = request.params.arguments as { name: string };
return doHello(input.name);
} else if (request.params.name === "github_analyze_codebase") {
console.error("Analyze codebase tool", request.params.arguments);
const input = request.params.arguments as { directory?: string };
return await analyzeCodebase(input.directory);
} else if (request.params.name === "github_check_resume") {
console.error("Check resume tool", request.params.arguments);
return await checkResume();
} else if (request.params.name === "github_enhance_resume_with_project") {
console.error("Enhance resume with project tool", request.params.arguments);
const input = request.params.arguments as { directory?: string };
return await enhanceResumeWithProject(input.directory);
}
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
});
server.onerror = (error: any) => {
console.error(error);
};
process.on("SIGINT", async () => {
await server.close();
process.exit(0);
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("JsonResume MCP Server running on stdio");
}
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});