Job Board MCP Server
This server provides MCP-compatible job search and lookup capabilities.
search_jobs: Search job listings across multiple platforms (e.g., Lever, Greenhouse, RemoteOK) by keyword/query, with optional location filter (defaults to Remote) and configurable result limit (default 10).
get_job_details: Retrieve detailed information for a specific job posting using its unique job ID (e.g., 'lever-abc123') returned from search results.
Multi-source aggregation: Combines listings from multiple ATS platforms into one interface.
Smart caching: Uses SQLite to reduce redundant API calls and improve performance.
MCP protocol support: Works with any MCP-compatible client like Claude Desktop.
Aggregates job listings from Greenhouse's ATS API, enabling search and retrieval of job postings from Greenhouse alongside other sources.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Job Board MCP Serverfind me remote software engineering jobs"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
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:#fffTech 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 startDocker
# Build the image
docker build -t job-board-mcp .
# Run the container
docker run -it job-board-mcp
# Or use docker-compose
docker compose upRunning Tests
# Run all tests
npm test
# Run with coverage report
npm test -- --coverageProject 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:
Run Tests β Installs dependencies, builds TypeScript, runs the full Jest suite with coverage reporting
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-sqlite3requires Python and build tools to compile. Solved with a multi-stage build that keeps the final image slim.Node version mismatch β
better-sqlite3needed 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-modulesflag and a customtsconfig.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 toolsget_job_detailsA
Get detailed information about a specific job posting using its ID (returned from search_jobs).
| Name | Required | Description | Default |
|---|---|---|---|
| jobId | Yes | The unique job ID from search results (e.g., 'lever-abc123') |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of results to return (default: 10) | |
| query | Yes | Job title or keyword (e.g., 'Cloud Engineer', 'DevOps', 'Software Engineer') | |
| location | No | Location filter (e.g., 'Singapore', 'Malaysia', 'Remote'). Defaults to 'Remote'. |
TDQS
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.
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.
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.
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.
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.
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.
2 tool updates
v1.0.0- First observed
get_job_details - First observed
search_jobs
TDQS
Scored across 2 tools
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.
Both tools follow the same verb_noun snake_case pattern: search_jobs and get_job_details. The naming is predictable and consistent.
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.
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
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
Public MCP server for discovering open jobs. Search, filter, and get application links.
GetJobzi MCP server for job search, application tracking, and career forecasting.
7 recruiting tools over one MCP endpoint: ATS boards, LinkedIn jobs, profiles, companies, Naukri.
- Cavuno MCPOAuthcom.cavuno
Connect Claude, Cursor, Codex, and other MCP clients to manage your Cavuno job board.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAggregates job listings from multiple ATS and job board APIs via MCP, enabling unified search and retrieval in a structured format without HTML parsing.1MIT
- FlicenseNot gradedqualityCmaintenanceEnables 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.-
- FlicenseAqualityCmaintenanceEnables searching job listings, tracking applications, managing resumes, and tailoring resumes to job posts, all locally via MCP.20-
- FlicenseNot gradedqualityCmaintenanceEnables searching and fetching HiringCafe job listings through MCP, using public HTML pages without authentication or browser automation.57-