apples-tracker
Click on "Deploy 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., "@apples-trackershow my postings in interview state"
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.
Apples Tracker
A small, single-user app for tracking job applications and the companies behind them, entirely vibecoded. Runs locally, no auth, no cloud. Data lives in one SQLite file. Humans use the web UI; AI assistants use an HTTP API (OpenAPI spec) or an MCP server over the same data.
Goals
One place to see every posting I applied to, its state, and the details I need.
Jump between a company and its postings (and back) in one click.
Fast to use: quick add, inline search, minimal fields required.
AI-friendly: an assistant can read and update the data without scraping the UI.
Zero operational burden:
npm install && npm run dev, one DB file to back up.
Related MCP server: JobTrack MCP Server
Features
Postings tab — table: company, title, date applied, state, description (short), URLs. Filter by state; a row click opens a details panel with Edit and Delete buttons. Group by selector: None, Company (groups A→Z) or State (groups ordered by stage, most advanced first). Groups have collapsible headers with a row count; within a group rows are sorted by date applied, newest first.
Companies tab — table: name, website, location, description, number of postings, URLs.
Cross-references — company cell in postings links to the company; company row shows/links its postings (filtered postings view). Postings link back to the company.
Forms — add / update / delete for both entities (modal or side panel). Creating a posting lets me pick an existing company or create one inline.
Search — one search box per tab: postings match job title or company name; companies match name. Descriptions and AI context are deliberately not searched.
Lists — user-defined named groups for companies and (separately) for postings, e.g. "Contractors" or "Never consider". An item can be in several lists. A List dropdown on the Postings/Companies toolbars filters to one list, Manage lists creates, renames and deletes them, and the item forms have a list picker. Rows in a list show a ☰ icon whose tooltip names every list, one per line. Deleting a list never deletes items, and deleting an item just removes it from its lists.
AI Context tab — one editable text area holding shared memory for the whole job search (target roles, CV highlights, preferences, current strategy, open questions). Assistants read it at the start of a session and update it as things change. Every overwrite is kept in a version history (last 50) that you can view and restore from the tab.
MCP server (primary AI interface) — tools for search/get/create/update/delete of postings and companies, plus read/update of the shared context. Ships usage instructions to the model (see "AI usage guide").
REST API + spec (secondary) — JSON API with an OpenAPI 3 document at
/openapi.json(Swagger UI at/docs), for scripts and non-MCP assistants.Storage — SQLite, single file (
data/apples.db), created and migrated on startup.
Stack
Node.js 22+, TypeScript, npm workspaces
Server: Fastify +
better-sqlite3, JSON-schema validation,@fastify/swaggerWeb: React + Vite + TypeScript, TanStack Table (and TanStack Query for fetching)
MCP:
@modelcontextprotocol/sdk, stdio transport, thin wrapper over the REST APITests: Vitest (server API and MCP tests against in-memory SQLite, web unit tests) and Playwright end-to-end tests (
npm run test:e2e)
Data model
companies: id, name (unique, case-insensitive), website, location, description, ai_context,
urls (JSON array of strings), created_at, updated_at
postings: id, company_id (FK → companies), title, state, applied_date
(ISO date, nullable), description (free text), ai_context (free text), urls (JSON array), created_at, updated_at
description is the human-readable summary shown in the tables. ai_context is extra
free-text meant for AI models (e.g. my CV-fit notes, interview prep, tone or constraints
for drafting messages). It is returned by the API and MCP tools, editable in the forms,
and not shown in the tables.
company_lists / posting_lists: id, name (unique per kind, case-insensitive),
created_at; membership lives in the join tables company_list_members and
posting_list_members (both sides ON DELETE CASCADE).
context (singleton, one row): content (free text, Markdown), updated_at. This is
the shared memory for the current job search, separate from the per-record ai_context.
state is one of: saved, applied, screening, interview, offer, rejected,
withdrawn, ghosted.
Stage order (used for grouping/sorting by state, top to bottom): offer →
interview → screening → applied → saved → then the closed states rejected →
withdrawn → ghosted. Active processes always sit above closed ones. The order is
defined once in schemas.ts and shared by the API and UI.
Deleting a company that still has postings is refused (409) unless ?cascade=true
is passed; the UI asks for confirmation first.
API summary
Method | Path | Notes |
GET/POST |
| list supports |
GET/PATCH/DELETE |
| GET includes the embedded company |
GET/POST |
| list supports |
GET/PATCH/DELETE |
| GET includes its postings; DELETE accepts |
GET/POST |
| named lists with |
GET/PUT |
| shared job-search memory; PUT replaces |
GET |
| earlier versions of the note (newest 50 kept); restore by PUTting a version's content |
GET |
| the AI usage guide as Markdown |
Errors: { "error": { "code", "message" } } with 400/404/409/422 status codes.
The server binds to 127.0.0.1 only.
MCP tools
search_postings, get_posting, create_posting, update_posting, delete_posting,
search_companies, get_company, create_company, update_company, delete_company,
get_lists, create_list, rename_list, delete_list (kind company or posting),
get_context, update_context. Postings and companies carry lists; create/update tools take
listIds (replaces the set) and the search tools take listId. Tool schemas mirror the API. create_posting and
update_posting accept companyName and create the company if missing, so an assistant
needs only one call.
Built-in safeguards: delete tools refuse unless called with confirm: true (the guide
tells the model to ask you first); update_context refuses until get_context has been
called and rejects the write if the note changed since (no blind overwrites), and a rewrite
that drops a long note below half its length is refused unless the model sets confirmShrink
(it is told to ask you first); search
results are compact and omit aiContext (use get_posting / get_company for it).
AI usage guide
A single Markdown file, server/src/ai-guide.md, is the source of truth for instructing
models. It is delivered three ways: as the MCP server instructions (sent on connect),
as the MCP resource apples://guide, and at GET /api/guide for REST clients. It covers:
Start every session by calling
get_context; end it by updating context if something durable changed (do not log chatter).Field semantics:
description(human summary) vs recordai_context(model notes) vs the global context.Search before creating, to avoid duplicate companies; use exact
statevalues.Prefer
update_*over delete; deletes need explicit user confirmation.Update
ai_contextby reading the current value first and rewriting it in full.Dates are ISO
YYYY-MM-DD; URLs are fullhttps://links.
Run
npm install
npm run dev # development: API on :3001 (next free port if busy), web (hot reload) on :5173
npm run seed # optional demo data (add `-- --reset` to wipe first)
npm test # unit tests; `npm run test:e2e` runs the browser tests
npm run build && npm start # single process: API + UI at http://127.0.0.1:3001Data lives in data/apples.db (override with DB_PATH). On server start (skipped if the newest
backup is under an hour old) and then daily, a consistent snapshot is written to backups/ next to
the DB file (data/backups/ by default) and the newest 7 are kept. To restore, stop the server and
copy a snapshot over apples.db (delete any apples.db-wal/-shm files first). Don't back up by
copying the live file: in WAL mode that can miss data.
If port 3001 is busy, the API (both npm run dev and npm start) moves to the next free port
and logs it; set PORT to pin one (then a busy port is an error). The server records the port it
actually uses in .api-port next to the database file, which is how the MCP server finds it.
Connect an AI assistant (MCP). The API must be running (npm run dev or npm start).
Claude Code:
claude mcp add apples-tracker -- npm run mcp --prefix <repo path>or any MCP client config: command npm, args ["run","mcp","--prefix","<repo path>"],
where <repo path> is the absolute path of this repository.
The MCP server locates the API on every request: APPLES_API_URL if set, otherwise the port
recorded in .api-port by a running server (same DB_PATH), otherwise http://127.0.0.1:3001.
This server cannot be deployed
Maintenance
Related MCP Connectors
Manage job applications — jobs, companies, boards, notes, and profile — from your AI client.
Job search for your AI: save jobs, research companies, keep stories, track applications.
1Job search tracker for Claude and ChatGPT: applications, interviews, follow-ups, company watch.
Track job applications: find the ones to follow up on, add jobs, update status, notes and steps.
Related MCP Servers
- FlicenseAqualityDmaintenanceEnables tracking job applications through a pipeline, scheduling follow-ups, and summarizing job search progress via natural language.7-
- FlicenseNot gradedqualityDmaintenanceManages job applications, companies, roles, statuses, deadlines, and reminders through natural language queries.-
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to manage a personal job application tracker, allowing users to list, add, update, and delete job applications with company, role, and status details through natural language.-
- AlicenseNot gradedqualityBmaintenanceEnables an MCP-capable coding agent to run a local-first job search: recording verified opportunities in a private SQLite tracker, tracking follow-ups and outcomes, and preparing evidence-based resumes, application answers, and outreach drafts. It keeps all personal data on the user's machine and stops before submitting applications, uploading documents, or contacting anyone.1MIT