File size: 7,537 Bytes
0a4b4e5
f77befc
8ae67db
bfad4de
3c1606d
8ae67db
 
 
ded9789
 
8a5fe3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c77afe4
 
8a5fe3d
 
 
c77afe4
8a5fe3d
0a4b4e5
5d20b78
e9525f9
01c0c63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10adf53
90365b1
ded9789
090b8ec
ded9789
 
e9525f9
090b8ec
ded9789
10adf53
bbcda92
 
 
 
 
 
 
 
 
064e5aa
bbcda92
ded9789
 
064e5aa
ded9789
 
10adf53
ded9789
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10adf53
ded9789
01c0c63
bbcda92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9525f9
ded9789
 
 
10adf53
 
ded9789
 
d39b588
 
 
ded9789
 
e80edff
 
 
 
d39b588
 
 
f77befc
ded9789
3c1606d
0a4b4e5
467f662
4ba0c26
e80edff
 
 
90365b1
4ba0c26
f77befc
3c1606d
5d5017c
 
 
f77befc
0a4b4e5
 
 
bfad4de
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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)