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.

Related MCP server: jobstack-mcp

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

Available Tools

2 tools
get_job_detailsA

Get detailed information about a specific job posting using its ID (returned from search_jobs).

ParametersJSON Schema
NameRequiredDescriptionDefault
jobIdYesThe unique job ID from search results (e.g., 'lever-abc123')

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It clearly implies a read-only lookup with no side effects, but it does not disclose behavior such as error handling, permission requirements, or what 'detailed information' includes. This is adequate for a simple getter but not rich.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence that states the operation, the object, and the prerequisite source of the ID with no extraneous detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter lookup tool, this is largely complete: the workflow dependency on search_jobs is specified and the parameter is fully described by the schema. It does not describe error behavior or the exact response shape, but those are not critical for basic invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter is already documented in the schema. The description reinforces that the ID comes from search_jobs but does not add new meaning beyond the schema's own description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the action ('Get detailed information'), the specific subject ('a specific job posting'), and the key qualifier ('using its ID'), which distinguishes it from the sibling tool search_jobs by focusing on a single known job rather than searching.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states the ID is '(returned from search_jobs)', which establishes the prerequisite workflow: run a search first, then fetch details for a selected job. It does not explicitly exclude other paths, but provides clear contextual usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_jobsB

Search for job listings across multiple platforms (Lever, etc.). Returns a list of jobs matching the query and location.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum number of results to return (default: 10)
queryYesJob title or keyword (e.g., 'Cloud Engineer', 'DevOps', 'Software Engineer')
locationNoLocation filter (e.g., 'Singapore', 'Malaysia', 'Remote'). Defaults to 'Remote'.

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It says it 'Returns a list of jobs' but doesn't disclose pagination behavior, result ordering, whether the 'limit' is a hard cap, or what happens when no jobs match. It also doesn't mention any rate limits or platform-specific quirks. For a search tool with no annotations, this is a notable gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that is concise and front-loaded with the main action. It includes a useful example platform and states the return type. It could be slightly more structured (e.g., separating the return behavior), but it's efficient and not bloated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with 3 parameters, 100% schema coverage, and no output schema, the description is adequate but not complete. It doesn't explain the relationship with get_job_details (e.g., 'use get_job_details to fetch full details for a returned job ID'), nor does it describe result structure or edge cases. The absence of an output schema means the description should clarify what fields are returned, but it doesn't.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all three parameters. The description adds minimal value beyond the schema: it mentions 'query and location' but doesn't clarify the default behavior of location ('Defaults to Remote' is in the schema, not the description). The description doesn't add meaning beyond what the schema provides, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Search') and resource ('job listings across multiple platforms'), and names an example platform (Lever). It clearly distinguishes from get_job_details, which is about retrieving details for a specific job. However, it doesn't explicitly contrast with the sibling, so it's clear but not fully differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context: use this to find jobs matching a query and location. It doesn't explicitly state when to use get_job_details instead, nor does it mention any exclusions or prerequisites. The sibling name suggests a complementary flow (search then get details), but the description doesn't spell that out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv1.0.0
    • First observedget_job_details
    • First observedsearch_jobs

TDQS

A3.6/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one searches for job listings and the other retrieves details for a specific job by ID. There is no overlap or ambiguity between them.

Naming Consistency5/5

Both tools follow the same verb_noun snake_case pattern: search_jobs and get_job_details. The naming is predictable and consistent.

Tool Count3/5

Two tools feels thin for a job board server. While they cover the basic search-and-detail flow, a typical job board server would likely benefit from additional tools such as listing recent jobs, filtering by platform, or paginating results.

Completeness3/5

The server covers the core read-only workflow of searching for jobs and viewing details, but it lacks other common operations like browsing all recent jobs without a query, pagination controls, or platform-specific listing. The surface is functional but minimal.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Aggregates job listings from multiple ATS and job board APIs via MCP, enabling unified search and retrieval in a structured format without HTML parsing.
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables searching and retrieving job postings from multiple job boards (USAJOBS, Adzuna, Jooble, The Muse, Reed, and ATS) through the JobStack API, exposing search_jobs and get_job as MCP tools.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Enables searching job listings, tracking applications, managing resumes, and tailoring resumes to job posts, all locally via MCP.
    20
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables searching and fetching HiringCafe job listings through MCP, using public HTML pages without authentication or browser automation.
    57
    -