#
tokens: 19484/50000 22/22 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .env.example
├── .gitignore
├── .prettierrc
├── apps
│   └── registry
│       └── scripts
│           └── jobs
│               └── recommendations
│                   └── product-requirements.md
├── Dockerfile
├── index.ts
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── codebase.ts
│   ├── github.ts
│   ├── openai.ts
│   ├── resume-enhancer.ts
│   ├── schemas.ts
│   ├── tools.ts
│   └── types.ts
├── tests
│   ├── check-openai.ts
│   ├── debug-enhance.ts
│   ├── debug-mock.ts
│   ├── test-direct.js
│   └── test-mcp.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
dist
node_modules
.env
.awolf
```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "arrowParens": "always",
  "ignore": ["dist/**/*"]
}

```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Environment variables for jsonresume-mcp
# Copy this file to .env and fill in your values

# GitHub personal access token with gist permissions
GITHUB_TOKEN=your_github_token_here

# OpenAI API key
OPENAI_API_KEY=your_openai_api_key_here

# GitHub username
GITHUB_USERNAME=your_github_username_here

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# JSON Resume MCP Server

<div align="center">

![JSON Resume Logo](https://jsonresume.org/img/logo.svg)

[![npm version](https://img.shields.io/npm/v/@jsonresume/mcp.svg)](https://www.npmjs.com/package/@jsonresume/mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![GitHub Issues](https://img.shields.io/github/issues/jsonresume/mcp.svg)](https://github.com/jsonresume/mcp/issues)
[![smithery badge](https://smithery.ai/badge/@jsonresume/mcp)](https://smithery.ai/server/@jsonresume/mcp)

**Automate your resume updates with AI by analyzing your coding projects**

[Installation](#installation) • [Features](#features) • [Usage](#usage) • [Configuration](#configuration) • [Contributing](#contributing) • [Testing](#testing)

</div>

## What is JSON Resume MCP Server?

This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.ai) server that enhances AI assistants with the ability to update your [JSON Resume](https://jsonresume.org) by analyzing your coding projects. The MCP server provides tools that allow AI assistants like those in [Windsurf](https://www.windsurf.io/) or [Cursor](https://cursor.sh/) to:

1. Check if you have an existing JSON Resume
2. Analyze your codebase to understand your technical skills and projects
3. Enhance your resume with details about your current project

With this tool, you can simply ask your AI assistant to "enhance my resume with my current project," and it will automatically analyze your code, extract relevant skills and project details, and update your resume accordingly.

Video demo: [https://x.com/ajaxdavis/status/1896953226282594381](https://x.com/ajaxdavis/status/1896953226282594381)

## Features

- **Resume Enhancement**: Automatically analyzes your codebase and adds project details to your resume
- **GitHub Integration**: Fetches and updates your resume stored in GitHub Gists
- **AI-Powered**: Uses OpenAI to generate professional descriptions of your projects and skills
- **TypeScript/Zod Validation**: Ensures your resume follows the JSON Resume standard
- **JSON Resume Ecosystem**: Compatible with the [JSON Resume registry](https://registry.jsonresume.org)

## Installation

### Prerequisites

- GitHub account with a personal access token (with gist scope)
- OpenAI API key
- Node.js 18+
- An IDE with MCP support (Windsurf or Cursor)

### Installing via Smithery

To install mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@jsonresume/mcp):

```bash
npx -y @smithery/cli install @jsonresume/mcp --client claude
```

### Install via NPM

```bash
npm install -g @jsonresume/mcp
```

### Install in Windsurf or Cursor

Add the following to your Windsurf or Cursor configuration:

#### Windsurf

Open Settings → MCP Servers and add:

```json
{
  "jsonresume": {
    "command": "npx",
    "args": ["-y", "@jsonresume/mcp"],
    "env": {
      "GITHUB_TOKEN": "your-github-token",
      "OPENAI_API_KEY": "your-openai-api-key",
      "GITHUB_USERNAME": "your-github-username"
    }
  }
}
```

#### Cursor

Add to your `~/.cursor/mcp_config.json`:

```json
{
  "mcpServers": {
    "jsonresume": {
      "command": "npx",
      "args": ["-y", "@jsonresume/mcp"],
      "env": {
        "GITHUB_TOKEN": "your-github-token",
        "OPENAI_API_KEY": "your-openai-api-key",
        "GITHUB_USERNAME": "your-github-username"
      }
    }
  }
}
```

## Usage

Once installed and configured, you can use the following commands with your AI assistant:

### Enhance Your Resume with Current Project

Ask your AI assistant:
```
"Can you enhance my resume with details from my current project?"
```

The assistant will:
1. Find your existing resume on GitHub (or create a new one if needed)
2. Analyze your current project's codebase
3. Generate professional descriptions of your project and skills
4. Update your resume with the new information
5. Save the changes back to GitHub
6. Provide a link to view your updated resume

### Check Your Resume Status

Ask your AI assistant:
```
"Can you check if I have a JSON Resume?"
```

The assistant will check if you have an existing resume and show its details.

### Analyze Your Codebase

Ask your AI assistant:
```
"What technologies am I using in this project?"
```

The assistant will analyze your codebase and provide insights about languages, technologies, and recent commits.

## Configuration

The MCP server requires the following environment variables:

| Variable | Description |
|----------|-------------|
| `GITHUB_TOKEN` | Your GitHub personal access token with gist permissions |
| `GITHUB_USERNAME` | Your GitHub username |
| `OPENAI_API_KEY` | Your OpenAI API key |

## Development

To run the server in development mode:

1. Clone the repository:
```bash
git clone https://github.com/jsonresume/mcp.git
cd mcp
```

2. Install dependencies:
```bash
npm install
```

3. Run in development mode:
```bash
npm run dev
```

This starts the MCP server with the inspector tool for debugging.

## Contributing

Contributions are welcome! Here's how you can contribute:

1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Commit your changes: `git commit -m 'Add some amazing feature'`
4. Push to the branch: `git push origin feature/amazing-feature`
5. Open a Pull Request

Please ensure your code follows the existing style and includes appropriate tests.

## Testing

The MCP server includes several test scripts to help debug and verify functionality.

### Running Tests

All test scripts are located in the `tests/` directory.

Before running tests, set your environment variables:

```bash
export GITHUB_TOKEN=your_github_token
export OPENAI_API_KEY=your_openai_api_key
export GITHUB_USERNAME=your_github_username
```

#### Check OpenAI API Key

Validates that your OpenAI API key is working correctly:

```bash
npx tsx tests/check-openai.ts
```

#### Mock Resume Enhancement

Tests the resume enhancement functionality using mock data (no API calls):

```bash
npx tsx tests/debug-mock.ts
```

#### Full Resume Enhancement Test

Tests the complete resume enhancement process with live API calls:

```bash
npx tsx tests/debug-enhance.ts
```

#### MCP Protocol Test

Tests the MCP server protocol communication:

```bash
node tests/test-mcp.js
```

### Adding to package.json

For convenience, you can add these test commands to your package.json:

```json
"scripts": {
  "test:openai": "tsx tests/check-openai.ts",
  "test:mock": "tsx tests/debug-mock.ts",
  "test:enhance": "tsx tests/debug-enhance.ts",
  "test:mcp": "node tests/test-mcp.js"
}
```

Then run them with `npm run test:mock`, etc.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Acknowledgments

- [JSON Resume](https://jsonresume.org) for the resume standard
- [Model Context Protocol](https://modelcontextprotocol.ai) for enabling AI tool integration
- [OpenAI](https://openai.com) for powering the AI resume enhancements

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "."
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

WORKDIR /app

# Copy package manifest
COPY package.json package-lock.json ./

# Install dependencies without running scripts (to skip potential issues)
RUN npm install --ignore-scripts

# Copy all source files
COPY . .

# Build the project using esbuild
RUN npm run build

# Expose a port if needed (not specified, so omitted)

# Run the MCP server using the built artifact
CMD ["node", "dist/index.cjs"]

```

--------------------------------------------------------------------------------
/tests/check-openai.ts:
--------------------------------------------------------------------------------

```typescript
import OpenAI from "openai";

// OpenAI key from environment
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

async function checkOpenAI() {
  try {
    // Validate environment variable
    if (!OPENAI_API_KEY) {
      throw new Error("OPENAI_API_KEY environment variable is required");
    }
    
    console.log("Testing OpenAI API key validity...");
    const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
    
    // Try a simple completion request
    const response = await openai.chat.completions.create({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: "Say hello" }],
      max_tokens: 5
    });
    
    console.log("OpenAI API key is valid!");
    console.log("Response:", response.choices[0]?.message?.content);
    return true;
  } catch (error) {
    console.log("Error testing OpenAI API key:", error);
    return false;
  }
}

checkOpenAI();

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - githubToken
      - githubUsername
      - openaiApiKey
    properties:
      githubToken:
        type: string
        description: Your GitHub personal access token with gist permissions.
      githubUsername:
        type: string
        description: Your GitHub username.
      openaiApiKey:
        type: string
        description: Your OpenAI API key.
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.cjs'],
      env: {
        GITHUB_TOKEN: config.githubToken,
        GITHUB_USERNAME: config.githubUsername,
        OPENAI_API_KEY: config.openaiApiKey
      }
    })
  exampleConfig:
    githubToken: your-github-token
    githubUsername: your-github-username
    openaiApiKey: your-openai-api-key

```

--------------------------------------------------------------------------------
/src/schemas.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// ISO date format validation (YYYY-MM-DD)
const isoDateSchema = z.string().regex(
  /^\d{4}-\d{2}-\d{2}$/,
  "Date must be in YYYY-MM-DD format"
);

// Project schema aligned with JSON Resume standard
export const projectSchema = z.object({
  name: z.string().min(1, "Project name is required"),
  startDate: isoDateSchema,
  endDate: isoDateSchema.optional(), // Optional for ongoing projects
  description: z.string().min(10, "Description should be meaningful and professional"),
  highlights: z.array(z.string()).optional(),
  url: z.string().url("URL must be valid").optional(),
});

// Skill schema with categorization support
export const skillSchema = z.object({
  name: z.string().min(1, "Skill name is required"),
  level: z.string().optional(),
  keywords: z.array(z.string()).optional(),
});

// Resume update schema for OpenAI function calls
export const resumeUpdateSchema = z.object({
  newProject: projectSchema,
  newSkills: z.array(skillSchema),
  changes: z.array(z.string()),
});

// Type definitions for the schemas
export type Skill = z.infer<typeof skillSchema>;
export type Project = z.infer<typeof projectSchema>;
export type ResumeUpdate = z.infer<typeof resumeUpdateSchema>;

```

--------------------------------------------------------------------------------
/src/tools.ts:
--------------------------------------------------------------------------------

```typescript
import { Tool } from "@modelcontextprotocol/sdk/types.js";

// Define MCP tools
export 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: [],
  },
};

export 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: [],
  },
};

export 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: [],
  },
};

// Export all tools as an array for convenience
export const tools = [
  ANALYZE_CODEBASE_TOOL,
  CHECK_RESUME_TOOL,
  ENHANCE_RESUME_WITH_PROJECT_TOOL,
];

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@jsonresume/mcp",
  "version": "3.0.3",
  "description": "ModelContextProtocol server for enhancing JSON Resumes",
  "type": "module",
  "private": false,
  "scripts": {
    "make-executable": "node -e \"fs.chmodSync('dist/index.cjs', '755');\" --require fs",
    "build": "esbuild index.ts --outfile=dist/index.cjs --bundle --platform=node --format=cjs --banner:js='#!/usr/bin/env node' && npm run make-executable",
    "watch": "esbuild index.ts --outfile=dist/index.cjs --bundle --platform=node --format=cjs --banner:js='#!/usr/bin/env node' --watch",
    "inspect": "npx @modelcontextprotocol/inspector node dist/index.cjs",
    "dev": "tsx index.ts",
    "start": "node dist/index.cjs",
    "start:stdio": "node dist/index.cjs stdio",
    "start:http": "node dist/index.cjs",
    "prepublishOnly": "npm run build",
    "test:openai": "tsx tests/check-openai.ts",
    "test:mock": "tsx tests/debug-mock.ts",
    "test:enhance": "tsx tests/debug-enhance.ts",
    "test:mcp": "node tests/test-mcp.js"
  },
  "bin": {
    "@jsonresume/mcp": "./dist/index.cjs"
  },
  "files": [
    "dist"
  ],
  "dependencies": {
    "@hono/node-server": "^1.14.0",
    "@modelcontextprotocol/sdk": "file:/home/ajax/repos/typescript-sdk",
    "axios": "^1.8.1",
    "dotenv": "^16.4.7",
    "hono": "^4.7.5",
    "octokit": "^3.2.1",
    "openai": "^4.86.1"
  },
  "devDependencies": {
    "@types/node": "^22.10.1",
    "concurrently": "^8.2.2",
    "esbuild": "^0.24.0",
    "prettier": "^3.4.2",
    "ts-node": "^10.9.2",
    "tsx": "^4.19.3",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/tests/debug-enhance.ts:
--------------------------------------------------------------------------------

```typescript
import { GitHubService } from "../src/github.js";
import { OpenAIService } from "../src/openai.js";
import { CodebaseAnalyzer } from "../src/codebase.js";
import { ResumeEnhancer } from "../src/resume-enhancer.js";
import { Resume } from "../src/types.js";

// 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;

async function main() {
  console.log("Starting resume enhancement test...");
  
  try {
    // Validate environment variables
    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");
    }
    
    // Initialize services
    console.log("Initializing services...");
    const githubService = new GitHubService(GITHUB_TOKEN, GITHUB_USERNAME);
    const openaiService = new OpenAIService(OPENAI_API_KEY);
    const codebaseAnalyzer = new CodebaseAnalyzer(process.cwd());
    const resumeEnhancer = new ResumeEnhancer(openaiService);
    
    // Get or create a sample resume
    console.log("Getting sample resume...");
    const sampleResume: Resume = {
      basics: {
        name: "Test User",
        label: "Software Developer",
        email: "[email protected]"
      },
      skills: [],
      projects: []
    };
    
    // Analyze the codebase
    console.log("Analyzing codebase...");
    const codebaseAnalysis = await codebaseAnalyzer.analyze();
    console.log("Codebase analysis complete:", {
      repoName: codebaseAnalysis.repoName,
      languages: Object.keys(codebaseAnalysis.languages || {}).join(", "),
      technologies: (codebaseAnalysis.technologies || []).join(", ")
    });
    
    // Enhance the resume
    console.log("Enhancing resume...");
    const enhancementResult = await resumeEnhancer.enhanceWithCurrentProject(
      sampleResume,
      codebaseAnalysis,
      GITHUB_USERNAME
    );
    
    console.log("Enhancement complete!");
    console.log("Summary:", enhancementResult.summary);
    console.log("Added skills:", enhancementResult.changes.addedSkills.join(", "));
    console.log("Project name:", enhancementResult.changes.updatedProjects[0]);
    
    return enhancementResult;
  } catch (error) {
    console.log("Error during enhancement process:", error);
    throw error;
  }
}

main().catch(error => {
  console.log("Fatal error:", error);
  process.exit(1);
});

```

--------------------------------------------------------------------------------
/tests/test-mcp.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import { spawn } from "child_process";
import path from "path";
import { fileURLToPath } from "url";

// Get current file's directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Path to your MCP server executable
const mcpServerPath = path.resolve(__dirname, "../dist/index.cjs");

// Environment variables
const env = {
  ...process.env,
  GITHUB_TOKEN: process.env.GITHUB_TOKEN,
  OPENAI_API_KEY: process.env.OPENAI_API_KEY,
  GITHUB_USERNAME: process.env.GITHUB_USERNAME,
};

// Validate required environment variables
if (!env.GITHUB_TOKEN) {
  console.log("Error: GITHUB_TOKEN environment variable is required");
  process.exit(1);
}

if (!env.OPENAI_API_KEY) {
  console.log("Error: OPENAI_API_KEY environment variable is required");
  process.exit(1);
}

if (!env.GITHUB_USERNAME) {
  console.log("Error: GITHUB_USERNAME environment variable is required");
  process.exit(1);
}

// Spawn the MCP server
const mcp = spawn("node", [mcpServerPath], { env });

// Listen for stdout (MCP responses)
mcp.stdout.on("data", (data) => {
  const message = data.toString().trim();
  try {
    // Try to parse JSON
    const jsonMessage = JSON.parse(message);
    console.log("Received MCP response:", JSON.stringify(jsonMessage, null, 2));

    // If this is the ListTools response, send a CallTool request
    if (jsonMessage.result && jsonMessage.result.tools) {
      console.log("Tools available, sending CallTool request...");
      sendCallToolRequest();
    }
  } catch (e) {
    // Not JSON, just log the message
    console.log("MCP server stdout:", message);
  }
});

// Listen for stderr (console.log messages from the MCP server)
mcp.stderr.on("data", (data) => {
  console.log("MCP server stderr:", data.toString().trim());
});

// When the MCP server exits
mcp.on("close", (code) => {
  console.log(`MCP server exited with code ${code}`);
});

// Send a JSON-RPC message to the MCP server
function sendMessage(message) {
  mcp.stdin.write(JSON.stringify(message) + "\n");
}

// Wait for server to start then send ListTools
setTimeout(() => {
  console.log("Sending ListTools request...");
  sendMessage({
    jsonrpc: "2.0",
    id: "1",
    method: "listTools",
    params: {},
  });
}, 1000);

// Send a CallTool request
function sendCallToolRequest() {
  console.log("Sending CallTool request for github_enhance_resume_with_project...");
  sendMessage({
    jsonrpc: "2.0",
    id: "2",
    method: "callTool",
    params: {
      name: "github_enhance_resume_with_project",
      arguments: {
        directory: process.cwd(),
      },
    },
  });
}

// Exit gracefully
process.on("SIGINT", () => {
  console.log("Exiting...");
  mcp.kill();
  process.exit(0);
});

```

--------------------------------------------------------------------------------
/src/resume-enhancer.ts:
--------------------------------------------------------------------------------

```typescript
import { Resume } from './types.js';
import { OpenAIService } from './openai.js';
import { CodebaseAnalysisResult } from './codebase.js';

export interface EnhancementResult {
  updatedResume: Resume;
  changes: {
    addedSkills: string[];
    updatedProjects: string[];
    updatedWork: string[];
    otherChanges: string[];
  };
  summary: string;
  userMessage?: string;
  resumeLink?: string;
}

export class ResumeEnhancer {
  private openAIService: OpenAIService;
  
  constructor(openAIService: OpenAIService) {
    this.openAIService = openAIService;
  }
  
  /**
   * Enhance a resume with details about the current project
   */
  async enhanceWithCurrentProject(
    resume: Resume,
    codebaseAnalysis: CodebaseAnalysisResult,
    githubUsername: string
  ): Promise<EnhancementResult> {
    try {
      console.log('Starting resume enhancement with codebase details:', JSON.stringify({
        repoName: codebaseAnalysis.repoName,
        languages: Object.keys(codebaseAnalysis.languages),
        technologies: codebaseAnalysis.technologies
      }));
      
      // Get resume updates from OpenAI
      console.log('Calling OpenAI to generate resume enhancement...');
      const update = await this.openAIService.generateResumeEnhancement(codebaseAnalysis);
      console.log('Received resume updates from OpenAI');
      
      // Apply the updates to the resume
      console.log('Enhancing resume with new data...');
      const updatedResume = await this.openAIService.enhanceResume(resume, update);
      console.log('Resume enhanced successfully');
      
      // Generate a summary of the changes
      console.log('Generating update summary...');
      const summary = await this.openAIService.generateUpdateSummary(update.changes);
      console.log('Summary generated');
      
      // Create user message
      const userMessage = this.createUserMessage(githubUsername, update.changes);
      
      return {
        updatedResume,
        changes: {
          addedSkills: update.newSkills.map(s => s.name),
          updatedProjects: [update.newProject.name],
          updatedWork: [],
          otherChanges: update.changes
        },
        summary,
        userMessage,
        resumeLink: `https://registry.jsonresume.org/${githubUsername}`
      };
    } catch (error) {
      console.log('Error enhancing resume with current project:', error);
      throw error;
    }
  }
  
  /**
   * Create a user-friendly message with details about the updates and a link to the resume
   */
  private createUserMessage(username: string, changes: string[]): string {
    return `Your resume has been updated with your latest project contributions! View it at https://registry.jsonresume.org/${username}\n\nChanges made:\n${changes.map(c => `- ${c}`).join('\n')}\n\n⚠️ Note: Please review the changes to ensure they match your preferences. You can revert to a previous version through your GitHub Gist revision history if needed.`;
  }
}

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
// JSON Resume schema types based on https://jsonresume.org/schema/

export interface Resume {
  basics?: Basics;
  work?: Work[];
  volunteer?: Volunteer[];
  education?: Education[];
  awards?: Award[];
  certificates?: Certificate[];
  publications?: Publication[];
  skills?: Skill[];
  languages?: Language[];
  interests?: Interest[];
  references?: Reference[];
  projects?: Project[];
  meta?: Meta;
}

export interface Basics {
  name?: string;
  label?: string;
  image?: string;
  email?: string;
  phone?: string;
  url?: string;
  summary?: string;
  location?: Location;
  profiles?: Profile[];
}

export interface Location {
  address?: string;
  postalCode?: string;
  city?: string;
  countryCode?: string;
  region?: string;
}

export interface Profile {
  network?: string;
  username?: string;
  url?: string;
}

export interface Work {
  name?: string;
  position?: string;
  url?: string;
  startDate?: string;
  endDate?: string;
  summary?: string;
  highlights?: string[];
  location?: string;
}

export interface Volunteer {
  organization?: string;
  position?: string;
  url?: string;
  startDate?: string;
  endDate?: string;
  summary?: string;
  highlights?: string[];
}

export interface Education {
  institution?: string;
  url?: string;
  area?: string;
  studyType?: string;
  startDate?: string;
  endDate?: string;
  score?: string;
  courses?: string[];
}

export interface Award {
  title?: string;
  date?: string;
  awarder?: string;
  summary?: string;
}

export interface Certificate {
  name?: string;
  date?: string;
  issuer?: string;
  url?: string;
}

export interface Publication {
  name?: string;
  publisher?: string;
  releaseDate?: string;
  url?: string;
  summary?: string;
}

export interface Skill {
  name?: string;
  level?: string;
  keywords?: string[];
  category?: string; // Added for skills grouping
}

export interface Language {
  language?: string;
  fluency?: string;
}

export interface Interest {
  name?: string;
  keywords?: string[];
}

export interface Reference {
  name?: string;
  reference?: string;
}

export interface Project {
  name?: string;
  description?: string;
  highlights?: string[];
  keywords?: string[];
  startDate?: string;
  endDate?: string;
  url?: string;
  roles?: string[];
  entity?: string;
  type?: string;
}

export interface Meta {
  canonical?: string;
  version?: string;
  lastModified?: string;
}

// Sample resume template
export const sampleResume: Resume = {
  basics: {
    name: "",
    label: "Software Developer",
    email: "",
    phone: "",
    summary: "Experienced software developer with a passion for creating efficient and scalable applications.",
    location: {
      city: "",
      countryCode: "",
      region: ""
    },
    profiles: [
      {
        network: "GitHub",
        username: "",
        url: ""
      },
      {
        network: "LinkedIn",
        username: "",
        url: ""
      }
    ]
  },
  work: [],
  education: [],
  skills: [],
  projects: [],
  meta: {
    version: "v1.0.0",
    lastModified: new Date().toISOString()
  }
};

```

--------------------------------------------------------------------------------
/tests/test-direct.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

import { GitHubService } from "./src/github.ts";
import { OpenAIService } from "./src/openai.ts";
import { CodebaseAnalyzer } from "./src/codebase.ts";
import { ResumeEnhancer } from "./src/resume-enhancer.ts";

// 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
async function init() {
  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");
    }

    console.log("Initializing services...");
    const githubService = new GitHubService(GITHUB_TOKEN, GITHUB_USERNAME);
    const openaiService = new OpenAIService(OPENAI_API_KEY);
    const codebaseAnalyzer = new CodebaseAnalyzer(process.cwd());
    const resumeEnhancer = new ResumeEnhancer(openaiService);

    console.log("Services initialized successfully");

    return { githubService, openaiService, codebaseAnalyzer, resumeEnhancer };
  } catch (error) {
    console.log("Error initializing services:", error);
    process.exit(1);
  }
}

async function enhanceResumeWithProject() {
  try {
    console.log("Starting resume enhancement with current project...");

    const { githubService, openaiService, codebaseAnalyzer, resumeEnhancer } = await init();

    // Step 1: Fetch the user's resume from GitHub gists
    console.log("Fetching resume from GitHub gists...");
    let resume = await githubService.getResumeFromGists();

    if (!resume) {
      // If no resume exists, create a sample one
      console.log("No resume found, creating a sample resume...");
      const userProfile = await githubService.getUserProfile();
      resume = await githubService.createSampleResume();
      console.log("Sample resume created successfully");
    } else {
      console.log("Existing resume found");
    }

    // Step 2: Analyze the current codebase
    console.log("Analyzing current project...");
    const codebaseAnalysis = await codebaseAnalyzer.analyze();
    console.log(
      "Codebase analysis completed:",
      JSON.stringify({
        repoName: codebaseAnalysis.repoName,
        languages: Object.keys(codebaseAnalysis.languages),
        technologies: codebaseAnalysis.technologies,
      })
    );

    // Step 3: Enhance the resume with the current project
    console.log("Enhancing resume with current project...");
    const { updatedResume, changes, summary, userMessage, resumeLink } =
      await resumeEnhancer.enhanceWithCurrentProject(resume, codebaseAnalysis, GITHUB_USERNAME);

    console.log("Resume enhancement completed successfully");
    console.log("Summary:", summary);

    // Step 4: Update the resume on GitHub
    console.log("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,
    };
  } catch (error) {
    console.log("Error enhancing resume with project:", error);
    throw error;
  }
}

enhanceResumeWithProject()
  .then((result) => {
    console.log("Result:", JSON.stringify(result, null, 2));
    process.exit(0);
  })
  .catch((error) => {
    console.log("Fatal error:", error);
    process.exit(1);
  });

```

--------------------------------------------------------------------------------
/tests/debug-mock.ts:
--------------------------------------------------------------------------------

```typescript
import { ResumeEnhancer } from "../src/resume-enhancer.js";
import { CodebaseAnalysisResult } from "../src/codebase.js";
import { Resume } from "../src/types.js";
import { ResumeUpdate } from "../src/schemas.js";

// Mock OpenAI service
class MockOpenAIService {
  async generateResumeEnhancement(): Promise<ResumeUpdate> {
    return {
      newProject: {
        name: "JSON Resume MCP Server",
        startDate: "2024-12",
        endDate: "Present",
        description: "Developed a Model Context Protocol (MCP) server that enhances AI assistants with the ability to update a JSON Resume by analyzing coding projects.",
        highlights: [
          "Implemented TypeScript/Node.js MCP server integration",
          "Created GitHub API integration for resume storage and retrieval",
          "Developed OpenAI function calling for intelligent resume enhancement",
          "Implemented codebase analysis tools for skills and project detection"
        ],
        url: "https://github.com/jsonresume/mcp-starter"
      },
      newSkills: [
        {
          name: "TypeScript",
          level: "Advanced",
          keywords: ["Node.js", "Static Typing", "ES6+"]
        },
        {
          name: "Model Context Protocol (MCP)",
          level: "Intermediate",
          keywords: ["AI Integration", "Function Calling", "API Design"]
        },
        {
          name: "GitHub API",
          level: "Intermediate",
          keywords: ["OAuth", "REST API", "Gist Management"]
        }
      ],
      changes: [
        "Added JSON Resume MCP Server project",
        "Added TypeScript skill with Node.js, Static Typing, ES6+ keywords",
        "Added Model Context Protocol (MCP) skill with AI Integration, Function Calling, API Design keywords",
        "Added GitHub API skill with OAuth, REST API, Gist Management keywords"
      ]
    };
  }

  async enhanceResume(resume: Resume, update: ResumeUpdate): Promise<Resume> {
    const result = { ...resume };
    
    // Add the new project
    result.projects = [...(result.projects || []), update.newProject];
    
    // Add new skills
    result.skills = [...(result.skills || []), ...update.newSkills];
    
    return result;
  }

  async generateUpdateSummary(changes: string[]): Promise<string> {
    return "Your resume has been enhanced with a new JSON Resume MCP Server project and skills in TypeScript, Model Context Protocol (MCP), and GitHub API.";
  }
}

async function testEnhanceWithMock() {
  try {
    // Create a sample resume
    const resume: Resume = {
      basics: {
        name: "Test User",
        label: "Software Developer",
        email: "[email protected]"
      },
      skills: [],
      projects: []
    };
    
    // Create a sample codebase analysis
    const codebaseAnalysis: CodebaseAnalysisResult = {
      repoName: "mcp-server",
      languages: {
        "TypeScript": 80,
        "Markdown": 15,
        "JSON": 5
      },
      fileCount: 10,
      recentCommits: [],
      technologies: ["Node.js", "TypeScript", "GitHub API", "OpenAI"],
      summary: "A Model Context Protocol server for enhancing resumes"
    };
    
    // Create the resume enhancer with the mock OpenAI service
    const mockOpenAIService = new MockOpenAIService();
    const resumeEnhancer = new ResumeEnhancer(mockOpenAIService as any);
    
    console.log("Enhancing resume with mock data...");
    const result = await resumeEnhancer.enhanceWithCurrentProject(
      resume,
      codebaseAnalysis,
      "testuser"
    );
    
    console.log("Enhancement successful!");
    console.log("Summary:", result.summary);
    console.log("Added skills:", result.changes.addedSkills.join(", "));
    console.log("Updated projects:", result.changes.updatedProjects.join(", "));
    
    return result;
  } catch (error) {
    console.log("Error in mock test:", error);
    throw error;
  }
}

testEnhanceWithMock()
  .then(result => {
    console.log("Test completed successfully");
    process.exit(0);
  })
  .catch(error => {
    console.log("Test failed:", error);
    process.exit(1);
  });

```

--------------------------------------------------------------------------------
/apps/registry/scripts/jobs/recommendations/product-requirements.md:
--------------------------------------------------------------------------------

```markdown
# JSON Resume Recommendation Engine

## Overview

The JSON Resume Recommendation Engine is a feature that analyzes a user's resume and current project to automatically enhance their resume with relevant skills, project details, and work experiences. This document outlines the requirements, architecture, and implementation details for this feature.

## Key Features

1. **Resume Analysis**: Analyzes existing JSON Resume content to understand the user's background
2. **Codebase Analysis**: Examines the user's current project to extract technologies, languages, and contributions
3. **Automatic Enhancement**:
   - Adds new skills based on technologies used in projects
   - Updates project details with current work
   - Makes recommendations for improving work experience descriptions
   - Validates against the JSON Resume schema to ensure compatibility
4. **User Experience**:
   - Provides a detailed summary of all changes made
   - Generates a direct link to view the updated resume
   - Includes warning about potential issues and how to revert changes
   - Maintains full resume.json output following JSON schema

## Architecture

The recommendation engine is built with the following components:

1. **Resume Enhancer**: Core class that orchestrates the enhancement process
2. **GitHub Service**: Handles resume retrieval and update through GitHub Gists
3. **Codebase Analyzer**: Extracts technologies, languages, and commit history from current project
4. **OpenAI Service**: Leverages AI to generate high-quality resume enhancements
5. **Schema Validation**: Ensures all modifications follow the JSON Resume schema

## Implementation Details

### ResumeEnhancer Class
- Extracts project information from codebase analysis
- Maps technologies to skill categories
- Processes changes through OpenAI
- Validates schema compliance
- Creates user-friendly output with detailed change information

### OpenAI Integration
- Generates new project data for the current codebase
- Identifies new skills from the codebase that aren't in the existing resume
- Strictly additive approach - never modifies or removes existing content
- Ensures professional tone and formatting in resume content
- Maintains consistency with existing resume by only adding complementary information

### Data Preservation Principles
- All existing resume data is preserved without modification
- New projects are only added if they don't already exist (checked by name)
- New skills are only added if they don't already exist in the resume
- Original formatting and structure of the resume is maintained
- Special properties (like _gistId) are preserved during enhancement

### Schema Validation
- Validates all modifications against JSON Resume schema
- Prevents invalid data structures or missing required fields
- Ensures backward compatibility with existing tools

### User Communication
- Provides markdown-formatted output with changes grouped by category
- Includes direct link to view updated resume
- Warns about potential issues and explains how to revert changes if needed

## Technical Requirements

1. TypeScript for type safety and improved development experience
2. GitHub API integration for resume storage and retrieval
3. OpenAI API for intelligent content generation
4. Schema validation to ensure compatibility
5. Error handling and logging for troubleshooting

## Implementation Notes

### OpenAIService Enhancements

The OpenAIService has been updated to use a more focused approach for resume enhancement:

1. **Targeted Prompt Design**:
   - Prompt now explicitly requests only a single new project and new skills
   - No mention of updating or modifying existing content
   - Clear instructions on what should and should not be generated

2. **Data Processing Changes**:
   - Added a dedicated `addProjectAndSkills` method that only adds new content
   - Pre-validation of existing projects to avoid duplicates
   - Strict filtering of skills to only add ones that don't already exist
   - Preserves special properties like `_gistId` during processing

3. **Response Structure**:
   - OpenAI now responds with specific `newProject` and `newSkills` properties
   - No full resume returned, only the specific additions
   - Includes a changes array describing what was added for user transparency

This approach ensures that resume enhancement is completely non-destructive and only adds relevant new information based on the user's current project.

## Future Enhancements

1. **Customization Options**: Allow users to control enhancement parameters
2. **Multi-project Analysis**: Analyze multiple projects for comprehensive skills extraction
3. **Historical Analysis**: Consider commit history and contribution patterns
4. **Industry-specific Recommendations**: Tailor enhancements based on industry norms
5. **Resume Optimization Scoring**: Provide metrics on resume completeness and quality

```

--------------------------------------------------------------------------------
/src/github.ts:
--------------------------------------------------------------------------------

```typescript
import { Octokit } from "octokit";
import { Resume, sampleResume } from "./types.js";

export class GitHubService {
  private octokit: Octokit;
  private username: string;
  private cachedGistId: string | null = null;

  constructor(token: string, username: string) {
    if (!token) {
      throw new Error("GitHub token is required");
    }
    if (!username) {
      throw new Error("GitHub username is required");
    }

    this.octokit = new Octokit({ auth: token });
    this.username = username;
  }

  /**
   * Fetch user profile information from GitHub
   */
  async getUserProfile() {
    try {
      const { data } = await this.octokit.rest.users.getByUsername({
        username: this.username,
      });
      return data;
    } catch (error) {
      console.log("Error fetching user profile:", error);
      throw error;
    }
  }

  /**
   * Get resume.json from user's gists
   */
  async getResumeFromGists(): Promise<Resume | null> {
    try {
      // If we've already found the gist ID in this session, use it
      if (this.cachedGistId) {
        console.log(`Using cached gist ID: ${this.cachedGistId}`);
        try {
          const { data: gist } = await this.octokit.rest.gists.get({
            gist_id: this.cachedGistId,
          });
          
          const files = gist.files || {};
          const resumeFile = Object.values(files).find(
            (file) => file?.filename === "resume.json"
          );

          if (resumeFile && resumeFile.raw_url) {
            const response = await fetch(resumeFile.raw_url);
            const resumeData = await response.json();
            console.log("Successfully fetched resume from cached gist ID");
            return { 
              ...resumeData,
              _gistId: this.cachedGistId
            };
          }
        } catch (error) {
          console.log("Error fetching from cached gist ID, will try listing all gists:", error);
          this.cachedGistId = null;
        }
      }

      // List all gists for the user
      console.log(`Listing gists for user: ${this.username}`);
      const { data: gists } = await this.octokit.rest.gists.list({
        username: this.username,
        per_page: 100,
      });
      
      console.log(`Found ${gists.length} gists, searching for resume.json`);

      // Find all gists containing resume.json and sort by updated_at
      const resumeGists = gists
        .filter(gist => {
          const files = gist.files || {};
          return Object.values(files).some(
            (file) => file?.filename === "resume.json"
          );
        })
        .sort((a, b) => {
          // Sort by updated_at in descending order (newest first)
          return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime();
        });

      if (resumeGists.length > 0) {
        // Use the most recently updated resume.json gist
        const mostRecentGist = resumeGists[0];
        console.log(`Found ${resumeGists.length} resume.json gists. Using most recent: ${mostRecentGist.id} (updated: ${mostRecentGist.updated_at})`);
        
        // Cache the gist ID for future use
        this.cachedGistId = mostRecentGist.id;
        
        const files = mostRecentGist.files || {};
        const resumeFile = Object.values(files).find(
          (file) => file?.filename === "resume.json"
        );
        
        if (resumeFile && resumeFile.raw_url) {
          // Fetch the content of resume.json
          const response = await fetch(resumeFile.raw_url);
          const resumeData = await response.json();
          return { 
            ...resumeData,
            _gistId: mostRecentGist.id // Store the gist ID for later updates
          };
        }
      }

      console.log("No resume.json found in any gists");
      return null; // No resume.json found
    } catch (error) {
      console.log("Error fetching resume from gists:", error);
      throw error;
    }
  }

  /**
   * Create a sample resume.json gist if none exists
   */
  async createSampleResume(): Promise<Resume> {
    try {
      // Get user profile to populate some basic fields
      const userProfile = await this.getUserProfile();
      
      // Create a copy of the sample resume and populate with GitHub profile info
      const newResume = JSON.parse(JSON.stringify(sampleResume)) as Resume;
      
      if (newResume.basics) {
        newResume.basics.name = userProfile.name || this.username;
        
        if (newResume.basics.profiles) {
          const githubProfile = newResume.basics.profiles.find(p => p.network === "GitHub");
          if (githubProfile) {
            githubProfile.username = this.username;
            githubProfile.url = `https://github.com/${this.username}`;
          }
        }
        
        newResume.basics.email = userProfile.email || "";
      }
      
      console.log("Creating new gist with resume.json");
      // Create a new gist with resume.json
      const { data: gist } = await this.octokit.rest.gists.create({
        files: {
          "resume.json": {
            content: JSON.stringify(newResume, null, 2),
          },
        },
        description: "My JSON Resume",
        public: true,
      });
      
      // Cache the gist ID for future use
      this.cachedGistId = gist.id;
      console.log(`Created new gist with ID: ${gist.id}`);
      
      return { 
        ...newResume,
        _gistId: gist.id
      };
    } catch (error) {
      console.log("Error creating sample resume:", error);
      throw error;
    }
  }

  /**
   * Update an existing resume.json gist
   */
  async updateResume(resume: Resume): Promise<Resume> {
    try {
      // Check if there's a _gistId in the resume
      let gistId = (resume as any)._gistId;
      
      // If not, check our cached gist ID
      if (!gistId && this.cachedGistId) {
        console.log(`No _gistId in resume object, using cached gistId: ${this.cachedGistId}`);
        gistId = this.cachedGistId;
      }
      
      // If we still don't have a gist ID, try to find it
      if (!gistId) {
        console.log("No gist ID found, attempting to find existing resume");
        const existingResume = await this.getResumeFromGists();
        if (existingResume && (existingResume as any)._gistId) {
          gistId = (existingResume as any)._gistId;
          console.log(`Found existing resume with gist ID: ${gistId}`);
        }
      }
      
      // If we still don't have a gist ID, create a new gist
      if (!gistId) {
        console.log("No existing resume found, creating a new one");
        const newResume = await this.createSampleResume();
        gistId = (newResume as any)._gistId;
        
        // Merge the existing resume data with our new resume
        resume = {
          ...newResume,
          ...resume,
          _gistId: gistId
        };
      }
      
      // Create a clean copy of the resume without the _gistId property
      const { _gistId, ...resumeData } = { ...resume, _gistId: undefined };
      
      // Update the lastModified date
      if (resumeData.meta) {
        resumeData.meta.lastModified = new Date().toISOString();
      } else {
        resumeData.meta = {
          lastModified: new Date().toISOString()
        };
      }
      
      console.log(`Updating gist with ID: ${gistId}`);
      // Update the gist
      const { data: updatedGist } = await this.octokit.rest.gists.update({
        gist_id: gistId as string,
        files: {
          "resume.json": {
            content: JSON.stringify(resumeData, null, 2),
          },
        },
      });
      
      // Cache the gist ID for future use
      this.cachedGistId = gistId;
      
      return { 
        ...resumeData,
        _gistId: gistId
      };
    } catch (error) {
      console.log("Error updating resume gist:", error);
      throw error;
    }
  }

  /**
   * Get user's repositories and their contributions
   */
  async getUserRepositories() {
    try {
      // Get user's repositories
      const { data: repos } = await this.octokit.rest.repos.listForUser({
        username: this.username,
        sort: "updated",
        per_page: 10, // Limit to recent 10 repos
      });
      
      return repos;
    } catch (error) {
      console.log("Error fetching user repositories:", error);
      throw error;
    }
  }

  /**
   * Get user's contributions to a specific repository
   */
  async getRepoContributions(owner: string, repo: string) {
    try {
      // Get user's commits to the repository
      const { data: commits } = await this.octokit.rest.repos.listCommits({
        owner,
        repo,
        author: this.username,
        per_page: 20,
      });
      
      return commits;
    } catch (error) {
      console.log(`Error fetching contributions to ${owner}/${repo}:`, error);
      return []; // Return empty array on error
    }
  }
}

```

--------------------------------------------------------------------------------
/src/codebase.ts:
--------------------------------------------------------------------------------

```typescript
import * as fs from 'fs';
import * as path from 'path';
import { exec, execSync } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

export interface CodebaseAnalysisResult {
  repoName: string;
  repoDescription?: string;
  languages: {[key: string]: number};
  fileCount: number;
  recentCommits: Array<{
    hash: string;
    author: string;
    date: string;
    message: string;
  }>;
  technologies: string[];
  summary: string;
  readmeContent?: string;
}

export class CodebaseAnalyzer {
  private rootDir: string;
  
  constructor(rootDir?: string) {
    // If no root directory is provided, use the current working directory
    this.rootDir = rootDir || process.cwd();
  }
  
  /**
   * Get the repository name from the remote URL
   */
  async getRepoDetails(): Promise<{name: string; owner: string; description?: string}> {
    try {
      // Get the remote URL
      const { stdout: remoteUrl } = await execAsync('git config --get remote.origin.url', { cwd: this.rootDir });
      
      // Parse the remote URL to get the owner and repo name
      const match = remoteUrl.trim().match(/github\.com[\/:]([^\/]+)\/([^\/\.]+)(?:\.git)?$/);
      
      if (!match) {
        return {
          name: path.basename(this.rootDir),
          owner: 'unknown'
        };
      }
      
      const owner = match[1];
      const name = match[2];
      
      // Try to get repository description from package.json if available
      let description: string | undefined;
      try {
        const packageJsonPath = path.join(this.rootDir, 'package.json');
        if (fs.existsSync(packageJsonPath)) {
          const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
          description = packageJson.description;
        }
      } catch (error) {
        console.log('Error reading package.json:', error);
      }
      
      return { name, owner, description };
    } catch (error) {
      console.log('Error getting repo details:', error);
      // If we can't get the repo details from git, use the directory name
      return {
        name: path.basename(this.rootDir),
        owner: 'unknown'
      };
    }
  }
  
  /**
   * Count the number of files by extension
   */
  async countFilesByLanguage(): Promise<{[key: string]: number}> {
    const languages: {[key: string]: number} = {};
    
    try {
      // Use git ls-files to get all tracked files
      const { stdout } = await execAsync('git ls-files', { cwd: this.rootDir });
      const files = stdout.split('\n').filter(Boolean);
      
      for (const file of files) {
        const ext = path.extname(file).toLowerCase();
        if (ext) {
          // Remove the leading dot from the extension
          const language = ext.substring(1);
          languages[language] = (languages[language] || 0) + 1;
        }
      }
    } catch (error) {
      console.log('Error counting files by language:', error);
      // Fallback to a simple directory walk if git command fails
      this.walkDirectory(this.rootDir, languages);
    }
    
    return languages;
  }
  
  /**
   * Walk a directory recursively to count files by extension
   */
  private walkDirectory(dir: string, languages: {[key: string]: number}): void {
    try {
      const files = fs.readdirSync(dir);
      
      for (const file of files) {
        const filePath = path.join(dir, file);
        const stat = fs.statSync(filePath);
        
        if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
          this.walkDirectory(filePath, languages);
        } else if (stat.isFile()) {
          const ext = path.extname(file).toLowerCase();
          if (ext) {
            // Remove the leading dot from the extension
            const language = ext.substring(1);
            languages[language] = (languages[language] || 0) + 1;
          }
        }
      }
    } catch (error) {
      console.log('Error walking directory:', error);
    }
  }
  
  /**
   * Get recent commits in the repository
   */
  async getRecentCommits(count: number = 20): Promise<Array<{hash: string; author: string; date: string; message: string}>> {
    try {
      const { stdout } = await execAsync(
        `git log -n ${count} --pretty=format:"%H|%an|%ad|%s"`,
        { cwd: this.rootDir }
      );
      
      return stdout.split('\n')
        .filter(Boolean)
        .map(line => {
          const [hash, author, date, ...messageParts] = line.split('|');
          return {
            hash,
            author,
            date,
            message: messageParts.join('|') // In case the message itself contains the delimiter
          };
        });
    } catch (error) {
      console.log('Error getting recent commits:', error);
      return [];
    }
  }
  
  /**
   * Detect technologies used in the project
   */
  async detectTechnologies(): Promise<string[]> {
    const technologies: Set<string> = new Set();
    
    try {
      // Check for common configuration files
      const files = await fs.promises.readdir(this.rootDir);
      
      // Framework detection
      if (files.includes('package.json')) {
        technologies.add('Node.js');
        
        // Read package.json to detect more technologies
        const packageJson = JSON.parse(await fs.promises.readFile(path.join(this.rootDir, 'package.json'), 'utf-8'));
        const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
        
        if (deps.react) technologies.add('React');
        if (deps.vue) technologies.add('Vue.js');
        if (deps.angular || deps['@angular/core']) technologies.add('Angular');
        if (deps.express) technologies.add('Express.js');
        if (deps.next) technologies.add('Next.js');
        if (deps.gatsby) technologies.add('Gatsby');
        if (deps.electron) technologies.add('Electron');
        if (deps.typescript) technologies.add('TypeScript');
        if (deps.webpack) technologies.add('Webpack');
        if (deps.jest || deps.mocha || deps.jasmine) technologies.add('Testing');
        if (deps.tailwindcss) technologies.add('Tailwind CSS');
        if (deps.bootstrap) technologies.add('Bootstrap');
        if (deps.eslint) technologies.add('ESLint');
        if (deps.prettier) technologies.add('Prettier');
        if (deps.prisma) technologies.add('Prisma');
        if (deps['@prisma/client']) technologies.add('Prisma');
        if (deps.sequelize) technologies.add('Sequelize');
        if (deps.mongoose) technologies.add('MongoDB');
        if (deps.redis) technologies.add('Redis');
        if (deps.graphql) technologies.add('GraphQL');
        if (deps.apollo) technologies.add('Apollo');
        if (deps['@supabase/supabase-js']) technologies.add('Supabase');
      }
      
      if (files.includes('go.mod')) technologies.add('Go');
      if (files.includes('Cargo.toml')) technologies.add('Rust');
      if (files.includes('requirements.txt') || files.includes('setup.py')) technologies.add('Python');
      if (files.includes('composer.json')) technologies.add('PHP');
      if (files.includes('Gemfile')) technologies.add('Ruby');
      if (files.includes('pom.xml') || files.includes('build.gradle')) technologies.add('Java');
      if (files.includes('Dockerfile')) technologies.add('Docker');
      if (files.includes('.github')) technologies.add('GitHub Actions');
      if (files.includes('.gitlab-ci.yml')) technologies.add('GitLab CI');
      if (files.includes('serverless.yml')) technologies.add('Serverless Framework');
      if (files.includes('terraform')) technologies.add('Terraform');
      
      // Database files
      if (files.includes('prisma')) technologies.add('Prisma');
      if (files.some(f => f.includes('migration'))) technologies.add('Database Migrations');
      
    } catch (error) {
      console.log('Error detecting technologies:', error);
    }
    
    return Array.from(technologies);
  }
  
  /**
   * Count total number of files in the repository
   */
  async countFiles(): Promise<number> {
    try {
      const { stdout } = await execAsync('git ls-files | wc -l', { cwd: this.rootDir });
      return parseInt(stdout.trim(), 10);
    } catch (error) {
      console.log('Error counting files:', error);
      return 0;
    }
  }
  
  /**
   * Read README.md content if it exists
   */
  async getReadmeContent(): Promise<string | undefined> {
    try {
      // Look for README.md (case insensitive)
      const files = await fs.promises.readdir(this.rootDir);
      const readmeFile = files.find(file => 
        file.toLowerCase() === 'readme.md' || file.toLowerCase() === 'readme.markdown'
      );
      
      if (readmeFile) {
        const readmePath = path.join(this.rootDir, readmeFile);
        const content = await fs.promises.readFile(readmePath, 'utf-8');
        return content;
      }
      
      return undefined;
    } catch (error) {
      console.log('Error reading README.md:', error);
      return undefined;
    }
  }
  
  /**
   * Analyze the codebase and collect information
   */
  async analyze(): Promise<CodebaseAnalysisResult> {
    const repoDetails = await this.getRepoDetails();
    
    const [
      languages,
      recentCommits,
      technologies,
      fileCount,
      readmeContent
    ] = await Promise.all([
      this.countFilesByLanguage(),
      this.getRecentCommits(),
      this.detectTechnologies(),
      this.countFiles(),
      this.getReadmeContent()
    ]);
    
    // Generate a summary
    const topLanguages = Object.entries(languages)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 3)
      .map(([lang]) => lang);
      
    const summary = `${repoDetails.name} is a ${topLanguages.join('/')} project with ${fileCount} files using ${technologies.slice(0, 5).join(', ')}.`;
    
    return {
      repoName: repoDetails.name,
      repoDescription: repoDetails.description,
      languages,
      fileCount,
      recentCommits,
      technologies,
      summary,
      readmeContent
    };
  }
}

```

--------------------------------------------------------------------------------
/src/openai.ts:
--------------------------------------------------------------------------------

```typescript
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.log("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.log("Received response from OpenAI API");
      
      const functionCall = response.choices[0]?.message?.function_call;
      if (!functionCall?.arguments) {
        console.log("Error: No function call arguments in OpenAI response");
        throw new Error("No function call arguments received from OpenAI");
      }

      // Parse and validate the response
      console.log("Parsing and validating OpenAI response...");
      try {
        const result = JSON.parse(functionCall.arguments);
        const validated = resumeUpdateSchema.parse(result);
        console.log("Successfully validated schema");
        return validated;
      } catch (parseError) {
        console.log("Error parsing OpenAI response:", 
          parseError instanceof SyntaxError ? "JSON parse error" : 
          parseError instanceof z.ZodError ? "Schema validation error" : 
          "Unknown error"
        );
        console.log("Raw function call arguments:", functionCall.arguments.substring(0, 200) + "...");
        throw parseError;
      }
    } catch (error) {
      if (error instanceof z.ZodError) {
        console.log("Schema validation error:", error.errors);
      } else if (error instanceof SyntaxError) {
        console.log("JSON parsing error:", error.message);
      } else if (error instanceof Error) {
        console.log("OpenAI API error:", error.message);
        console.log("Error details:", error);
      } else {
        console.log("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.log("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.log("Error generating update summary:", error);
      return "Resume updated with new project details and skills.";
    }
  }
}

```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
import { config } from "dotenv";
import { Server } from "../typescript-sdk/src/server/index.js";
import { StdioServerTransport } from "../typescript-sdk/src/server/stdio.js";
import { SSEServerTransport } from "../typescript-sdk/src/server/sse.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
  Tool,
} from "../typescript-sdk/src/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";
import { tools, ANALYZE_CODEBASE_TOOL, CHECK_RESUME_TOOL, ENHANCE_RESUME_WITH_PROJECT_TOOL } from "./src/tools.js";
import { Hono } from "hono";
import { serve } from "@hono/node-server";

// Load environment variables from .env file
config();

const server = new Server(
  {
    name: "jsonresume-mcp",
    version: "1.0.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
      logging: {},
    },
  }
);

// Environment variables (loaded from .env file via dotenv)
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.log("Services initialized successfully");
} catch (error) {
  console.log("Error initializing services:", error);
  process.exit(1);
}



server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools,
}));

function doHello(name: string) {
  return {
    message: `Hello, ${name}!`,
  };
}

async function analyzeCodebase(directory?: string) {
  try {
    console.log("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.log("Codebase analysis completed");
    
    return {
      message: "Codebase analysis completed successfully",
      analysis,
      summary: analysis.summary
    };
  } catch (error) {
    console.log("Error analyzing codebase:", error);
    throw error;
  }
}

async function checkResume() {
  try {
    console.log("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.log("Error checking resume:", error);
    throw error;
  }
}

async function enhanceResumeWithProject(directory?: string) {
  try {
    console.log("Starting resume enhancement with current project...");
    
    // Step 1: Fetch the user's resume from GitHub gists
    console.log("Fetching resume from GitHub gists...");
    let resume = await githubService.getResumeFromGists();
    
    if (!resume) {
      // If no resume exists, create a sample one
      console.log("No resume found, creating a sample resume...");
      const userProfile = await githubService.getUserProfile();
      resume = await githubService.createSampleResume();
      console.log("Sample resume created successfully");
    } else {
      console.log("Existing resume found");
    }
    
    // Step 2: Analyze the current codebase
    console.log("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.log("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.log("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.log("Error enhancing resume with project:", error);
    throw error;
  }
}

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  try {
    console.log(`[MCP] Tool call: ${request.params.name}`, request.params.arguments);
    
    // Validate the tool name
    if (!tools.some(tool => tool.name === request.params.name)) {
      return {
        isError: true,
        content: [{
          type: "text",
          text: `Error: Unknown tool '${request.params.name}'`
        }]
      };
    }
    
    // Execute the appropriate tool
    if (request.params.name === "github_hello_tool") {
      const input = request.params.arguments as { name: string };
      const result = doHello(input.name);
      
      return {
        content: [{
          type: "text",
          text: result.message
        }]
      };
    } else if (request.params.name === ANALYZE_CODEBASE_TOOL.name) {
      const input = request.params.arguments as { directory?: string };
      const result = await analyzeCodebase(input.directory);
      
      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      };
    } else if (request.params.name === CHECK_RESUME_TOOL.name) {
      const result = await checkResume();
      
      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      };
    } else if (request.params.name === ENHANCE_RESUME_WITH_PROJECT_TOOL.name) {
      const input = request.params.arguments as { directory?: string };
      const result = await enhanceResumeWithProject(input.directory);
      
      return {
        content: [{
          type: "text",
          text: JSON.stringify(result, null, 2)
        }]
      };
    }
    
    // This should never happen due to our validation above
    return {
      isError: true,
      content: [{
        type: "text",
        text: `Error: Tool '${request.params.name}' implementation not found`
      }]
    };
  } catch (error) {
    console.error(`[MCP] Error executing tool ${request.params.name}:`, error);
    
    // Return a proper error response
    return {
      isError: true,
      content: [{
        type: "text",
        text: `Error executing tool: ${error.message || String(error)}`
      }]
    };
  }
});

server.onerror = (error: any) => {
  console.log(error);
};

process.on("SIGINT", async () => {
  await server.close();
  process.exit(0);
});

async function runStdioServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.log("JsonResume MCP Server running on stdio");
}

async function runHttpServer() {
  const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
  const app = new Hono();
  
  app.get('/', (c) => {
    return c.json({
      message: 'Hello, I\'m JSON Resume MCP Server',
      description: 'This is a ModelContextProtocol server for enhancing JSON Resumes',
      usage: {
        http: 'npx -y @jsonresume/mcp',
        stdio: 'npx -y @jsonresume/mcp stdio'
      },
      version: '3.0.3'
    });
  });
  
  // Add MCP message endpoint for client->server communication
  app.post('/message', async (c) => {
    const sessionId = c.req.query('sessionId');
    if (!sessionId) {
      return c.json({ error: 'No session ID provided' }, 400);
    }

    try {
      const body = await c.req.json();
      console.log(`[MCP] Received message for session ${sessionId}:`, body);
      
      // Find the transport for this session and handle the message
      const transport = activeTransports.get(sessionId);
      if (!transport) {
        return c.json({ error: 'Invalid session ID' }, 404);
      }
      
      // Special handling for initialize message
      if (body.method === 'initialize') {
        console.log(`[MCP] Handling initialize message for session ${sessionId}`);
        // Send initialize response directly via SSE
        const response = {
          jsonrpc: "2.0",
          id: body.id,
          result: {
            serverInfo: {
              name: "jsonresume-mcp",
              version: "1.0.0",
            },
            capabilities: {
              tools: {},
              resources: {},
              logging: {}
            }
          }
        };
        
        // Let's directly use the controller we stored when setting up the SSE connection
        console.log(`[MCP] Sending initialize response to session ${sessionId}`);
        
        // Store controllers along with transports
        const sseData = `data: ${JSON.stringify(response)}\n\n`;
        
        // Directly write to the response stream
        // This bypasses the transport's send method
        const res = transport['res'];
        if (res && typeof res.write === 'function') {
          try {
            res.write(sseData);
            console.log(`[MCP] Successfully wrote initialize response to stream`);
          } catch (error) {
            console.error(`[MCP] Error writing to stream:`, error);
          }
        } else {
          console.error(`[MCP] Could not access response object for session ${sessionId}`);
        }
        
        return c.json({ status: 'ok' });
      }
      
      await transport.handlePostMessage(c.req.raw, new Response() as any, body);
      return c.json({ status: 'ok' });
    } catch (error) {
      console.error(`[MCP] Error handling message:`, error);
      return c.json({ error: String(error) }, 500);
    }
  });

  // Store active SSE transports
  const activeTransports = new Map<string, SSEServerTransport>();

  // Add SSE endpoint for server->client streaming
  app.get('/sse', async (c) => {
    // Set up SSE headers
    c.header('Content-Type', 'text/event-stream');
    c.header('Cache-Control', 'no-cache');
    c.header('Connection', 'keep-alive');
    
    // Create a wrapper for the Hono response object to mimic Node's ServerResponse
    const honoResponseAdapter = {
      _data: [] as string[],
      headersSent: false,
      writeHead: function(statusCode: number, headers?: Record<string, string>) {
        console.log(`[Adapter] writeHead called with status ${statusCode}`);
        this.headersSent = true;
        return this;
      },
      write: function(chunk: string) {
        console.log(`[Adapter] write called with chunk length ${chunk.length}`);
        this._data.push(chunk);
        return true;
      },
      end: function() {
        console.log(`[Adapter] end called`);
        return this;
      },
      _listeners: {} as Record<string, Function[]>,
      on: function(event: string, listener: Function) {
        console.log(`[Adapter] Adding listener for ${event}`);
        if (!this._listeners[event]) {
          this._listeners[event] = [];
        }
        this._listeners[event].push(listener);
        return this;
      },
    };
    
    // Create transport with message endpoint as the target for client->server messages
    const transport = new SSEServerTransport("/message", honoResponseAdapter as unknown as ServerResponse);
    let sessionId: string;

    // Keep the connection alive
    return new Response(
      new ReadableStream({
        start(controller) {
          console.log("[SSE] Stream started");
          
          // Add adapter method to forward data from the adapter to the stream
          const adapter = transport['res'] as any;
          const originalWrite = adapter.write;
          adapter.write = function(chunk: string) {
            console.log(`[Stream Relay] Received chunk of length ${chunk.length}, forwarding to client`);
            controller.enqueue(chunk);
            return originalWrite.call(this, chunk);
          };

          // Set up transport event handlers
          transport.onmessage = (message) => {
            console.log(`[SSE] Server message:`, message);
            controller.enqueue(`data: ${JSON.stringify(message)}\n\n`);
          };
          
          transport.onerror = (error) => {
            console.error(`[SSE] Transport error:`, error);
            controller.error(error);
          };

          // Connect transport to server and store it
          server.connect(transport).then(() => {
            sessionId = transport.sessionId;
            activeTransports.set(sessionId, transport);
            
            // Send endpoint event with session ID
            const endpointUrl = `/message?sessionId=${sessionId}`;
            controller.enqueue(`event: endpoint\ndata: ${endpointUrl}\n\n`);
            
            // Send initial connected event
            controller.enqueue('event: connected\ndata: {"status":"connected"}\n\n');
          }).catch(error => {
            console.error(`[SSE] Connection error:`, error);
            controller.error(error);
          });
          
          // Set up keep-alive ping
          const pingInterval = setInterval(() => {
            try {
              console.log(`[SSE] Sending ping`);
              controller.enqueue(`event: ping\ndata: ${Date.now()}\n\n`);
            } catch (err) {
              console.error(`[SSE] Ping error:`, err);
              clearInterval(pingInterval);
            }
          }, 15000);
          
          // Handle client disconnect
          c.req.raw.signal.addEventListener('abort', () => {
            console.log(`[SSE] Client disconnected, cleaning up session ${sessionId}`);
            clearInterval(pingInterval);
            if (sessionId) {
              // Just remove the transport from our map
              activeTransports.delete(sessionId);
              // Don't call server.disconnect() as it's not a function on the server instance
              // Instead, we'll just clean up the transport
              try {
                // Notify any listeners that might be attached to the transport
                if (transport.onerror) {
                  transport.onerror(new Error('Client disconnected'));
                }
              } catch (err) {
                console.error(`[SSE] Error during cleanup:`, err);
              }
            }
          });
        }
      }),
      {
        headers: {
          'Content-Type': 'text/event-stream',
          'Cache-Control': 'no-cache',
          'Connection': 'keep-alive'
        }
      }
    );
  });
  
  console.log(`JsonResume MCP Server starting on port ${PORT}...`);
  serve({
    fetch: app.fetch,
    port: Number(PORT)
  });
  
  console.log(`JsonResume MCP Server running at http://localhost:${PORT}`);
  console.log(`SSE endpoint available at http://localhost:${PORT}/sse`);
  console.log(`To use in stdio mode, run: npx -y @jsonresume/mcp stdio`);
}

// Determine which server mode to run based on command line arguments
const args = process.argv.slice(2);
const shouldRunStdio = args.includes('stdio');

if (shouldRunStdio) {
  runStdioServer().catch((error) => {
    console.log("Fatal error running stdio server:", error);
    process.exit(1);
  });
} else {
  runHttpServer().catch((error) => {
    console.log("Fatal error running HTTP server:", error);
    process.exit(1);
  });
}

```