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.

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

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

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 -dThat'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-remotebridges Claude Desktop's local stdio transport to this server's HTTP endpoint, since Desktop doesn't speak Streamable HTTP natively yet.--transport http-onlyis 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 plainAuthorizationheader.
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 a new job application (optionally with initial milestone dates). |
| Fetch one job application by id. |
| Partially update a job application by id (only supplied fields change). |
| Permanently delete a job application by id. |
| List/filter ( |
| Add a milestone date (OA deadline, interview, follow-up, ...) to an application. |
| Remove a milestone date. |
| Mark a milestone date done (or not). |
| Not-yet-completed dates due within a lookahead window (default 14 days), across all your applications. |
| 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 |
| UUID | server-generated |
| string | required |
| string | required |
| enum |
|
| string (optional) | free-text detail within a status, e.g. |
|
| required |
|
| milestone dates; managed via the dedicated tools above, not bulk-edited through |
| string (optional) | |
| 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 SDKTypeScript + 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/argon2for password hashing, SHA-256-hashed API keys,express-rate-limiton the auth endpointspino — 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/mcpOther 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 serverVerifying it works
1. Automated tests (no DB needed):
npm test2. Raw HTTP smoke test (needs a running server — Docker or npm run dev):
npm run verify-httpRegisters 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/inspectorChoose 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 stepDeploying publicly
This repo containerizes and documents deployment; it doesn't provision or manage any hosting for you.
Get a Postgres database you control — Neon's free tier gives you a ready
postgresql://...?sslmode=requirestring in under a minute.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.)
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 setDATABASE_URL/NODE_ENVas secrets and point their generated public URL at container port3000.
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
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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