# Job Prep Assistant (MCP Server) (`abhi72/job-prep-assistant`) Actor

MCP server that fetches, stores, and returns structured job-search data (postings, company signals, interview experiences, resume-match history, gap intelligence) so a calling AI agent can reason over it. Persists independently of any chat session.

- **URL**: https://apify.com/abhi72/job-prep-assistant.md
- **Developed by:** [Abhishek Kumar](https://apify.com/abhi72) (community)
- **Categories:** Developer tools
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $100.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Job Prep Assistant

An MCP (Model Context Protocol) server that helps you prepare for job applications — parsing postings, pulling real company signals, surfacing genuine interview experiences, tracking which resume version you used where, and spotting the skill gaps that keep coming up across your search. All of it persists on its own, independent of any single chat session.

Built as an [Apify Actor](https://apify.com) so it runs for free (Serper's free tier, no credit card) and your data lives in your own Apify account.

### Why this exists

A normal chat with Claude or ChatGPT forgets everything the moment you close the tab, switch devices, or start a new conversation. If you're applying to 40 jobs over three months, that's a real problem: you lose track of which resume version you sent where, you re-notice the same skill gap for the fifth time without ever fixing it, and you have no record of what you learned about a company two weeks ago.

Job Prep Assistant fixes that by storing everything in Apify's Key-Value Store, scoped to your Apify account. Whoever runs the Actor owns their own data — no separate signup, no database to manage.

### Design principle

This server does **no AI reasoning**. It only fetches, stores, and returns clean structured data — the calling agent (Claude, Cursor, or whatever you connect it to) does all the actual thinking. Every tool is small, composable, and returns flat, table-ready JSON so the agent can render it as clean Markdown without extra transformation.

### The five tools

| Tool | What it does |
|---|---|
| `parse_job_posting(url)` | Fetches a job posting, strips boilerplate, and splits it into responsibilities / required / preferred / salary\_range — with a guaranteed `raw_text` fallback so nothing is ever silently dropped. |
| `get_company_signals(company_name)` | Recent news (~90 days) and engineering blog posts about a company, via Serper. |
| `find_interview_experiences(company_name, role)` | Real interview experiences from Reddit, Blind, and Medium — snippets and links only, never full article bodies. |
| `save_application(...)` | Persists one analyzed application (company, role, resume version used, notes, identified gaps, status) to your Apify account. |
| `get_dashboard(filter_status?, job_url?)` | The main event: lists every saved application, ranks your past resume versions against a new job posting by keyword overlap, and surfaces which skill gaps keep recurring across your whole search. |

All five tools are written to be used **proactively** — the calling agent is nudged (via each tool's description) to reach for them whenever the conversation touches job search, applications, or a specific company/role, not only when explicitly asked.

### How persistence works

- One record per analyzed application, stored under a key like `application__{company}__{role}__{timestamp}`.
- A lightweight running index (`application_index`) lists every record key, so `get_dashboard` never has to scan the whole store.
- Data is scoped per Apify account. There's no separate login for this tool — whoever runs the Actor is the "user."

### Running it on Apify

1. Open the Actor in [Apify Console](https://console.apify.com).
2. Fill in the input:
   - `action` — one of `parse_job_posting`, `get_company_signals`, `find_interview_experiences`, `save_application`, `get_dashboard`
   - `serperApiKey` — your free key from [serper.dev](https://serper.dev) (required for every action; only actually used by the two search tools)
   - the action-specific fields below
3. Run it. Output shows up in the run's dataset and as the default key-value store `OUTPUT` record.

#### Input reference

| Field | Type | Used by |
|---|---|---|
| `action` | string (enum) | all — required |
| `serperApiKey` | string (secret) | all — required by the schema; only `get_company_signals` and `find_interview_experiences` actually call Serper |
| `jobUrl` | string | `parse_job_posting` (required), `save_application` (optional), `get_dashboard` (optional — triggers resume matching) |
| `companyName` | string | `get_company_signals` (required), `find_interview_experiences` (required), `save_application` (required) |
| `role` | string | `find_interview_experiences` (required), `save_application` (required) |
| `resumeVersionUsed` | string | `save_application` (optional) |
| `matchNotes` | string | `save_application` (optional) |
| `identifiedGaps` | string\[] | `save_application` (optional) |
| `status` | string | `save_application` (optional, default `"applied"`) |
| `filterStatus` | string | `get_dashboard` (optional) |

#### Example inputs

**Parse a job posting**

```json
{
  "action": "parse_job_posting",
  "serperApiKey": "YOUR_KEY",
  "jobUrl": "https://boards.greenhouse.io/example/jobs/123456"
}
```

**Get company signals**

```json
{
  "action": "get_company_signals",
  "serperApiKey": "YOUR_KEY",
  "companyName": "Acme Corp"
}
```

**Find interview experiences**

```json
{
  "action": "find_interview_experiences",
  "serperApiKey": "YOUR_KEY",
  "companyName": "Acme Corp",
  "role": "Backend Engineer"
}
```

**Save an application**

```json
{
  "action": "save_application",
  "serperApiKey": "YOUR_KEY",
  "companyName": "Acme Corp",
  "role": "Backend Engineer",
  "jobUrl": "https://boards.greenhouse.io/example/jobs/123456",
  "resumeVersionUsed": "FDE-tailored",
  "matchNotes": "Strong backend + client-facing overlap",
  "identifiedGaps": ["Kubernetes", "AWS"],
  "status": "applied"
}
```

**Get the dashboard**

```json
{
  "action": "get_dashboard",
  "serperApiKey": "YOUR_KEY",
  "filterStatus": "applied",
  "jobUrl": "https://boards.greenhouse.io/another-example/jobs/789"
}
```

### Local development

See [`examples/README.md`](examples/README.md) for full local setup instructions (`.env`, running tests, running a single action from the command line).

Quick version:

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
cp .env.example .env   # fill in your own SERPER_API_KEY
pytest -q
ruff check src tests
```

### Tech stack

Python 3.12, the [Apify SDK](https://docs.apify.com/sdk/python/) for storage, `httpx` for HTTP, `trafilatura` (+ `readability-lxml` fallback) for HTML extraction, `python-dotenv` for local config, `pytest` + `ruff` for tests/lint.

### Limitations

- Section-splitting in `parse_job_posting` is heuristic (common header keywords), not a layout-aware parser — unusually-formatted postings fall back to `raw_text` rather than guessing wrong.
- `find_interview_experiences` only searches Reddit, Blind, and Medium, and only returns short snippets — it does not scrape Glassdoor, LeetCode Discuss, or any site with restrictive terms of service, and never fetches full third-party article bodies.
- Resume-version matching in `get_dashboard` is a simple, transparent keyword-overlap score (no ML) — it's meant to be a starting point for the calling agent's reasoning, not a verdict.
- Serper's free tier is 2,500 queries total; each `get_company_signals` call uses 2 queries and each `find_interview_experiences` call uses 3.
- No AI reasoning happens inside this server, by design — all judgment calls (is this a good match? is this gap worth addressing?) belong to the calling agent.

# Actor input Schema

## `action` (type: `string`):

Which tool to run.

## `jobUrl` (type: `string`):

Required for parse\_job\_posting. Optional for save\_application and get\_dashboard (resume-version matching).

## `companyName` (type: `string`):

Required for get\_company\_signals, find\_interview\_experiences, and save\_application.

## `role` (type: `string`):

Required for find\_interview\_experiences and save\_application.

## `resumeVersionUsed` (type: `string`):

Used by save\_application, e.g. 'FDE-tailored'.

## `matchNotes` (type: `string`):

Used by save\_application -- short free-text note about the fit.

## `identifiedGaps` (type: `array`):

Used by save\_application -- skill gaps noticed for this application.

## `status` (type: `string`):

Used by save\_application, e.g. 'applied', 'interviewing', 'rejected', 'offer'.

## `filterStatus` (type: `string`):

Used by get\_dashboard to only list applications with this status.

## `serperApiKey` (type: `string`):

Your Serper API key (free tier, no credit card). Get one at https://serper.dev

## Actor input object example

```json
{
  "status": "applied"
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("abhi72/job-prep-assistant").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("abhi72/job-prep-assistant").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call abhi72/job-prep-assistant --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abhi72/job-prep-assistant"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Zc4UvXngaozTyH4EE/builds/lXQoHCOgFeVFCqiKa/openapi.json
