jobspy-js 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +99 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/index.cjs +13 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/mcp/index.cjs +114 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.js +1 -1
- package/dist/scraper-CrXYVFMP.cjs +2461 -0
- package/dist/scraper-CrXYVFMP.cjs.map +1 -0
- package/dist/scraper-CuXnl6Gf.js.map +1 -0
- package/package.json +7 -4
- package/vite.config.ts +5 -3
- package/dist/shared/scraper-CuXnl6Gf.js.map +0 -1
- /package/dist/{shared/scraper-CuXnl6Gf.js → scraper-CuXnl6Gf.js} +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const commander = require("commander");
|
|
4
|
+
const scraper = require("../scraper-CrXYVFMP.cjs");
|
|
5
|
+
const node_fs = require("node:fs");
|
|
6
|
+
const program = new commander.Command();
|
|
7
|
+
program.name("jobspy").description(
|
|
8
|
+
"Job scraper for LinkedIn, Indeed, Glassdoor, Google, ZipRecruiter, Bayt, Naukri & BDJobs"
|
|
9
|
+
).version("1.0.0").option(
|
|
10
|
+
"-s, --site <sites...>",
|
|
11
|
+
"Job boards to scrape (linkedin, indeed, zip_recruiter, glassdoor, google, bayt, naukri, bdjobs)"
|
|
12
|
+
).option("-q, --search-term <term>", "Search term").option("--google-search-term <term>", "Google-specific search term").option("-l, --location <location>", "Job location").option("-d, --distance <miles>", "Distance in miles", "50").option("-r, --remote", "Filter for remote jobs").option(
|
|
13
|
+
"-t, --job-type <type>",
|
|
14
|
+
"Job type (fulltime, parttime, contract, internship)"
|
|
15
|
+
).option("--easy-apply", "Filter for easy apply jobs").option("-n, --results <count>", "Number of results wanted", "15").option(
|
|
16
|
+
"-c, --country <country>",
|
|
17
|
+
"Country for Indeed/Glassdoor",
|
|
18
|
+
"usa"
|
|
19
|
+
).option(
|
|
20
|
+
"-p, --proxies <proxies...>",
|
|
21
|
+
"Proxy servers (user:pass@host:port)"
|
|
22
|
+
).option(
|
|
23
|
+
"--format <format>",
|
|
24
|
+
"Description format (markdown, html, plain)",
|
|
25
|
+
"markdown"
|
|
26
|
+
).option("--linkedin-fetch-description", "Fetch full LinkedIn descriptions").option(
|
|
27
|
+
"--linkedin-company-ids <ids...>",
|
|
28
|
+
"LinkedIn company IDs to filter"
|
|
29
|
+
).option("--offset <offset>", "Start from offset", "0").option(
|
|
30
|
+
"--hours-old <hours>",
|
|
31
|
+
"Filter jobs posted within N hours"
|
|
32
|
+
).option("--enforce-annual-salary", "Convert all salaries to annual").option("-v, --verbose <level>", "Verbosity (0=errors, 1=warnings, 2=all)", "0").option("-o, --output <file>", "Output file path (JSON or CSV based on extension)").action(async (opts) => {
|
|
33
|
+
try {
|
|
34
|
+
const result = await scraper.scrapeJobs({
|
|
35
|
+
site_name: opts.site,
|
|
36
|
+
search_term: opts.searchTerm,
|
|
37
|
+
google_search_term: opts.googleSearchTerm,
|
|
38
|
+
location: opts.location,
|
|
39
|
+
distance: parseInt(opts.distance),
|
|
40
|
+
is_remote: opts.remote ?? false,
|
|
41
|
+
job_type: opts.jobType,
|
|
42
|
+
easy_apply: opts.easyApply,
|
|
43
|
+
results_wanted: parseInt(opts.results),
|
|
44
|
+
country_indeed: opts.country,
|
|
45
|
+
proxies: opts.proxies,
|
|
46
|
+
description_format: opts.format,
|
|
47
|
+
linkedin_fetch_description: opts.linkedinFetchDescription,
|
|
48
|
+
linkedin_company_ids: opts.linkedinCompanyIds?.map(Number),
|
|
49
|
+
offset: parseInt(opts.offset),
|
|
50
|
+
hours_old: opts.hoursOld ? parseInt(opts.hoursOld) : void 0,
|
|
51
|
+
enforce_annual_salary: opts.enforceAnnualSalary ?? false,
|
|
52
|
+
verbose: parseInt(opts.verbose)
|
|
53
|
+
});
|
|
54
|
+
console.log(`Found ${result.jobs.length} jobs`);
|
|
55
|
+
if (opts.output) {
|
|
56
|
+
const outPath = opts.output;
|
|
57
|
+
if (outPath.endsWith(".csv")) {
|
|
58
|
+
node_fs.writeFileSync(outPath, jobsToCsv(result.jobs));
|
|
59
|
+
console.log(`Results written to ${outPath}`);
|
|
60
|
+
} else {
|
|
61
|
+
node_fs.writeFileSync(outPath, JSON.stringify(result.jobs, null, 2));
|
|
62
|
+
console.log(`Results written to ${outPath}`);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
for (const job of result.jobs) {
|
|
66
|
+
const line = [
|
|
67
|
+
job.site?.padEnd(14),
|
|
68
|
+
(job.title ?? "").slice(0, 40).padEnd(42),
|
|
69
|
+
(job.company ?? "").slice(0, 20).padEnd(22),
|
|
70
|
+
(job.location ?? "").slice(0, 25).padEnd(27),
|
|
71
|
+
job.date_posted ?? ""
|
|
72
|
+
].join("");
|
|
73
|
+
console.log(line);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(`Error: ${e.message}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
function jobsToCsv(jobs) {
|
|
82
|
+
if (jobs.length === 0) return "";
|
|
83
|
+
const headers = Object.keys(jobs[0]);
|
|
84
|
+
const escape = (val) => {
|
|
85
|
+
if (val == null) return "";
|
|
86
|
+
const str = String(val);
|
|
87
|
+
if (str.includes(",") || str.includes('"') || str.includes("\n")) {
|
|
88
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
89
|
+
}
|
|
90
|
+
return str;
|
|
91
|
+
};
|
|
92
|
+
const lines = [headers.join(",")];
|
|
93
|
+
for (const job of jobs) {
|
|
94
|
+
lines.push(headers.map((h) => escape(job[h])).join(","));
|
|
95
|
+
}
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
program.parse();
|
|
99
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/cli/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { scrapeJobs } from \"../scraper\";\nimport { Site } from \"../types\";\nimport { writeFileSync } from \"node:fs\";\n\nconst program = new Command();\n\nprogram\n .name(\"jobspy\")\n .description(\n \"Job scraper for LinkedIn, Indeed, Glassdoor, Google, ZipRecruiter, Bayt, Naukri & BDJobs\",\n )\n .version(\"1.0.0\")\n .option(\n \"-s, --site <sites...>\",\n \"Job boards to scrape (linkedin, indeed, zip_recruiter, glassdoor, google, bayt, naukri, bdjobs)\",\n )\n .option(\"-q, --search-term <term>\", \"Search term\")\n .option(\"--google-search-term <term>\", \"Google-specific search term\")\n .option(\"-l, --location <location>\", \"Job location\")\n .option(\"-d, --distance <miles>\", \"Distance in miles\", \"50\")\n .option(\"-r, --remote\", \"Filter for remote jobs\")\n .option(\n \"-t, --job-type <type>\",\n \"Job type (fulltime, parttime, contract, internship)\",\n )\n .option(\"--easy-apply\", \"Filter for easy apply jobs\")\n .option(\"-n, --results <count>\", \"Number of results wanted\", \"15\")\n .option(\n \"-c, --country <country>\",\n \"Country for Indeed/Glassdoor\",\n \"usa\",\n )\n .option(\n \"-p, --proxies <proxies...>\",\n \"Proxy servers (user:pass@host:port)\",\n )\n .option(\n \"--format <format>\",\n \"Description format (markdown, html, plain)\",\n \"markdown\",\n )\n .option(\"--linkedin-fetch-description\", \"Fetch full LinkedIn descriptions\")\n .option(\n \"--linkedin-company-ids <ids...>\",\n \"LinkedIn company IDs to filter\",\n )\n .option(\"--offset <offset>\", \"Start from offset\", \"0\")\n .option(\n \"--hours-old <hours>\",\n \"Filter jobs posted within N hours\",\n )\n .option(\"--enforce-annual-salary\", \"Convert all salaries to annual\")\n .option(\"-v, --verbose <level>\", \"Verbosity (0=errors, 1=warnings, 2=all)\", \"0\")\n .option(\"-o, --output <file>\", \"Output file path (JSON or CSV based on extension)\")\n .action(async (opts) => {\n try {\n const result = await scrapeJobs({\n site_name: opts.site,\n search_term: opts.searchTerm,\n google_search_term: opts.googleSearchTerm,\n location: opts.location,\n distance: parseInt(opts.distance),\n is_remote: opts.remote ?? false,\n job_type: opts.jobType,\n easy_apply: opts.easyApply,\n results_wanted: parseInt(opts.results),\n country_indeed: opts.country,\n proxies: opts.proxies,\n description_format: opts.format,\n linkedin_fetch_description: opts.linkedinFetchDescription,\n linkedin_company_ids: opts.linkedinCompanyIds?.map(Number),\n offset: parseInt(opts.offset),\n hours_old: opts.hoursOld ? parseInt(opts.hoursOld) : undefined,\n enforce_annual_salary: opts.enforceAnnualSalary ?? false,\n verbose: parseInt(opts.verbose),\n });\n\n console.log(`Found ${result.jobs.length} jobs`);\n\n if (opts.output) {\n const outPath = opts.output as string;\n if (outPath.endsWith(\".csv\")) {\n writeFileSync(outPath, jobsToCsv(result.jobs));\n console.log(`Results written to ${outPath}`);\n } else {\n writeFileSync(outPath, JSON.stringify(result.jobs, null, 2));\n console.log(`Results written to ${outPath}`);\n }\n } else {\n // Print summary table to stdout\n for (const job of result.jobs) {\n const line = [\n job.site?.padEnd(14),\n (job.title ?? \"\").slice(0, 40).padEnd(42),\n (job.company ?? \"\").slice(0, 20).padEnd(22),\n (job.location ?? \"\").slice(0, 25).padEnd(27),\n job.date_posted ?? \"\",\n ].join(\"\");\n console.log(line);\n }\n }\n } catch (e: any) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n });\n\nfunction jobsToCsv(jobs: any[]): string {\n if (jobs.length === 0) return \"\";\n const headers = Object.keys(jobs[0]);\n const escape = (val: any): string => {\n if (val == null) return \"\";\n const str = String(val);\n if (str.includes(\",\") || str.includes('\"') || str.includes(\"\\n\")) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n const lines = [headers.join(\",\")];\n for (const job of jobs) {\n lines.push(headers.map((h) => escape(job[h])).join(\",\"));\n }\n return lines.join(\"\\n\");\n}\n\nprogram.parse();\n"],"names":["Command","scrapeJobs","writeFileSync"],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { s as scrapeJobs } from "../
|
|
3
|
+
import { s as scrapeJobs } from "../scraper-CuXnl6Gf.js";
|
|
4
4
|
import { writeFileSync } from "node:fs";
|
|
5
5
|
const program = new Command();
|
|
6
6
|
program.name("jobspy").description(
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const scraper = require("./scraper-CrXYVFMP.cjs");
|
|
4
|
+
exports.CompensationInterval = scraper.CompensationInterval;
|
|
5
|
+
exports.DESIRED_COLUMNS = scraper.DESIRED_COLUMNS;
|
|
6
|
+
exports.DescriptionFormat = scraper.DescriptionFormat;
|
|
7
|
+
exports.JobType = scraper.JobType;
|
|
8
|
+
exports.SalarySource = scraper.SalarySource;
|
|
9
|
+
exports.Site = scraper.Site;
|
|
10
|
+
exports.displayLocation = scraper.displayLocation;
|
|
11
|
+
exports.getCountry = scraper.getCountry;
|
|
12
|
+
exports.scrapeJobs = scraper.scrapeJobs;
|
|
13
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
const mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4
|
+
const stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const zod = require("zod");
|
|
6
|
+
const scraper = require("../scraper-CrXYVFMP.cjs");
|
|
7
|
+
const server = new mcp_js.McpServer({
|
|
8
|
+
name: "jobspy",
|
|
9
|
+
version: "1.0.0"
|
|
10
|
+
});
|
|
11
|
+
server.tool(
|
|
12
|
+
"scrape_jobs",
|
|
13
|
+
"Scrape job listings from multiple job boards (LinkedIn, Indeed, Glassdoor, Google, ZipRecruiter, Bayt, Naukri, BDJobs)",
|
|
14
|
+
{
|
|
15
|
+
site_name: zod.z.array(
|
|
16
|
+
zod.z.enum([
|
|
17
|
+
"linkedin",
|
|
18
|
+
"indeed",
|
|
19
|
+
"zip_recruiter",
|
|
20
|
+
"glassdoor",
|
|
21
|
+
"google",
|
|
22
|
+
"bayt",
|
|
23
|
+
"naukri",
|
|
24
|
+
"bdjobs"
|
|
25
|
+
])
|
|
26
|
+
).optional().describe(
|
|
27
|
+
"Job boards to scrape. Defaults to all. Options: linkedin, indeed, zip_recruiter, glassdoor, google, bayt, naukri, bdjobs"
|
|
28
|
+
),
|
|
29
|
+
search_term: zod.z.string().optional().describe("Search term / job title to search for"),
|
|
30
|
+
google_search_term: zod.z.string().optional().describe("Google-specific search term (overrides search_term for Google)"),
|
|
31
|
+
location: zod.z.string().optional().describe("Job location (e.g. 'San Francisco, CA')"),
|
|
32
|
+
distance: zod.z.number().optional().default(50).describe("Search radius in miles"),
|
|
33
|
+
is_remote: zod.z.boolean().optional().default(false).describe("Filter for remote jobs"),
|
|
34
|
+
job_type: zod.z.enum(["fulltime", "parttime", "contract", "internship"]).optional().describe("Filter by job type"),
|
|
35
|
+
results_wanted: zod.z.number().optional().default(10).describe("Number of results to return per site"),
|
|
36
|
+
country_indeed: zod.z.string().optional().default("usa").describe("Country for Indeed/Glassdoor (e.g. 'usa', 'uk', 'canada')"),
|
|
37
|
+
hours_old: zod.z.number().optional().describe("Filter jobs posted within the last N hours"),
|
|
38
|
+
description_format: zod.z.enum(["markdown", "html", "plain"]).optional().default("markdown").describe("Format for job descriptions"),
|
|
39
|
+
linkedin_fetch_description: zod.z.boolean().optional().default(false).describe("Fetch full descriptions from LinkedIn (slower)")
|
|
40
|
+
},
|
|
41
|
+
async (params) => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await scraper.scrapeJobs({
|
|
44
|
+
site_name: params.site_name,
|
|
45
|
+
search_term: params.search_term,
|
|
46
|
+
google_search_term: params.google_search_term,
|
|
47
|
+
location: params.location,
|
|
48
|
+
distance: params.distance,
|
|
49
|
+
is_remote: params.is_remote,
|
|
50
|
+
job_type: params.job_type,
|
|
51
|
+
results_wanted: params.results_wanted,
|
|
52
|
+
country_indeed: params.country_indeed,
|
|
53
|
+
hours_old: params.hours_old,
|
|
54
|
+
description_format: params.description_format,
|
|
55
|
+
linkedin_fetch_description: params.linkedin_fetch_description
|
|
56
|
+
});
|
|
57
|
+
if (result.jobs.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: "No jobs found matching the search criteria."
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const summary = result.jobs.map((job, i) => {
|
|
68
|
+
const parts = [
|
|
69
|
+
`${i + 1}. **${job.title}**`,
|
|
70
|
+
` Company: ${job.company ?? "N/A"}`,
|
|
71
|
+
` Location: ${job.location ?? "N/A"}${job.is_remote ? " (Remote)" : ""}`,
|
|
72
|
+
` URL: ${job.job_url}`
|
|
73
|
+
];
|
|
74
|
+
if (job.date_posted) parts.push(` Posted: ${job.date_posted}`);
|
|
75
|
+
if (job.min_amount && job.max_amount) {
|
|
76
|
+
parts.push(
|
|
77
|
+
` Salary: ${job.currency ?? "$"}${job.min_amount.toLocaleString()} - ${job.currency ?? "$"}${job.max_amount.toLocaleString()} (${job.interval ?? "yearly"})`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (job.job_type) parts.push(` Type: ${job.job_type}`);
|
|
81
|
+
return parts.join("\n");
|
|
82
|
+
}).join("\n\n");
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: `Found ${result.jobs.length} jobs:
|
|
88
|
+
|
|
89
|
+
${summary}`
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Error scraping jobs: ${e.message}`
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
isError: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
async function main() {
|
|
107
|
+
const transport = new stdio_js.StdioServerTransport();
|
|
108
|
+
await server.connect(transport);
|
|
109
|
+
}
|
|
110
|
+
main().catch((e) => {
|
|
111
|
+
console.error("MCP server error:", e);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/mcp/index.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\nimport { scrapeJobs } from \"../scraper\";\n\nconst server = new McpServer({\n name: \"jobspy\",\n version: \"1.0.0\",\n});\n\nserver.tool(\n \"scrape_jobs\",\n \"Scrape job listings from multiple job boards (LinkedIn, Indeed, Glassdoor, Google, ZipRecruiter, Bayt, Naukri, BDJobs)\",\n {\n site_name: z\n .array(\n z.enum([\n \"linkedin\",\n \"indeed\",\n \"zip_recruiter\",\n \"glassdoor\",\n \"google\",\n \"bayt\",\n \"naukri\",\n \"bdjobs\",\n ]),\n )\n .optional()\n .describe(\n \"Job boards to scrape. Defaults to all. Options: linkedin, indeed, zip_recruiter, glassdoor, google, bayt, naukri, bdjobs\",\n ),\n search_term: z\n .string()\n .optional()\n .describe(\"Search term / job title to search for\"),\n google_search_term: z\n .string()\n .optional()\n .describe(\"Google-specific search term (overrides search_term for Google)\"),\n location: z.string().optional().describe(\"Job location (e.g. 'San Francisco, CA')\"),\n distance: z\n .number()\n .optional()\n .default(50)\n .describe(\"Search radius in miles\"),\n is_remote: z.boolean().optional().default(false).describe(\"Filter for remote jobs\"),\n job_type: z\n .enum([\"fulltime\", \"parttime\", \"contract\", \"internship\"])\n .optional()\n .describe(\"Filter by job type\"),\n results_wanted: z\n .number()\n .optional()\n .default(10)\n .describe(\"Number of results to return per site\"),\n country_indeed: z\n .string()\n .optional()\n .default(\"usa\")\n .describe(\"Country for Indeed/Glassdoor (e.g. 'usa', 'uk', 'canada')\"),\n hours_old: z\n .number()\n .optional()\n .describe(\"Filter jobs posted within the last N hours\"),\n description_format: z\n .enum([\"markdown\", \"html\", \"plain\"])\n .optional()\n .default(\"markdown\")\n .describe(\"Format for job descriptions\"),\n linkedin_fetch_description: z\n .boolean()\n .optional()\n .default(false)\n .describe(\"Fetch full descriptions from LinkedIn (slower)\"),\n },\n async (params) => {\n try {\n const result = await scrapeJobs({\n site_name: params.site_name,\n search_term: params.search_term,\n google_search_term: params.google_search_term,\n location: params.location,\n distance: params.distance,\n is_remote: params.is_remote,\n job_type: params.job_type,\n results_wanted: params.results_wanted,\n country_indeed: params.country_indeed,\n hours_old: params.hours_old,\n description_format: params.description_format,\n linkedin_fetch_description: params.linkedin_fetch_description,\n });\n\n if (result.jobs.length === 0) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: \"No jobs found matching the search criteria.\",\n },\n ],\n };\n }\n\n // Format jobs as a readable summary + structured data\n const summary = result.jobs\n .map((job, i) => {\n const parts = [\n `${i + 1}. **${job.title}**`,\n ` Company: ${job.company ?? \"N/A\"}`,\n ` Location: ${job.location ?? \"N/A\"}${job.is_remote ? \" (Remote)\" : \"\"}`,\n ` URL: ${job.job_url}`,\n ];\n if (job.date_posted) parts.push(` Posted: ${job.date_posted}`);\n if (job.min_amount && job.max_amount) {\n parts.push(\n ` Salary: ${job.currency ?? \"$\"}${job.min_amount.toLocaleString()} - ${job.currency ?? \"$\"}${job.max_amount.toLocaleString()} (${job.interval ?? \"yearly\"})`,\n );\n }\n if (job.job_type) parts.push(` Type: ${job.job_type}`);\n return parts.join(\"\\n\");\n })\n .join(\"\\n\\n\");\n\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Found ${result.jobs.length} jobs:\\n\\n${summary}`,\n },\n ],\n };\n } catch (e: any) {\n return {\n content: [\n {\n type: \"text\" as const,\n text: `Error scraping jobs: ${e.message}`,\n },\n ],\n isError: true,\n };\n }\n },\n);\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((e) => {\n console.error(\"MCP server error:\", e);\n process.exit(1);\n});\n"],"names":["McpServer","z","scrapeJobs","StdioServerTransport"],"mappings":"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"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { s as scrapeJobs } from "../
|
|
5
|
+
import { s as scrapeJobs } from "../scraper-CuXnl6Gf.js";
|
|
6
6
|
const server = new McpServer({
|
|
7
7
|
name: "jobspy",
|
|
8
8
|
version: "1.0.0"
|