Job Ad Intelligence MCP

A paid MCP server that helps AI agents analyse job advertisements. Five tools: extract structured data from any job ad (text or URL), normalise salary strings into min/max/currency/period, detect seniority level from job titles, score a CV against a job ad, and generate targeted application questions. Priced from $0.002 to $0.05 per call, paid in USDC on Base via x402. No API key required.

Job Ad Intelligence MCP

A paid MCP-compatible service that helps LLM agents analyse job advertisements. Exposes five tools for extracting structured data, normalising salaries, detecting seniority, scoring candidate fit, and generating application questions.

Built with Node.js, TypeScript, the Model Context Protocol SDK, and an x402-ready payment guard.


Quick start

# 1. Clone / copy the project
cd job-ad-intelligence-mcp

# 2. Install dependencies
npm install

# 3. Configure environment
cp .env.example .env
# Edit .env as needed (defaults are fine for local development)

# 4. Run in development mode
npm run dev

# 5. Run tests
npm test

# 6. Build for production
npm run build
npm start

Environment variables

VariableDefaultDescription
PORT3000HTTP port to listen on
HOST0.0.0.0Bind address
PAYMENTS_ENABLEDfalseEnable x402 payment gating (true/false)
FETCH_TIMEOUT_MS10000Max ms to wait when fetching a job ad URL
FETCH_MAX_BYTES1048576Max response size when fetching a URL (bytes)
NODE_ENVdevelopmentNode environment

See .env.example for a full annotated example.


MCP endpoint

The server exposes a Streamable HTTP transport at:

POST   http://localhost:3100/mcp   ← send MCP messages / initialise session
GET    http://localhost:3100/mcp   ← SSE stream for server-initiated messages
DELETE http://localhost:3100/mcp   ← terminate session

Health check (no auth required):

GET http://localhost:3100/health

Tools reference

1. extract_job_ad

Extracts structured data from a job advert. Accepts either raw text or a URL.

Input:

{
  "text": "Senior Software Engineer at Acme Corp...",
  "url": "https://example.com/jobs/123"
}

One of text or url is required. When both are provided, text is preferred.

Output:

{
  "title": "Senior Software Engineer",
  "company": "Acme Corp",
  "location": "London",
  "remote_policy": "hybrid",
  "employment_type": "full_time",
  "salary": {
    "raw": "£70,000 - £90,000",
    "min": 70000,
    "max": 90000,
    "currency": "GBP",
    "period": "year"
  },
  "seniority": "senior",
  "skills": {
    "required": ["typescript", "react", "postgresql"],
    "preferred": ["graphql", "redis"]
  },
  "responsibilities": ["Build and maintain APIs", "..."],
  "requirements": ["5+ years experience", "..."],
  "benefits": ["25 days holiday", "health insurance"],
  "application_instructions": "Apply via our portal...",
  "confidence": 0.85
}

2. normalise_salary

Parses a freeform salary string into a structured object.

Input:

{
  "salary_text": "£45k to £60k per annum",
  "location": "London, UK"
}

Output:

{
  "raw": "£45k to £60k per annum",
  "min": 45000,
  "max": 60000,
  "currency": "GBP",
  "period": "year",
  "notes": []
}

Handles: £45k–£60k, up to £70,000, from £500 per day, $120k, €80,000 pa, competitive, DOE.


3. detect_seniority

Classifies the seniority level from a job title and optional description.

Input:

{
  "title": "Senior Software Engineer",
  "description": "You'll have 6+ years of experience..."
}

Output:

{
  "seniority": "senior",
  "signals": ["Title match: Senior / Sr / experienced keyword detected in title \"Senior Software Engineer\""],
  "confidence": 0.8
}

Levels: entry | junior | mid | senior | lead | head_of | executive | unknown


4. score_candidate_fit

Compares a CV against a job advert. Skills-based only — no protected-characteristic inference.

Input:

{
  "cv_text": "..full CV text...",
  "job_ad_text": "..full job ad text.."
}

Output:

{
  "overall_score": 78,
  "summary": "Overall fit is good (78/100)...",
  "matched_skills": ["typescript", "react", "docker"],
  "missing_skills": ["kubernetes", "terraform"],
  "experience_alignment": "strong",
  "red_flags": [],
  "interview_talking_points": ["Highlight direct experience with: typescript, react..."],
  "disclaimer": "This is an automated skills-based comparison and should not be used as the sole basis for hiring or employment decisions."
}

5. generate_application_questions

Generates 5–10 targeted questions a candidate can ask before applying or during screening, based on signals in the job ad.

Input:

{
  "job_ad_text": "..full job ad text.."
}

Output:

{
  "questions": [
    {
      "question": "Can you share the salary range for this role?",
      "why_it_matters": "The advert uses vague compensation language...",
      "category": "compensation"
    }
  ]
}

Categories: role | company | compensation | flexibility | process | expectations


Using with an MCP client

Claude Desktop (claude_desktop_config.json)

{
  "mcpServers": {
    "job-ad-intelligence": {
      "url": "http://localhost:3100/mcp",
      "transport": "streamable-http"
    }
  }
}

Programmatic (TypeScript with @modelcontextprotocol/sdk)

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(
  new URL('http://localhost:3100/mcp'),
);

const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(transport);

// List available tools
const tools = await client.listTools();
console.log(tools);

// Call normalise_salary
const result = await client.callTool({
  name: 'normalise_salary',
  arguments: { salary_text: '£45k - £60k per year' },
});
console.log(result.content[0].text);

await client.close();

Payment gating (x402)

The server uses the official x402 protocol via @x402/express. Payments are collected in USDC on Base (or any supported EVM/Solana network).

Development mode (default)

PAYMENTS_ENABLED=false — all requests pass through freely. No wallet needed.

Enabling payments (testnet — safe, no real money)

  1. Get a wallet address (any EVM wallet, e.g. MetaMask)
  2. Get testnet USDC from the Coinbase CDP Faucet
  3. Set your .env:
PAYMENTS_ENABLED=true
X402_PAY_TO_ADDRESS=0xYourWalletAddress
X402_PRICE=$0.001
X402_NETWORK=eip155:84532          # Base Sepolia (testnet)
X402_FACILITATOR_URL=https://x402.org/facilitator

Without payment, every /mcp request gets a 402 Payment Required:

curl -X POST http://localhost:3100/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'
# → HTTP 402  { "error": "Payment Required", "accepts": [...] }

Clients pay by signing a USDC transfer and including the X-PAYMENT header — the x402 client SDK (or an x402-aware AI agent) handles this automatically.

Going to mainnet (real payments)

  1. Sign up at portal.cdp.coinbase.com and create API keys
  2. Update .env:
X402_NETWORK=eip155:8453
X402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
CDP_API_KEY_ID=your-key-id
CDP_API_KEY_SECRET=your-key-secret
  1. In src/payments/paymentGuard.ts, swap the facilitator client as shown in the comments (~line 30)

All payment logic is isolated in src/payments/paymentGuard.ts — no other files need to change.


Project structure

src/
  server.ts                    ← MCP server + Express HTTP transport
  types.ts                     ← All TypeScript types and Zod schemas
  payments/
    paymentGuard.ts            ← x402-ready payment middleware (isolated)
  tools/
    extractJobAd.ts            ← Tool 1
    normaliseSalary.ts         ← Tool 2
    detectSeniority.ts         ← Tool 3
    scoreCandidateFit.ts       ← Tool 4
    generateApplicationQuestions.ts ← Tool 5
  utils/
    fetchUrl.ts                ← Secure URL fetcher with SSRF protection
    salaryParser.ts            ← Deterministic salary parsing
    textHelpers.ts             ← Shared text utilities
tests/
  normaliseSalary.test.ts
  detectSeniority.test.ts
  scoreCandidateFit.test.ts
  paymentGuard.test.ts
  generateApplicationQuestions.test.ts

Scripts

npm run dev        # Run with tsx (hot-reload friendly)
npm run build      # Compile TypeScript to dist/
npm start          # Run compiled JS
npm test           # Run all tests with Vitest
npm run test:run   # Run tests once (no watch)
npm run lint       # ESLint

Security notes

  • URL fetching rejects localhost, private IP ranges, and non-http/https protocols (SSRF protection)
  • Response size is capped at FETCH_MAX_BYTES (default 1 MB)
  • Fetch timeout is enforced at FETCH_TIMEOUT_MS (default 10s)
  • No hardcoded secrets — all configuration via environment variables
  • The score_candidate_fit tool compares only skills, experience, tools, and seniority — no protected-characteristic inference

เซิร์ฟเวอร์ที่เกี่ยวข้อง

NotebookLM Web Importer

นำเข้าหน้าเว็บและวิดีโอ YouTube ไปยัง NotebookLM ด้วยคลิกเดียว ผู้ใช้กว่า 200,000 คนไว้วางใจ

ติดตั้งส่วนขยาย Chrome