
Company News
Socket Joins New OpenJS Program to Fund Node.js Security Work
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.
MCP server for ENTRA — the agent-ready job platform. Candidates: search, match and rank thousands of verified AI & tech jobs aggregated straight from company ATSs (OpenAI, Anthropic, Stripe, SpaceX…), salary stats, companies hiring, human-in-the-loop appl
Let your AI find your job. This MCP server connects Claude, ChatGPT, Cursor, Windsurf or any MCP client — locally via npx or hosted at https://mcp.entracareers.com/mcp — to ENTRA — thousands of verified AI & tech jobs aggregated straight from company hiring systems (OpenAI, Anthropic, SpaceX, Stripe + 300 more). Zero ghost jobs.
Agents search, match, rank and prepare. Humans decide and apply — no spam, no auto-submission.
Claude Code
claude mcp add entra -- npx -y entra-mcp
Claude Desktop — one-click extension (.mcpb)
Download entra-mcp.mcpb and open it with Claude Desktop (Settings → Extensions → Install from file). No Node install needed. Employers can paste their API key in the extension settings.
Claude Desktop (claude_desktop_config.json), Cursor (.cursor/mcp.json), Windsurf (~/.codeium/windsurf/mcp_config.json)
{
"mcpServers": {
"entra": { "command": "npx", "args": ["-y", "entra-mcp"] }
}
}
From source:
npm install && npm run build
claude mcp add entra -- node "/path/to/entra-mcp/dist/index.js"
Requires Node 18+. No API key needed for candidate tools — they are read-only against the public ENTRA API. Employers add one env var, see Employer mode.
Then just ask: "Find me remote ML engineer roles paying $200K+, tell me which companies are hiring most, and prepare an application for the best fit."
The same server runs at https://mcp.entracareers.com/mcp (MCP Streamable HTTP). Nothing to install, no account for candidate tools.
| Client | How |
|---|---|
| ChatGPT | Settings → Connectors → Create → MCP Server URL https://mcp.entracareers.com/mcp, authentication "None" (custom connectors need a ChatGPT plan that supports them, e.g. Plus/Pro/Business/Enterprise with developer mode) |
| claude.ai | Settings → Connectors → Add custom connector → URL https://mcp.entracareers.com/mcp |
Cursor (.cursor/mcp.json) | { "mcpServers": { "entra": { "url": "https://mcp.entracareers.com/mcp" } } } |
| Claude Code | claude mcp add --transport http entra https://mcp.entracareers.com/mcp |
| Gemini CLI / Smithery / any Streamable HTTP client | point it at the URL above |
Employer mode over HTTP: send your key on every request as Authorization: Bearer entra_live_… and the 8 employer tools appear for that request (Cursor: add "headers": { "Authorization": "Bearer entra_live_…" } next to url). ChatGPT and claude.ai connectors cannot send custom headers today, so there you get candidate mode — employers use the local npx setup with ENTRA_API_KEY (Employer mode).
How the hosted server behaves:
Mcp-Session-Id), so it scales and redeploys without breaking clients. GET /mcp and DELETE /mcp answer 405 as the spec allows.Authorization header is used for that one request and is neither stored nor logged. No key is read from the server's environment./mcp; request bodies up to 1 MB; CORS open for browser-based agents. Health: GET /healthz.| Tool | What it does | Example call |
|---|---|---|
search_jobs | Search live verified jobs. Filters: work_location (remote/onsite/hybrid), country (ISO-2), company (slug or name), salary_min (annual), experience, posted_within_days. rank_by: date (default) · salary · fit (with profile_keywords). Returns total_found + jobs with id/title/company/location/salary/published_at/url. | {"query":"machine learning engineer","remote":true,"country":"US","salary_min":200000,"rank_by":"salary","posted_within_days":14,"limit":10} |
get_job | Full role details: description, requirements, skills, salary band, apply link | {"job_id":"<id from search_jobs>"} |
list_companies | Who's hiring on ENTRA, with open-role counts and profile links | {"query":"anthropic"} |
match_jobs | AI matching, live. Pass resume_text (≤8000 chars) or skills[], optional target_role, remote_only, country, min_salary, limit (≤20). Extracts skills/role/seniority deterministically, runs 2–4 searches, dedupes, and returns a ranked list with fit_score (0–100), why[], matched_skills, skill_gaps, salary, interview_chance_hint (high/medium/low). | {"resume_text":"Senior ML engineer, 7 years, PyTorch, RAG, AWS…","remote_only":true,"limit":10} |
match_profile | Backward-compatible alias of match_jobs (skills[], role_hint) | {"skills":["python","llm","rag"],"role_hint":"ML engineer"} |
salary_stats | Salary stats for a role from listings that publish a band: count_with_salary, min / p25 / median / p75 / max per currency (annualized; USD as USD-equivalent, other currencies as-is), top 5 companies | {"query":"software engineer","country":"US"} |
companies_hiring | Which companies are hiring for a role right now: open-role counts, sample titles, latest post date, verified flag, profile URLs | {"query":"AI engineer","remote_only":true,"limit":15} |
prepare_application | Human-in-the-loop apply link + checklist | {"job_id":"<id>"} |
Every job in every response carries verified: true, a salary band when the company lists one, and an apply URL of the form https://entracareers.com/{country}/vacancies/{id}?utm_source=agent&utm_medium=mcp — we welcome agent traffic.
match_jobs{
"source": "ENTRA — verified roles aggregated from company ATSs. Finds and ranks; you apply.",
"profile": { "skills_used": ["machine learning", "python", "rag", "llm", "pytorch", "aws"], "target_role": "machine learning engineer", "seniority": "6+ yrs (7+ years mentioned)" },
"queries_run": ["machine learning engineer", "python", "rag", "llm"],
"candidates_considered": 142,
"matches": [
{
"title": "Machine Learning Engineer, Inference",
"company": "OpenAI",
"location": "San Francisco, United States",
"work_location": "remote",
"salary": "$295K–555K/yr",
"url": "https://entracareers.com/us/vacancies/…?utm_source=agent&utm_medium=mcp",
"fit_score": 88,
"interview_chance_hint": "high",
"why": [
"Skills: 5/6 matched (machine learning, python, llm, pytorch, aws)",
"Title match: \"Machine Learning Engineer, Inference\" fits \"machine learning engineer\"",
"Seniority: job asks 6+ yrs, you read as 6+ yrs — match",
"Remote role (as requested)",
"Salary band listed: $295K–555K/yr"
],
"skill_gaps": ["rag"]
}
]
}
fit_score is explainable, not a prediction; interview_chance_hint is derived from fit_score only (never a made-up percentage).salary_stats uses listed bands only — roles without a published band are excluded, and nothing is estimated.search_jobs rank_by: "salary" returns salaried listings only; posted_within_days is applied client-side over the most recent 500 matches.prepare_application returns a link and a checklist; the human applies.Same package, one env var: with ENTRA_API_KEY set, the 8 candidate tools stay and 8 employer tools are added — post jobs, manage listings, read applications, search and match candidates for your company.
Claude Code
export ENTRA_API_KEY="entra_live_…"
claude mcp add entra -e ENTRA_API_KEY=$ENTRA_API_KEY -- npx -y entra-mcp --employer
Claude Desktop / Cursor / Windsurf
{
"mcpServers": {
"entra": {
"command": "npx",
"args": ["-y", "entra-mcp", "--employer"],
"env": { "ENTRA_API_KEY": "entra_live_…" }
}
}
}
Then ask: "Post a Senior Sales Manager job in Dubai — hybrid, 15–22K AED/month, 45 days — then show me the 5 best candidates for it and who has already applied."
How it behaves:
--employer alone just prints a hint on stderr.GET /employer/api-keys/me and caches them.429 is retried once after retry-after.| Tool | Scope | What it does | Example call |
|---|---|---|---|
employer_whoami | any | Company, key name/prefix, granted scopes, which tools are usable | {} |
post_job | jobs:write | Publish a job. Human-friendly input: title, description, employment_type (full_time/part_time/contract/freelance/internship), work_location (remote/onsite/hybrid), country (ISO-2 or name), city (name — required by ENTRA even for remote roles), optional specialization (free text → best active match, defaults to the title), experience_level, salary_min/max/currency/period, benefits, skills (names), internal_job_id, expires_in_days (default 30). Returns the public URL, manage URL and every resolution made (resolved, notes). | {"title":"Senior Sales Manager","description":"…","employment_type":"full_time","work_location":"hybrid","country":"AE","city":"Dubai","salary_min":15000,"salary_max":22000,"salary_currency":"AED","salary_period":"monthly","expires_in_days":45} |
my_jobs | jobs:read | Your company's jobs: status active (default) / closed / all, search, limit, page. Status, salary, applications count, expiry, URLs. | {"status":"active"} |
update_job | jobs:write | Change a subset of fields: title, description, requirements, employment/work location, experience, salary, benefits, expires_in_days (extend), country+city, specialization, is_active (re-open) | {"job_id":"…","salary_max":25000,"expires_in_days":60} |
close_job | jobs:write | Unpublish a job. Without confirm:true returns a preview only; with it, PATCH {isActive:false} (reversible) | {"job_id":"…","confirm":true} |
job_applications | applications:read | Applications to one job (job_id) or all company jobs; optional status (client-side), limit, page. Candidate, resume headline, cover letter, contact details exactly as returned | {"job_id":"…","status":"pending"} |
search_candidates | candidates:read | Published resumes: query, skills[] (ANY), specialization, country/city, experience, work_location/remote_only, employment_type, ready_to_relocate, expected-salary bounds, has_linkedin/has_portfolio, published_within_days, limit ≤25 | {"query":"sales manager","country":"AE","experience":"3_6_years"} |
match_candidates | candidates:read | Rank candidates for job_id (or job_text): deterministic extraction of skills/role/seniority, 2–3 resume searches, fit_score 0–100 with why[], matched_skills, skill_gaps | {"job_id":"…","limit":10} |
| Situation | What you get |
|---|---|
| Key invalid / revoked | {"error":…,"code":"AUTH_ERROR","status":401,"hint":"…create a new key at …/employer/profile?tab=api-keys"} |
| Key lacks a scope | {"code":"MISSING_SCOPE","scope":"jobs:write",…} — checked locally before any request |
| No free vacancy slot | post_job → {"needs_subscription":true,"pricing_url":"https://entracareers.com/employer/pricing","note":"Founding plan from $10"} (not an error) |
| Resume database needs a plan | search_candidates / match_candidates → {"needs_subscription":true,…,"note":"Resume Access"} |
| Rate limited | retried once, then {"code":"API_KEY_RATE_LIMIT_EXCEEDED","status":429,…} |
| Validation failed | {"status":400,"validation":[{field,message}]} |
match_candidates is deterministic keyword scoring (skills overlap, headline vs role, seniority, location/remote) — explainable, not a prediction. Review profiles before contacting anyone.post_job reports every guess it made: city / specialization match quality, a detected or defaulted experience_level (1_3_years if undetectable), and salary_period defaulting to monthly (the ENTRA default — pass "yearly" for annual figures).close_job sets isActive:false and can be undone with update_job {is_active:true}. Application status changes are not available through API keys — use the employer dashboard.verified: true + salary bands + utm_source=agent (we welcome agent traffic).AI matching is live via match_jobs — connect your profile at entracareers.com.
Employers: entracareers.com/employer/pricing — Founding plan from $10.
npm run build # tsc → dist/
npm run selftest # calls the live API directly, no MCP client (candidate tools)
npm run smoke # spawns the server over stdio with the MCP SDK client and exercises every candidate tool (live API)
npm run smoke:employer # employer mode against an in-process mock of the employer API (no network, no real key)
npm run smoke:http # starts dist/http.js on a random port and drives it with the SDK's Streamable HTTP client (live API + fake employer key)
npm run start:http # hosted transport locally: http://localhost:8080/mcp (PORT, HOST, RATE_LIMIT_PER_MINUTE, TRUST_PROXY, MCP_ALLOWED_HOSTS)
npm run bundle # builds dist-bundle/entra-mcp.mcpb (Claude Desktop extension / Smithery local bundle) from manifest.json
docker build -t entra-mcp . && docker run -p 8080:8080 entra-mcp # the image the hosted endpoint runs (node:22-alpine, dist/http.js)
Layout: src/server.ts builds the server (createServer({ apiKey? }) — tools + per-request key context), src/index.ts is the stdio entry (npx entra-mcp), src/http.ts the hosted Streamable HTTP entry.
Environment: ENTRA_API_KEY (stdio only — enables employer mode), ENTRA_API_URL (default https://entracareers.com/api), ENTRA_SITE_URL (default https://entracareers.com).
MIT © ENTRA
This server sends only your tool inputs to the public ENTRA API (https://entracareers.com/api). No telemetry, no third-party services. Locally (npx entra-mcp) employer mode uses your API key from the ENTRA_API_KEY environment variable; on the hosted endpoint (https://mcp.entracareers.com/mcp) the key comes from the Authorization header of each request, is forwarded only to ENTRA employer endpoints, and is never stored or logged. The hosted server keeps no sessions or request history beyond a short access log (method, tool name, status, timing). Full policy: https://entracareers.com/privacy-policy
FAQs
MCP server for ENTRA — the agent-ready job platform. Candidates: search, match and rank thousands of verified AI & tech jobs aggregated straight from company ATSs (OpenAI, Anthropic, Stripe, SpaceX…), salary stats, companies hiring, human-in-the-loop appl
We found that entra-mcp demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Socket is joining the OpenJS Security Stewardship Program to fund Node.js vulnerability research, maintainer remediation, and security releases.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.