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 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.1',
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), resumePdfUrl, dashboardUrl.',
159
- ' On that job page the Chrome extension can inject or download the PDF when resumePdfUrl is set.',
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 textResult(JSON.stringify(payload, null, 2));
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
- }, async ({ profileJson, name, isDefault, model }) => {
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. Call get_profile_job with jobId=${jobId}.`
532
- : '1. Call list_pending_profile_jobs; for each QUEUED/CLAIMED job call get_profile_job.',
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 — local resume file (user selected / open in this tool):',
537
- '1. Read the resume file and extract full text.',
538
- '2. Call get_profile_build_messages with that resumeText.',
539
- '3. Execute messages verbatim; output ONLY CPM profile JSON.',
540
- '4. Call submit_built_profile (optional name from file / identity.name).',
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 a local resume is available.',
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 local resume).',
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
- '1. Call list_pending_profile_jobs; for each QUEUED/CLAIMED job call get_profile_job.',
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 — local resume file (user selected / open in this tool):',
184
- '1. Read the resume file and extract full text.',
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "techprufer-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "TechPrufer MCP — tailor resumes and build profiles from your AI tool",
5
5
  "homepage": "https://techprufer.com",
6
6
  "type": "module",