job-apply-agent-mcp
Allows automated job applications on Greenhouse, including filling multi-step application forms, uploading resumes, and verifying submissions.
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-apply-agent-mcpDry-run apply to https://jobs.lever.co/acme/12345"
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 Apply Agent - Test Project
A minimal Stagehand + Playwright project that fills Lever and Greenhouse
job application forms using dummy candidate data stored in data/candidate.json.
Defaults to dry-run mode: it runs the full flow (navigate, discover fields from the DOM, Playwright fill, verify) but stops before clicking submit, so you can safely point it at a real company's posting without sending a fake application.
Project structure
lever-apply-test/
├── data/
│ ├── candidate.json # dummy candidate profile (edit this to test different data)
│ ├── generate_dummy_resume.py
│ ├── dummy_resume.pdf # generated locally, gitignored
│ ├── users/ # per-user MCP profiles (gitignored)
│ └── screenshots/ # form screenshots (gitignored)
├── hermes/skills/job-apply/
│ └── SKILL.md # Hermes skill for Slack / chat apply flow
├── src/
│ ├── config.py # env var loading
│ ├── schemas.py # Pydantic models (candidate + form/audit schemas)
│ ├── candidate_loader.py # loads/validates candidate.json
│ ├── user_store.py # per-user MCP profiles under data/users/
│ ├── mcp_server.py # MCP stdio server for Hermes
│ ├── base_agent.py # shared Stagehand session + Playwright fill/audit
│ ├── playwright_form.py # DOM discover / fill / typeahead / readback
│ ├── field_mapper.py # label → profile mapping (aliases, then one LLM call)
│ ├── session_handoff.py # CAPTCHA / OTP live-view handoff
│ ├── greenhouse_locators.py # Greenhouse Apply/Next/Submit + classic IDs
│ ├── lever_locators.py # Lever Apply/Submit locators
│ ├── ats_detection.py # auto-detect Lever vs Greenhouse from URL
│ ├── apply_lever.py # Lever automation logic
│ └── apply_greenhouse.py # Greenhouse automation logic
├── tests/
│ └── fixtures/ # local Greenhouse/Lever HTML for Playwright fill tests
├── main.py # CLI entry point
├── start_mcp.sh # run the MCP server from this repo
├── requirements.txt
├── pytest.ini
└── .env.exampleRelated MCP server: mcpforwork
Supported ATS systems
ATS | Example URL | Auto-detected |
Lever |
| Yes |
Greenhouse |
| Yes |
Greenhouse (alt) |
| Yes |
Embedded Greenhouse |
| No — use |
What you need to actually run a test
Python 3.10+
Install dependencies
pip install -r requirements.txt playwright install chromiumplaywright installdownloads the actual browser binary - this is a separate step frompip install, easy to forget.An LLM API key. Stagehand still launches the browser and is the fallback for widgets Playwright cannot target. Copy
.env.exampleto.envand fill inMODEL_API_KEY(Anthropic, OpenAI, or Azure OpenAI all work). Playwright-first fixture tests do not need a model key.Generate the dummy resume once:
python data/generate_dummy_resume.pyA real job posting URL to test against. Dummy candidate data only gets you halfway - you need an actual live apply page for Stagehand to interact with.
Important: don't run
--liveagainst a real company's posting - that sends an actual application under fake data to a real employer, which is both bad practice and likely a ToS violation. Two safer options:Run in dry-run mode (the default) against a real posting - it fills and verifies but never submits, so it's safe to test the fill logic end-to-end.
Set up your own sandbox/test job if your team gets a trial account, and only use
--liveagainst that.
(Optional) A Browserbase account if you want cloud browsers, session replay, or their captcha-handling infrastructure instead of running a local Chromium window. Leave
BROWSERBASE_API_KEYblank to just run locally - simplest for early testing.
Running it
# Lever - auto-detected from URL (dry-run, default)
python main.py --job-url https://jobs.lever.co/some-company/some-posting-id
# Greenhouse - auto-detected from URL
python main.py --job-url https://boards.greenhouse.io/some-company/jobs/12345
# Custom-domain Greenhouse embed - explicit override
python main.py --job-url https://careers.example.com/jobs/123 --ats greenhouse
# Watch it work instead of running headless
# (set HEADLESS=false in .env, which is also the default)
# Only once you have your own sandbox posting to test against:
python main.py --job-url <your-sandbox-url> --live
# Test with different candidate data
python main.py --job-url <url> --candidate data/another_candidate.json
# Leave browser open after fill so you can manually verify fields
python main.py --job-url <url> --keep-open
# Skip the terminal prompt for fields still blank after the automatic retry
python main.py --job-url <url> --no-promptSet KEEP_BROWSER_OPEN=true in .env to make this the default behavior.
MCP server (Hermes / Slack)
The apply CLI is unchanged. src/mcp_server.py exposes the same flow as MCP tools so Hermes can call them from Slack or another chat app.
Run it locally (stdio):
python -m src.mcp_serverPoint Hermes at it (~/.hermes/config.yaml or your active profile):
mcp_servers:
job_apply:
command: "/absolute/path/to/lever-apply-test/venv/bin/python"
args: ["-m", "src.mcp_server"]
timeout: 600
env:
PYTHONPATH: "/absolute/path/to/lever-apply-test"Or run ./start_mcp.sh from this repo.
Start Hermes from this repo (or ensure the python module path can see it), then /reload-mcp.
Tools:
Tool | What it does |
| Lever vs Greenhouse from the job URL |
| Load |
| Save a profile for that chat |
| Dry-run fill + verify, then attach form screenshot(s) for Hermes |
| Save extra field values from the Slack user ( |
| Retry the last dry-run job after extra answers are saved |
| Live submit — only after reviewing the dry-run / screenshots |
| Submit an OTP while a live handoff session is open |
| Close a CAPTCHA live-view handoff |
| Close any open OTP / CAPTCHA handoff |
Hermes must pass user_id (for Slack, the member id like U01ABC2DEF3). That is how the server tells users apart.
apply_to_job / resume_application / submit_application return JSON plus JPEG screenshots of the filled form (pre-submit, and post-submit when live). The JSON includes fill_audit (filled, missing, unknown, reason), fill_attempts, and needs_user_input / questions when fields are still blank. Screenshots are also saved under data/screenshots/.
CLI missing fields: after 1 automatic retry, the terminal asks for each blank field and fills them in the same browser session. Use --no-prompt to skip. --keep-open is still available for visual review.
Hermes missing fields: the browser session closes (Slack round-trips are too slow to hold Chromium). If needs_user_input is true, Hermes should ask the user for questions, call provide_application_answers, then resume_application. Extra answers are stored on the user profile as extra_answers and reused on later jobs.
Example Slack flow: save a profile → “apply to <job url>” → if asked, supply missing answers → review the screenshot(s) → “submit it”.
Notes
ATS auto-detection uses the job URL hostname. If detection fails (e.g. Greenhouse embedded on a custom careers domain), pass
--ats leveror--ats greenhouseexplicitly.Greenhouse multi-step forms discover and fill each step, then click Next / Continue only if Submit is not visible. Optional EEO/demographic sections are skipped or answered with "Decline to answer" when possible.
Resume upload uses the held Playwright page (
set_input_files) when CDP is available, then falls back to Stagehandact()if needed.Location / city fields are treated as searchable typeaheads: Playwright types the value and clicks a matching suggestion, with Stagehand as fallback.
Candidate profile is nested:
personal,answers,education,skills,documents,demographics,consent, plusextra_answersfor job-specific questions that are not in the schema. Existing profiles still load; new sections default to empty. Dummy values live indata/candidate.json.Custom / extended questions (education, skills, DOB, citizenship, relocate, salary, notice, consent flags) are filled from that structured profile. Anything still unmatched goes to CLI/Hermes
extra_answersinstead of being invented.Label mapping matches DOM-discovered fields to the profile with aliases first (no LLM). Only unmatched labels use one off-browser mapping call. Values always come from the candidate profile. Playwright fills by selector / label; Stagehand
act()is last resort for leftover widgets.Form screenshots are captured after fill (and after live submit) so Hermes can show the filled application without opening a headed browser. This needs a CDP URL on the Stagehand session (typical with Browserbase; local mode may omit it). Keep Hermes
job_apply.timeoutat 600.Pre-close fill audit: after fill, the agent reads real
input.value/ selected options from the DOM and logs filled vs missing vs unknown.unknownmeans the control was not on this step or could not be read — it is not the same as empty. If expected fields are still blank, only those lagging fields are retried once. Live submit is blocked whilemissingis non-empty. Unmapped fields are not invented: CLI prompts in-session, Hermes asks via chat then retries in a new session. The audit is logged again just before the session closes and is returned to MCP asfill_audit+fill_attempts.CAPTCHA is not solved automatically. Live submissions may hit CAPTCHA on both Lever and Greenhouse; dry-run mode never clicks submit.
Login-required postings and account-creation flows are not supported in this version.
The exact Stagehand Python method signatures may drift slightly as the SDK evolves. If something doesn't match, check https://docs.stagehand.dev first.
This server cannot be installed
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 Servers
- Alicense-qualityDmaintenanceAn MCP server that enables AI-assisted job search workflows including job discovery, application tracking, resume evaluation, and cover letter generation, with support for multiple job sources and scheduled scraping.331AGPL 3.0
- AlicenseBqualityBmaintenanceMCP server for job search, enabling profile creation, job hunting, review, and supervised application preparation without auto-submission.41Apache 2.0
- Alicense-qualityCmaintenanceAn MCP server that exposes job-search and application-management capabilities to compatible AI clients, enabling discovery of vacancies, drafting of tailored application materials, and coordinated human-approved submissions.MIT
- AlicenseAqualityCmaintenanceA privacy-first MCP server for locally managing job, fellowship, and graduate-school applications. It offers tools for tracking application status, analyzing role fit, generating LaTeX CV/cover letters, interview prep, and discovering public jobs from ATS APIs.8MIT
Related MCP Connectors
MCP server for AI job search — find jobs, track applications, get alerts. Claude, ChatGPT, Cursor.
GetJobzi MCP server for job search, application tracking, and career forecasting.
Managed LinkedIn MCP server for AI agents: search, connect, message and enrich on accounts you own.
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/pragnakalpdev64/job-apply-app_mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server