jobseek-mcp 0.3.0 → 0.5.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/index.js +19 -2
- package/dist/prompts.d.ts +50 -0
- package/dist/prompts.js +100 -0
- package/dist/tools/launch-pad.d.ts +20 -0
- package/dist/tools/launch-pad.js +109 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { optimizeResumeTool, handleOptimizeResume } from "./tools/optimize-resume.js";
|
|
6
6
|
import { downloadResumePdfTool, handleDownloadResumePdf } from "./tools/download-resume-pdf.js";
|
|
7
7
|
import { uploadResumeTool, handleUploadResume } from "./tools/upload-resume.js";
|
|
8
|
+
import { launchPadTool, handleLaunchPad } from "./tools/launch-pad.js";
|
|
8
9
|
import { allResources, handleReadResource } from "./resources.js";
|
|
10
|
+
import { allPrompts, getPromptMessages } from "./prompts.js";
|
|
9
11
|
// Configuration
|
|
10
12
|
const JOBSEEK_API_URL = process.env.JOBSEEK_API_URL || "https://jobseek-iota.vercel.app";
|
|
11
13
|
const JOBSEEK_API_KEY = process.env.JOBSEEK_API_KEY;
|
|
12
14
|
// Create the MCP server
|
|
13
15
|
const server = new Server({
|
|
14
16
|
name: "jobseek-mcp",
|
|
15
|
-
version: "0.
|
|
17
|
+
version: "0.5.0",
|
|
16
18
|
}, {
|
|
17
19
|
capabilities: {
|
|
18
20
|
tools: {},
|
|
19
21
|
resources: {},
|
|
22
|
+
prompts: {},
|
|
20
23
|
},
|
|
21
24
|
});
|
|
22
25
|
// List available tools
|
|
@@ -26,6 +29,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
26
29
|
optimizeResumeTool,
|
|
27
30
|
downloadResumePdfTool,
|
|
28
31
|
uploadResumeTool,
|
|
32
|
+
launchPadTool,
|
|
29
33
|
],
|
|
30
34
|
};
|
|
31
35
|
});
|
|
@@ -39,6 +43,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
39
43
|
return handleDownloadResumePdf(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
40
44
|
case "upload_resume":
|
|
41
45
|
return handleUploadResume(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
46
|
+
case "launch_pad":
|
|
47
|
+
return handleLaunchPad(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
42
48
|
default:
|
|
43
49
|
throw new Error(`Unknown tool: ${name}`);
|
|
44
50
|
}
|
|
@@ -54,6 +60,17 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
54
60
|
const { uri } = request.params;
|
|
55
61
|
return handleReadResource(uri);
|
|
56
62
|
});
|
|
63
|
+
// List available prompts
|
|
64
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
65
|
+
return {
|
|
66
|
+
prompts: allPrompts,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// Handle prompt requests
|
|
70
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
71
|
+
const { name, arguments: args } = request.params;
|
|
72
|
+
return getPromptMessages(name, args || {});
|
|
73
|
+
});
|
|
57
74
|
// Start the server
|
|
58
75
|
async function main() {
|
|
59
76
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const launchPadPrompt: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
arguments: {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
required: boolean;
|
|
8
|
+
}[];
|
|
9
|
+
};
|
|
10
|
+
export declare const optimizeResumePrompt: {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
arguments: {
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
required: boolean;
|
|
17
|
+
}[];
|
|
18
|
+
};
|
|
19
|
+
export declare const reviewMatchesPrompt: {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
arguments: never[];
|
|
23
|
+
};
|
|
24
|
+
export declare const uploadResumePrompt: {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
arguments: {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
required: boolean;
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
export declare const allPrompts: {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
arguments: {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
required: boolean;
|
|
40
|
+
}[];
|
|
41
|
+
}[];
|
|
42
|
+
export declare function getPromptMessages(name: string, args: Record<string, string>): {
|
|
43
|
+
messages: Array<{
|
|
44
|
+
role: string;
|
|
45
|
+
content: {
|
|
46
|
+
type: string;
|
|
47
|
+
text: string;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
};
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// MCP Prompts - Slash commands for common workflows
|
|
2
|
+
// Prompt definitions
|
|
3
|
+
export const launchPadPrompt = {
|
|
4
|
+
name: "launch_pad",
|
|
5
|
+
description: "Open job boards for your job search. Uses your resume to determine search terms.",
|
|
6
|
+
arguments: [
|
|
7
|
+
{
|
|
8
|
+
name: "query",
|
|
9
|
+
description: "Optional: custom search query to override resume-based search",
|
|
10
|
+
required: false
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
export const optimizeResumePrompt = {
|
|
15
|
+
name: "optimize_resume",
|
|
16
|
+
description: "Optimize your resume for ATS (Applicant Tracking Systems).",
|
|
17
|
+
arguments: [
|
|
18
|
+
{
|
|
19
|
+
name: "feedback",
|
|
20
|
+
description: "Optional: specific feedback or areas to focus on",
|
|
21
|
+
required: false
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
export const reviewMatchesPrompt = {
|
|
26
|
+
name: "review_matches",
|
|
27
|
+
description: "Review and discuss your recent job matches from the Chrome extension.",
|
|
28
|
+
arguments: []
|
|
29
|
+
};
|
|
30
|
+
export const uploadResumePrompt = {
|
|
31
|
+
name: "upload_resume",
|
|
32
|
+
description: "Upload a PDF resume to JobSeek.",
|
|
33
|
+
arguments: [
|
|
34
|
+
{
|
|
35
|
+
name: "path",
|
|
36
|
+
description: "Path to the PDF file (e.g., ~/Downloads/resume.pdf)",
|
|
37
|
+
required: true
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
};
|
|
41
|
+
// All prompts
|
|
42
|
+
export const allPrompts = [
|
|
43
|
+
launchPadPrompt,
|
|
44
|
+
optimizeResumePrompt,
|
|
45
|
+
reviewMatchesPrompt,
|
|
46
|
+
uploadResumePrompt,
|
|
47
|
+
];
|
|
48
|
+
// Generate prompt messages
|
|
49
|
+
export function getPromptMessages(name, args) {
|
|
50
|
+
switch (name) {
|
|
51
|
+
case "launch_pad":
|
|
52
|
+
const query = args.query ? ` for "${args.query}"` : "";
|
|
53
|
+
return {
|
|
54
|
+
messages: [{
|
|
55
|
+
role: "user",
|
|
56
|
+
content: {
|
|
57
|
+
type: "text",
|
|
58
|
+
text: `Open the job search launch pad${query}. Use the launch_pad tool to open job boards (LinkedIn, Indeed, Glassdoor, etc.) in my browser.`
|
|
59
|
+
}
|
|
60
|
+
}]
|
|
61
|
+
};
|
|
62
|
+
case "optimize_resume":
|
|
63
|
+
const feedback = args.feedback ? `\n\nFocus on: ${args.feedback}` : "";
|
|
64
|
+
return {
|
|
65
|
+
messages: [{
|
|
66
|
+
role: "user",
|
|
67
|
+
content: {
|
|
68
|
+
type: "text",
|
|
69
|
+
text: `Optimize my resume for ATS. Use the optimize_resume tool to enhance my resume for applicant tracking systems.${feedback}`
|
|
70
|
+
}
|
|
71
|
+
}]
|
|
72
|
+
};
|
|
73
|
+
case "review_matches":
|
|
74
|
+
return {
|
|
75
|
+
messages: [{
|
|
76
|
+
role: "user",
|
|
77
|
+
content: {
|
|
78
|
+
type: "text",
|
|
79
|
+
text: `Review my recent job matches. Read my applications from JobSeek (jobseek://applications resource) and help me understand:
|
|
80
|
+
1. Which jobs are the best matches?
|
|
81
|
+
2. What are my key strengths for these roles?
|
|
82
|
+
3. What gaps should I address?
|
|
83
|
+
4. Which should I prioritize applying to?`
|
|
84
|
+
}
|
|
85
|
+
}]
|
|
86
|
+
};
|
|
87
|
+
case "upload_resume":
|
|
88
|
+
return {
|
|
89
|
+
messages: [{
|
|
90
|
+
role: "user",
|
|
91
|
+
content: {
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `Upload my resume from ${args.path} to JobSeek using the upload_resume tool.`
|
|
94
|
+
}
|
|
95
|
+
}]
|
|
96
|
+
};
|
|
97
|
+
default:
|
|
98
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const launchPadTool: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: string;
|
|
6
|
+
properties: {
|
|
7
|
+
query: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
required: never[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export declare function handleLaunchPad(args: unknown, apiUrl: string, apiKey?: string): Promise<{
|
|
16
|
+
content: Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
text: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
// Tool definition
|
|
6
|
+
export const launchPadTool = {
|
|
7
|
+
name: "launch_pad",
|
|
8
|
+
description: "Open job search tabs across major job boards (LinkedIn, Indeed, Glassdoor, ZipRecruiter, Wellfound, Dice). Uses your resume to determine the search query, or you can provide a custom query.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
query: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Optional custom search query. If not provided, uses your most recent job title from your resume."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
required: []
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
// Input validation
|
|
21
|
+
const LaunchPadInput = z.object({
|
|
22
|
+
query: z.string().optional()
|
|
23
|
+
});
|
|
24
|
+
// Tool handler
|
|
25
|
+
export async function handleLaunchPad(args, apiUrl, apiKey) {
|
|
26
|
+
const input = LaunchPadInput.parse(args);
|
|
27
|
+
if (!apiKey) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `❌ **API Key Required**\n\nGet your API key at: ${apiUrl}/dashboard/api-keys`
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
// Fetch launchpad links from API
|
|
37
|
+
const queryParam = input.query ? `?query=${encodeURIComponent(input.query)}` : '';
|
|
38
|
+
const response = await fetch(`${apiUrl}/api/launchpad${queryParam}`, {
|
|
39
|
+
headers: {
|
|
40
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
if (response.status === 401) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: `❌ **Invalid API Key**\n\nGenerate a new key at: ${apiUrl}/dashboard/api-keys`
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const error = await response.json().catch(() => ({}));
|
|
53
|
+
return {
|
|
54
|
+
content: [{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: `❌ **Error**\n\n${error.error || 'Failed to get launchpad links'}`
|
|
57
|
+
}]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
const { searchQuery, links } = data;
|
|
62
|
+
// Open each URL in the default browser
|
|
63
|
+
const opened = [];
|
|
64
|
+
for (const link of links) {
|
|
65
|
+
try {
|
|
66
|
+
// Works on macOS. For cross-platform: use 'start' on Windows, 'xdg-open' on Linux
|
|
67
|
+
await execAsync(`open "${link.url}"`);
|
|
68
|
+
opened.push(link.name);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error(`Failed to open ${link.name}:`, err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (opened.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `❌ **Failed to open tabs**\n\nCould not open browser tabs. You may need to open them manually.`
|
|
79
|
+
}]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `🚀 **Launch Pad Activated!**
|
|
86
|
+
|
|
87
|
+
**Search Query:** ${searchQuery}
|
|
88
|
+
|
|
89
|
+
**Opened ${opened.length} job boards:**
|
|
90
|
+
${opened.map(name => `- ${name}`).join('\n')}
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
**Next steps:**
|
|
95
|
+
1. Browse the job listings
|
|
96
|
+
2. When you find interesting jobs, click the JobSeek extension to analyze them
|
|
97
|
+
3. Return here to discuss your matches: "What are my top matches from today?"`
|
|
98
|
+
}]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
content: [{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: `❌ **Error**\n\nFailed to launch job boards: ${error.message}`
|
|
106
|
+
}]
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|