latitude-mcp-server 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools.d.ts +8 -9
- package/dist/tools.js +109 -149
- package/package.json +1 -1
- package/prompts/cover-letter-generate.promptl +71 -0
- package/prompts/cv-ingest-questions.promptl +386 -0
- package/prompts/cv-ingest.promptl +449 -0
- package/prompts/job-filter-bootstrap.promptl +115 -0
- package/prompts/job-filter-refine.promptl +173 -0
- package/prompts/linkedin-search.promptl +225 -0
- package/prompts/pattern-bootstrap.promptl +2753 -0
- package/prompts/pattern-refine.promptl +247 -0
- package/prompts/question-generate.promptl +172 -0
- package/prompts/research-discover.promptl +235 -0
- package/prompts/research-validate.promptl +193 -0
package/dist/tools.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tools for Latitude
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - list_prompts
|
|
6
|
-
* - get_prompt
|
|
7
|
-
* - run_prompt
|
|
8
|
-
* - push_prompts
|
|
9
|
-
* - pull_prompts
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* - docs : Documentation (help, get topic, find query)
|
|
4
|
+
* 7 Tools:
|
|
5
|
+
* - list_prompts : List all prompt names in LIVE
|
|
6
|
+
* - get_prompt : Get full prompt content by name
|
|
7
|
+
* - run_prompt : Execute a prompt with parameters (dynamic prompt list + variables)
|
|
8
|
+
* - push_prompts : FULL SYNC to remote (adds, modifies, DELETES remote prompts not in local)
|
|
9
|
+
* - pull_prompts : FULL SYNC from remote (deletes ALL local, downloads ALL from LIVE)
|
|
10
|
+
* - add_prompt : ADDITIVE - add/overwrite prompts without deleting others (dynamic prompt list)
|
|
11
|
+
* - docs : Documentation (help, get topic, find query)
|
|
13
12
|
*/
|
|
14
13
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
14
|
export declare function registerTools(server: McpServer): Promise<void>;
|
package/dist/tools.js
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* MCP Tools for Latitude
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* - list_prompts
|
|
7
|
-
* - get_prompt
|
|
8
|
-
* - run_prompt
|
|
9
|
-
* - push_prompts
|
|
10
|
-
* - pull_prompts
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* - docs : Documentation (help, get topic, find query)
|
|
5
|
+
* 7 Tools:
|
|
6
|
+
* - list_prompts : List all prompt names in LIVE
|
|
7
|
+
* - get_prompt : Get full prompt content by name
|
|
8
|
+
* - run_prompt : Execute a prompt with parameters (dynamic prompt list + variables)
|
|
9
|
+
* - push_prompts : FULL SYNC to remote (adds, modifies, DELETES remote prompts not in local)
|
|
10
|
+
* - pull_prompts : FULL SYNC from remote (deletes ALL local, downloads ALL from LIVE)
|
|
11
|
+
* - add_prompt : ADDITIVE - add/overwrite prompts without deleting others (dynamic prompt list)
|
|
12
|
+
* - docs : Documentation (help, get topic, find query)
|
|
14
13
|
*/
|
|
15
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
15
|
exports.registerTools = registerTools;
|
|
@@ -106,6 +105,73 @@ function formatAvailablePrompts(names) {
|
|
|
106
105
|
const formatted = names.map(n => `\`${n}\``).join(', ');
|
|
107
106
|
return `\n\n**Available prompts (${names.length}):** ${formatted}`;
|
|
108
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Extract variable names from prompt content
|
|
110
|
+
* Looks for {{ variable }} and { variable } patterns
|
|
111
|
+
*/
|
|
112
|
+
function extractVariables(content) {
|
|
113
|
+
const variables = new Set();
|
|
114
|
+
// Match {{ variable }} and { variable } patterns (PromptL syntax)
|
|
115
|
+
const patterns = [
|
|
116
|
+
/\{\{\s*(\w+)\s*\}\}/g, // {{ variable }}
|
|
117
|
+
/\{\s*(\w+)\s*\}/g, // { variable }
|
|
118
|
+
];
|
|
119
|
+
for (const pattern of patterns) {
|
|
120
|
+
let match;
|
|
121
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
122
|
+
// Exclude control flow keywords
|
|
123
|
+
const varName = match[1];
|
|
124
|
+
if (!['if', 'else', 'each', 'let', 'end', 'for', 'unless'].includes(varName)) {
|
|
125
|
+
variables.add(varName);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return Array.from(variables);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Build dynamic description for run_prompt with prompt names and their variables
|
|
133
|
+
*/
|
|
134
|
+
async function buildRunPromptDescription() {
|
|
135
|
+
const names = await getCachedPromptNames();
|
|
136
|
+
let desc = 'Execute a prompt with parameters.';
|
|
137
|
+
if (names.length === 0) {
|
|
138
|
+
desc += '\n\n**No prompts in LIVE yet.**';
|
|
139
|
+
return desc;
|
|
140
|
+
}
|
|
141
|
+
desc += `\n\n**Available prompts (${names.length}):**`;
|
|
142
|
+
// Fetch each prompt to get its variables (limit to avoid too long description)
|
|
143
|
+
const maxToShow = Math.min(names.length, 10);
|
|
144
|
+
for (let i = 0; i < maxToShow; i++) {
|
|
145
|
+
try {
|
|
146
|
+
const doc = await (0, api_js_1.getDocument)(names[i], 'live');
|
|
147
|
+
const vars = extractVariables(doc.content);
|
|
148
|
+
if (vars.length > 0) {
|
|
149
|
+
desc += `\n- \`${names[i]}\` (params: ${vars.map(v => `\`${v}\``).join(', ')})`;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
desc += `\n- \`${names[i]}\` (no params)`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
desc += `\n- \`${names[i]}\``;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (names.length > maxToShow) {
|
|
160
|
+
desc += `\n- ... and ${names.length - maxToShow} more`;
|
|
161
|
+
}
|
|
162
|
+
return desc;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build dynamic description for add_prompt with available prompts
|
|
166
|
+
*/
|
|
167
|
+
async function buildAddPromptDescription() {
|
|
168
|
+
const names = await getCachedPromptNames();
|
|
169
|
+
let desc = 'Add or update prompt(s) in LIVE without deleting others. ';
|
|
170
|
+
desc += 'If a prompt with the same name exists, it will be overwritten. ';
|
|
171
|
+
desc += 'Provide `prompts` array OR `filePaths` to .promptl files.';
|
|
172
|
+
desc += formatAvailablePrompts(names);
|
|
173
|
+
return desc;
|
|
174
|
+
}
|
|
109
175
|
/**
|
|
110
176
|
* Validate all prompts BEFORE pushing.
|
|
111
177
|
* If ANY prompt fails validation, returns all errors and NOTHING is pushed.
|
|
@@ -303,25 +369,20 @@ async function handlePushPrompts(args) {
|
|
|
303
369
|
return formatError(error);
|
|
304
370
|
}
|
|
305
371
|
}
|
|
306
|
-
const
|
|
372
|
+
const AddPromptSchema = zod_1.z.object({
|
|
307
373
|
prompts: zod_1.z
|
|
308
374
|
.array(zod_1.z.object({
|
|
309
375
|
name: zod_1.z.string().describe('Prompt name'),
|
|
310
376
|
content: zod_1.z.string().describe('Prompt content'),
|
|
311
377
|
}))
|
|
312
378
|
.optional()
|
|
313
|
-
.describe('Prompts to
|
|
379
|
+
.describe('Prompts to add/update - overwrites if exists, adds if new'),
|
|
314
380
|
filePaths: zod_1.z
|
|
315
381
|
.array(zod_1.z.string())
|
|
316
382
|
.optional()
|
|
317
|
-
.describe('File paths to .promptl files -
|
|
318
|
-
overwrite: zod_1.z
|
|
319
|
-
.boolean()
|
|
320
|
-
.optional()
|
|
321
|
-
.default(false)
|
|
322
|
-
.describe('If true, update existing prompts with same name (still no deletions)'),
|
|
383
|
+
.describe('File paths to .promptl files - overwrites if exists, adds if new'),
|
|
323
384
|
});
|
|
324
|
-
async function
|
|
385
|
+
async function handleAddPrompt(args) {
|
|
325
386
|
try {
|
|
326
387
|
// Build prompts from either direct input or file paths
|
|
327
388
|
let prompts = [];
|
|
@@ -337,9 +398,9 @@ async function handleAppendPrompts(args) {
|
|
|
337
398
|
if (prompts.length === 0) {
|
|
338
399
|
return formatError(new Error('No prompts provided. Use either prompts array or filePaths.'));
|
|
339
400
|
}
|
|
340
|
-
// PRE-VALIDATE ALL PROMPTS BEFORE
|
|
341
|
-
// If ANY prompt fails validation, return errors and
|
|
342
|
-
logger.info(`Validating ${prompts.length} prompt(s) before
|
|
401
|
+
// PRE-VALIDATE ALL PROMPTS BEFORE ADDING
|
|
402
|
+
// If ANY prompt fails validation, return errors and add NOTHING
|
|
403
|
+
logger.info(`Validating ${prompts.length} prompt(s) before add...`);
|
|
343
404
|
const validation = await validateAllPrompts(prompts);
|
|
344
405
|
if (!validation.valid) {
|
|
345
406
|
logger.warn(`Validation failed for ${validation.errors.length} prompt(s)`);
|
|
@@ -349,26 +410,20 @@ async function handleAppendPrompts(args) {
|
|
|
349
410
|
// Get existing prompts
|
|
350
411
|
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
351
412
|
const existingMap = new Map(existingDocs.map((d) => [d.path, d]));
|
|
352
|
-
// Build changes -
|
|
413
|
+
// Build changes - ALWAYS overwrite if exists, add if new, NEVER delete
|
|
353
414
|
const changes = [];
|
|
354
|
-
const skipped = [];
|
|
355
415
|
for (const prompt of prompts) {
|
|
356
416
|
const existingDoc = existingMap.get(prompt.name);
|
|
357
417
|
if (existingDoc) {
|
|
358
|
-
if
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
// If same content, skip silently (unchanged)
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
skipped.push(prompt.name);
|
|
418
|
+
// Only include if content is different
|
|
419
|
+
if (existingDoc.content !== prompt.content) {
|
|
420
|
+
changes.push({
|
|
421
|
+
path: prompt.name,
|
|
422
|
+
content: prompt.content,
|
|
423
|
+
status: 'modified',
|
|
424
|
+
});
|
|
371
425
|
}
|
|
426
|
+
// If same content, skip silently (unchanged)
|
|
372
427
|
}
|
|
373
428
|
else {
|
|
374
429
|
// New prompt
|
|
@@ -382,28 +437,19 @@ async function handleAppendPrompts(args) {
|
|
|
382
437
|
// Summarize
|
|
383
438
|
const added = changes.filter((c) => c.status === 'added');
|
|
384
439
|
const modified = changes.filter((c) => c.status === 'modified');
|
|
385
|
-
if (changes.length === 0
|
|
440
|
+
if (changes.length === 0) {
|
|
386
441
|
const newNames = await forceRefreshAndGetNames();
|
|
387
442
|
return formatSuccess('No Changes Needed', `All ${prompts.length} prompt(s) are already up to date.\n\n` +
|
|
388
443
|
`**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`);
|
|
389
444
|
}
|
|
390
|
-
if (changes.length === 0) {
|
|
391
|
-
const newNames = await forceRefreshAndGetNames();
|
|
392
|
-
let content = `**Skipped:** ${skipped.length} (already exist, use overwrite=true to update)\n`;
|
|
393
|
-
content += `\n---\n**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`;
|
|
394
|
-
return formatSuccess('No Changes Made', content);
|
|
395
|
-
}
|
|
396
445
|
// Push all changes in one batch
|
|
397
446
|
try {
|
|
398
|
-
const result = await (0, api_js_1.deployToLive)(changes, '
|
|
447
|
+
const result = await (0, api_js_1.deployToLive)(changes, 'add');
|
|
399
448
|
// Force refresh cache after mutations
|
|
400
449
|
const newNames = await forceRefreshAndGetNames();
|
|
401
450
|
let content = `**Summary:**\n`;
|
|
402
451
|
content += `- Added: ${added.length}\n`;
|
|
403
452
|
content += `- Updated: ${modified.length}\n`;
|
|
404
|
-
if (skipped.length > 0) {
|
|
405
|
-
content += `- Skipped: ${skipped.length} (use overwrite=true)\n`;
|
|
406
|
-
}
|
|
407
453
|
content += `- Documents processed: ${result.documentsProcessed}\n\n`;
|
|
408
454
|
if (added.length > 0) {
|
|
409
455
|
content += `### Added\n${added.map((c) => `- \`${c.path}\``).join('\n')}\n\n`;
|
|
@@ -411,11 +457,8 @@ async function handleAppendPrompts(args) {
|
|
|
411
457
|
if (modified.length > 0) {
|
|
412
458
|
content += `### Updated\n${modified.map((c) => `- \`${c.path}\``).join('\n')}\n\n`;
|
|
413
459
|
}
|
|
414
|
-
if (skipped.length > 0) {
|
|
415
|
-
content += `### Skipped (already exist)\n${skipped.map(n => `- \`${n}\``).join('\n')}\n\n`;
|
|
416
|
-
}
|
|
417
460
|
content += `---\n**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`;
|
|
418
|
-
return formatSuccess('Prompts
|
|
461
|
+
return formatSuccess('Prompts Added to LIVE', content);
|
|
419
462
|
}
|
|
420
463
|
catch (error) {
|
|
421
464
|
// Detailed error from API
|
|
@@ -472,93 +515,13 @@ async function handlePullPrompts(args) {
|
|
|
472
515
|
content += `**Written:** ${written.length} file(s)\n\n`;
|
|
473
516
|
content += `### Files\n\n`;
|
|
474
517
|
content += written.map((f) => `- \`${f}\``).join('\n');
|
|
475
|
-
content += `\n\n**Tip:** Edit files locally, then use \`
|
|
518
|
+
content += `\n\n**Tip:** Edit files locally, then use \`add_prompt\` with \`filePaths\` to push changes.`;
|
|
476
519
|
return formatSuccess('Prompts Pulled from LIVE', content);
|
|
477
520
|
}
|
|
478
521
|
catch (error) {
|
|
479
522
|
return formatError(error);
|
|
480
523
|
}
|
|
481
524
|
}
|
|
482
|
-
// Dynamic description builder for replace_prompt
|
|
483
|
-
async function buildReplacePromptDescription() {
|
|
484
|
-
const names = await getCachedPromptNames();
|
|
485
|
-
let desc = 'Replace or create a single prompt in LIVE. ';
|
|
486
|
-
desc += 'Provide either `content` directly or `filePath` to read from local file.';
|
|
487
|
-
desc += formatAvailablePrompts(names);
|
|
488
|
-
return desc;
|
|
489
|
-
}
|
|
490
|
-
const ReplacePromptSchema = zod_1.z.object({
|
|
491
|
-
name: zod_1.z
|
|
492
|
-
.string()
|
|
493
|
-
.optional()
|
|
494
|
-
.describe('Prompt name to replace (auto-detected from filePath if not provided)'),
|
|
495
|
-
content: zod_1.z
|
|
496
|
-
.string()
|
|
497
|
-
.optional()
|
|
498
|
-
.describe('New prompt content (alternative to filePath)'),
|
|
499
|
-
filePath: zod_1.z
|
|
500
|
-
.string()
|
|
501
|
-
.optional()
|
|
502
|
-
.describe('Path to .promptl file to read content from (alternative to content)'),
|
|
503
|
-
});
|
|
504
|
-
async function handleReplacePrompt(args) {
|
|
505
|
-
try {
|
|
506
|
-
let name = args.name;
|
|
507
|
-
let content = args.content;
|
|
508
|
-
// If filePath provided, read from file
|
|
509
|
-
if (args.filePath) {
|
|
510
|
-
const fileData = readPromptFile(args.filePath);
|
|
511
|
-
content = fileData.content;
|
|
512
|
-
// Use filename as name if not explicitly provided
|
|
513
|
-
if (!name) {
|
|
514
|
-
name = fileData.name;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
// Validate we have both name and content
|
|
518
|
-
if (!name) {
|
|
519
|
-
return formatError(new Error('Prompt name is required. Provide `name` or use `filePath` (name derived from filename).'));
|
|
520
|
-
}
|
|
521
|
-
if (!content) {
|
|
522
|
-
return formatError(new Error('Prompt content is required. Provide either `content` or `filePath`.'));
|
|
523
|
-
}
|
|
524
|
-
// PRE-VALIDATE PROMPT BEFORE REPLACING
|
|
525
|
-
// If validation fails, return error and replace NOTHING
|
|
526
|
-
logger.info(`Validating prompt "${name}" before replace...`);
|
|
527
|
-
const validation = await validateAllPrompts([{ name, content }]);
|
|
528
|
-
if (!validation.valid) {
|
|
529
|
-
logger.warn(`Validation failed for prompt "${name}"`);
|
|
530
|
-
return formatValidationErrors(validation.errors);
|
|
531
|
-
}
|
|
532
|
-
logger.info(`Prompt "${name}" passed validation`);
|
|
533
|
-
// Check if prompt exists
|
|
534
|
-
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
535
|
-
const exists = existingDocs.some((d) => d.path === name);
|
|
536
|
-
const changes = [
|
|
537
|
-
{
|
|
538
|
-
path: name,
|
|
539
|
-
content: content,
|
|
540
|
-
status: exists ? 'modified' : 'added',
|
|
541
|
-
},
|
|
542
|
-
];
|
|
543
|
-
// Deploy to LIVE (creates branch → pushes → publishes)
|
|
544
|
-
const { version } = await (0, api_js_1.deployToLive)(changes, `replace ${name}`);
|
|
545
|
-
// Force refresh cache after mutation
|
|
546
|
-
const newNames = await forceRefreshAndGetNames();
|
|
547
|
-
const action = exists ? 'Replaced' : 'Created';
|
|
548
|
-
let result = `**Prompt:** \`${name}\`\n`;
|
|
549
|
-
result += `**Action:** ${action}\n`;
|
|
550
|
-
result += `**Version:** \`${version.uuid}\`\n`;
|
|
551
|
-
if (args.filePath) {
|
|
552
|
-
result += `**Source:** \`${args.filePath}\`\n`;
|
|
553
|
-
}
|
|
554
|
-
result += `\n### Content Preview\n\n\`\`\`promptl\n${content.substring(0, 500)}${content.length > 500 ? '...' : ''}\n\`\`\``;
|
|
555
|
-
result += `\n\n---\n**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`;
|
|
556
|
-
return formatSuccess(`Prompt ${action}`, result);
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
return formatError(error);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
525
|
const DocsSchema = zod_1.z.object({
|
|
563
526
|
action: zod_1.z
|
|
564
527
|
.enum(['help', 'get', 'find'])
|
|
@@ -614,37 +577,34 @@ async function registerTools(server) {
|
|
|
614
577
|
description: 'Get full prompt content by name',
|
|
615
578
|
inputSchema: GetPromptSchema,
|
|
616
579
|
}, handleGetPrompt);
|
|
580
|
+
// Build dynamic description with prompt names and their variables
|
|
581
|
+
const runDesc = await buildRunPromptDescription();
|
|
617
582
|
server.registerTool('run_prompt', {
|
|
618
583
|
title: 'Run Prompt',
|
|
619
|
-
description:
|
|
584
|
+
description: runDesc,
|
|
620
585
|
inputSchema: RunPromptSchema,
|
|
621
586
|
}, handleRunPrompt);
|
|
622
587
|
server.registerTool('push_prompts', {
|
|
623
|
-
title: 'Push Prompts',
|
|
624
|
-
description: 'Replace ALL prompts in LIVE.
|
|
588
|
+
title: 'Push Prompts (FULL SYNC)',
|
|
589
|
+
description: 'FULL SYNC: Replace ALL prompts in LIVE. Deletes remote prompts not in your list. Use for initialization or complete sync.',
|
|
625
590
|
inputSchema: PushPromptsSchema,
|
|
626
591
|
}, handlePushPrompts);
|
|
627
|
-
server.registerTool('append_prompts', {
|
|
628
|
-
title: 'Append Prompts',
|
|
629
|
-
description: 'Add prompts to LIVE without removing existing. Provide `prompts` array OR `filePaths`. Use overwrite=true to replace existing.',
|
|
630
|
-
inputSchema: AppendPromptsSchema,
|
|
631
|
-
}, handleAppendPrompts);
|
|
632
592
|
server.registerTool('pull_prompts', {
|
|
633
|
-
title: 'Pull Prompts',
|
|
634
|
-
description: 'Download all prompts from LIVE to local ./prompts/*.promptl files',
|
|
593
|
+
title: 'Pull Prompts (FULL SYNC)',
|
|
594
|
+
description: 'FULL SYNC: Download all prompts from LIVE to local ./prompts/*.promptl files. Deletes existing local files first.',
|
|
635
595
|
inputSchema: PullPromptsSchema,
|
|
636
596
|
}, handlePullPrompts);
|
|
637
597
|
// Build dynamic description with available prompts
|
|
638
|
-
const
|
|
639
|
-
server.registerTool('
|
|
640
|
-
title: '
|
|
641
|
-
description:
|
|
642
|
-
inputSchema:
|
|
643
|
-
},
|
|
598
|
+
const addDesc = await buildAddPromptDescription();
|
|
599
|
+
server.registerTool('add_prompt', {
|
|
600
|
+
title: 'Add/Update Prompt',
|
|
601
|
+
description: addDesc,
|
|
602
|
+
inputSchema: AddPromptSchema,
|
|
603
|
+
}, handleAddPrompt);
|
|
644
604
|
server.registerTool('docs', {
|
|
645
605
|
title: 'Documentation',
|
|
646
606
|
description: 'Get documentation. Actions: help (overview), get (topic), find (search)',
|
|
647
607
|
inputSchema: DocsSchema,
|
|
648
608
|
}, handleDocs);
|
|
649
|
-
logger.info(`Registered
|
|
609
|
+
logger.info(`Registered 7 MCP tools (${cachedPromptNames.length} prompts cached)`);
|
|
650
610
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latitude-mcp-server",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Simplified MCP server for Latitude.so prompt management - 8 focused tools for push, pull, run, and manage prompts",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
provider: LiteLLM
|
|
3
|
+
model: claude-haiku-4-5
|
|
4
|
+
temperature: 0.7
|
|
5
|
+
schema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
cover_letter:
|
|
9
|
+
type: string
|
|
10
|
+
description: "The complete cover letter text"
|
|
11
|
+
key_points:
|
|
12
|
+
type: array
|
|
13
|
+
items:
|
|
14
|
+
type: string
|
|
15
|
+
description: "3-5 key points that make this candidate special for this role"
|
|
16
|
+
tone:
|
|
17
|
+
type: string
|
|
18
|
+
enum: [professional, enthusiastic, confident, conversational]
|
|
19
|
+
description: "The tone used in the letter"
|
|
20
|
+
required: [cover_letter, key_points, tone]
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<system>
|
|
24
|
+
# ROLE: The Ghostwriter - Elite Cover Letter Specialist
|
|
25
|
+
|
|
26
|
+
You write cover letters that get interviews. Your letters are:
|
|
27
|
+
- **Personal**: Reference specific career achievements
|
|
28
|
+
- **Targeted**: Connect experience directly to job requirements
|
|
29
|
+
- **Concise**: 250-350 words, no fluff
|
|
30
|
+
- **Unique**: Never generic, always tailored
|
|
31
|
+
|
|
32
|
+
## FORMAT
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
[Opening: Hook with relevant achievement]
|
|
36
|
+
|
|
37
|
+
[Body 1: Connect 2-3 career patterns to job requirements]
|
|
38
|
+
|
|
39
|
+
[Body 2: Address company/role specifically]
|
|
40
|
+
|
|
41
|
+
[Closing: Clear call to action]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## RULES
|
|
45
|
+
|
|
46
|
+
1. **Use specific numbers** from patterns (years, percentages, team sizes)
|
|
47
|
+
2. **Mirror job language** - use their keywords naturally
|
|
48
|
+
3. **Show, don't tell** - achievements over adjectives
|
|
49
|
+
4. **Address gaps proactively** if skills don't match
|
|
50
|
+
5. **NO clichés**: "passionate", "team player", "hard worker", "excited to apply"
|
|
51
|
+
|
|
52
|
+
## KEY POINTS SELECTION
|
|
53
|
+
|
|
54
|
+
Extract 3-5 points that:
|
|
55
|
+
- Directly match required skills
|
|
56
|
+
- Show quantifiable impact
|
|
57
|
+
- Demonstrate growth/leadership
|
|
58
|
+
- Are unique to this candidate
|
|
59
|
+
</system>
|
|
60
|
+
|
|
61
|
+
<user>
|
|
62
|
+
Write a cover letter for this job:
|
|
63
|
+
|
|
64
|
+
**Job Details:**
|
|
65
|
+
{{ job_details }}
|
|
66
|
+
|
|
67
|
+
**Candidate's Career Patterns:**
|
|
68
|
+
{{ career_patterns }}
|
|
69
|
+
|
|
70
|
+
Generate a personalized cover letter that connects their experience to this specific role at {{ company_name }}.
|
|
71
|
+
</user>
|