jobseek-mcp 0.7.0 → 0.8.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
CHANGED
|
@@ -114,6 +114,23 @@ Upload a PDF resume to JobSeek.
|
|
|
114
114
|
### `analyze_matches`
|
|
115
115
|
Review your recent job matches from the Chrome extension.
|
|
116
116
|
|
|
117
|
+
### `my_applications`
|
|
118
|
+
Get a list of your job applications with their current status, match scores, and status history with dates.
|
|
119
|
+
|
|
120
|
+
**Example:** "Show me my recent applications"
|
|
121
|
+
|
|
122
|
+
### `update_application_status`
|
|
123
|
+
Update the status of a job application. Perfect for tracking application progress from email updates (Gmail parsing).
|
|
124
|
+
|
|
125
|
+
**Parameters:**
|
|
126
|
+
- `company` (required) - Company name to find the application
|
|
127
|
+
- `jobTitle` (optional) - Job title to match (useful if you applied to multiple roles at same company)
|
|
128
|
+
- `status` (required) - One of: `received`, `interviewing`, `rejected`, `offer`, `accepted`, `declined`
|
|
129
|
+
- `date` (optional) - Date of the status change (YYYY-MM-DD format, defaults to today)
|
|
130
|
+
- `notes` (optional) - Additional context (e.g., "Interview with Sarah scheduled")
|
|
131
|
+
|
|
132
|
+
**Example:** "Update my Interplay application to interviewing status - interview scheduled for January 13th with Stacy Greco"
|
|
133
|
+
|
|
117
134
|
## Automated Job Search (with Claude for Chrome)
|
|
118
135
|
|
|
119
136
|
The most powerful feature! Use the `/auto_job_search` prompt to have Claude:
|
package/dist/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getResumeLinkTool, handleGetResumeLink } from "./tools/get-resume-link.
|
|
|
9
9
|
import { myApplicationsTool, handleMyApplications } from "./tools/my-applications.js";
|
|
10
10
|
import { getFormDataTool, handleGetFormData } from "./tools/get-form-data.js";
|
|
11
11
|
import { evaluateJobTool, handleEvaluateJob } from "./tools/evaluate-job.js";
|
|
12
|
+
import { updateApplicationStatusTool, handleUpdateApplicationStatus } from "./tools/update-application-status.js";
|
|
12
13
|
import { allResources, handleReadResource } from "./resources.js";
|
|
13
14
|
import { allPrompts, getPromptMessages } from "./prompts.js";
|
|
14
15
|
export function createServer(apiKey) {
|
|
@@ -39,6 +40,7 @@ export function createServer(apiKey) {
|
|
|
39
40
|
myApplicationsTool,
|
|
40
41
|
getFormDataTool,
|
|
41
42
|
evaluateJobTool,
|
|
43
|
+
updateApplicationStatusTool,
|
|
42
44
|
],
|
|
43
45
|
};
|
|
44
46
|
});
|
|
@@ -64,6 +66,8 @@ export function createServer(apiKey) {
|
|
|
64
66
|
return handleGetFormData(args, JOBSEEK_API_URL, apiKey);
|
|
65
67
|
case "evaluate_job":
|
|
66
68
|
return handleEvaluateJob(args, JOBSEEK_API_URL, apiKey);
|
|
69
|
+
case "update_application_status":
|
|
70
|
+
return handleUpdateApplicationStatus(args, JOBSEEK_API_URL, apiKey);
|
|
67
71
|
default:
|
|
68
72
|
throw new Error(`Unknown tool: ${name}`);
|
|
69
73
|
}
|
|
@@ -97,19 +97,33 @@ export async function handleMyApplications(args, apiUrl, apiKey) {
|
|
|
97
97
|
};
|
|
98
98
|
for (const app of applications) {
|
|
99
99
|
const emoji = statusEmoji[app.status.toLowerCase()] || '📄';
|
|
100
|
-
const
|
|
100
|
+
const createdDate = new Date(app.createdAt).toLocaleDateString('en-US', {
|
|
101
101
|
month: 'short',
|
|
102
102
|
day: 'numeric',
|
|
103
103
|
year: 'numeric'
|
|
104
104
|
});
|
|
105
|
+
// Get most recent status date from history
|
|
106
|
+
const latestHistory = app.statusHistory && app.statusHistory.length > 0
|
|
107
|
+
? app.statusHistory[app.statusHistory.length - 1]
|
|
108
|
+
: null;
|
|
109
|
+
const statusDate = latestHistory?.date
|
|
110
|
+
? new Date(latestHistory.date).toLocaleDateString('en-US', {
|
|
111
|
+
month: 'short',
|
|
112
|
+
day: 'numeric',
|
|
113
|
+
year: 'numeric'
|
|
114
|
+
})
|
|
115
|
+
: createdDate;
|
|
105
116
|
output += `## ${emoji} ${app.job.title}\n`;
|
|
106
117
|
output += `**${app.job.company}**`;
|
|
107
118
|
if (app.job.location)
|
|
108
119
|
output += ` • ${app.job.location}`;
|
|
109
120
|
output += '\n';
|
|
110
|
-
output += `- Status: **${app.status}
|
|
121
|
+
output += `- Status: **${app.status}** (${statusDate})\n`;
|
|
122
|
+
if (latestHistory?.notes) {
|
|
123
|
+
output += `- Notes: ${latestHistory.notes}\n`;
|
|
124
|
+
}
|
|
111
125
|
output += `- Match Score: ${app.job.matchScore}%\n`;
|
|
112
|
-
output += `- Applied: ${
|
|
126
|
+
output += `- Applied: ${createdDate}\n`;
|
|
113
127
|
output += `- Has Cover Letter: ${app.coverLetter ? 'Yes' : 'No'}\n`;
|
|
114
128
|
output += `- Has Custom Resume: ${app.customResume ? 'Yes' : 'No'}\n`;
|
|
115
129
|
output += '\n';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const updateApplicationStatusTool: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: string;
|
|
6
|
+
properties: {
|
|
7
|
+
company: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
jobTitle: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
status: {
|
|
16
|
+
type: string;
|
|
17
|
+
enum: readonly ["received", "interviewing", "rejected", "offer", "accepted", "declined"];
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
date: {
|
|
21
|
+
type: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
notes: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
required: string[];
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export declare function handleUpdateApplicationStatus(args: unknown, apiUrl: string, apiKey?: string): Promise<{
|
|
33
|
+
content: Array<{
|
|
34
|
+
type: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}>;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Valid status values
|
|
3
|
+
const VALID_STATUSES = ['received', 'interviewing', 'rejected', 'offer', 'accepted', 'declined'];
|
|
4
|
+
// Tool definition
|
|
5
|
+
export const updateApplicationStatusTool = {
|
|
6
|
+
name: "update_application_status",
|
|
7
|
+
description: `Update the status of a job application. Use this after parsing Gmail for job application emails to track interview requests, rejections, offers, etc.
|
|
8
|
+
|
|
9
|
+
Statuses:
|
|
10
|
+
- received: Application was received/acknowledged
|
|
11
|
+
- interviewing: Interview scheduled or in progress
|
|
12
|
+
- rejected: Application was rejected
|
|
13
|
+
- offer: Received a job offer
|
|
14
|
+
- accepted: Accepted an offer
|
|
15
|
+
- declined: Declined an offer
|
|
16
|
+
|
|
17
|
+
The tool finds the application by company name (and optionally job title).`,
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
company: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "The company name (required). Used to find the matching application."
|
|
24
|
+
},
|
|
25
|
+
jobTitle: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "The job title (optional). Helps find the correct application if user applied to multiple positions at the same company."
|
|
28
|
+
},
|
|
29
|
+
status: {
|
|
30
|
+
type: "string",
|
|
31
|
+
enum: VALID_STATUSES,
|
|
32
|
+
description: "The new status: received, interviewing, rejected, offer, accepted, or declined"
|
|
33
|
+
},
|
|
34
|
+
date: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "The date of the status change (YYYY-MM-DD format). Defaults to today if not provided."
|
|
37
|
+
},
|
|
38
|
+
notes: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Optional notes about this status update (e.g., 'Interview scheduled with John Smith', 'Received confirmation email')"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ["company", "status"]
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// Input validation
|
|
47
|
+
const UpdateApplicationStatusInput = z.object({
|
|
48
|
+
company: z.string(),
|
|
49
|
+
jobTitle: z.string().optional(),
|
|
50
|
+
status: z.enum(VALID_STATUSES),
|
|
51
|
+
date: z.string().optional(),
|
|
52
|
+
notes: z.string().optional(),
|
|
53
|
+
});
|
|
54
|
+
// Tool handler
|
|
55
|
+
export async function handleUpdateApplicationStatus(args, apiUrl, apiKey) {
|
|
56
|
+
const input = UpdateApplicationStatusInput.parse(args);
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `**API Key Required**\n\nGet your API key at: ${apiUrl}/dashboard/api-keys`
|
|
62
|
+
}]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(`${apiUrl}/api/applications/update-status`, {
|
|
67
|
+
method: 'PATCH',
|
|
68
|
+
headers: {
|
|
69
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
company: input.company,
|
|
74
|
+
jobTitle: input.jobTitle,
|
|
75
|
+
status: input.status,
|
|
76
|
+
date: input.date,
|
|
77
|
+
notes: input.notes
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
if (response.status === 401) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `**Invalid API Key**\n\nGenerate a new key at: ${apiUrl}/dashboard/api-keys`
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (response.status === 404) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `**Application Not Found**\n\nNo application found for company "${input.company}"${input.jobTitle ? ` with title "${input.jobTitle}"` : ''}.\n\nMake sure the application exists in JobSeek first.`
|
|
94
|
+
}]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const error = await response.json().catch(() => ({}));
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: `**Error**\n\n${error.error || 'Failed to update application status'}`
|
|
102
|
+
}]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const result = await response.json();
|
|
106
|
+
// Format status emoji
|
|
107
|
+
const statusEmoji = {
|
|
108
|
+
'received': '📬',
|
|
109
|
+
'interviewing': '🎯',
|
|
110
|
+
'rejected': '❌',
|
|
111
|
+
'offer': '🎉',
|
|
112
|
+
'accepted': '✅',
|
|
113
|
+
'declined': '🚫',
|
|
114
|
+
};
|
|
115
|
+
const emoji = statusEmoji[input.status] || '📋';
|
|
116
|
+
return {
|
|
117
|
+
content: [{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: `${emoji} **Status Updated**\n\n**${result.job.title}** at **${result.job.company}**\n\nStatus: **${input.status}**${input.date ? `\nDate: ${input.date}` : ''}${input.notes ? `\nNotes: ${input.notes}` : ''}\n\n---\nView application: ${apiUrl}/dashboard/applications`
|
|
120
|
+
}]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return {
|
|
125
|
+
content: [{
|
|
126
|
+
type: "text",
|
|
127
|
+
text: `**Error**\n\nFailed to update application status: ${error.message}`
|
|
128
|
+
}]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jobseek-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "JobSeek MCP Server - AI-powered job search automation for Claude. Enables automated job searching with Claude for Chrome browser control.",
|
|
5
5
|
"author": "Shawn Mitchell",
|
|
6
6
|
"license": "MIT",
|