visa-jobs-mcp
Identify US visa-sponsoring opportunities on LinkedIn and access the right contacts to accelerate your outreach.
visa-jobs-mcp

visa-jobs-mcp is an MCP server that helps agents find relevant jobs from fresh LinkedIn searches, with optional visa-aware filtering.
It is built for speed and practical outcomes:
- search jobs in a location,
- match employers against sponsorship history,
- return actionable results with links and contact info,
- keep user data local.
Get Started
1. Install on macOS (Homebrew)
brew tap neosh11/visa-jobs-mcp
brew install neosh11/visa-jobs-mcp/visa-jobs-mcp
2. Install on macOS/Linux (bash installer)
curl -fsSL https://raw.githubusercontent.com/neosh11/visa-jobs-mcp/main/scripts/install.sh | bash
Pin a specific version:
curl -fsSL https://raw.githubusercontent.com/neosh11/visa-jobs-mcp/main/scripts/install.sh | bash -s -- --version 0.3.1
3. Install on Windows (PowerShell installer)
irm https://raw.githubusercontent.com/neosh11/visa-jobs-mcp/main/scripts/install.ps1 | iex
Pin a specific version:
$env:VISA_JOBS_MCP_VERSION = "0.3.1"
irm https://raw.githubusercontent.com/neosh11/visa-jobs-mcp/main/scripts/install.ps1 | iex
4. Register in Codex (macOS/Linux)
codex mcp add visa-jobs-mcp --env VISA_JOB_SITES=linkedin -- visa-jobs-mcp
Register in Codex (Windows PowerShell):
codex mcp add visa-jobs-mcp --env VISA_JOB_SITES=linkedin -- "$env:LOCALAPPDATA\\Programs\\visa-jobs-mcp\\bin\\visa-jobs-mcp.exe"
Verify:
codex mcp list
codex mcp get visa-jobs-mcp
5. Use it in chat
In a new Codex session, ask naturally:
Find IT jobs in Delhi for my skills.Set my visa preference to E3 and find software engineer jobs in New York that sponsor E3.
Build from source (optional)
go build -o visa-jobs-mcp ./cmd/visa-jobs-mcp
./visa-jobs-mcp --version
If you need to refresh data/companies.csv from source, run:
./scripts/run_internal_pipeline.sh
Note: MCP runtime is Go-only. Python is only needed for maintainers running the internal dataset pipeline.
Live LinkedIn E2E (manual)
Run a real end-to-end search against LinkedIn (not stubbed tests):
./scripts/run_live_linkedin_e2e.sh
Optional timeout override:
VISA_E2E_TEST_TIMEOUT=8m ./scripts/run_live_linkedin_e2e.sh
Optional live test parameters:
VISA_E2E_VISA_TYPE=E3 \
VISA_E2E_LOCATION="New York, NY" \
VISA_E2E_JOB_TITLE="Software Engineer" \
./scripts/run_live_linkedin_e2e.sh
What It Supports
- LinkedIn-only search.
- General skill/location job search with no visa setup required.
- Optional visa preference matching when
preferred_visa_typesis set. - Search sessions with pagination and resume support.
- Saved jobs and ignored jobs.
- Employer contact extraction when available.
- Local-first private data storage.
- No proxy usage.
- No LLM calls inside MCP runtime (agent handles reasoning).
Core MCP Tools
start_job_searchget_job_search_statusget_job_search_resultsset_user_preferencesstart_visa_job_searchget_visa_job_search_statusget_visa_job_search_resultssave_job_for_laterignore_joblist_saved_jobslist_ignored_jobsget_mcp_capabilities
Tip: ask the agent to call get_mcp_capabilities first for a machine-readable contract.
MCP Contract (Generated)
Generated from get_mcp_capabilities() via scripts/generate_contract_docs.py.
Server
server:visa-jobs-mcpversion:0.3.1capabilities_schema_version:1.2.0confidence_model_version:v1.1.0-rules-go
Required Before Search
tool:start_job_searchrequired_fields:user_id
Design Decisions
agent_is_reasoning_layer:Truebackground_search_runs_local_persistence:Truedata_not_shared_or_sold:Truefirst_class_job_management:Truefree_forever:Truefresh_job_search_per_query:Trueignored_companies_local_persistence:Trueignored_jobs_local_persistence:Truelicense:MITllm_api_keys_required_by_mcp:Falsellm_runtime_inside_mcp:Falseno_fake_reviews_or_bot_marketing:Trueproxies_used:Falserate_limit_backoff_retries:Truesaved_jobs_local_persistence:Truesearch_sessions_local_persistence:Truestrict_user_visa_match:Falsestrictness_modes_supported:['balanced', 'strict']supported_job_sites:['linkedin']visa_matching_optional:True
Defaults
dataset_stale_after_days:30job_db_path:data/app/visa_jobs.dbmax_scan_results:1200max_search_sessions_per_user:20rate_limit_initial_backoff_seconds:2rate_limit_max_backoff_seconds:30rate_limit_retry_window_seconds:180scan_multiplier:8search_run_ttl_seconds:21600search_session_ttl_seconds:21600strictness_mode:stricttool_call_soft_timeout_seconds:48
Tools
| Tool | Description | Required Inputs | Optional Inputs |
|---|---|---|---|
get_mcp_capabilities | Return MCP capabilities, tools, and contracts for agent self-discovery. | - | - |
set_user_preferences | Save the user's visa preferences for optional visa-specific matching. | user_id, preferred_visa_types | - |
set_user_constraints | Save urgency and work-mode constraints used for personalized guidance. | user_id | - |
get_user_preferences | Fetch the saved user preferences and constraints. | user_id | - |
get_user_readiness | Report whether the user and local dataset are ready for search. | user_id | - |
find_related_titles | Return adjacent role titles to widen low-yield searches. | job_title | - |
add_user_memory_line | Append a profile memory line (skills, goals, fears, constraints). | user_id, content | - |
query_user_memory_blob | Query the user's local memory blob with optional text filtering. | user_id | - |
delete_user_memory_line | Delete one memory line by id from the local blob. | user_id, line_id | - |
save_job_for_later | Save a job to the user's local shortlist for follow-up. | user_id | job_url, result_id, session_id |
list_saved_jobs | List saved jobs in reverse-chronological order. | user_id | - |
delete_saved_job | Remove one saved job from the local shortlist. | user_id, saved_job_id | - |
ignore_job | Hide one job from future results for this user. | user_id | job_url, result_id, session_id |
list_ignored_jobs | List ignored jobs in reverse-chronological order. | user_id | - |
unignore_job | Unhide a previously ignored job by id. | user_id, ignored_job_id | - |
ignore_company | Hide all jobs from a company in future searches. | user_id | - |
list_ignored_companies | List ignored companies in reverse-chronological order. | user_id | - |
unignore_company | Remove one company from the ignored list. | user_id, ignored_company_id | - |
mark_job_applied | Mark a job as applied and persist pipeline state. | user_id | - |
update_job_stage | Update lifecycle stage for a tracked job (saved/applied/interview/etc). | user_id, stage | - |
list_jobs_by_stage | List tracked jobs filtered by lifecycle stage. | user_id, stage | - |
add_job_note | Attach or append a note to a tracked job record. | user_id, note | - |
list_recent_job_events | List recent stage transitions and lifecycle events. | user_id | - |
get_job_pipeline_summary | Summarize tracked pipeline counts by stage for one user. | user_id | - |
clear_search_session | Delete one cached search session or all sessions for a user. | user_id | - |
export_user_data | Export all local records for a user across stores. | user_id | - |
delete_user_data | Permanently delete all local records for a user. | user_id, confirm | - |
get_best_contact_strategy | Suggest best outreach channel/contact for a job. | user_id | - |
generate_outreach_message | Generate a practical outreach draft tailored to user and role. | user_id | - |
start_job_search | Start a background job search without requiring visa preferences. | location, job_title, user_id | - |
get_job_search_status | Poll incremental progress/events for a background job search run. | user_id, run_id | - |
get_job_search_results | Fetch current result page from a background job search run. | user_id, run_id | - |
cancel_job_search | Request cancellation of an in-progress background job search run. | user_id, run_id | - |
start_visa_job_search | Start a background search run for long scans. | location, job_title, user_id | - |
get_visa_job_search_status | Poll incremental progress/events for a background search run. | user_id, run_id | - |
get_visa_job_search_results | Fetch current result page from a background search run. | user_id, run_id | - |
cancel_visa_job_search | Request cancellation of an in-progress background run. | user_id, run_id | - |
discover_latest_dol_disclosure_urls | Discover latest DOL LCA/PERM disclosure sources. | - | - |
run_internal_dol_pipeline | Run internal pipeline to refresh sponsor-company dataset. | - | - |
refresh_company_dataset_cache | Clear and reload in-memory company dataset cache. | - | - |
Search Response Fields
runstatusstatsguidancedataset_freshnesspaginationrecovery_suggestionsjobs[].result_idjobs[].job_urljobs[].titlejobs[].companyjobs[].locationjobs[].sitejobs[].date_postedjobs[].description_fetchedjobs[].descriptionjobs[].description_excerptjobs[].salary_textjobs[].salary_currencyjobs[].salary_intervaljobs[].salary_min_amountjobs[].salary_max_amountjobs[].salary_sourcejobs[].job_typejobs[].job_leveljobs[].company_industryjobs[].job_functionjobs[].job_url_directjobs[].is_remotejobs[].employer_contactsjobs[].visa_countsjobs[].visas_sponsoredjobs[].visa_match_strengthjobs[].eligibility_reasonsjobs[].confidence_scorejobs[].confidence_model_versionjobs[].agent_guidance
Paths
dataset_default:data/companies.csvignored_companies_default:data/config/ignored_companies.jsonignored_jobs_default:data/config/ignored_jobs.jsonjob_management_db_default:data/app/visa_jobs.dbpipeline_manifest_default:data/pipeline/last_run.jsonsaved_jobs_default:data/config/saved_jobs.jsonsearch_runs_store_default:data/config/search_runs.jsonsearch_session_store_default:data/config/search_sessions.jsonuser_memory_blob_default:data/config/user_memory_blob.jsonuser_preferences_default:data/config/user_preferences.json
Deprecations
build_company_dataset_from_dol_disclosures->run_internal_dol_pipeline(soft_deprecated)
{
"capabilities_schema_version": "1.2.0",
"confidence_model_version": "v1.1.0-rules-go",
"defaults": {
"dataset_stale_after_days": 30,
"job_db_path": "data/app/visa_jobs.db",
"max_scan_results": 1200,
"max_search_sessions_per_user": 20,
"rate_limit_initial_backoff_seconds": 2,
"rate_limit_max_backoff_seconds": 30,
"rate_limit_retry_window_seconds": 180,
"scan_multiplier": 8,
"search_run_ttl_seconds": 21600,
"search_session_ttl_seconds": 21600,
"strictness_mode": "strict",
"tool_call_soft_timeout_seconds": 48
},
"deprecations": [
{
"name": "build_company_dataset_from_dol_disclosures",
"replacement": "run_internal_dol_pipeline",
"status": "soft_deprecated"
}
],
"design_decisions": {
"agent_is_reasoning_layer": true,
"background_search_runs_local_persistence": true,
"data_not_shared_or_sold": true,
"first_class_job_management": true,
"free_forever": true,
"fresh_job_search_per_query": true,
"ignored_companies_local_persistence": true,
"ignored_jobs_local_persistence": true,
"license": "MIT",
"llm_api_keys_required_by_mcp": false,
"llm_runtime_inside_mcp": false,
"no_fake_reviews_or_bot_marketing": true,
"proxies_used": false,
"rate_limit_backoff_retries": true,
"saved_jobs_local_persistence": true,
"search_sessions_local_persistence": true,
"strict_user_visa_match": false,
"strictness_modes_supported": [
"balanced",
"strict"
],
"supported_job_sites": [
"linkedin"
],
"visa_matching_optional": true
},
"pagination_contract": {
"next_step": "use pagination.next_offset to request the next page",
"offset_model": "offset is applied to accepted jobs, not raw scraped jobs",
"result_id_aliases": "use jobs[].result_id in save_job_for_later/ignore_job to avoid URL copy friction",
"scan_behavior": "server can increase raw scan depth when auto_expand_scan=true",
"session_behavior": "pass search_session.session_id for stable paging without redundant rescans"
},
"paths": {
"dataset_default": "data/companies.csv",
"ignored_companies_default": "data/config/ignored_companies.json",
"ignored_jobs_default": "data/config/ignored_jobs.json",
"job_management_db_default": "data/app/visa_jobs.db",
"pipeline_manifest_default": "data/pipeline/last_run.json",
"saved_jobs_default": "data/config/saved_jobs.json",
"search_runs_store_default": "data/config/search_runs.json",
"search_session_store_default": "data/config/search_sessions.json",
"user_memory_blob_default": "data/config/user_memory_blob.json",
"user_preferences_default": "data/config/user_preferences.json"
},
"rate_limit_contract": {
"failure_message": "asks agent to retry shortly when the retry window is exhausted",
"max_retry_window_seconds": 180,
"retry_behavior": "automatic exponential backoff on rate-limit errors (429/Too Many Requests)"
},
"required_before_search": {
"required_fields": [
"user_id"
],
"tool": "start_job_search"
},
"search_response_fields_for_agents": [
"run",
"status",
"stats",
"guidance",
"dataset_freshness",
"pagination",
"recovery_suggestions",
"jobs[].result_id",
"jobs[].job_url",
"jobs[].title",
"jobs[].company",
"jobs[].location",
"jobs[].site",
"jobs[].date_posted",
"jobs[].description_fetched",
"jobs[].description",
"jobs[].description_excerpt",
"jobs[].salary_text",
"jobs[].salary_currency",
"jobs[].salary_interval",
"jobs[].salary_min_amount",
"jobs[].salary_max_amount",
"jobs[].salary_source",
"jobs[].job_type",
"jobs[].job_level",
"jobs[].company_industry",
"jobs[].job_function",
"jobs[].job_url_direct",
"jobs[].is_remote",
"jobs[].employer_contacts",
"jobs[].visa_counts",
"jobs[].visas_sponsored",
"jobs[].visa_match_strength",
"jobs[].eligibility_reasons",
"jobs[].confidence_score",
"jobs[].confidence_model_version",
"jobs[].agent_guidance"
],
"server": "visa-jobs-mcp",
"tools": [
{
"description": "Return MCP capabilities, tools, and contracts for agent self-discovery.",
"name": "get_mcp_capabilities",
"required_inputs": []
},
{
"description": "Save the user's visa preferences for optional visa-specific matching.",
"name": "set_user_preferences",
"required_inputs": [
"user_id",
"preferred_visa_types"
]
},
{
"description": "Save urgency and work-mode constraints used for personalized guidance.",
"name": "set_user_constraints",
"required_inputs": [
"user_id"
]
},
{
"description": "Fetch the saved user preferences and constraints.",
"name": "get_user_preferences",
"required_inputs": [
"user_id"
]
},
{
"description": "Report whether the user and local dataset are ready for search.",
"name": "get_user_readiness",
"required_inputs": [
"user_id"
]
},
{
"description": "Return adjacent role titles to widen low-yield searches.",
"name": "find_related_titles",
"required_inputs": [
"job_title"
]
},
{
"description": "Append a profile memory line (skills, goals, fears, constraints).",
"name": "add_user_memory_line",
"required_inputs": [
"user_id",
"content"
]
},
{
"description": "Query the user's local memory blob with optional text filtering.",
"name": "query_user_memory_blob",
"required_inputs": [
"user_id"
]
},
{
"description": "Delete one memory line by id from the local blob.",
"name": "delete_user_memory_line",
"required_inputs": [
"user_id",
"line_id"
]
},
{
"description": "Save a job to the user's local shortlist for follow-up.",
"name": "save_job_for_later",
"optional_inputs": [
"job_url",
"result_id",
"session_id"
],
"required_inputs": [
"user_id"
]
},
{
"description": "List saved jobs in reverse-chronological order.",
"name": "list_saved_jobs",
"required_inputs": [
"user_id"
]
},
{
"description": "Remove one saved job from the local shortlist.",
"name": "delete_saved_job",
"required_inputs": [
"user_id",
"saved_job_id"
]
},
{
"description": "Hide one job from future results for this user.",
"name": "ignore_job",
"optional_inputs": [
"job_url",
"result_id",
"session_id"
],
"required_inputs": [
"user_id"
]
},
{
"description": "List ignored jobs in reverse-chronological order.",
"name": "list_ignored_jobs",
"required_inputs": [
"user_id"
]
},
{
"description": "Unhide a previously ignored job by id.",
"name": "unignore_job",
"required_inputs": [
"user_id",
"ignored_job_id"
]
},
{
"description": "Hide all jobs from a company in future searches.",
"name": "ignore_company",
"required_inputs": [
"user_id"
]
},
{
"description": "List ignored companies in reverse-chronological order.",
"name": "list_ignored_companies",
"required_inputs": [
"user_id"
]
},
{
"description": "Remove one company from the ignored list.",
"name": "unignore_company",
"required_inputs": [
"user_id",
"ignored_company_id"
]
},
{
"description": "Mark a job as applied and persist pipeline state.",
"name": "mark_job_applied",
"required_inputs": [
"user_id"
]
},
{
"description": "Update lifecycle stage for a tracked job (saved/applied/interview/etc).",
"name": "update_job_stage",
"required_inputs": [
"user_id",
"stage"
]
},
{
"description": "List tracked jobs filtered by lifecycle stage.",
"name": "list_jobs_by_stage",
"required_inputs": [
"user_id",
"stage"
]
},
{
"description": "Attach or append a note to a tracked job record.",
"name": "add_job_note",
"required_inputs": [
"user_id",
"note"
]
},
{
"description": "List recent stage transitions and lifecycle events.",
"name": "list_recent_job_events",
"required_inputs": [
"user_id"
]
},
{
"description": "Summarize tracked pipeline counts by stage for one user.",
"name": "get_job_pipeline_summary",
"required_inputs": [
"user_id"
]
},
{
"description": "Delete one cached search session or all sessions for a user.",
"name": "clear_search_session",
"required_inputs": [
"user_id"
]
},
{
"description": "Export all local records for a user across stores.",
"name": "export_user_data",
"required_inputs": [
"user_id"
]
},
{
"description": "Permanently delete all local records for a user.",
"name": "delete_user_data",
"required_inputs": [
"user_id",
"confirm"
]
},
{
"description": "Suggest best outreach channel/contact for a job.",
"name": "get_best_contact_strategy",
"required_inputs": [
"user_id"
]
},
{
"description": "Generate a practical outreach draft tailored to user and role.",
"name": "generate_outreach_message",
"required_inputs": [
"user_id"
]
},
{
"description": "Start a background job search without requiring visa preferences.",
"name": "start_job_search",
"required_inputs": [
"location",
"job_title",
"user_id"
]
},
{
"description": "Poll incremental progress/events for a background job search run.",
"name": "get_job_search_status",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Fetch current result page from a background job search run.",
"name": "get_job_search_results",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Request cancellation of an in-progress background job search run.",
"name": "cancel_job_search",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Start a background search run for long scans.",
"name": "start_visa_job_search",
"required_inputs": [
"location",
"job_title",
"user_id"
]
},
{
"description": "Poll incremental progress/events for a background search run.",
"name": "get_visa_job_search_status",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Fetch current result page from a background search run.",
"name": "get_visa_job_search_results",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Request cancellation of an in-progress background run.",
"name": "cancel_visa_job_search",
"required_inputs": [
"user_id",
"run_id"
]
},
{
"description": "Discover latest DOL LCA/PERM disclosure sources.",
"name": "discover_latest_dol_disclosure_urls",
"required_inputs": []
},
{
"description": "Run internal pipeline to refresh sponsor-company dataset.",
"name": "run_internal_dol_pipeline",
"required_inputs": []
},
{
"description": "Clear and reload in-memory company dataset cache.",
"name": "refresh_company_dataset_cache",
"required_inputs": []
}
],
"version": "0.3.1"
}
Regenerate this section and website contract block with:
python3 scripts/generate_contract_docs.py
Validate generated blocks are current:
python3 scripts/generate_contract_docs.py --check
Manual CLI (optional)
Check binary version:
visa-jobs-mcp --version
Run the MCP server directly (for debugging):
visa-jobs-mcp
Run the internal DOL pipeline (maintainer workflow, from source checkout):
./scripts/run_internal_pipeline.sh
Troubleshooting
- If search returns no jobs, keep polling
get_visa_job_search_statusand callget_visa_job_search_resultsagain for the samerun_id. - If upstream rate limits happen, wait a few minutes and retry.
- If Homebrew install fails due missing release assets, retry after release workflows complete.
Data and Privacy
- Data is stored locally by default.
- No telemetry or external data selling.
- Sponsorship matching uses
data/companies.csvand DOL-based pipeline outputs.
For Maintainers
- Homebrew tap repository:
https://github.com/neosh11/homebrew-visa-jobs-mcp - Contributor guide:
AGENTS.md - Release workflow:
.github/workflows/build-release-binaries.yml - Local release artifact build:
./scripts/build_release_binaries.sh(native Go binary + bundleddata/companies.csv)
License
MIT. See LICENSE.
Related Servers
Bright Data
sponsorDiscover, extract, and interact with the web - one interface powering automated access across the public internet.
Cloudflare Playwright
Control a browser for web automation tasks like navigation, typing, clicking, and taking screenshots using Playwright on Cloudflare Workers.
medical-mcp
About An MCP server that provides comprehensive medical information by querying multiple authoritative medical APIs including FDA, WHO, PubMed, Google Scholar, and RxNorm.
Hyperbrowser
Hyperbrowser is the next-generation platform empowering AI agents and enabling effortless, scalable browser automation.
Fetch as Markdown MCP Server
Fetches web pages and converts them to clean markdown, focusing on main content extraction.
Website Snapshot
A MCP server that provides comprehensive website snapshot capabilities using Playwright. This server enables LLMs to capture and analyze web pages through structured accessibility snapshots, network monitoring, and console message collection.
MCP NPX Fetch
Fetch and transform web content into various formats like HTML, JSON, Markdown, or Plain Text.
XPath MCP Server
Execute XPath queries on XML content.
Real Estate MCP Server
Property search and market analysis from Redfin with neighborhood insights
Skrapr
An intelligent web scraping tool using AI and browser automation to extract structured data from websites.
RedNote MCP
Access and interact with content from Xiaohongshu (RedNote).