jobseek-mcp 0.5.1 → 0.7.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/README.md +131 -53
- package/dist/index.js +6 -72
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +87 -0
- package/dist/resources.d.ts +6 -0
- package/dist/resources.js +102 -2
- package/dist/server.d.ts +20 -0
- package/dist/server.js +94 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +62 -0
- package/dist/standalone.d.ts +1 -0
- package/dist/standalone.js +5 -0
- package/dist/tools/analyze-matches.d.ts +25 -0
- package/dist/tools/analyze-matches.js +149 -0
- package/dist/tools/evaluate-job.d.ts +40 -0
- package/dist/tools/evaluate-job.js +169 -0
- package/dist/tools/get-form-data.d.ts +23 -0
- package/dist/tools/get-form-data.js +204 -0
- package/dist/tools/get-resume-link.d.ts +15 -0
- package/dist/tools/get-resume-link.js +82 -0
- package/dist/tools/launch-pad.d.ts +4 -0
- package/dist/tools/launch-pad.js +61 -20
- package/dist/tools/my-applications.d.ts +24 -0
- package/dist/tools/my-applications.js +142 -0
- package/dist/tools/my-profile.d.ts +15 -0
- package/dist/tools/my-profile.js +120 -0
- package/package.json +14 -5
package/README.md
CHANGED
|
@@ -1,24 +1,58 @@
|
|
|
1
1
|
# JobSeek MCP Server
|
|
2
2
|
|
|
3
|
-
AI-powered job search automation for Claude
|
|
3
|
+
AI-powered job search automation for Claude. Works with Claude for macOS + Claude for Chrome for fully automated job searching and application assistance.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/jobseek-mcp)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **🚀 Automated Job Search** - Claude navigates job boards, finds matching roles, and helps you apply
|
|
10
|
+
- **📋 Smart Form Filling** - Get your profile data ready for any application form
|
|
11
|
+
- **🎯 Job Matching** - AI-powered scoring to find the best fit roles
|
|
12
|
+
- **📄 Resume Optimization** - ATS-friendly resume enhancements
|
|
4
13
|
|
|
5
14
|
## Quick Start
|
|
6
15
|
|
|
7
|
-
###
|
|
16
|
+
### Install from npm
|
|
17
|
+
|
|
8
18
|
```bash
|
|
9
|
-
|
|
10
|
-
npm install
|
|
11
|
-
npm run build
|
|
19
|
+
npx jobseek-mcp
|
|
12
20
|
```
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
Or install globally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g jobseek-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 1. Generate an API Key
|
|
15
29
|
- Go to https://getjobseek.com/dashboard/api-keys
|
|
16
30
|
- Click "Generate Key"
|
|
17
31
|
- Copy the key (you won't be able to see it again!)
|
|
18
32
|
|
|
19
|
-
###
|
|
33
|
+
### 2. Configure Claude
|
|
34
|
+
|
|
35
|
+
#### For Claude for macOS / Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to your Claude config (`~/Library/Application Support/Claude/claude_desktop_config.json` on Mac):
|
|
20
38
|
|
|
21
|
-
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"jobseek": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["jobseek-mcp"],
|
|
45
|
+
"env": {
|
|
46
|
+
"JOBSEEK_API_KEY": "sk_live_your_key_here"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### For Claude Code
|
|
54
|
+
|
|
55
|
+
Add to `~/.claude.json`:
|
|
22
56
|
|
|
23
57
|
```json
|
|
24
58
|
{
|
|
@@ -27,10 +61,9 @@ Open `~/.claude.json` in your editor and find your project's `mcpServers` sectio
|
|
|
27
61
|
"mcpServers": {
|
|
28
62
|
"jobseek": {
|
|
29
63
|
"type": "stdio",
|
|
30
|
-
"command": "
|
|
31
|
-
"args": ["
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["jobseek-mcp"],
|
|
32
66
|
"env": {
|
|
33
|
-
"JOBSEEK_API_URL": "https://getjobseek.com",
|
|
34
67
|
"JOBSEEK_API_KEY": "sk_live_your_key_here"
|
|
35
68
|
}
|
|
36
69
|
}
|
|
@@ -40,81 +73,126 @@ Open `~/.claude.json` in your editor and find your project's `mcpServers` sectio
|
|
|
40
73
|
}
|
|
41
74
|
```
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
- `/path/to/your/project` with your project directory
|
|
45
|
-
- `/path/to/jobseek-mcp/dist/index.js` with the actual path to the built MCP server
|
|
46
|
-
- `sk_live_your_key_here` with your generated API key
|
|
76
|
+
### 3. Restart Claude
|
|
47
77
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
After saving the config, restart Claude Code. Type `/mcp` to verify the server is connected.
|
|
78
|
+
After saving the config, restart Claude. Type `/mcp` to verify the server is connected.
|
|
51
79
|
|
|
52
80
|
## Available Tools
|
|
53
81
|
|
|
54
|
-
### `
|
|
55
|
-
|
|
82
|
+
### `my_profile`
|
|
83
|
+
Get your profile information including resume summary, skills, and experience.
|
|
84
|
+
|
|
85
|
+
### `get_form_data`
|
|
86
|
+
Get structured profile data optimized for filling job application forms. Returns:
|
|
87
|
+
- Personal info (name, email, phone, location)
|
|
88
|
+
- Professional summary and headline
|
|
89
|
+
- Skills (as array and comma-separated text)
|
|
90
|
+
- Work history formatted for forms
|
|
91
|
+
- Education details
|
|
92
|
+
- Default cover letter text
|
|
93
|
+
|
|
94
|
+
**Example:** "Get my form data for applying to jobs"
|
|
56
95
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
96
|
+
### `evaluate_job`
|
|
97
|
+
Score a job against your profile. Provide the job title, company, and description to get:
|
|
98
|
+
- Match score (0-100%)
|
|
99
|
+
- Recommendation (strongly_recommend, recommend, consider, skip)
|
|
100
|
+
- Strengths and gaps analysis
|
|
101
|
+
- Application strategy
|
|
60
102
|
|
|
61
|
-
**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
**Example:** "Evaluate this job for me: [paste job description]"
|
|
104
|
+
|
|
105
|
+
### `launch_pad`
|
|
106
|
+
Open job search tabs across major job boards (LinkedIn, Indeed, Glassdoor, etc.) using your resume to determine search terms.
|
|
65
107
|
|
|
66
108
|
### `optimize_resume`
|
|
67
109
|
Optimize your resume for ATS (Applicant Tracking Systems).
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- "Regenerate my resume with a more technical summary"
|
|
72
|
-
- "Make my resume more concise"
|
|
111
|
+
### `upload_resume`
|
|
112
|
+
Upload a PDF resume to JobSeek.
|
|
73
113
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- Adds measurable impact statements
|
|
77
|
-
- Optimizes keywords for ATS scanning
|
|
78
|
-
- Returns the optimized content + rationale
|
|
114
|
+
### `analyze_matches`
|
|
115
|
+
Review your recent job matches from the Chrome extension.
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
Download your resume as a file.
|
|
117
|
+
## Automated Job Search (with Claude for Chrome)
|
|
82
118
|
|
|
83
|
-
|
|
84
|
-
- "Download my resume"
|
|
85
|
-
- "Save my optimized resume to a file"
|
|
86
|
-
- "Download my ATS-optimized resume to ~/Documents/resume.txt"
|
|
119
|
+
The most powerful feature! Use the `/auto_job_search` prompt to have Claude:
|
|
87
120
|
|
|
88
|
-
**
|
|
89
|
-
-
|
|
90
|
-
|
|
121
|
+
1. **Prepare** - Fetch your profile and form data
|
|
122
|
+
2. **Search** - Navigate to job sites and search for matching roles
|
|
123
|
+
3. **Evaluate** - Score each job against your profile
|
|
124
|
+
4. **Present** - Show you good matches (65%+ score) and ask for approval
|
|
125
|
+
5. **Apply** - Fill out application forms with your data
|
|
126
|
+
6. **Track** - Keep count of applied/skipped jobs
|
|
127
|
+
|
|
128
|
+
### Usage
|
|
129
|
+
|
|
130
|
+
In Claude for macOS with Claude for Chrome installed:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
/auto_job_search site:linkedin query:"Senior Software Engineer" location:"Remote"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or simply ask:
|
|
137
|
+
> "Help me search for jobs on LinkedIn and apply to good matches"
|
|
138
|
+
|
|
139
|
+
### Requirements
|
|
140
|
+
- Claude for macOS
|
|
141
|
+
- Claude for Chrome extension (for browser control)
|
|
142
|
+
- JobSeek MCP configured with your API key
|
|
91
143
|
|
|
92
144
|
## Full Workflow Example
|
|
93
145
|
|
|
94
146
|
```
|
|
95
|
-
You: "
|
|
96
|
-
|
|
147
|
+
You: "Start an automated job search on LinkedIn for product manager roles"
|
|
148
|
+
|
|
149
|
+
Claude: [fetches your profile]
|
|
150
|
+
"I found your profile. You're a Product Manager with 8 years of experience..."
|
|
151
|
+
|
|
152
|
+
Claude: [navigates to LinkedIn, searches, finds a job]
|
|
153
|
+
"Found: Senior Product Manager at Stripe
|
|
154
|
+
Match Score: 87%
|
|
155
|
+
Strengths: Product strategy, data analysis, cross-functional leadership
|
|
156
|
+
Would you like me to apply to this role?"
|
|
157
|
+
|
|
158
|
+
You: "Yes, apply"
|
|
97
159
|
|
|
98
|
-
|
|
99
|
-
|
|
160
|
+
Claude: [fills out application form]
|
|
161
|
+
"I've filled out the application:
|
|
162
|
+
- Name: John Doe
|
|
163
|
+
- Email: john@example.com
|
|
164
|
+
- Resume: [attached]
|
|
165
|
+
- Why interested: [drafted response]
|
|
166
|
+
|
|
167
|
+
Ready to submit?"
|
|
168
|
+
|
|
169
|
+
You: "Looks good, submit it"
|
|
170
|
+
|
|
171
|
+
Claude: [submits] "Application submitted! Moving to next job..."
|
|
100
172
|
```
|
|
101
173
|
|
|
102
174
|
## Troubleshooting
|
|
103
175
|
|
|
104
176
|
### "1 MCP server failed" error
|
|
105
177
|
- Check `/mcp` in Claude Code for details
|
|
106
|
-
-
|
|
107
|
-
-
|
|
178
|
+
- Make sure you have a valid API key
|
|
179
|
+
- Try running `npx jobseek-mcp` directly to test
|
|
108
180
|
|
|
109
181
|
### "Authentication Required" error
|
|
110
|
-
- Verify your API key is correct
|
|
182
|
+
- Verify your API key is correct
|
|
111
183
|
- Make sure you have a resume uploaded at https://getjobseek.com/dashboard/profile
|
|
112
|
-
- Check that JOBSEEK_API_URL is set to `https://getjobseek.com`
|
|
113
184
|
|
|
114
185
|
## Development
|
|
115
186
|
|
|
116
187
|
```bash
|
|
188
|
+
git clone https://github.com/shawnmitchell/jobseek.git
|
|
189
|
+
cd jobseek/jobseek-mcp
|
|
190
|
+
npm install
|
|
117
191
|
npm run dev # Run with tsx (hot reload)
|
|
118
192
|
npm run build # Build for production
|
|
119
193
|
npm start # Run production build
|
|
120
194
|
```
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT © Shawn Mitchell
|
package/dist/index.js
CHANGED
|
@@ -1,78 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
5
|
-
import { optimizeResumeTool, handleOptimizeResume } from "./tools/optimize-resume.js";
|
|
6
|
-
import { downloadResumePdfTool, handleDownloadResumePdf } from "./tools/download-resume-pdf.js";
|
|
7
|
-
import { uploadResumeTool, handleUploadResume } from "./tools/upload-resume.js";
|
|
8
|
-
import { launchPadTool, handleLaunchPad } from "./tools/launch-pad.js";
|
|
9
|
-
import { allResources, handleReadResource } from "./resources.js";
|
|
10
|
-
import { allPrompts, getPromptMessages } from "./prompts.js";
|
|
11
|
-
// Configuration
|
|
12
|
-
const JOBSEEK_API_URL = process.env.JOBSEEK_API_URL || "https://jobseek-iota.vercel.app";
|
|
13
|
-
const JOBSEEK_API_KEY = process.env.JOBSEEK_API_KEY;
|
|
14
|
-
// Create the MCP server
|
|
15
|
-
const server = new Server({
|
|
16
|
-
name: "jobseek-mcp",
|
|
17
|
-
version: "0.5.0",
|
|
18
|
-
}, {
|
|
19
|
-
capabilities: {
|
|
20
|
-
tools: {},
|
|
21
|
-
resources: {},
|
|
22
|
-
prompts: {},
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
// List available tools
|
|
26
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
|
-
return {
|
|
28
|
-
tools: [
|
|
29
|
-
optimizeResumeTool,
|
|
30
|
-
downloadResumePdfTool,
|
|
31
|
-
uploadResumeTool,
|
|
32
|
-
launchPadTool,
|
|
33
|
-
],
|
|
34
|
-
};
|
|
35
|
-
});
|
|
36
|
-
// Handle tool calls
|
|
37
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
38
|
-
const { name, arguments: args } = request.params;
|
|
39
|
-
switch (name) {
|
|
40
|
-
case "optimize_resume":
|
|
41
|
-
return handleOptimizeResume(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
42
|
-
case "download_resume_pdf":
|
|
43
|
-
return handleDownloadResumePdf(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
44
|
-
case "upload_resume":
|
|
45
|
-
return handleUploadResume(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
46
|
-
case "launch_pad":
|
|
47
|
-
return handleLaunchPad(args, JOBSEEK_API_URL, JOBSEEK_API_KEY);
|
|
48
|
-
default:
|
|
49
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
// List available resources
|
|
53
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
54
|
-
return {
|
|
55
|
-
resources: allResources,
|
|
56
|
-
};
|
|
57
|
-
});
|
|
58
|
-
// Handle resource reads
|
|
59
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
60
|
-
const { uri } = request.params;
|
|
61
|
-
return handleReadResource(uri);
|
|
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
|
-
});
|
|
74
|
-
// Start the server
|
|
3
|
+
import { createServer } from "./server.js";
|
|
75
4
|
async function main() {
|
|
5
|
+
const apiKey = process.env.JOBSEEK_API_KEY;
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
throw new Error("JOBSEEK_API_KEY environment variable is required");
|
|
8
|
+
}
|
|
9
|
+
const server = createServer(apiKey);
|
|
76
10
|
const transport = new StdioServerTransport();
|
|
77
11
|
await server.connect(transport);
|
|
78
12
|
console.error("JobSeek MCP Server running on stdio");
|
package/dist/prompts.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ export declare const uploadResumePrompt: {
|
|
|
30
30
|
required: boolean;
|
|
31
31
|
}[];
|
|
32
32
|
};
|
|
33
|
+
export declare const autoJobSearchPrompt: {
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
arguments: {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
required: boolean;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
33
42
|
export declare const allPrompts: {
|
|
34
43
|
name: string;
|
|
35
44
|
description: string;
|
package/dist/prompts.js
CHANGED
|
@@ -38,12 +38,34 @@ export const uploadResumePrompt = {
|
|
|
38
38
|
}
|
|
39
39
|
]
|
|
40
40
|
};
|
|
41
|
+
export const autoJobSearchPrompt = {
|
|
42
|
+
name: "auto_job_search",
|
|
43
|
+
description: "Automated job search: Navigate job sites, find matching jobs, and help apply. Requires Claude for Chrome browser control.",
|
|
44
|
+
arguments: [
|
|
45
|
+
{
|
|
46
|
+
name: "site",
|
|
47
|
+
description: "Which job site to search: linkedin, indeed, glassdoor, or all",
|
|
48
|
+
required: false
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "query",
|
|
52
|
+
description: "Optional: specific job title or keywords to search for",
|
|
53
|
+
required: false
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "location",
|
|
57
|
+
description: "Optional: location to search in (e.g., 'Remote', 'New York, NY')",
|
|
58
|
+
required: false
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
};
|
|
41
62
|
// All prompts
|
|
42
63
|
export const allPrompts = [
|
|
43
64
|
launchPadPrompt,
|
|
44
65
|
optimizeResumePrompt,
|
|
45
66
|
reviewMatchesPrompt,
|
|
46
67
|
uploadResumePrompt,
|
|
68
|
+
autoJobSearchPrompt,
|
|
47
69
|
];
|
|
48
70
|
// Generate prompt messages
|
|
49
71
|
export function getPromptMessages(name, args) {
|
|
@@ -94,6 +116,71 @@ export function getPromptMessages(name, args) {
|
|
|
94
116
|
}
|
|
95
117
|
}]
|
|
96
118
|
};
|
|
119
|
+
case "auto_job_search":
|
|
120
|
+
const site = args.site || "linkedin";
|
|
121
|
+
const searchQuery = args.query || "";
|
|
122
|
+
const location = args.location || "Remote";
|
|
123
|
+
return {
|
|
124
|
+
messages: [{
|
|
125
|
+
role: "user",
|
|
126
|
+
content: {
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `# Automated Job Search Session
|
|
129
|
+
|
|
130
|
+
You are my job search assistant. Using browser control (Claude for Chrome) and the JobSeek MCP tools, help me find and apply to jobs.
|
|
131
|
+
|
|
132
|
+
## Your Workflow:
|
|
133
|
+
|
|
134
|
+
### Phase 1: Preparation
|
|
135
|
+
1. First, use the \`my_profile\` tool to understand my background, skills, and experience
|
|
136
|
+
2. Use the \`get_form_data\` tool to have my application data ready for form filling
|
|
137
|
+
|
|
138
|
+
### Phase 2: Search
|
|
139
|
+
3. Navigate to ${site === 'all' ? 'LinkedIn first, then Indeed' : site} job search
|
|
140
|
+
4. Search for: ${searchQuery ? `"${searchQuery}"` : 'roles matching my most recent job title from my profile'}
|
|
141
|
+
5. Filter by location: ${location}
|
|
142
|
+
6. Filter for jobs posted in the last week if possible
|
|
143
|
+
|
|
144
|
+
### Phase 3: Evaluate & Present
|
|
145
|
+
For each promising job listing:
|
|
146
|
+
7. Click to view the full job description
|
|
147
|
+
8. Read the job details (title, company, requirements, salary if shown)
|
|
148
|
+
9. Use the \`evaluate_job\` tool to score the match against my profile
|
|
149
|
+
10. If match score is 65% or higher, present it to me with:
|
|
150
|
+
- Job title and company
|
|
151
|
+
- Match score and key strengths
|
|
152
|
+
- Any concerns or gaps
|
|
153
|
+
- Ask: "Would you like me to apply to this role?"
|
|
154
|
+
|
|
155
|
+
### Phase 4: Apply (if I approve)
|
|
156
|
+
If I say yes to applying:
|
|
157
|
+
11. Look for "Apply" or "Easy Apply" button
|
|
158
|
+
12. Click to start the application
|
|
159
|
+
13. Use \`get_form_data\` to fill in form fields:
|
|
160
|
+
- Name, email, phone → from my profile
|
|
161
|
+
- Work history → from my experience
|
|
162
|
+
- Education → from my education
|
|
163
|
+
- For text questions, draft thoughtful responses based on my profile
|
|
164
|
+
14. Before final submission, show me a summary and ask for confirmation
|
|
165
|
+
15. If I confirm, submit. If not, make any edits I request.
|
|
166
|
+
|
|
167
|
+
### Phase 5: Track
|
|
168
|
+
16. After each application (or skip), move to the next job listing
|
|
169
|
+
17. Keep a running count: "Applied: X, Skipped: Y, Remaining to review: Z"
|
|
170
|
+
|
|
171
|
+
## Important Rules:
|
|
172
|
+
- ALWAYS ask before submitting an application
|
|
173
|
+
- ALWAYS show me what you're about to submit
|
|
174
|
+
- If you encounter a CAPTCHA or login wall, pause and ask for help
|
|
175
|
+
- If an application requires a resume upload, let me know
|
|
176
|
+
- Skip jobs requiring skills I clearly don't have (score < 50%)
|
|
177
|
+
- Take your time - accuracy is more important than speed
|
|
178
|
+
|
|
179
|
+
## Ready?
|
|
180
|
+
Let's start! First, fetch my profile, then navigate to the job site.`
|
|
181
|
+
}
|
|
182
|
+
}]
|
|
183
|
+
};
|
|
97
184
|
default:
|
|
98
185
|
throw new Error(`Unknown prompt: ${name}`);
|
|
99
186
|
}
|
package/dist/resources.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export declare const resumeResource: {
|
|
|
4
4
|
description: string;
|
|
5
5
|
mimeType: string;
|
|
6
6
|
};
|
|
7
|
+
export declare const resumeTextResource: {
|
|
8
|
+
uri: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
};
|
|
7
13
|
export declare const applicationsResource: {
|
|
8
14
|
uri: string;
|
|
9
15
|
name: string;
|
package/dist/resources.js
CHANGED
|
@@ -5,10 +5,16 @@ const getApiKey = () => process.env.JOBSEEK_API_KEY;
|
|
|
5
5
|
// Resource definitions
|
|
6
6
|
export const resumeResource = {
|
|
7
7
|
uri: "jobseek://resume",
|
|
8
|
-
name: "Current Resume",
|
|
9
|
-
description: "Your resume data stored in JobSeek including skills, experience, education, and summary",
|
|
8
|
+
name: "Current Resume (JSON)",
|
|
9
|
+
description: "Your resume data stored in JobSeek as structured JSON including skills, experience, education, and summary",
|
|
10
10
|
mimeType: "application/json"
|
|
11
11
|
};
|
|
12
|
+
export const resumeTextResource = {
|
|
13
|
+
uri: "jobseek://resume/text",
|
|
14
|
+
name: "Current Resume (Text)",
|
|
15
|
+
description: "Your resume formatted as readable plain text - use this to understand the user's background",
|
|
16
|
+
mimeType: "text/plain"
|
|
17
|
+
};
|
|
12
18
|
export const applicationsResource = {
|
|
13
19
|
uri: "jobseek://applications",
|
|
14
20
|
name: "Job Applications",
|
|
@@ -18,6 +24,7 @@ export const applicationsResource = {
|
|
|
18
24
|
// All resources
|
|
19
25
|
export const allResources = [
|
|
20
26
|
resumeResource,
|
|
27
|
+
resumeTextResource,
|
|
21
28
|
applicationsResource,
|
|
22
29
|
];
|
|
23
30
|
// Fetch resume data
|
|
@@ -62,6 +69,91 @@ async function fetchResume() {
|
|
|
62
69
|
}, null, 2);
|
|
63
70
|
}
|
|
64
71
|
}
|
|
72
|
+
// Format resume data as readable text
|
|
73
|
+
function formatResumeAsText(resume) {
|
|
74
|
+
let text = '';
|
|
75
|
+
// Header
|
|
76
|
+
text += `# ${resume.name || 'Resume'}\n`;
|
|
77
|
+
if (resume.email)
|
|
78
|
+
text += `Email: ${resume.email}\n`;
|
|
79
|
+
if (resume.linkedinUrl)
|
|
80
|
+
text += `LinkedIn: ${resume.linkedinUrl}\n`;
|
|
81
|
+
text += '\n';
|
|
82
|
+
// Summary
|
|
83
|
+
if (resume.summary) {
|
|
84
|
+
text += `## Professional Summary\n${resume.summary}\n\n`;
|
|
85
|
+
}
|
|
86
|
+
// Skills
|
|
87
|
+
if (resume.skills && resume.skills.length > 0) {
|
|
88
|
+
text += `## Skills\n`;
|
|
89
|
+
text += resume.skills.map((skill) => `• ${skill}`).join('\n');
|
|
90
|
+
text += '\n\n';
|
|
91
|
+
}
|
|
92
|
+
// Experience
|
|
93
|
+
if (resume.experience && resume.experience.length > 0) {
|
|
94
|
+
text += `## Work Experience\n`;
|
|
95
|
+
for (const exp of resume.experience) {
|
|
96
|
+
text += `### ${exp.title || 'Position'} at ${exp.company || 'Company'}\n`;
|
|
97
|
+
if (exp.location)
|
|
98
|
+
text += `Location: ${exp.location}\n`;
|
|
99
|
+
if (exp.startDate || exp.endDate || exp.duration) {
|
|
100
|
+
const duration = exp.duration || `${exp.startDate || ''} - ${exp.endDate || 'Present'}`;
|
|
101
|
+
text += `Duration: ${duration}\n`;
|
|
102
|
+
}
|
|
103
|
+
if (exp.description)
|
|
104
|
+
text += `\n${exp.description}\n`;
|
|
105
|
+
if (exp.bullets && exp.bullets.length > 0) {
|
|
106
|
+
text += exp.bullets.map((b) => `• ${b}`).join('\n') + '\n';
|
|
107
|
+
}
|
|
108
|
+
text += '\n';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Education
|
|
112
|
+
if (resume.education && resume.education.length > 0) {
|
|
113
|
+
text += `## Education\n`;
|
|
114
|
+
for (const edu of resume.education) {
|
|
115
|
+
text += `### ${edu.degree || 'Degree'}`;
|
|
116
|
+
if (edu.field)
|
|
117
|
+
text += ` in ${edu.field}`;
|
|
118
|
+
text += '\n';
|
|
119
|
+
if (edu.institution || edu.school)
|
|
120
|
+
text += `Institution: ${edu.institution || edu.school}\n`;
|
|
121
|
+
if (edu.year || edu.graduationDate)
|
|
122
|
+
text += `Year: ${edu.year || edu.graduationDate}\n`;
|
|
123
|
+
text += '\n';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
// Fetch resume as formatted text
|
|
129
|
+
async function fetchResumeText() {
|
|
130
|
+
const apiKey = getApiKey();
|
|
131
|
+
const apiUrl = getApiUrl();
|
|
132
|
+
if (!apiKey) {
|
|
133
|
+
return "Error: No API key configured. Get your API key at " + apiUrl + "/dashboard/api-keys";
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(`${apiUrl}/api/resumes/current`, {
|
|
137
|
+
headers: {
|
|
138
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
if (response.status === 401) {
|
|
143
|
+
return "Error: Invalid API key. Generate a new key at " + apiUrl + "/dashboard/api-keys";
|
|
144
|
+
}
|
|
145
|
+
if (response.status === 404) {
|
|
146
|
+
return "Error: No resume found. Upload a resume first using the upload_resume tool.";
|
|
147
|
+
}
|
|
148
|
+
return `Error: Failed to fetch resume (status ${response.status})`;
|
|
149
|
+
}
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
return formatResumeAsText(data);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return `Error: Network error - ${error.message}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
65
157
|
// Fetch applications data
|
|
66
158
|
async function fetchApplications() {
|
|
67
159
|
const apiKey = getApiKey();
|
|
@@ -108,6 +200,14 @@ export async function handleReadResource(uri) {
|
|
|
108
200
|
text: await fetchResume()
|
|
109
201
|
}]
|
|
110
202
|
};
|
|
203
|
+
case "jobseek://resume/text":
|
|
204
|
+
return {
|
|
205
|
+
contents: [{
|
|
206
|
+
uri,
|
|
207
|
+
mimeType: "text/plain",
|
|
208
|
+
text: await fetchResumeText()
|
|
209
|
+
}]
|
|
210
|
+
};
|
|
111
211
|
case "jobseek://applications":
|
|
112
212
|
return {
|
|
113
213
|
contents: [{
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
export declare function createServer(apiKey: string): Server<{
|
|
3
|
+
method: string;
|
|
4
|
+
params?: import("zod").objectOutputType<{
|
|
5
|
+
_meta: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
6
|
+
progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
|
|
7
|
+
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
|
|
8
|
+
progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
|
|
9
|
+
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
|
|
10
|
+
progressToken: import("zod").ZodOptional<import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodNumber]>>;
|
|
11
|
+
}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
12
|
+
}, import("zod").ZodTypeAny, "passthrough"> | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
method: string;
|
|
15
|
+
params?: import("zod").objectOutputType<{
|
|
16
|
+
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
17
|
+
}, import("zod").ZodTypeAny, "passthrough"> | undefined;
|
|
18
|
+
}, import("zod").objectOutputType<{
|
|
19
|
+
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
20
|
+
}, import("zod").ZodTypeAny, "passthrough">>;
|