Skip to main content
Glama
Danialesss

Job Board MCP Server

by Danialesss

Job Board MCP Server

🌐 Live Demo: https://job-board-mcp.onrender.com

A Model Context Protocol (MCP) server that aggregates job listings from multiple platforms into a single searchable interface. Built with TypeScript, containerized with Docker, and deployed with a full CI/CD pipeline.

Tests Docker Build Node TypeScript License

Why I Built This

I wanted a way to search job listings across multiple platforms without hopping between different sites. Most job aggregators either require paid API access or scrape sites in ways that break constantly. So I built one that pulls from public ATS (Applicant Tracking System) APIs, which is both legitimate and reliable.

This also gave me an excuse to build something end-to-end: TypeScript, testing, Docker, CI/CD, the whole stack.

Features

  • Multi-source aggregation — Pulls from Lever, Greenhouse, and RemoteOK simultaneously

  • Smart caching — SQLite layer reduces redundant API calls by 70%+

  • Round-robin merging — Results are interleaved so no single source dominates

  • Parallel fetching — All API calls happen concurrently for faster responses

  • MCP protocol — Works with any MCP-compatible client (Claude Desktop, etc.)

  • Containerized — Multi-stage Docker build produces a 197MB production image

  • Automated CI/CD — Every push runs tests and builds Docker images

Architecture

graph TD
    A[MCP Client<br/>Claude Desktop, etc.] -->|MCP Protocol stdio| B[MCP Server<br/>Node.js + TypeScript]
    B --> C[Tool Handlers]
    B --> D[Cache Layer]
    C --> E[Lever API]
    C --> F[Greenhouse API]
    C --> G[RemoteOK API]
    C --> D
    D --> H[(SQLite Database)]
    
    style A fill:#4a90e2,stroke:#2c3e50,color:#fff
    style B fill:#27ae60,stroke:#1e8449,color:#fff
    style H fill:#e67e22,stroke:#a0522d,color:#fff

Tech Stack

Layer

Tech

Runtime

Node.js 22

Language

TypeScript 5

Framework

@modelcontextprotocol/sdk

HTTP Client

Axios

Database

SQLite (better-sqlite3)

Testing

Jest

Container

Docker (multi-stage build)

CI/CD

GitHub Actions

Available Tools

search_jobs

Search for job listings across all connected platforms.

Parameters:

  • query (required) — Job title or keyword (e.g., "Cloud Engineer")

  • location (optional) — Location filter (default: "Remote")

  • limit (optional) — Max results (default: 10)

get_job_details

Get detailed information for a specific job posting.

Parameters:

  • jobId (required) — The unique job ID from search results

Quick Start

Prerequisites

  • Node.js 22 or higher

  • npm

Local Development

# Clone the repo
git clone https://github.com/Danialesss/job-board-mcp.git
cd job-board-mcp

# Install dependencies
npm install

# Build TypeScript
npm run build

# Run the server
npm start

Docker

# Build the image
docker build -t job-board-mcp .

# Run the container
docker run -it job-board-mcp

# Or use docker-compose
docker compose up

Running Tests

# Run all tests
npm test

# Run with coverage report
npm test -- --coverage

Project Structure

job-board-mcp/ ā”œā”€ā”€ src/ │ ā”œā”€ā”€ index.ts # MCP server entry point │ ā”œā”€ā”€ tools/ # MCP tool handlers │ │ ā”œā”€ā”€ searchJobs.ts │ │ └── getJobDetails.ts │ ā”œā”€ā”€ datasources/ │ │ ā”œā”€ā”€ cache.ts # SQLite caching layer │ │ └── apis/ # External API integrations │ │ ā”œā”€ā”€ leverApi.ts │ │ ā”œā”€ā”€ greenhouseApi.ts │ │ └── remoteOkApi.ts ā”œā”€ā”€ tests/ # Jest test suites ā”œā”€ā”€ .github/workflows/ # GitHub Actions CI/CD ā”œā”€ā”€ Dockerfile # Multi-stage container build └── docker-compose.yml

CI/CD Pipeline

Every push to main triggers two automated workflows:

  1. Run Tests — Installs dependencies, builds TypeScript, runs the full Jest suite with coverage reporting

  2. Build Docker Image — Builds the multi-stage Docker image with layer caching to verify the container ships correctly

Testing

The project has 12 test cases covering the cache layer, tool handlers, and error paths. Coverage sits at around 85% for business logic (excluding server bootstrapping).

Challenges I Ran Into

  • Native modules in Docker — better-sqlite3 requires Python and build tools to compile. Solved with a multi-stage build that keeps the final image slim.

  • Node version mismatch — better-sqlite3 needed Node 22+, which conflicted with GitHub Actions' default matrix. Simplified to Node 22 only.

  • ESM + Jest — TypeScript ESM support in Jest is famously painful. Solved with --experimental-vm-modules flag and a custom tsconfig.test.json.

  • Source dominance — Lever was returning first and filling the result limit before Greenhouse/RemoteOK. Fixed by interleaving results round-robin.

  • CI cache directory — SQLite failed on GitHub Actions because the data/ directory didn't exist. Added a check to create it if missing.

Future Improvements

  • Add more ATS sources (Ashby, Workable, SmartRecruiters)

  • Add job filtering by salary range

  • Add pagination for large result sets

  • Deploy to Render or Fly.io for public access

  • Add rate limiting per source

  • Implement scheduled cache refresh

License

MIT

Author

Tun Danial Adli Bin Tun Ali
GitHub: @Danialesss