Spaces:
Running
Running
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" | |
] | |
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) | |