|
|
import gradio as gr |
|
|
from fastmcp import FastMCP |
|
|
import logging |
|
|
import requests |
|
|
from datetime import datetime |
|
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") |
|
|
logger = logging.getLogger(__name__) |
|
|
mcp = FastMCP("Jobicy Remote Jobs Agent") |
|
|
|
|
|
COMPANY_INDUSTRIES = [ |
|
|
("", ""), |
|
|
("Business Development", "business"), |
|
|
("Content & Editorial", "copywriting"), |
|
|
("Creative & Design", "design-multimedia"), |
|
|
("Customer Success", "supporting"), |
|
|
("Data Science & Analytics", "data-science"), |
|
|
("DevOps & Infrastructure", "admin"), |
|
|
("Finance & Accounting", "accounting-finance"), |
|
|
("HR & Recruiting", "hr"), |
|
|
("Legal & Compliance", "legal"), |
|
|
("Marketing & Sales", "marketing"), |
|
|
("Product & Operations", "management"), |
|
|
("Programming", "dev"), |
|
|
("Sales", "seller"), |
|
|
("SEO", "seo"), |
|
|
("Social Media Marketing", "smm"), |
|
|
("Software Engineering", "engineering"), |
|
|
("Technical Support", "technical-support"), |
|
|
("Web & App Design", "web-app-design"), |
|
|
] |
|
|
COUNTRY_CHOICES = [ |
|
|
"", "USA", "Canada", "UK", "China", "APAC", "EMEA", "LATAM", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", |
|
|
"Costa Rica", "Croatia", "Cyprus", "Czechia", "Denmark", "Estonia", "Europe", |
|
|
"Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Israel", "Italy", "Japan", |
|
|
"Latvia", "Lithuania", "Mexico", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", |
|
|
"Portugal", "Romania", "Singapore", "Slovakia", "Slovenia", "South Korea", "Spain", "Sweden", |
|
|
"Switzerland", "Thailand", "Türkiye", "UAE", "Vietnam" |
|
|
] |
|
|
|
|
|
@mcp.tool(name="search_jobs") |
|
|
def search_jobs_tool(industry: str = "", country: str = "", keyword: str = "", limit: int = 20) -> dict: |
|
|
""" |
|
|
Search remote jobs from the Jobicy API with optional filters. |
|
|
Parameters: |
|
|
industry (str): Company industry to filter jobs by (e.g., 'business', 'design-multimedia'). |
|
|
Leave empty to include all industries. |
|
|
country (str): Region or country to filter jobs by (e.g., 'usa', 'canada'). |
|
|
Leave empty or 'anywhere' to include all locations. |
|
|
keyword (str): Keyword or tag to search for in job listings (e.g., 'python', 'data'). |
|
|
Leave empty for no keyword filtering. |
|
|
limit (int): Number of job results to return, between 1 and 50. Defaults to 20. |
|
|
Returns: |
|
|
dict: A dictionary with a "jobs" key containing a list of job dictionaries with: |
|
|
- title: Job title |
|
|
- company: Company name |
|
|
- location: Job location |
|
|
- url: Application or job posting URL |
|
|
- pubDate: Date posted (YYYY-MM-DD) |
|
|
- salary: Salary range or 'Not specified' |
|
|
|
|
|
Or an "error" key with an error message if the fetch fails. |
|
|
""" |
|
|
base = "https://jobicy.com/api/v2/remote-jobs" |
|
|
params = {"count": max(1, min(limit, 50))} |
|
|
if industry: |
|
|
params["industry"] = industry.lower().replace(" & ", "-").replace(" ", "-") |
|
|
if country and country.lower() != "anywhere": |
|
|
params["geo"] = country.lower() |
|
|
if keyword: |
|
|
params["tag"] = keyword.strip() |
|
|
|
|
|
logger.info(f"Requesting Jobicy API with params: {params}") |
|
|
|
|
|
session = requests.Session() |
|
|
session.headers.update({ |
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0 Safari/537.36", |
|
|
"Accept": "application/json, text/javascript, */*; q=0.01", |
|
|
"Referer": "https://jobicy.com/remote-jobs", |
|
|
"Origin": "https://jobicy.com", |
|
|
}) |
|
|
|
|
|
try: |
|
|
resp = session.get(base, params=params, timeout=10) |
|
|
resp.raise_for_status() |
|
|
jobs_raw = resp.json().get("jobs", []) |
|
|
except Exception as e: |
|
|
return {"error": f"Fetch error: {e}"} |
|
|
|
|
|
def fmt(j): |
|
|
posted = j.get("pubDate", "")[:10] |
|
|
sal_min = j.get("annualSalaryMin") |
|
|
sal_max = j.get("annualSalaryMax") |
|
|
cur = j.get("salaryCurrency", "") |
|
|
salary = f"{sal_min}–{sal_max} {cur}".strip() if sal_min or sal_max else "Not specified" |
|
|
return { |
|
|
"title": j.get("jobTitle", "No Title"), |
|
|
"company": j.get("companyName", ""), |
|
|
"location": j.get("jobGeo", ""), |
|
|
"url": j.get("url", "#"), |
|
|
"pubDate": posted, |
|
|
"salary": salary |
|
|
} |
|
|
|
|
|
sorted_jobs = sorted(jobs_raw, key=lambda x: x.get("pubDate", ""), reverse=True)[:params["count"]] |
|
|
return {"jobs": [fmt(j) for j in sorted_jobs]} |
|
|
|
|
|
def search_jobs_ui(industry, country, keyword, limit): |
|
|
""" |
|
|
Search remote jobs from the Jobicy API with optional filters. |
|
|
|
|
|
Parameters: |
|
|
industry (str): Company industry to filter jobs by (e.g., 'business', 'design-multimedia'). |
|
|
Leave empty to include all industries. |
|
|
country (str): Region or country to filter jobs by (e.g., 'usa', 'canada'). |
|
|
Leave empty or 'anywhere' to include all locations. |
|
|
keyword (str): Keyword or tag to search for in job listings (e.g., 'python', 'data'). |
|
|
Leave empty for no keyword filtering. |
|
|
limit (int): Number of job results to return, between 1 and 50. Defaults to 20. |
|
|
|
|
|
Returns: |
|
|
dict: A dictionary with a "jobs" key containing a list of job dictionaries with: |
|
|
- title: Job title |
|
|
- company: Company name |
|
|
- location: Job location |
|
|
- url: Application or job posting URL |
|
|
- pubDate: Date posted (YYYY-MM-DD) |
|
|
- salary: Salary range or 'Not specified' |
|
|
|
|
|
Or an "error" key with an error message if the fetch fails. |
|
|
""" |
|
|
res = search_jobs_tool(industry=industry, country=country, keyword=keyword, limit=limit) |
|
|
if "error" in res: |
|
|
return f"❌ {res['error']}" |
|
|
if not res["jobs"]: |
|
|
return "No jobs found with these filters." |
|
|
|
|
|
md = "# Remote Jobs Results\n\n" |
|
|
for i, j in enumerate(res["jobs"], 1): |
|
|
search_query = f"{j['title']} {j['company']}" |
|
|
search_url = f"https://www.google.com/search?q={requests.utils.quote(search_query)}" |
|
|
|
|
|
md += ( |
|
|
f"### {i}. {j['title']}\n\n" |
|
|
f"**Company:** {j['company']}\n\n" |
|
|
f"**Location:** {j['location']}\n\n" |
|
|
f"**Salary:** {j['salary']}\n\n" |
|
|
f"**Posted On:** {j['pubDate']}\n\n" |
|
|
f"🔗 [Apply / More Info]({j['url']}) \n" |
|
|
f"🔍 [Google Search]({search_url})\n\n" |
|
|
"---\n\n" |
|
|
) |
|
|
return md |
|
|
|
|
|
app = gr.Interface( |
|
|
fn=search_jobs_ui, |
|
|
inputs=[ |
|
|
gr.Dropdown(label="Company Industry (optional) (Empty = All)", choices=COMPANY_INDUSTRIES, value=""), |
|
|
gr.Dropdown(label="Country / Region (optional) (Empty = Anywhere)", choices=COUNTRY_CHOICES, value=""), |
|
|
gr.Textbox(label="Keyword / Tag (optional)", placeholder="e.g., python, data, web"), |
|
|
gr.Slider(minimum=1, maximum=50, value=20, step=1, label="Number of Results"), |
|
|
], |
|
|
outputs=gr.Markdown(), |
|
|
title="Jobicy Remote Job Search", |
|
|
description="""""Search remote jobs by industry, region, and keyword. Results sorted by most recent. |
|
|
The Apply Now in the Jobicy is only for Paid accounts. |
|
|
If you don't have one, just click the Google Search to search the job link in Google and apply there.""", |
|
|
theme="huggingface" |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
app.launch(mcp_server=True) |
|
|
|