techprufer-mcp 0.4.0 → 0.4.1

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/api.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare class ApiError extends Error {
5
5
  constructor(status: number, body: unknown, message?: string);
6
6
  }
7
7
  export declare function setActiveClientInfo(info: ClientInfo | null): void;
8
+ export declare function setClientInfoSync(fn: (() => ClientInfo | null | undefined) | null): void;
8
9
  export declare function getActiveClientInfo(): ClientInfo | null;
9
10
  export declare function apiFetch<T>(path: string, init?: RequestInit & {
10
11
  token?: string | null;
package/dist/api.js CHANGED
@@ -22,12 +22,28 @@ function formatFetchError(err) {
22
22
  }
23
23
  /** Set by the stdio server after the MCP initialize handshake. */
24
24
  let activeClientInfo = null;
25
+ /** Re-read MCP clientInfo from the SDK before each API call (hosts may omit it until later). */
26
+ let clientInfoSync = null;
25
27
  export function setActiveClientInfo(info) {
26
28
  activeClientInfo = info;
27
29
  }
30
+ export function setClientInfoSync(fn) {
31
+ clientInfoSync = fn;
32
+ }
28
33
  export function getActiveClientInfo() {
29
34
  return activeClientInfo;
30
35
  }
36
+ function refreshActiveClientInfo() {
37
+ if (!clientInfoSync)
38
+ return;
39
+ const info = clientInfoSync();
40
+ if (info?.name?.trim()) {
41
+ setActiveClientInfo({
42
+ name: info.name.trim(),
43
+ version: info.version?.trim() || undefined,
44
+ });
45
+ }
46
+ }
31
47
  export async function apiFetch(path, init = {}) {
32
48
  const creds = loadCredentials();
33
49
  const token = init.token === undefined ? creds?.token : init.token;
@@ -45,6 +61,7 @@ export async function apiFetch(path, init = {}) {
45
61
  }
46
62
  if (token)
47
63
  headers.set('Authorization', `Bearer ${token}`);
64
+ refreshActiveClientInfo();
48
65
  const clientHeader = formatClientHeader(activeClientInfo);
49
66
  if (clientHeader)
50
67
  headers.set(TP_CLIENT_HEADER, clientHeader);
package/dist/cli.js CHANGED
@@ -46,11 +46,13 @@ async function main() {
46
46
  const cmd = args[0];
47
47
  if (cmd === 'login') {
48
48
  const nameIdx = args.indexOf('--name');
49
- const deviceName = nameIdx >= 0 ? args[nameIdx + 1] : undefined;
49
+ const explicitDeviceName = nameIdx >= 0 ? args[nameIdx + 1] : undefined;
50
50
  const skipSetup = hasFlag(args, '--no-setup');
51
51
  const timeoutSec = parseTimeoutSec(args);
52
52
  const tools = parseToolsFlag(args);
53
- let configuredIds = [];
53
+ let configuredIds = tools?.length
54
+ ? tools
55
+ : [];
54
56
  if (!skipSetup) {
55
57
  const outcome = await runSetup({
56
58
  beforeLogin: true,
@@ -65,6 +67,7 @@ async function main() {
65
67
  return;
66
68
  console.error('');
67
69
  }
70
+ const deviceName = explicitDeviceName ?? configuredIds[0];
68
71
  await runLogin(deviceName, {
69
72
  stepLabel: skipSetup ? undefined : 'login',
70
73
  timeoutSec,
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod';
4
- import { apiFetch, ApiError, setActiveClientInfo } from './api.js';
4
+ import { apiFetch, ApiError, setActiveClientInfo, setClientInfoSync } from './api.js';
5
5
  import { brandIcons } from './brand.js';
6
6
  import { loadCredentials, credentialsFilePath } from './config.js';
7
7
  import { runLogin } from './login.js';
@@ -21,18 +21,25 @@ export function createServer() {
21
21
  const server = new McpServer({
22
22
  name: 'techprufer',
23
23
  title: 'TechPrufer',
24
- version: '0.4.0',
24
+ version: '0.4.1',
25
25
  description: 'Tailor resumes and build profiles with your AI tool — no Gemini API key.',
26
26
  websiteUrl: 'https://techprufer.com',
27
27
  icons: brandIcons(),
28
28
  });
29
- server.tool('list_pending_tailor_jobs', 'List TechPrufer tailor jobs queued for MCP. Returns id, company, role, status.', {}, async () => {
29
+ server.tool('list_pending_tailor_jobs', 'List queued MCP tailor jobs. Always includes Prepare-stage applications needing a resume ' +
30
+ '(prepareSummary + prepareApplications) so when the queue is empty you can show the user ' +
31
+ 'their Prepare pipeline backlog and proceed to enqueue_tailor_job / company picker.', {}, async () => {
30
32
  const token = await ensureToken();
31
33
  if (!token)
32
34
  return textResult(requireAuthMessage(), true);
33
35
  try {
34
36
  const data = await apiFetch('/api/agent/jobs');
35
- return textResult(JSON.stringify(data.jobs ?? [], null, 2));
37
+ return textResult(JSON.stringify({
38
+ jobs: data.jobs ?? [],
39
+ prepareSummary: data.prepareSummary ?? null,
40
+ prepareApplications: data.prepareApplications ?? [],
41
+ prepareTotal: data.prepareTotal ?? 0,
42
+ }, null, 2));
36
43
  }
37
44
  catch (e) {
38
45
  const msg = e instanceof Error ? e.message : String(e);
@@ -124,14 +131,21 @@ export function createServer() {
124
131
  '### Flow',
125
132
  jobId
126
133
  ? `1) Process jobId=${jobId}: get_tailor_job → execute → submit_tailor_result.`
127
- : '1) Call list_pending_tailor_jobs. Show company/role for each. Ask: process them now, or SKIP to company picker? If process: ONE AT A TIME (get_tailor_job → execute → submit_tailor_result; fix once on correctiveMessage).',
134
+ : [
135
+ '1) Call list_pending_tailor_jobs.',
136
+ ' - If jobs[] is non-empty: show company/role for each queued job. Ask: process them now, or SKIP to Prepare picker?',
137
+ ' If process: ONE AT A TIME (get_tailor_job → execute → submit_tailor_result; fix once on correctiveMessage).',
138
+ ' - If jobs[] is empty: show prepareSummary (totalPrepare / needingTailor) and prepareApplications',
139
+ ' (company, role, dashboardUrl for each Prepare-stage role still missing a tailored resume).',
140
+ ' Ask which to tailor, or SKIP to the company picker (step 4).',
141
+ ].join('\n'),
128
142
  applicationId
129
143
  ? `2) applicationId=${applicationId}: enqueue_tailor_job({ applicationId }) then process, OR get_resume(kind=tailored) to edit/delete.`
130
144
  : '2) If user gave an application id: enqueue_tailor_job or edit/delete that app.',
131
145
  profileId
132
146
  ? `3) profileId=${profileId}: get_resume(kind=base) → update_resume / delete_resume.`
133
147
  : '3) If user gave a base profile id: edit/delete that base resume.',
134
- '4) Company picker (when skipping pending / no id):',
148
+ '4) Company picker (when skipping pending / no id / user picks from Prepare list):',
135
149
  ' - list_tracked_companies (top 10, paginate, or query= typed name). Show total / withTailored / withoutTailored.',
136
150
  ' - Ask which company (or they type the name).',
137
151
  ' - list_applications_at_company(company, onlyUntailored=true preferred for new tailor). If multiple roles, list roles and ask which.',
@@ -536,15 +550,18 @@ export function createServer() {
536
550
  }
537
551
  export async function startStdioServer() {
538
552
  const server = createServer();
553
+ const syncClientInfo = () => {
554
+ const info = server.server.getClientVersion();
555
+ if (!info?.name?.trim())
556
+ return null;
557
+ return { name: info.name.trim(), version: info.version?.trim() || undefined };
558
+ };
539
559
  // Capture IDE identity from the MCP initialize handshake (not env vars).
560
+ setClientInfoSync(syncClientInfo);
540
561
  server.server.oninitialized = () => {
541
- const info = server.server.getClientVersion();
542
- if (info?.name) {
543
- setActiveClientInfo({
544
- name: info.name,
545
- version: info.version,
546
- });
547
- }
562
+ const info = syncClientInfo();
563
+ if (info)
564
+ setActiveClientInfo(info);
548
565
  };
549
566
  const transport = new StdioServerTransport();
550
567
  await server.connect(transport);
package/dist/tools.js CHANGED
@@ -164,7 +164,7 @@ export function codexPromptsDir() {
164
164
  const CODEX_TAILOR_PROMPT = [
165
165
  'TechPrufer MCP — all resume control under /techprufer-tailor.',
166
166
  '',
167
- '1) list_pending_tailor_jobs — show them; ask process now or SKIP to company picker.',
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
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).',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "techprufer-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "TechPrufer MCP — tailor resumes and build profiles from your AI tool",
5
5
  "homepage": "https://techprufer.com",
6
6
  "type": "module",
@@ -15,6 +15,11 @@
15
15
  "engines": {
16
16
  "node": ">=18"
17
17
  },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "test": "npm run build && node --test dist/*.test.js",
21
+ "prepublishOnly": "npm run build"
22
+ },
18
23
  "dependencies": {
19
24
  "@modelcontextprotocol/sdk": "^1.12.1",
20
25
  "zod": "^3.24.2"
@@ -29,9 +34,5 @@
29
34
  "resume",
30
35
  "tailor"
31
36
  ],
32
- "license": "MIT",
33
- "scripts": {
34
- "build": "tsc -p tsconfig.json",
35
- "test": "npm run build && node --test dist/*.test.js"
36
- }
37
- }
37
+ "license": "MIT"
38
+ }