Motivation
As a daily Cursor IDE user, I've grown to love its AI capabilities and extensibility. I frequently use HeadHunter's API for job search and recruitment automation, so naturally I wanted to bring these two tools together. Building an MCP server seemed like the perfect opportunity to both enhance my workflow and get hands-on experience with the MCP protocol.
Implementation
Let's walk through building a production-ready MCP server for HeadHunter API integration. We'll use TypeScript with Zod validation and follow best practices for type safety and error handling.
Project Structure
First, let's set up our project structure:
mcp-server/
├── src/
│ ├── server.ts # Main MCP server implementation
│ ├── main.ts # Entry point
│ └── types.ts # Type definitions
├── package.json
└── tsconfig.json
Installing Dependencies
Create a new project and install the required dependencies:
npm init -y
npm install @cursor/mcp-server axios dotenv zod
npm install -D typescript @types/node
Update package.json with the necessary configuration:
{
"name": "headhunter-mcp",
"version": "1.0.0",
"main": "dist/main.js",
"bin": {
"headhunter-mcp": "dist/main.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/main.js"
}
}
Environment Configuration
Create server.ts and add environment variable parsing:
import { z } from 'zod';
import dotenv from 'dotenv';
dotenv.config();
const envSchema = z.object({
HH_ACCESS_TOKEN: z.string(),
PORT: z.string().default('3000'),
});
const env = envSchema.parse(process.env);
API Client Setup
Add the HeadHunter API client with proper authentication:
import axios from 'axios';
const client = axios.create({
baseURL: 'https://api.hh.ru',
headers: {
'Authorization': `Bearer ${env.HH_ACCESS_TOKEN}`,
'User-Agent': 'HeadHunter-MCP-Server',
}
});
MCP Server Instance
Create the MCP server instance with error handling:
import { McpServer } from '@cursor/mcp-server';
const server = new McpServer();
server.onError((error) => {
console.error('MCP Server error:', error);
return {
error: error.message
};
});
Adding Tools
Let's implement a tool for searching vacancies:
const searchVacanciesSchema = z.object({
text: z.string(),
area: z.string().optional(),
experience: z.string().optional(),
});
server.tool({
name: 'search_vacancies',
description: 'Search for job vacancies on HeadHunter',
parameters: searchVacanciesSchema,
handler: async (params) => {
const { data } = await client.get('/vacancies', {
params: {
text: params.text,
area: params.area,
experience: params.experience
}
});
return {
items: data.items.map(item => ({
id: item.id,
name: item.name,
employer: item.employer.name,
salary: item.salary,
url: item.alternate_url
}))
};
}
});
Entry Point
Create main.ts to start the server:
#!/usr/bin/env node
import { server } from './server';
const port = parseInt(process.env.PORT || '3000');
server.listen(port, () => {
console.log(`HeadHunter MCP Server listening on port ${port}`);
});
Usage
Global Installation
Install the package globally to use with Cursor IDE:
npm install -g headhunter-mcp
Cursor Configuration
Add the MCP server to your Cursor IDE configuration by editing ~/.cursor/mcp.json:
{
"servers": {
"headhunter": {
"command": "headhunter-mcp",
"env": {
"HH_ACCESS_TOKEN": "your-token-here"
}
}
}
}
Example Prompts
Here are some example prompts you can use in Cursor IDE:
- Search for Senior Developer positions:
Find senior developer jobs in Moscow using HeadHunter API
- Get specific vacancy details:
Show me details for vacancy ID 12345 from HeadHunter
- Search with salary range:
Find Python developer jobs with salary range 150000-200000 RUB
How It Works
Here's a sequence diagram showing how the integration works:
Rendering diagram...
The MCP server acts as a bridge between Cursor IDE and HeadHunter's API, handling authentication, request formatting, and response processing. When you make a request in Cursor, it's translated into an API call, and the results are formatted for easy consumption by the AI.