Skip to main content
Glama
Kanavpreet-Singh

JobTrack MCP Server

JobTrack MCP — Job Application Tracker MCP Server

An MCP (Model Context Protocol) server for managing your job search — companies, roles, statuses, stages, dates, notes, and milestone reminders (OAs, interviews, deadlines, follow-ups) — exposed as tools any MCP-compatible client can call. Ask your assistant to log an application, check what's coming up, or mark something done, in plain English.

Multi-user, Postgres-backed, API-key authenticated, and fully containerized — clone it, run one command, and connect it to Claude Desktop.

See it in action

Creating an application

Claude asks clarifying questions, then calls the create_job_application tool.

Creating a job application entry via natural language

Natural-language dates

"next week on Monday" and "I applied yesterday" get resolved and checked against existing records before creating a new one.

Adding an OA entry with relative dates, after checking existing applications

Upcoming reminders

list_upcoming_events pulls every not-yet-completed milestone across all applications, soonest first.

Listing all upcoming interviews and OAs

Quickstart (Docker, one command)

Requires only Docker — no Node.js needed on your machine.

git clone https://github.com/Kanavpreet-Singh/JobTrack-MCP-Server.git
cd JobTrack-MCP-Server
docker compose up -d

That's it — this builds the image, starts a bundled Postgres, applies the database schema automatically on first boot, and starts the server at http://localhost:3000/mcp. Postgres is published on host port 5433 (not 5432) so it won't clash with any Postgres you might already have running locally; the app talks to it over Docker's internal network on the standard port regardless.

Confirm it's up:

curl http://localhost:3000/healthz
# => {"status":"ok"}

Tear it down with docker compose down (add -v to also wipe the database volume).

Connect it to Claude Desktop

1. Register an account (the server has no default user — anyone connecting needs their own API key):

curl -s -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"a strong passphrase"}'

Copy the apiKey from the response — it's shown once and can't be recovered later, only rotated (see Authentication).

2. Edit your Claude Desktop config%APPDATA%\Claude\claude_desktop_config.json on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS. Add a jobtrack entry to mcpServers (keep any other servers already there):

{
  "mcpServers": {
    "jobtrack": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "http://localhost:3000/mcp",
        "--transport", "http-only",
        "--header", "Authorization:${AUTH_HEADER}"
      ],
      "env": { "AUTH_HEADER": "Bearer YOUR_API_KEY" }
    }
  }
}

Two details that matter here:

  • mcp-remote bridges Claude Desktop's local stdio transport to this server's HTTP endpoint, since Desktop doesn't speak Streamable HTTP natively yet.

  • --transport http-only is required. mcp-remote's default strategy probes for OAuth support and, on certain failures during that probe, falls back to an SSE transport this server doesn't implement (it's intentionally stateless and POST-only). This flag skips that fallback and connects directly with the plain Authorization header.

3. Fully quit and reopen Claude Desktop (not just close the window — it only reads this file on startup). Check Settings → Connectors for jobtrack showing as connected with 10 tools, then just talk to it: "log an application for a Backend Engineer role at Acme Corp, I applied today".

Tools

Tool

Description

create_job_application

Create a new job application (optionally with initial milestone dates).

get_job_application

Fetch one job application by id.

update_job_application

Partially update a job application by id (only supplied fields change).

delete_job_application

Permanently delete a job application by id.

list_job_applications

List/filter (status, company, role) or free-text search (query) job applications, with limit/offset pagination.

add_important_date

Add a milestone date (OA deadline, interview, follow-up, ...) to an application.

remove_important_date

Remove a milestone date.

mark_important_date_completed

Mark a milestone date done (or not).

list_upcoming_events

Not-yet-completed dates due within a lookahead window (default 14 days), across all your applications.

list_overdue_actions

Not-yet-completed dates that have already passed, across all your applications.

Every tool returns both a human-readable content block and a typed structuredContent object. Not-found errors surface as tool-level errors (isError: true) rather than protocol errors, so an LLM client can see and react to them.

Data model

Each job application is a single record, scoped to the authenticated user:

Field

Type

Notes

id

UUID

server-generated

company

string

required

role

string

required

status

enum

applied, OA, interview, offer, rejected, withdrawn — defaults to applied

stage

string (optional)

free-text detail within a status, e.g. "Onsite Round 2"

applicationDate

YYYY-MM-DD

required

importantDates

{ id, label, date, completed }[]

milestone dates; managed via the dedicated tools above, not bulk-edited through update_job_application

notes

string (optional)

createdAt / updatedAt

ISO 8601 timestamp

server-set

Authentication

Every /mcp request must carry Authorization: Bearer <api key>. There is no anonymous access — all data is scoped per user.

  • POST /auth/register{ email, password } → creates an account and returns a freshly generated API key.

  • POST /auth/login{ email, password } → verifies the password and issues a new API key, invalidating the previous one. This is the recovery path if a key is lost.

Both endpoints are rate-limited (10 requests / 15 min / IP) against brute-forcing.

Stack

  • @modelcontextprotocol/sdk ^1.29.0 — official TypeScript MCP SDK

  • TypeScript + Node.js (ESM, >=18)

  • Zod v4 — input validation and self-documenting tool schemas

  • Express 5 — HTTP transport (via the SDK's createMcpExpressApp, which adds DNS-rebinding protection out of the box)

  • Streamable HTTP, stateless mode — a fresh MCP server + repository per request, scoped to the caller's user id

  • PostgreSQL + Drizzle ORM — persistent, per-user-isolated storage; migrations applied automatically on container startup

  • API-key Bearer auth@node-rs/argon2 for password hashing, SHA-256-hashed API keys, express-rate-limit on the auth endpoints

  • pino — structured logging

  • vitest — unit + integration tests

  • Docker — multi-stage build, non-root runtime user, health-checked

Running without Docker

Useful if you're developing on the server itself. Requires Node.js >=18 and a Postgres database — a free Neon project works well if you don't want to run one locally.

npm install
cp .env.example .env   # set DATABASE_URL
npm run db:migrate     # applies drizzle/*.sql to DATABASE_URL
npm run dev            # tsx watch, serves http://localhost:3000/mcp

Other scripts:

npm run build && npm start   # compiled production build (dist/server.js)
npm test                     # unit + integration tests (vitest) — no DB needed, uses an in-memory repo
npm run coverage              # tests with coverage report
npm run db:generate           # generate a new migration after editing src/db/schema.ts
npm run db:migrate            # apply pending migrations to DATABASE_URL
npm run verify-http           # smoke test against a running, DB-backed server

Verifying it works

1. Automated tests (no DB needed):

npm test

2. Raw HTTP smoke test (needs a running server — Docker or npm run dev):

npm run verify-http

Registers a throwaway account, then exercises /healthz, the 401-without-auth case, tools/list, and the full create → get/update/list → important-date lifecycle → delete flow over real HTTP.

3. MCP Inspector (interactive):

npx @modelcontextprotocol/inspector

Choose transport "Streamable HTTP", connect to http://localhost:3000/mcp, and add header Authorization: Bearer <api key> (register one via curl first — see Authentication).

Connecting other MCP clients

Any client with native Streamable HTTP support can point directly at http://localhost:3000/mcp with header Authorization: Bearer <your api key> — no mcp-remote bridge needed.

For a client that manages per-user credentials for multiple backend services (GitHub PATs, other bearer-token integrations, etc.), register one JobTrack account per end user and store their apiKey the same way — the server has no notion of your client's own auth scheme, only that a valid JobTrack API key is presented.

Project structure

src/
  server.ts                     Express app: /healthz, /auth/*, /mcp (authMiddleware + stateless Streamable HTTP), entrypoint
  mcpServer.ts                  Builds a configured McpServer (transport-agnostic)
  config.ts                     Env loading/validation (PORT, NODE_ENV, LOG_LEVEL, DATABASE_URL)
  logger.ts                     pino logger
  errors.ts                     NotFoundError + tool-error mapping
  schemas/jobApplication.ts     Zod schemas (JobApplication, ImportantDate, create/update inputs)
  db/schema.ts                  Drizzle table definitions (users, job_applications, important_dates)
  db/client.ts                  pg Pool + Drizzle instance (fails fast if DATABASE_URL is unset)
  db/migrate.ts                 Programmatic migration runner (used by the container's entrypoint)
  auth/passwords.ts             argon2 hash/verify
  auth/apiKeys.ts               API key generation + SHA-256 hashing
  auth/authMiddleware.ts        Bearer token -> req.userId, or 401
  auth/authRoutes.ts            POST /auth/register, POST /auth/login
  storage/types.ts              JobApplicationRepository interface — the seam between tools and storage
  storage/inMemoryRepository.ts In-memory implementation (used by tests)
  storage/postgresRepository.ts Postgres implementation, constructed per-request and scoped to one user
  tools/                        One file per tool + registerAllTools()
drizzle/                        Generated SQL migrations (npm run db:generate)
drizzle.config.ts               drizzle-kit config
scripts/verify-http.ts          Raw HTTP smoke test (registers a user, exercises every tool)
tests/unit/                     Schema, repository, and auth-logic unit tests
tests/integration/              Full tool lifecycle over an in-process MCP client
Dockerfile                      Multi-stage build: compile -> minimal non-root runtime, migrate-then-start entrypoint
docker-compose.yml              App + local Postgres — one command, no separate migration step

Deploying publicly

This repo containerizes and documents deployment; it doesn't provision or manage any hosting for you.

  1. Get a Postgres database you control — Neon's free tier gives you a ready postgresql://...?sslmode=require string in under a minute.

  2. Build and run the image, pointing at it:

    docker build -t jobtrack-mcp .
    docker run -d -p 3000:3000 -e DATABASE_URL="<your connection string>" -e NODE_ENV=production jobtrack-mcp

    (Migrations run automatically on container start — no separate step.)

  3. Put it on the public internet — either a host that runs Docker behind a reverse proxy that terminates TLS (e.g. Caddy: your-domain.com { reverse_proxy localhost:3000 }, automatic HTTPS), or a container PaaS (Fly.io, Railway, Render) where you set DATABASE_URL/NODE_ENV as secrets and point their generated public URL at container port 3000.

Known dev-dependency advisories

npm audit flags moderate/high issues in transitive dev dependencies (esbuild's dev-server request handling via vite/vitest, and a Windows path-traversal issue in @hono/node-server, an optional dependency of the MCP SDK this project does not use). Neither is exploitable in this project's actual usage — noted here for visibility rather than treated as blocking.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kanavpreet-Singh/JobTrack-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server