techprufer-mcp 0.4.1 → 0.4.3
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/server.js +103 -14
- package/dist/tools.js +11 -11
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -8,6 +8,13 @@ import { runLogin } from './login.js';
|
|
|
8
8
|
function textResult(text, isError = false) {
|
|
9
9
|
return { content: [{ type: 'text', text }], isError };
|
|
10
10
|
}
|
|
11
|
+
/** Prefer API `markdown` field; fall back to JSON for structured payloads. */
|
|
12
|
+
function apiResult(data, isError = false) {
|
|
13
|
+
const md = typeof data.markdown === 'string' ? data.markdown : null;
|
|
14
|
+
if (md)
|
|
15
|
+
return textResult(md, isError);
|
|
16
|
+
return textResult(JSON.stringify(data, null, 2), isError);
|
|
17
|
+
}
|
|
11
18
|
function requireAuthMessage() {
|
|
12
19
|
return (`Not logged in. Run: npx techprufer-mcp login\n` +
|
|
13
20
|
`Credentials file: ${credentialsFilePath()}\n` +
|
|
@@ -21,7 +28,7 @@ export function createServer() {
|
|
|
21
28
|
const server = new McpServer({
|
|
22
29
|
name: 'techprufer',
|
|
23
30
|
title: 'TechPrufer',
|
|
24
|
-
version: '0.4.
|
|
31
|
+
version: '0.4.3',
|
|
25
32
|
description: 'Tailor resumes and build profiles with your AI tool — no Gemini API key.',
|
|
26
33
|
websiteUrl: 'https://techprufer.com',
|
|
27
34
|
icons: brandIcons(),
|
|
@@ -155,8 +162,9 @@ export function createServer() {
|
|
|
155
162
|
' - Tell the user: this company has N other tracked roles — attach this resume to them?',
|
|
156
163
|
' - If yes: preview_company_siblings then attach_tailored_to_siblings({ sourceApplicationId, sessionId }).',
|
|
157
164
|
' - If no: leave siblings alone.',
|
|
158
|
-
'6) Always show the user from submit result: jdUrl (job link),
|
|
159
|
-
'
|
|
165
|
+
'6) Always show the user from submit result: jdUrl (job link), dashboardUrl.',
|
|
166
|
+
' resumePdfUrl is an on-demand API URL (requires login) — never show /generated/ paths.',
|
|
167
|
+
' On the dashboard application page the Chrome extension can inject or download the PDF.',
|
|
160
168
|
'',
|
|
161
169
|
'Zero hallucination. Complete JSON only when submitting or saving.',
|
|
162
170
|
].join('\n'),
|
|
@@ -164,6 +172,73 @@ export function createServer() {
|
|
|
164
172
|
},
|
|
165
173
|
],
|
|
166
174
|
}));
|
|
175
|
+
server.tool('list_profile_templates', 'List all 6 TechPrufer resume templates with descriptions, tiers, focus order, and boosts. ' +
|
|
176
|
+
'Use when the user wants to compare template options during build-profile.', {}, async () => {
|
|
177
|
+
const token = await ensureToken();
|
|
178
|
+
if (!token)
|
|
179
|
+
return textResult(requireAuthMessage(), true);
|
|
180
|
+
try {
|
|
181
|
+
const data = await apiFetch('/api/agent/templates');
|
|
182
|
+
return apiResult(data);
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
186
|
+
return textResult(`Failed to list templates: ${msg}`, true);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
server.tool('recommend_profile_template', 'Analyze resume text and recommend the best of 6 templates, with ranked alternatives and a comparison guide. ' +
|
|
190
|
+
'Run after extracting text from a PDF the user attached in chat.', {
|
|
191
|
+
resumeText: z.string().describe('Full plain text extracted from the resume PDF'),
|
|
192
|
+
}, async ({ resumeText }) => {
|
|
193
|
+
const token = await ensureToken();
|
|
194
|
+
if (!token)
|
|
195
|
+
return textResult(requireAuthMessage(), true);
|
|
196
|
+
try {
|
|
197
|
+
const data = await apiFetch('/api/agent/templates/recommend', {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
body: JSON.stringify({ resumeText }),
|
|
200
|
+
});
|
|
201
|
+
return apiResult(data);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
205
|
+
return textResult(`Template recommendation failed: ${msg}`, true);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
server.tool('analyze_profile_job', 'Analyze a queued site PDF job for template recommendation without claiming it. ' +
|
|
209
|
+
'Use before get_profile_job when templateSlug is not set.', {
|
|
210
|
+
jobId: z.string().describe('Profile job id from list_pending_profile_jobs'),
|
|
211
|
+
}, async ({ jobId }) => {
|
|
212
|
+
const token = await ensureToken();
|
|
213
|
+
if (!token)
|
|
214
|
+
return textResult(requireAuthMessage(), true);
|
|
215
|
+
try {
|
|
216
|
+
const data = await apiFetch(`/api/agent/profile-jobs/${encodeURIComponent(jobId)}/analyze`);
|
|
217
|
+
return apiResult(data);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
221
|
+
return textResult(`Analyze job failed: ${msg}`, true);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
server.tool('set_profile_job_template', 'Set the gallery template slug on a queued profile job before parsing/build.', {
|
|
225
|
+
jobId: z.string(),
|
|
226
|
+
templateSlug: z
|
|
227
|
+
.string()
|
|
228
|
+
.describe('Slug from list_profile_templates / recommend_profile_template (e.g. student-classic)'),
|
|
229
|
+
}, async ({ jobId, templateSlug }) => {
|
|
230
|
+
const token = await ensureToken();
|
|
231
|
+
if (!token)
|
|
232
|
+
return textResult(requireAuthMessage(), true);
|
|
233
|
+
try {
|
|
234
|
+
const data = await apiFetch(`/api/agent/profile-jobs/${encodeURIComponent(jobId)}/template`, { method: 'PATCH', body: JSON.stringify({ templateSlug }) });
|
|
235
|
+
return textResult(JSON.stringify(data, null, 2));
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
239
|
+
return textResult(`Set template failed: ${msg}`, true);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
167
242
|
server.tool('list_pending_profile_jobs', 'List TechPrufer profile-build jobs queued for MCP (from PDF upload on the site).', {}, async () => {
|
|
168
243
|
const token = await ensureToken();
|
|
169
244
|
if (!token)
|
|
@@ -198,7 +273,7 @@ export function createServer() {
|
|
|
198
273
|
id = first.id;
|
|
199
274
|
}
|
|
200
275
|
const payload = await apiFetch(`/api/agent/profile-jobs/${encodeURIComponent(id)}/payload`);
|
|
201
|
-
return
|
|
276
|
+
return apiResult(payload);
|
|
202
277
|
}
|
|
203
278
|
catch (e) {
|
|
204
279
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -253,7 +328,11 @@ export function createServer() {
|
|
|
253
328
|
name: z.string().optional().describe('Profile display name'),
|
|
254
329
|
isDefault: z.boolean().optional(),
|
|
255
330
|
model: z.string().optional(),
|
|
256
|
-
|
|
331
|
+
templateSlug: z
|
|
332
|
+
.string()
|
|
333
|
+
.optional()
|
|
334
|
+
.describe('Gallery template slug chosen by the user (from recommend_profile_template)'),
|
|
335
|
+
}, async ({ profileJson, name, isDefault, model, templateSlug }) => {
|
|
257
336
|
const token = await ensureToken();
|
|
258
337
|
if (!token)
|
|
259
338
|
return textResult(requireAuthMessage(), true);
|
|
@@ -261,7 +340,7 @@ export function createServer() {
|
|
|
261
340
|
const parsed = typeof profileJson === 'string' ? JSON.parse(profileJson) : profileJson;
|
|
262
341
|
const result = await apiFetch('/api/agent/profiles', {
|
|
263
342
|
method: 'POST',
|
|
264
|
-
body: JSON.stringify({ profileJson: parsed, name, isDefault, model }),
|
|
343
|
+
body: JSON.stringify({ profileJson: parsed, name, isDefault, model, templateSlug }),
|
|
265
344
|
});
|
|
266
345
|
return textResult(JSON.stringify(result, null, 2));
|
|
267
346
|
}
|
|
@@ -526,20 +605,30 @@ export function createServer() {
|
|
|
526
605
|
text: [
|
|
527
606
|
'Build TechPrufer master profile(s) via MCP.',
|
|
528
607
|
'',
|
|
608
|
+
'TEMPLATE STEP (always before parsing — PDF in chat or queued job):',
|
|
609
|
+
'1. Extract full resume text (from attached PDF or analyze_profile_job for site queue).',
|
|
610
|
+
'2. Call recommend_profile_template(resumeText) OR analyze_profile_job(jobId).',
|
|
611
|
+
'3. Tell the user: show the **markdown** from the tool response (recommended template + ask if they want other options).',
|
|
612
|
+
'4. Ask: "Want to see other template versions?"',
|
|
613
|
+
' - If yes: the markdown already includes ranked alternatives and a comparison section — walk through the differences.',
|
|
614
|
+
'5. Wait for the user to pick a template slug.',
|
|
615
|
+
' - Queued job: set_profile_job_template(jobId, slug) before get_profile_job.',
|
|
616
|
+
' - Local PDF: pass templateSlug to submit_built_profile.',
|
|
617
|
+
'',
|
|
529
618
|
'Path A — queued PDF jobs from the site:',
|
|
530
619
|
jobId
|
|
531
|
-
? `1.
|
|
532
|
-
: '1.
|
|
620
|
+
? `1. analyze_profile_job(${jobId}) → template pick → set_profile_job_template → get_profile_job.`
|
|
621
|
+
: '1. list_pending_profile_jobs → analyze_profile_job → template pick → set_profile_job_template → get_profile_job.',
|
|
533
622
|
'2. Execute returned messages verbatim; output ONLY CPM profile JSON.',
|
|
534
623
|
'3. Call submit_profile_job_result. On correctiveMessage, fix once and resubmit.',
|
|
535
624
|
'',
|
|
536
|
-
'Path B —
|
|
537
|
-
'1. Read
|
|
538
|
-
'2.
|
|
539
|
-
'3.
|
|
540
|
-
'4.
|
|
625
|
+
'Path B — PDF/resume attached in this chat:',
|
|
626
|
+
'1. Read/extract full text from the attached PDF.',
|
|
627
|
+
'2. recommend_profile_template → user picks template (steps above).',
|
|
628
|
+
'3. get_profile_build_messages(resumeText) → execute verbatim → CPM JSON only.',
|
|
629
|
+
'4. submit_built_profile with templateSlug + optional name.',
|
|
541
630
|
'',
|
|
542
|
-
'Prefer Path A if pending jobs exist; otherwise Path B when
|
|
631
|
+
'Prefer Path A if pending jobs exist; otherwise Path B when user attached a PDF.',
|
|
543
632
|
'Zero hallucination. Complete JSON only. Faithful extraction, not rewriting.',
|
|
544
633
|
].join('\n'),
|
|
545
634
|
},
|
package/dist/tools.js
CHANGED
|
@@ -167,26 +167,26 @@ const CODEX_TAILOR_PROMPT = [
|
|
|
167
167
|
'1) list_pending_tailor_jobs — if jobs[]: offer to process; if empty: show prepareSummary + prepareApplications (Prepare pipeline roles needing a resume).',
|
|
168
168
|
'2) applicationId / profileId: enqueue or edit/delete.',
|
|
169
169
|
'3) Company picker: list_tracked_companies (top 10 / query) → list_applications_at_company → enqueue_tailor_job → process.',
|
|
170
|
-
'4) After submit_tailor_result: show jdUrl + resumePdfUrl. If siblingCount>0, ask to attach to other roles (preview_company_siblings → attach_tailored_to_siblings).',
|
|
170
|
+
'4) After submit_tailor_result: show jdUrl + dashboardUrl. resumePdfUrl only if present (API URL, not /generated/). If siblingCount>0, ask to attach to other roles (preview_company_siblings → attach_tailored_to_siblings).',
|
|
171
171
|
'',
|
|
172
172
|
'Zero hallucination. Complete JSON only when submitting or saving.',
|
|
173
173
|
'',
|
|
174
174
|
].join('\n');
|
|
175
175
|
const CODEX_BUILD_PROFILE_PROMPT = [
|
|
176
|
-
'Process TechPrufer MCP profile-build jobs (or build from a
|
|
176
|
+
'Process TechPrufer MCP profile-build jobs (or build from a PDF/resume in chat).',
|
|
177
|
+
'',
|
|
178
|
+
'TEMPLATE STEP (before parsing):',
|
|
179
|
+
'1. Extract resume text (PDF attachment or analyze_profile_job for site queue).',
|
|
180
|
+
'2. recommend_profile_template → show the markdown to the user (recommended + ask if they want other options).',
|
|
181
|
+
'3. If yes: markdown includes ranked alternatives and comparison — explain differences.',
|
|
182
|
+
'4. User picks slug → set_profile_job_template (queued) or templateSlug on submit_built_profile (local).',
|
|
177
183
|
'',
|
|
178
184
|
'Path A — queued PDF jobs from the site:',
|
|
179
|
-
'
|
|
180
|
-
'2. Execute returned messages verbatim; output ONLY CPM profile JSON.',
|
|
181
|
-
'3. Call submit_profile_job_result. On correctiveMessage, fix once and resubmit.',
|
|
185
|
+
'analyze_profile_job → template pick → set_profile_job_template → get_profile_job → submit_profile_job_result.',
|
|
182
186
|
'',
|
|
183
|
-
'Path B —
|
|
184
|
-
'
|
|
185
|
-
'2. Call get_profile_build_messages with that resumeText.',
|
|
186
|
-
'3. Execute messages verbatim; output ONLY CPM profile JSON.',
|
|
187
|
-
'4. Call submit_built_profile (optional name from file / identity.name).',
|
|
187
|
+
'Path B — PDF attached in chat:',
|
|
188
|
+
'extract text → recommend_profile_template → user picks → get_profile_build_messages → submit_built_profile(templateSlug).',
|
|
188
189
|
'',
|
|
189
|
-
'Prefer Path A if pending jobs exist; otherwise Path B when a local resume is available.',
|
|
190
190
|
'Zero hallucination. Complete JSON only. Faithful extraction, not rewriting.',
|
|
191
191
|
'',
|
|
192
192
|
].join('\n');
|