resuml 2.0.0 → 3.0.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.
@@ -3,10 +3,13 @@ import {
3
3
  analyzeAts,
4
4
  generateResumeYaml,
5
5
  getInstalledVersion,
6
+ getRubricEntry,
6
7
  isThemeInstalled,
8
+ listRubricMarkdown,
9
+ loadConfig,
7
10
  loadTheme,
8
11
  processResumeData
9
- } from "../chunk-GRIYYG45.js";
12
+ } from "../chunk-R4MD5YMV.js";
10
13
 
11
14
  // src/mcp/server.ts
12
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -133,87 +136,10 @@ languages:
133
136
  - Never use first person (I, my, me, we)
134
137
  - Summary: 2-4 sentences positioning the candidate for the specific role
135
138
  `;
136
- var ATS_SCORING_RUBRIC = `# ATS Scoring Rubric
137
-
138
- resuml performs deterministic, offline ATS (Applicant Tracking System) analysis.
139
-
140
- ## Scoring system
141
-
142
- ### Rating scale
143
- | Score | Rating | Description |
144
- |-------|--------|-------------|
145
- | 90-100 | Excellent | Resume is well-optimized for ATS |
146
- | 75-89 | Good | Resume passes most ATS checks |
147
- | 60-74 | Needs Work | Several improvements recommended |
148
- | 0-59 | Poor | Significant issues found |
149
-
150
- ### Weight system
151
- Each check has a weight that affects the final score:
152
- - **High weight (3x)**: Critical checks that significantly impact ATS parsing
153
- - **Medium weight (2x)**: Important but not critical checks
154
- - **Low weight (1x)**: Nice-to-have improvements
155
-
156
- ### Combined scoring (with job description)
157
- When a job description is provided:
158
- - Generic checks: 60% of final score
159
- - Keyword match: 40% of final score
160
-
161
- ## Checks performed
162
-
163
- ### Contact Information (category: contact)
164
- | Check | Weight | What it verifies |
165
- |-------|--------|-----------------|
166
- | contact-complete | High | Name, email, phone, and city are all present |
167
- | has-linkedin | Medium | LinkedIn profile exists in profiles section |
168
-
169
- ### Content Quality (category: content)
170
- | Check | Weight | What it verifies |
171
- |-------|--------|-----------------|
172
- | has-summary | High | Professional summary exists (15-100 words) |
173
- | work-highlights | High | Each work entry has at least 2 highlights |
174
- | action-verbs | Medium | Highlights start with action verbs |
175
- | quantified-impact | Medium | 50%+ of highlights include numbers/metrics |
176
- | no-first-person | Low | No first-person pronouns (I, my, me, we) |
177
-
178
- ### Resume Structure (category: structure)
179
- | Check | Weight | What it verifies |
180
- |-------|--------|-----------------|
181
- | date-consistency | Medium | All dates are valid ISO 8601 format |
182
- | skills-populated | Medium | At least 3 skill categories defined |
183
- | education-complete | Medium | Education section has institution and area |
184
- | essential-sections | High | Work, education, and skills sections present |
185
-
186
- ## Job description matching
187
- When a job description is provided, resuml extracts keywords using TF-based extraction
188
- and matches them against the resume using stem matching. Results include:
189
- - **matched**: Keywords found in the resume
190
- - **missing**: Keywords not found (add these to improve score)
191
- - **matchPercentage**: Percentage of JD keywords found in resume
192
-
193
- ## Fit Assessment
194
- When a job description is provided, a \`fitAssessment\` field is included in the result:
195
- | Match % | Level | Meaning |
196
- |---------|-------|---------|
197
- | >= 70% | strong | Resume aligns well with the job description |
198
- | 50-69% | partial | Some alignment; emphasize transferable skills |
199
- | < 50% | weak | Significant skill gaps; role may not match profile |
200
-
201
- The assessment includes the top 5 missing keywords as specific gaps to address.
202
- Use this to advise users whether to apply or focus effort elsewhere.
203
-
204
- ## Tips for improving ATS score
205
- 1. Include all contact information (name, email, phone, city)
206
- 2. Add a LinkedIn profile URL
207
- 3. Write a 2-4 sentence professional summary
208
- 4. Use action verbs to start each highlight
209
- 5. Quantify achievements with numbers (%, $, time saved, team size)
210
- 6. Include at least 3 skill categories with relevant keywords
211
- 7. When targeting a job, mirror exact terminology from the job description
212
- `;
213
139
  function createServer() {
214
140
  const server = new McpServer({
215
141
  name: "resuml",
216
- version: "1.0.0"
142
+ version: "2.0.0"
217
143
  });
218
144
  server.registerResource(
219
145
  "json-resume-schema",
@@ -223,26 +149,30 @@ function createServer() {
223
149
  mimeType: "text/markdown"
224
150
  },
225
151
  () => ({
226
- contents: [{
227
- uri: "resuml://schema/json-resume",
228
- mimeType: "text/markdown",
229
- text: JSON_RESUME_SCHEMA_REFERENCE
230
- }]
152
+ contents: [
153
+ {
154
+ uri: "resuml://schema/json-resume",
155
+ mimeType: "text/markdown",
156
+ text: JSON_RESUME_SCHEMA_REFERENCE
157
+ }
158
+ ]
231
159
  })
232
160
  );
233
161
  server.registerResource(
234
- "ats-scoring-rubric",
235
- "resuml://docs/ats-scoring",
162
+ "ats-rubric",
163
+ "resuml://docs/ats-rubric",
236
164
  {
237
- description: "ATS scoring rubric: checks performed, weight system, rating scale, and tips for improving score",
165
+ description: "Tiered ATS rubric: every check, its tier, weight, evidence level, description, and source URL.",
238
166
  mimeType: "text/markdown"
239
167
  },
240
168
  () => ({
241
- contents: [{
242
- uri: "resuml://docs/ats-scoring",
243
- mimeType: "text/markdown",
244
- text: ATS_SCORING_RUBRIC
245
- }]
169
+ contents: [
170
+ {
171
+ uri: "resuml://docs/ats-rubric",
172
+ mimeType: "text/markdown",
173
+ text: listRubricMarkdown()
174
+ }
175
+ ]
246
176
  })
247
177
  );
248
178
  server.registerResource(
@@ -261,11 +191,13 @@ function createServer() {
261
191
  version: getInstalledVersion(t.pkg)
262
192
  }));
263
193
  return {
264
- contents: [{
265
- uri: "resuml://themes/catalog",
266
- mimeType: "application/json",
267
- text: JSON.stringify({ themes, totalCount: themes.length }, null, 2)
268
- }]
194
+ contents: [
195
+ {
196
+ uri: "resuml://themes/catalog",
197
+ mimeType: "application/json",
198
+ text: JSON.stringify({ themes, totalCount: themes.length }, null, 2)
199
+ }
200
+ ]
269
201
  };
270
202
  }
271
203
  );
@@ -278,15 +210,18 @@ function createServer() {
278
210
  jobDescription: z.string().describe("The full job description text"),
279
211
  candidateName: z.string().optional().describe("Candidate full name"),
280
212
  candidateEmail: z.string().optional().describe("Candidate email address"),
281
- candidateBackground: z.string().optional().describe("Brief summary of the candidate background, skills, and experience to incorporate")
213
+ candidateBackground: z.string().optional().describe(
214
+ "Brief summary of the candidate background, skills, and experience to incorporate"
215
+ )
282
216
  }
283
217
  },
284
218
  ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({
285
- messages: [{
286
- role: "user",
287
- content: {
288
- type: "text",
289
- text: `Create a tailored resume in YAML format optimized for the following job description.
219
+ messages: [
220
+ {
221
+ role: "user",
222
+ content: {
223
+ type: "text",
224
+ text: `Create a tailored resume in YAML format optimized for the following job description.
290
225
 
291
226
  ## Job Description
292
227
  ${jobDescription}
@@ -310,13 +245,14 @@ ${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ""}
310
245
  ## Workflow
311
246
  After generating the YAML:
312
247
  1. Use \`resuml_validate\` to check schema compliance
313
- 2. Use \`resuml_ats_check\` with the job description text. Target: score >= 75, keyword match >= 70%
248
+ 2. Use \`resuml_ats_check\` with the job description text. Target: total >= 75, parsing tier grade A, hard-skill-overlap >= 70%
314
249
  3. If ATS score is low, revise the YAML and re-check
315
250
  4. Use \`resuml_render\` with theme "even" for the final output
316
251
 
317
252
  Output the resume YAML first, then run the validation and ATS check tools.`
253
+ }
318
254
  }
319
- }]
255
+ ]
320
256
  })
321
257
  );
322
258
  server.registerPrompt(
@@ -331,11 +267,12 @@ Output the resume YAML first, then run the validation and ATS check tools.`
331
267
  }
332
268
  },
333
269
  ({ resumeYaml, jobDescription, targetScore }) => ({
334
- messages: [{
335
- role: "user",
336
- content: {
337
- type: "text",
338
- text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : " (target: 85+)"}.
270
+ messages: [
271
+ {
272
+ role: "user",
273
+ content: {
274
+ type: "text",
275
+ text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : " (target: 85+)"}.
339
276
 
340
277
  ## Current Resume YAML
341
278
  \`\`\`yaml
@@ -348,7 +285,7 @@ ${jobDescription}
348
285
  ## Instructions
349
286
 
350
287
  1. First, run \`resuml_ats_check\` on the current YAML${jobDescription ? " with the job description" : ""} to get the baseline score
351
- 2. Read the ATS scoring rubric (resuml://docs/ats-scoring) to understand what checks are performed
288
+ 2. Read the ATS rubric (resuml://docs/ats-rubric) to understand what checks are performed, their tier, and weight
352
289
  3. Review each failed or low-scoring check and fix the issues:
353
290
  - Missing contact info \u2192 add it
354
291
  - No summary \u2192 write a 2-4 sentence professional summary
@@ -359,8 +296,9 @@ ${jobDescription}
359
296
  5. Repeat until the target score is reached
360
297
 
361
298
  Output the improved YAML with a summary of changes made.`
299
+ }
362
300
  }
363
- }]
301
+ ]
364
302
  })
365
303
  );
366
304
  server.registerPrompt(
@@ -373,11 +311,12 @@ Output the improved YAML with a summary of changes made.`
373
311
  }
374
312
  },
375
313
  ({ resumeYaml }) => ({
376
- messages: [{
377
- role: "user",
378
- content: {
379
- type: "text",
380
- text: `Perform a comprehensive review of this resume.
314
+ messages: [
315
+ {
316
+ role: "user",
317
+ content: {
318
+ type: "text",
319
+ text: `Perform a comprehensive review of this resume.
381
320
 
382
321
  ## Resume YAML
383
322
  \`\`\`yaml
@@ -400,8 +339,9 @@ ${resumeYaml}
400
339
  - **Strengths**: What the resume does well
401
340
  - **Improvements**: Specific, actionable suggestions
402
341
  - **Revised YAML**: An improved version if significant changes are recommended`
342
+ }
403
343
  }
404
- }]
344
+ ]
405
345
  })
406
346
  );
407
347
  server.registerTool(
@@ -439,13 +379,20 @@ ${resumeYaml}
439
379
  await processResumeData([yaml]);
440
380
  restoreStdout();
441
381
  return {
442
- content: [{ type: "text", text: JSON.stringify({ valid: true, errors: [] }, null, 2) }]
382
+ content: [
383
+ { type: "text", text: JSON.stringify({ valid: true, errors: [] }, null, 2) }
384
+ ]
443
385
  };
444
386
  } catch (e) {
445
387
  restoreStdout();
446
388
  const message = e instanceof Error ? e.message : String(e);
447
389
  return {
448
- content: [{ type: "text", text: JSON.stringify({ valid: false, errors: [message] }, null, 2) }]
390
+ content: [
391
+ {
392
+ type: "text",
393
+ text: JSON.stringify({ valid: false, errors: [message] }, null, 2)
394
+ }
395
+ ]
449
396
  };
450
397
  }
451
398
  }
@@ -465,9 +412,11 @@ ${resumeYaml}
465
412
  suppressStdout();
466
413
  try {
467
414
  const resume = await processResumeData([yaml]);
415
+ const cfg = loadConfig();
468
416
  const result = analyzeAts(resume, {
469
- language: language ?? "en",
470
- jobDescription
417
+ language: language ?? cfg.locale,
418
+ jobDescription,
419
+ config: cfg
471
420
  });
472
421
  restoreStdout();
473
422
  return {
@@ -476,12 +425,42 @@ ${resumeYaml}
476
425
  } catch (e) {
477
426
  restoreStdout();
478
427
  return {
479
- content: [{ type: "text", text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],
428
+ content: [
429
+ {
430
+ type: "text",
431
+ text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) })
432
+ }
433
+ ],
480
434
  isError: true
481
435
  };
482
436
  }
483
437
  }
484
438
  );
439
+ server.registerTool(
440
+ "resuml_ats_explain",
441
+ {
442
+ title: "ATS Rubric Explain",
443
+ description: "Return the rubric entry (tier, weight, evidence level, description, source) for a given check id.",
444
+ inputSchema: {
445
+ checkId: z.string().describe("Check id, e.g. quantification-density")
446
+ }
447
+ },
448
+ ({ checkId }) => {
449
+ const entry = getRubricEntry(checkId);
450
+ if (!entry) {
451
+ return {
452
+ content: [
453
+ {
454
+ type: "text",
455
+ text: JSON.stringify({ error: `Unknown check id: ${checkId}` })
456
+ }
457
+ ],
458
+ isError: true
459
+ };
460
+ }
461
+ return { content: [{ type: "text", text: JSON.stringify(entry, null, 2) }] };
462
+ }
463
+ );
485
464
  server.registerTool(
486
465
  "resuml_render",
487
466
  {
@@ -545,7 +524,9 @@ ${resumeYaml}
545
524
  theme: z.string().default("even").describe("Theme name"),
546
525
  format: z.enum(["A4", "Letter"]).default("A4").describe("Paper format"),
547
526
  locale: z.string().optional().describe("Locale for theme rendering (e.g. en, de)"),
548
- margin: z.string().optional().describe('Page margins. Single value (e.g. "10mm") for all sides, two values (e.g. "10mm,15mm") for vertical/horizontal, or four values (e.g. "10mm,15mm,10mm,15mm") for top/right/bottom/left')
527
+ margin: z.string().optional().describe(
528
+ 'Page margins. Single value (e.g. "10mm") for all sides, two values (e.g. "10mm,15mm") for vertical/horizontal, or four values (e.g. "10mm,15mm,10mm,15mm") for top/right/bottom/left'
529
+ )
549
530
  }
550
531
  },
551
532
  async (args) => {
@@ -566,7 +547,14 @@ ${resumeYaml}
566
547
  } catch {
567
548
  restoreStdout();
568
549
  return {
569
- content: [{ type: "text", text: JSON.stringify({ error: "Playwright is not installed. Run: npm install playwright" }) }],
550
+ content: [
551
+ {
552
+ type: "text",
553
+ text: JSON.stringify({
554
+ error: "Playwright is not installed. Run: npm install playwright"
555
+ })
556
+ }
557
+ ],
570
558
  isError: true
571
559
  };
572
560
  }
@@ -582,19 +570,26 @@ ${resumeYaml}
582
570
  await browser.close();
583
571
  restoreStdout();
584
572
  return {
585
- content: [{
586
- type: "text",
587
- text: JSON.stringify({
588
- pdf: Buffer.from(pdfBuffer).toString("base64"),
589
- encoding: "base64",
590
- format
591
- })
592
- }]
573
+ content: [
574
+ {
575
+ type: "text",
576
+ text: JSON.stringify({
577
+ pdf: Buffer.from(pdfBuffer).toString("base64"),
578
+ encoding: "base64",
579
+ format
580
+ })
581
+ }
582
+ ]
593
583
  };
594
584
  } catch (e) {
595
585
  restoreStdout();
596
586
  return {
597
- content: [{ type: "text", text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],
587
+ content: [
588
+ {
589
+ type: "text",
590
+ text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) })
591
+ }
592
+ ],
598
593
  isError: true
599
594
  };
600
595
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { processResumeData } from '../core';\nimport { analyzeAts } from '../ats/index';\nimport { loadTheme } from '../utils/themeLoader';\nimport { generateResumeYaml } from '../utils/resumeTemplate';\nimport { KNOWN_THEMES, isThemeInstalled, getInstalledVersion } from '../utils/themeInfo';\n\n// Redirect console.log to stderr so it doesn't corrupt the MCP stdio channel\nconst originalLog = console.log;\nconst originalWarn = console.warn;\n\nfunction suppressStdout() {\n console.log = (...args: unknown[]) => { console.error('[resuml]', ...args); };\n console.warn = (...args: unknown[]) => { console.error('[resuml]', ...args); };\n}\n\nfunction restoreStdout() {\n console.log = originalLog;\n console.warn = originalWarn;\n}\n\n// ── Shared data ─────────────────────────────────────────────────────\n\nconst JSON_RESUME_SCHEMA_REFERENCE = `# JSON Resume Schema Reference\n\nThe JSON Resume schema defines the structure for resume data. resuml uses YAML as the input format.\n\n## Top-level sections\n\n| Section | Required | Description |\n|---------|----------|-------------|\n| basics | Yes | Name, label, email, phone, url, summary, location, profiles |\n| work | Recommended | Work experience entries |\n| education | Recommended | Education entries |\n| skills | Recommended | Skill categories with keywords |\n| projects | Optional | Project entries |\n| volunteer | Optional | Volunteer experience |\n| awards | Optional | Awards and honors |\n| certificates | Optional | Professional certifications |\n| publications | Optional | Published works |\n| languages | Optional | Language proficiencies |\n| interests | Optional | Personal interests |\n| references | Optional | Professional references |\n\n## Section schemas\n\n### basics\n\\`\\`\\`yaml\nbasics:\n name: \"Full Name\" # required\n label: \"Professional Title\"\n email: \"email@example.com\"\n phone: \"+1-555-123-4567\"\n url: \"https://website.com\"\n summary: \"2-4 sentence professional summary\"\n location:\n city: \"City\"\n countryCode: \"US\"\n region: \"State\"\n profiles:\n - network: \"LinkedIn\"\n username: \"username\"\n url: \"https://linkedin.com/in/username\"\n\\`\\`\\`\n\n### work\n\\`\\`\\`yaml\nwork:\n - name: \"Company Name\"\n position: \"Job Title\"\n url: \"https://company.com\"\n startDate: \"2020-01-01\" # ISO 8601\n endDate: \"2023-12-31\" # omit for current position\n summary: \"Role description\"\n highlights:\n - \"Achievement with measurable result\"\n\\`\\`\\`\n\n### education\n\\`\\`\\`yaml\neducation:\n - institution: \"University\"\n area: \"Field of Study\"\n studyType: \"Degree Type\" # e.g. Bachelor, Master, PhD\n startDate: \"2014-09-01\"\n endDate: \"2018-06-01\"\n\\`\\`\\`\n\n### skills\n\\`\\`\\`yaml\nskills:\n - name: \"Category\"\n level: \"Expert\" # Master, Expert, Advanced, Intermediate, Beginner\n keywords: [\"Skill1\", \"Skill2\"]\n\\`\\`\\`\n\n### projects\n\\`\\`\\`yaml\nprojects:\n - name: \"Project Name\"\n description: \"What it does\"\n highlights: [\"Key achievement\"]\n keywords: [\"Tech1\", \"Tech2\"]\n startDate: \"2023-01-01\"\n url: \"https://github.com/...\"\n\\`\\`\\`\n\n### certificates\n\\`\\`\\`yaml\ncertificates:\n - name: \"Certificate Name\"\n date: \"2023-01-01\"\n issuer: \"Issuing Organization\"\n url: \"https://credential-url.com\"\n\\`\\`\\`\n\n### languages\n\\`\\`\\`yaml\nlanguages:\n - language: \"English\"\n fluency: \"Native speaker\" # Native speaker, Fluent, Advanced, Intermediate, Elementary\n\\`\\`\\`\n\n## Formatting rules\n- Dates: ISO 8601 format (YYYY-MM-DD or YYYY-MM)\n- Start highlights with action verbs: Developed, Implemented, Led, Optimized, Reduced, Built, Designed\n- Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\")\n- Never use first person (I, my, me, we)\n- Summary: 2-4 sentences positioning the candidate for the specific role\n`;\n\nconst ATS_SCORING_RUBRIC = `# ATS Scoring Rubric\n\nresuml performs deterministic, offline ATS (Applicant Tracking System) analysis.\n\n## Scoring system\n\n### Rating scale\n| Score | Rating | Description |\n|-------|--------|-------------|\n| 90-100 | Excellent | Resume is well-optimized for ATS |\n| 75-89 | Good | Resume passes most ATS checks |\n| 60-74 | Needs Work | Several improvements recommended |\n| 0-59 | Poor | Significant issues found |\n\n### Weight system\nEach check has a weight that affects the final score:\n- **High weight (3x)**: Critical checks that significantly impact ATS parsing\n- **Medium weight (2x)**: Important but not critical checks\n- **Low weight (1x)**: Nice-to-have improvements\n\n### Combined scoring (with job description)\nWhen a job description is provided:\n- Generic checks: 60% of final score\n- Keyword match: 40% of final score\n\n## Checks performed\n\n### Contact Information (category: contact)\n| Check | Weight | What it verifies |\n|-------|--------|-----------------|\n| contact-complete | High | Name, email, phone, and city are all present |\n| has-linkedin | Medium | LinkedIn profile exists in profiles section |\n\n### Content Quality (category: content)\n| Check | Weight | What it verifies |\n|-------|--------|-----------------|\n| has-summary | High | Professional summary exists (15-100 words) |\n| work-highlights | High | Each work entry has at least 2 highlights |\n| action-verbs | Medium | Highlights start with action verbs |\n| quantified-impact | Medium | 50%+ of highlights include numbers/metrics |\n| no-first-person | Low | No first-person pronouns (I, my, me, we) |\n\n### Resume Structure (category: structure)\n| Check | Weight | What it verifies |\n|-------|--------|-----------------|\n| date-consistency | Medium | All dates are valid ISO 8601 format |\n| skills-populated | Medium | At least 3 skill categories defined |\n| education-complete | Medium | Education section has institution and area |\n| essential-sections | High | Work, education, and skills sections present |\n\n## Job description matching\nWhen a job description is provided, resuml extracts keywords using TF-based extraction\nand matches them against the resume using stem matching. Results include:\n- **matched**: Keywords found in the resume\n- **missing**: Keywords not found (add these to improve score)\n- **matchPercentage**: Percentage of JD keywords found in resume\n\n## Fit Assessment\nWhen a job description is provided, a \\`fitAssessment\\` field is included in the result:\n| Match % | Level | Meaning |\n|---------|-------|---------|\n| >= 70% | strong | Resume aligns well with the job description |\n| 50-69% | partial | Some alignment; emphasize transferable skills |\n| < 50% | weak | Significant skill gaps; role may not match profile |\n\nThe assessment includes the top 5 missing keywords as specific gaps to address.\nUse this to advise users whether to apply or focus effort elsewhere.\n\n## Tips for improving ATS score\n1. Include all contact information (name, email, phone, city)\n2. Add a LinkedIn profile URL\n3. Write a 2-4 sentence professional summary\n4. Use action verbs to start each highlight\n5. Quantify achievements with numbers (%, $, time saved, team size)\n6. Include at least 3 skill categories with relevant keywords\n7. When targeting a job, mirror exact terminology from the job description\n`;\n\nfunction createServer(): McpServer {\n const server = new McpServer({\n name: 'resuml',\n version: '1.0.0',\n });\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCES\n // ═══════════════════════════════════════════════════════════════════\n\n // ── JSON Resume Schema Reference ──────────────────────────────────\n\n server.registerResource(\n 'json-resume-schema',\n 'resuml://schema/json-resume',\n {\n description: 'JSON Resume schema reference with all sections, field types, and formatting rules',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [{\n uri: 'resuml://schema/json-resume',\n mimeType: 'text/markdown',\n text: JSON_RESUME_SCHEMA_REFERENCE,\n }],\n }),\n );\n\n // ── ATS Scoring Rubric ────────────────────────────────────────────\n\n server.registerResource(\n 'ats-scoring-rubric',\n 'resuml://docs/ats-scoring',\n {\n description: 'ATS scoring rubric: checks performed, weight system, rating scale, and tips for improving score',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [{\n uri: 'resuml://docs/ats-scoring',\n mimeType: 'text/markdown',\n text: ATS_SCORING_RUBRIC,\n }],\n }),\n );\n\n // ── Theme Catalog ─────────────────────────────────────────────────\n\n server.registerResource(\n 'theme-catalog',\n 'resuml://themes/catalog',\n {\n description: 'Available resume themes with descriptions and installation status',\n mimeType: 'application/json',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n contents: [{\n uri: 'resuml://themes/catalog',\n mimeType: 'application/json',\n text: JSON.stringify({ themes, totalCount: themes.length }, null, 2),\n }],\n };\n },\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPTS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── Tailor Resume to Job Description ──────────────────────────────\n\n server.registerPrompt(\n 'tailor-resume-to-jd',\n {\n title: 'Tailor Resume to Job Description',\n description: 'Generate a tailored resume YAML optimized for a specific job description',\n argsSchema: {\n jobDescription: z.string().describe('The full job description text'),\n candidateName: z.string().optional().describe('Candidate full name'),\n candidateEmail: z.string().optional().describe('Candidate email address'),\n candidateBackground: z.string().optional().describe('Brief summary of the candidate background, skills, and experience to incorporate'),\n },\n },\n ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: `Create a tailored resume in YAML format optimized for the following job description.\n\n## Job Description\n${jobDescription}\n\n${candidateBackground ? `## Candidate Background\\n${candidateBackground}\\n` : ''}\n## Instructions\n\n1. Analyze the job description to identify required skills, technologies, experience level, and industry terms\n2. Generate a complete resume YAML following the JSON Resume schema (read the resuml://schema/json-resume resource for the full schema)\n3. Mirror exact terminology from the job description in skills and highlights\n4. Start every highlight with an action verb (Developed, Implemented, Led, Optimized, Reduced, Built, Designed)\n5. Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\", \"Managed team of 8\")\n6. Never use \"I\", \"my\", \"me\", \"we\"\n7. Write a 2-4 sentence summary positioning the candidate for this specific role\n8. Use ISO 8601 dates (YYYY-MM-DD or YYYY-MM)\n${candidateName ? `9. Use candidate name: ${candidateName}` : ''}\n${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ''}\n\n## Workflow\nAfter generating the YAML:\n1. Use \\`resuml_validate\\` to check schema compliance\n2. Use \\`resuml_ats_check\\` with the job description text. Target: score >= 75, keyword match >= 70%\n3. If ATS score is low, revise the YAML and re-check\n4. Use \\`resuml_render\\` with theme \"even\" for the final output\n\nOutput the resume YAML first, then run the validation and ATS check tools.`,\n },\n }],\n }),\n );\n\n // ── Optimize ATS Score ────────────────────────────────────────────\n\n server.registerPrompt(\n 'optimize-ats-score',\n {\n title: 'Optimize ATS Score',\n description: 'Analyze and improve an existing resume YAML to maximize its ATS score',\n argsSchema: {\n resumeYaml: z.string().describe('The current resume YAML content'),\n jobDescription: z.string().optional().describe('Optional job description to optimize against'),\n targetScore: z.string().optional().describe('Target ATS score (default: 85)'),\n },\n },\n ({ resumeYaml, jobDescription, targetScore }) => ({\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : ' (target: 85+)'}.\n\n## Current Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n${jobDescription ? `## Job Description\\n${jobDescription}\\n` : ''}\n## Instructions\n\n1. First, run \\`resuml_ats_check\\` on the current YAML${jobDescription ? ' with the job description' : ''} to get the baseline score\n2. Read the ATS scoring rubric (resuml://docs/ats-scoring) to understand what checks are performed\n3. Review each failed or low-scoring check and fix the issues:\n - Missing contact info → add it\n - No summary → write a 2-4 sentence professional summary\n - Weak highlights → rewrite with action verbs and quantified metrics\n - Missing keywords → incorporate them naturally into skills and highlights\n - Structural issues → ensure all essential sections are present\n4. Run \\`resuml_ats_check\\` again to verify improvement\n5. Repeat until the target score is reached\n\nOutput the improved YAML with a summary of changes made.`,\n },\n }],\n }),\n );\n\n // ── Review Resume ─────────────────────────────────────────────────\n\n server.registerPrompt(\n 'review-resume',\n {\n title: 'Review Resume',\n description: 'Comprehensive review of a resume YAML with ATS analysis and improvement suggestions',\n argsSchema: {\n resumeYaml: z.string().describe('The resume YAML content to review'),\n },\n },\n ({ resumeYaml }) => ({\n messages: [{\n role: 'user',\n content: {\n type: 'text',\n text: `Perform a comprehensive review of this resume.\n\n## Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n## Review steps\n\n1. Run \\`resuml_validate\\` to check schema compliance\n2. Run \\`resuml_ats_check\\` for ATS analysis\n3. Review content quality:\n - Is the summary compelling and role-specific?\n - Do highlights use strong action verbs?\n - Are achievements quantified with metrics?\n - Are skills well-organized and comprehensive?\n - Is the work history clear and impactful?\n4. Provide a structured review with:\n - **ATS Score**: Current score and rating\n - **Schema Issues**: Any validation errors\n - **Strengths**: What the resume does well\n - **Improvements**: Specific, actionable suggestions\n - **Revised YAML**: An improved version if significant changes are recommended`,\n },\n }],\n }),\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // TOOLS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── resuml_init_resume ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_init_resume',\n {\n title: 'Init Resume',\n description: 'Generate a starter resume YAML template following the JSON Resume schema',\n inputSchema: {\n name: z.string().optional().describe('Full name for the resume'),\n title: z.string().optional().describe('Professional title/label'),\n email: z.string().optional().describe('Email address'),\n },\n },\n ({ name, title, email }) => {\n const yaml = generateResumeYaml(\n name ?? 'Your Name',\n email ?? 'email@example.com',\n title ?? 'Professional Title',\n );\n return { content: [{ type: 'text' as const, text: yaml }] };\n },\n );\n\n // ── resuml_validate ─────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_validate',\n {\n title: 'Validate Resume',\n description: 'Validate resume YAML against the JSON Resume schema',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n },\n },\n async ({ yaml }) => {\n suppressStdout();\n try {\n await processResumeData([yaml]);\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ valid: true, errors: [] }, null, 2) }],\n };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ valid: false, errors: [message] }, null, 2) }],\n };\n }\n },\n );\n\n // ── resuml_ats_check ────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_check',\n {\n title: 'ATS Check',\n description: 'Run ATS (Applicant Tracking System) analysis on a resume, optionally matching against a job description',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n jobDescription: z.string().optional().describe('Job description text to match keywords against'),\n language: z.enum(['en', 'de']).optional().describe('Language for analysis (default: en)'),\n },\n },\n async ({ yaml, jobDescription, language }) => {\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const result = analyzeAts(resume, {\n language: language ?? 'en',\n jobDescription,\n });\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],\n isError: true,\n };\n }\n },\n );\n\n // ── resuml_render ───────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_render',\n {\n title: 'Render Resume',\n description: 'Render a resume to HTML using a specified theme',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z.string().default('even').describe('Theme name (e.g. even, stackoverflow, elegant, paper, kendall)'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n },\n },\n async (args) => {\n const { yaml, theme } = args;\n const locale = args['locale'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n restoreStdout();\n return { content: [{ type: 'text' as const, text: html }] };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n const hint = message.includes('Cannot find module')\n ? `. Install with: resuml themes --install ${theme}`\n : '';\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: message + hint }) }],\n isError: true,\n };\n }\n },\n );\n\n // ── resuml_list_themes ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_list_themes',\n {\n title: 'List Themes',\n description: 'List available resume themes with their installation status',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ themes }, null, 2) }],\n };\n },\n );\n\n // ── resuml_export_pdf ───────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_export_pdf',\n {\n title: 'Export PDF',\n description: 'Export a resume as PDF (requires Playwright to be installed)',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z.string().default('even').describe('Theme name'),\n format: z.enum(['A4', 'Letter']).default('A4').describe('Paper format'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n margin: z.string().optional().describe('Page margins. Single value (e.g. \"10mm\") for all sides, two values (e.g. \"10mm,15mm\") for vertical/horizontal, or four values (e.g. \"10mm,15mm,10mm,15mm\") for top/right/bottom/left'),\n },\n },\n async (args) => {\n const { yaml, theme, format } = args;\n const locale = args['locale'];\n const margin = args['margin'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n\n let chromium;\n try {\n const pw = await import('playwright');\n chromium = pw.chromium;\n } catch {\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Playwright is not installed. Run: npm install playwright' }) }],\n isError: true,\n };\n }\n\n const parsedMargin = parseMargin(margin);\n const browser = await chromium.launch({ headless: true });\n const page = await browser.newPage();\n await page.setContent(html, { waitUntil: 'networkidle' });\n const pdfBuffer = await page.pdf({\n format,\n printBackground: true,\n margin: parsedMargin,\n });\n await browser.close();\n restoreStdout();\n\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n pdf: Buffer.from(pdfBuffer).toString('base64'),\n encoding: 'base64',\n format,\n }),\n }],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],\n isError: true,\n };\n }\n },\n );\n\n return server;\n}\n\nfunction parseMargin(margin?: string): Record<string, string> {\n const defaultMargin = { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' };\n if (!margin) return defaultMargin;\n\n const parts = margin.split(',').map((s) => s.trim());\n if (parts.length === 1 && parts[0]) {\n return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };\n }\n if (parts.length === 2 && parts[0] && parts[1]) {\n return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };\n }\n if (parts.length === 4 && parts[0] && parts[1] && parts[2] && parts[3]) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };\n }\n return defaultMargin;\n}\n\nexport async function startMcpServer(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { processResumeData } from '../core';\nimport { analyzeAts } from '../ats/index';\nimport { listRubricMarkdown, getRubricEntry } from '../ats/rubric';\nimport { loadConfig } from '../utils/config';\nimport { loadTheme } from '../utils/themeLoader';\nimport { generateResumeYaml } from '../utils/resumeTemplate';\nimport { KNOWN_THEMES, isThemeInstalled, getInstalledVersion } from '../utils/themeInfo';\n\n// Redirect console.log to stderr so it doesn't corrupt the MCP stdio channel\nconst originalLog = console.log;\nconst originalWarn = console.warn;\n\nfunction suppressStdout() {\n console.log = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n console.warn = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n}\n\nfunction restoreStdout() {\n console.log = originalLog;\n console.warn = originalWarn;\n}\n\n// ── Shared data ─────────────────────────────────────────────────────\n\nconst JSON_RESUME_SCHEMA_REFERENCE = `# JSON Resume Schema Reference\n\nThe JSON Resume schema defines the structure for resume data. resuml uses YAML as the input format.\n\n## Top-level sections\n\n| Section | Required | Description |\n|---------|----------|-------------|\n| basics | Yes | Name, label, email, phone, url, summary, location, profiles |\n| work | Recommended | Work experience entries |\n| education | Recommended | Education entries |\n| skills | Recommended | Skill categories with keywords |\n| projects | Optional | Project entries |\n| volunteer | Optional | Volunteer experience |\n| awards | Optional | Awards and honors |\n| certificates | Optional | Professional certifications |\n| publications | Optional | Published works |\n| languages | Optional | Language proficiencies |\n| interests | Optional | Personal interests |\n| references | Optional | Professional references |\n\n## Section schemas\n\n### basics\n\\`\\`\\`yaml\nbasics:\n name: \"Full Name\" # required\n label: \"Professional Title\"\n email: \"email@example.com\"\n phone: \"+1-555-123-4567\"\n url: \"https://website.com\"\n summary: \"2-4 sentence professional summary\"\n location:\n city: \"City\"\n countryCode: \"US\"\n region: \"State\"\n profiles:\n - network: \"LinkedIn\"\n username: \"username\"\n url: \"https://linkedin.com/in/username\"\n\\`\\`\\`\n\n### work\n\\`\\`\\`yaml\nwork:\n - name: \"Company Name\"\n position: \"Job Title\"\n url: \"https://company.com\"\n startDate: \"2020-01-01\" # ISO 8601\n endDate: \"2023-12-31\" # omit for current position\n summary: \"Role description\"\n highlights:\n - \"Achievement with measurable result\"\n\\`\\`\\`\n\n### education\n\\`\\`\\`yaml\neducation:\n - institution: \"University\"\n area: \"Field of Study\"\n studyType: \"Degree Type\" # e.g. Bachelor, Master, PhD\n startDate: \"2014-09-01\"\n endDate: \"2018-06-01\"\n\\`\\`\\`\n\n### skills\n\\`\\`\\`yaml\nskills:\n - name: \"Category\"\n level: \"Expert\" # Master, Expert, Advanced, Intermediate, Beginner\n keywords: [\"Skill1\", \"Skill2\"]\n\\`\\`\\`\n\n### projects\n\\`\\`\\`yaml\nprojects:\n - name: \"Project Name\"\n description: \"What it does\"\n highlights: [\"Key achievement\"]\n keywords: [\"Tech1\", \"Tech2\"]\n startDate: \"2023-01-01\"\n url: \"https://github.com/...\"\n\\`\\`\\`\n\n### certificates\n\\`\\`\\`yaml\ncertificates:\n - name: \"Certificate Name\"\n date: \"2023-01-01\"\n issuer: \"Issuing Organization\"\n url: \"https://credential-url.com\"\n\\`\\`\\`\n\n### languages\n\\`\\`\\`yaml\nlanguages:\n - language: \"English\"\n fluency: \"Native speaker\" # Native speaker, Fluent, Advanced, Intermediate, Elementary\n\\`\\`\\`\n\n## Formatting rules\n- Dates: ISO 8601 format (YYYY-MM-DD or YYYY-MM)\n- Start highlights with action verbs: Developed, Implemented, Led, Optimized, Reduced, Built, Designed\n- Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\")\n- Never use first person (I, my, me, we)\n- Summary: 2-4 sentences positioning the candidate for the specific role\n`;\n\nfunction createServer(): McpServer {\n const server = new McpServer({\n name: 'resuml',\n version: '2.0.0',\n });\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCES\n // ═══════════════════════════════════════════════════════════════════\n\n // ── JSON Resume Schema Reference ──────────────────────────────────\n\n server.registerResource(\n 'json-resume-schema',\n 'resuml://schema/json-resume',\n {\n description:\n 'JSON Resume schema reference with all sections, field types, and formatting rules',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://schema/json-resume',\n mimeType: 'text/markdown',\n text: JSON_RESUME_SCHEMA_REFERENCE,\n },\n ],\n })\n );\n\n // ── ATS Rubric ───────────────────────────────────────────────────\n\n server.registerResource(\n 'ats-rubric',\n 'resuml://docs/ats-rubric',\n {\n description:\n 'Tiered ATS rubric: every check, its tier, weight, evidence level, description, and source URL.',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://docs/ats-rubric',\n mimeType: 'text/markdown',\n text: listRubricMarkdown(),\n },\n ],\n })\n );\n\n // ── Theme Catalog ─────────────────────────────────────────────────\n\n server.registerResource(\n 'theme-catalog',\n 'resuml://themes/catalog',\n {\n description: 'Available resume themes with descriptions and installation status',\n mimeType: 'application/json',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n contents: [\n {\n uri: 'resuml://themes/catalog',\n mimeType: 'application/json',\n text: JSON.stringify({ themes, totalCount: themes.length }, null, 2),\n },\n ],\n };\n }\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPTS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── Tailor Resume to Job Description ──────────────────────────────\n\n server.registerPrompt(\n 'tailor-resume-to-jd',\n {\n title: 'Tailor Resume to Job Description',\n description: 'Generate a tailored resume YAML optimized for a specific job description',\n argsSchema: {\n jobDescription: z.string().describe('The full job description text'),\n candidateName: z.string().optional().describe('Candidate full name'),\n candidateEmail: z.string().optional().describe('Candidate email address'),\n candidateBackground: z\n .string()\n .optional()\n .describe(\n 'Brief summary of the candidate background, skills, and experience to incorporate'\n ),\n },\n },\n ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Create a tailored resume in YAML format optimized for the following job description.\n\n## Job Description\n${jobDescription}\n\n${candidateBackground ? `## Candidate Background\\n${candidateBackground}\\n` : ''}\n## Instructions\n\n1. Analyze the job description to identify required skills, technologies, experience level, and industry terms\n2. Generate a complete resume YAML following the JSON Resume schema (read the resuml://schema/json-resume resource for the full schema)\n3. Mirror exact terminology from the job description in skills and highlights\n4. Start every highlight with an action verb (Developed, Implemented, Led, Optimized, Reduced, Built, Designed)\n5. Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\", \"Managed team of 8\")\n6. Never use \"I\", \"my\", \"me\", \"we\"\n7. Write a 2-4 sentence summary positioning the candidate for this specific role\n8. Use ISO 8601 dates (YYYY-MM-DD or YYYY-MM)\n${candidateName ? `9. Use candidate name: ${candidateName}` : ''}\n${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ''}\n\n## Workflow\nAfter generating the YAML:\n1. Use \\`resuml_validate\\` to check schema compliance\n2. Use \\`resuml_ats_check\\` with the job description text. Target: total >= 75, parsing tier grade A, hard-skill-overlap >= 70%\n3. If ATS score is low, revise the YAML and re-check\n4. Use \\`resuml_render\\` with theme \"even\" for the final output\n\nOutput the resume YAML first, then run the validation and ATS check tools.`,\n },\n },\n ],\n })\n );\n\n // ── Optimize ATS Score ────────────────────────────────────────────\n\n server.registerPrompt(\n 'optimize-ats-score',\n {\n title: 'Optimize ATS Score',\n description: 'Analyze and improve an existing resume YAML to maximize its ATS score',\n argsSchema: {\n resumeYaml: z.string().describe('The current resume YAML content'),\n jobDescription: z\n .string()\n .optional()\n .describe('Optional job description to optimize against'),\n targetScore: z.string().optional().describe('Target ATS score (default: 85)'),\n },\n },\n ({ resumeYaml, jobDescription, targetScore }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : ' (target: 85+)'}.\n\n## Current Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n${jobDescription ? `## Job Description\\n${jobDescription}\\n` : ''}\n## Instructions\n\n1. First, run \\`resuml_ats_check\\` on the current YAML${jobDescription ? ' with the job description' : ''} to get the baseline score\n2. Read the ATS rubric (resuml://docs/ats-rubric) to understand what checks are performed, their tier, and weight\n3. Review each failed or low-scoring check and fix the issues:\n - Missing contact info → add it\n - No summary → write a 2-4 sentence professional summary\n - Weak highlights → rewrite with action verbs and quantified metrics\n - Missing keywords → incorporate them naturally into skills and highlights\n - Structural issues → ensure all essential sections are present\n4. Run \\`resuml_ats_check\\` again to verify improvement\n5. Repeat until the target score is reached\n\nOutput the improved YAML with a summary of changes made.`,\n },\n },\n ],\n })\n );\n\n // ── Review Resume ─────────────────────────────────────────────────\n\n server.registerPrompt(\n 'review-resume',\n {\n title: 'Review Resume',\n description:\n 'Comprehensive review of a resume YAML with ATS analysis and improvement suggestions',\n argsSchema: {\n resumeYaml: z.string().describe('The resume YAML content to review'),\n },\n },\n ({ resumeYaml }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Perform a comprehensive review of this resume.\n\n## Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n## Review steps\n\n1. Run \\`resuml_validate\\` to check schema compliance\n2. Run \\`resuml_ats_check\\` for ATS analysis\n3. Review content quality:\n - Is the summary compelling and role-specific?\n - Do highlights use strong action verbs?\n - Are achievements quantified with metrics?\n - Are skills well-organized and comprehensive?\n - Is the work history clear and impactful?\n4. Provide a structured review with:\n - **ATS Score**: Current score and rating\n - **Schema Issues**: Any validation errors\n - **Strengths**: What the resume does well\n - **Improvements**: Specific, actionable suggestions\n - **Revised YAML**: An improved version if significant changes are recommended`,\n },\n },\n ],\n })\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // TOOLS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── resuml_init_resume ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_init_resume',\n {\n title: 'Init Resume',\n description: 'Generate a starter resume YAML template following the JSON Resume schema',\n inputSchema: {\n name: z.string().optional().describe('Full name for the resume'),\n title: z.string().optional().describe('Professional title/label'),\n email: z.string().optional().describe('Email address'),\n },\n },\n ({ name, title, email }) => {\n const yaml = generateResumeYaml(\n name ?? 'Your Name',\n email ?? 'email@example.com',\n title ?? 'Professional Title'\n );\n return { content: [{ type: 'text' as const, text: yaml }] };\n }\n );\n\n // ── resuml_validate ─────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_validate',\n {\n title: 'Validate Resume',\n description: 'Validate resume YAML against the JSON Resume schema',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n },\n },\n async ({ yaml }) => {\n suppressStdout();\n try {\n await processResumeData([yaml]);\n restoreStdout();\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify({ valid: true, errors: [] }, null, 2) },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ valid: false, errors: [message] }, null, 2),\n },\n ],\n };\n }\n }\n );\n\n // ── resuml_ats_check ────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_check',\n {\n title: 'ATS Check',\n description:\n 'Run ATS (Applicant Tracking System) analysis on a resume, optionally matching against a job description',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n jobDescription: z\n .string()\n .optional()\n .describe('Job description text to match keywords against'),\n language: z.enum(['en', 'de']).optional().describe('Language for analysis (default: en)'),\n },\n },\n async ({ yaml, jobDescription, language }) => {\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const cfg = loadConfig();\n const result = analyzeAts(resume, {\n language: language ?? cfg.locale,\n jobDescription,\n config: cfg,\n });\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_ats_explain ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_explain',\n {\n title: 'ATS Rubric Explain',\n description:\n 'Return the rubric entry (tier, weight, evidence level, description, source) for a given check id.',\n inputSchema: {\n checkId: z.string().describe('Check id, e.g. quantification-density'),\n },\n },\n ({ checkId }) => {\n const entry = getRubricEntry(checkId);\n if (!entry) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: `Unknown check id: ${checkId}` }),\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: 'text' as const, text: JSON.stringify(entry, null, 2) }] };\n }\n );\n\n // ── resuml_render ───────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_render',\n {\n title: 'Render Resume',\n description: 'Render a resume to HTML using a specified theme',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z\n .string()\n .default('even')\n .describe('Theme name (e.g. even, stackoverflow, elegant, paper, kendall)'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n },\n },\n async (args) => {\n const { yaml, theme } = args;\n const locale = args['locale'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n restoreStdout();\n return { content: [{ type: 'text' as const, text: html }] };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n const hint = message.includes('Cannot find module')\n ? `. Install with: resuml themes --install ${theme}`\n : '';\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: message + hint }) }],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_list_themes ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_list_themes',\n {\n title: 'List Themes',\n description: 'List available resume themes with their installation status',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ themes }, null, 2) }],\n };\n }\n );\n\n // ── resuml_export_pdf ───────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_export_pdf',\n {\n title: 'Export PDF',\n description: 'Export a resume as PDF (requires Playwright to be installed)',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z.string().default('even').describe('Theme name'),\n format: z.enum(['A4', 'Letter']).default('A4').describe('Paper format'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n margin: z\n .string()\n .optional()\n .describe(\n 'Page margins. Single value (e.g. \"10mm\") for all sides, two values (e.g. \"10mm,15mm\") for vertical/horizontal, or four values (e.g. \"10mm,15mm,10mm,15mm\") for top/right/bottom/left'\n ),\n },\n },\n async (args) => {\n const { yaml, theme, format } = args;\n const locale = args['locale'];\n const margin = args['margin'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n\n let chromium;\n try {\n const pw = await import('playwright');\n chromium = pw.chromium;\n } catch {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Playwright is not installed. Run: npm install playwright',\n }),\n },\n ],\n isError: true,\n };\n }\n\n const parsedMargin = parseMargin(margin);\n const browser = await chromium.launch({ headless: true });\n const page = await browser.newPage();\n await page.setContent(html, { waitUntil: 'networkidle' });\n const pdfBuffer = await page.pdf({\n format,\n printBackground: true,\n margin: parsedMargin,\n });\n await browser.close();\n restoreStdout();\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n pdf: Buffer.from(pdfBuffer).toString('base64'),\n encoding: 'base64',\n format,\n }),\n },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n return server;\n}\n\nfunction parseMargin(margin?: string): Record<string, string> {\n const defaultMargin = { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' };\n if (!margin) return defaultMargin;\n\n const parts = margin.split(',').map((s) => s.trim());\n if (parts.length === 1 && parts[0]) {\n return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };\n }\n if (parts.length === 2 && parts[0] && parts[1]) {\n return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };\n }\n if (parts.length === 4 && parts[0] && parts[1] && parts[2] && parts[3]) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };\n }\n return defaultMargin;\n}\n\nexport async function startMcpServer(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resuml",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Generate JSON resumes from YAML with theme support. CLI + MCP server for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {