latitude-mcp-server 3.0.1 → 3.2.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/README.md +421 -139
- package/dist/api.d.ts +1 -8
- package/dist/api.js +0 -38
- package/dist/index.js +1 -1
- package/dist/server.js +1 -1
- package/dist/tools.d.ts +8 -9
- package/dist/tools.js +117 -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/api.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - POST /projects/:id/versions/:uuid/documents/create-or-update - create/update document
|
|
12
12
|
* - POST /projects/:id/versions/:uuid/documents/run - run document
|
|
13
13
|
*/
|
|
14
|
-
import type { LatitudeError,
|
|
14
|
+
import type { LatitudeError, Document, DocumentChange, DeployResult, RunResult } from './types.js';
|
|
15
15
|
export declare class LatitudeApiError extends Error {
|
|
16
16
|
readonly errorCode: string;
|
|
17
17
|
readonly statusCode: number;
|
|
@@ -32,14 +32,8 @@ export declare class LatitudeApiError extends Error {
|
|
|
32
32
|
* Get project ID from config
|
|
33
33
|
*/
|
|
34
34
|
export declare function getProjectId(): string;
|
|
35
|
-
export declare function listVersions(): Promise<Version[]>;
|
|
36
|
-
export declare function getVersion(versionUuid: string): Promise<Version>;
|
|
37
|
-
export declare function createVersion(name: string): Promise<Version>;
|
|
38
|
-
export declare function publishVersion(versionUuid: string, title?: string): Promise<Version>;
|
|
39
35
|
export declare function listDocuments(versionUuid?: string): Promise<Document[]>;
|
|
40
36
|
export declare function getDocument(path: string, versionUuid?: string): Promise<Document>;
|
|
41
|
-
export declare function createOrUpdateDocument(versionUuid: string, path: string, content: string, force?: boolean): Promise<Document>;
|
|
42
|
-
export declare function deleteDocument(versionUuid: string, path: string): Promise<void>;
|
|
43
37
|
/**
|
|
44
38
|
* Push response from the API
|
|
45
39
|
*/
|
|
@@ -88,5 +82,4 @@ export declare function validatePromptLContent(content: string, path: string): P
|
|
|
88
82
|
* This is the same workflow the CLI uses, ensuring proper validation.
|
|
89
83
|
*/
|
|
90
84
|
export declare function deployToLive(changes: DocumentChange[], _versionName?: string): Promise<DeployResult>;
|
|
91
|
-
export declare function getPromptNames(): Promise<string[]>;
|
|
92
85
|
export {};
|
package/dist/api.js
CHANGED
|
@@ -15,20 +15,13 @@
|
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.LatitudeApiError = void 0;
|
|
17
17
|
exports.getProjectId = getProjectId;
|
|
18
|
-
exports.listVersions = listVersions;
|
|
19
|
-
exports.getVersion = getVersion;
|
|
20
|
-
exports.createVersion = createVersion;
|
|
21
|
-
exports.publishVersion = publishVersion;
|
|
22
18
|
exports.listDocuments = listDocuments;
|
|
23
19
|
exports.getDocument = getDocument;
|
|
24
|
-
exports.createOrUpdateDocument = createOrUpdateDocument;
|
|
25
|
-
exports.deleteDocument = deleteDocument;
|
|
26
20
|
exports.pushChanges = pushChanges;
|
|
27
21
|
exports.computeDiff = computeDiff;
|
|
28
22
|
exports.runDocument = runDocument;
|
|
29
23
|
exports.validatePromptLContent = validatePromptLContent;
|
|
30
24
|
exports.deployToLive = deployToLive;
|
|
31
|
-
exports.getPromptNames = getPromptNames;
|
|
32
25
|
const logger_util_js_1 = require("./utils/logger.util.js");
|
|
33
26
|
const config_util_js_1 = require("./utils/config.util.js");
|
|
34
27
|
const promptl_ai_1 = require("promptl-ai");
|
|
@@ -266,14 +259,6 @@ exports.LatitudeApiError = LatitudeApiError;
|
|
|
266
259
|
function getProjectId() {
|
|
267
260
|
return getConfig().projectId;
|
|
268
261
|
}
|
|
269
|
-
async function listVersions() {
|
|
270
|
-
const projectId = getProjectId();
|
|
271
|
-
return request(`/projects/${projectId}/versions`);
|
|
272
|
-
}
|
|
273
|
-
async function getVersion(versionUuid) {
|
|
274
|
-
const projectId = getProjectId();
|
|
275
|
-
return request(`/projects/${projectId}/versions/${versionUuid}`);
|
|
276
|
-
}
|
|
277
262
|
async function createVersion(name) {
|
|
278
263
|
const projectId = getProjectId();
|
|
279
264
|
return request(`/projects/${projectId}/versions`, {
|
|
@@ -298,19 +283,6 @@ async function getDocument(path, versionUuid = 'live') {
|
|
|
298
283
|
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
299
284
|
return request(`/projects/${projectId}/versions/${versionUuid}/documents/${normalizedPath}`);
|
|
300
285
|
}
|
|
301
|
-
async function createOrUpdateDocument(versionUuid, path, content, force = false) {
|
|
302
|
-
const projectId = getProjectId();
|
|
303
|
-
logger.debug(`Creating/updating document: ${path} (${content.length} chars, force=${force})`);
|
|
304
|
-
return request(`/projects/${projectId}/versions/${versionUuid}/documents/create-or-update`, {
|
|
305
|
-
method: 'POST',
|
|
306
|
-
body: { path, prompt: content, force },
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
async function deleteDocument(versionUuid, path) {
|
|
310
|
-
const projectId = getProjectId();
|
|
311
|
-
const normalizedPath = path.startsWith('/') ? path.slice(1) : path;
|
|
312
|
-
await request(`/projects/${projectId}/versions/${versionUuid}/documents/${normalizedPath}`, { method: 'DELETE' });
|
|
313
|
-
}
|
|
314
286
|
/**
|
|
315
287
|
* Push changes to a version in a single batch
|
|
316
288
|
* This is the CLI-style push that sends all changes at once
|
|
@@ -740,13 +712,3 @@ async function deployToLive(changes, _versionName) {
|
|
|
740
712
|
throw publishError;
|
|
741
713
|
}
|
|
742
714
|
}
|
|
743
|
-
async function getPromptNames() {
|
|
744
|
-
try {
|
|
745
|
-
const docs = await listDocuments('live');
|
|
746
|
-
return docs.map((doc) => doc.path);
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
logger.warn('Failed to fetch prompt names', error);
|
|
750
|
-
return [];
|
|
751
|
-
}
|
|
752
|
-
}
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ async function main() {
|
|
|
20
20
|
(0, server_js_1.setupGracefulShutdown)();
|
|
21
21
|
// Log startup info
|
|
22
22
|
logger.info('='.repeat(50));
|
|
23
|
-
logger.info('Latitude MCP Server
|
|
23
|
+
logger.info('Latitude MCP Server v3.1.0');
|
|
24
24
|
logger.info('='.repeat(50));
|
|
25
25
|
// Validate required environment variables
|
|
26
26
|
const apiKey = config_util_js_1.config.get('LATITUDE_API_KEY');
|
package/dist/server.js
CHANGED
|
@@ -17,7 +17,7 @@ const logger = logger_util_js_1.Logger.forContext('server.ts');
|
|
|
17
17
|
// Server Configuration
|
|
18
18
|
// ============================================================================
|
|
19
19
|
const SERVER_NAME = 'latitude-mcp-server';
|
|
20
|
-
const SERVER_VERSION = '
|
|
20
|
+
const SERVER_VERSION = '3.1.0';
|
|
21
21
|
// ============================================================================
|
|
22
22
|
// Server Instance
|
|
23
23
|
// ============================================================================
|
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.
|
|
@@ -226,6 +292,10 @@ const PushPromptsSchema = zod_1.z.object({
|
|
|
226
292
|
.array(zod_1.z.string())
|
|
227
293
|
.optional()
|
|
228
294
|
.describe('File paths to .promptl files - FULL SYNC: deletes remote prompts not in this list'),
|
|
295
|
+
versionName: zod_1.z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe('Optional version name (like git branch: feat/add-auth, fix/typo). If omitted, auto-generates timestamp.'),
|
|
229
299
|
});
|
|
230
300
|
async function handlePushPrompts(args) {
|
|
231
301
|
try {
|
|
@@ -268,7 +338,7 @@ async function handlePushPrompts(args) {
|
|
|
268
338
|
}
|
|
269
339
|
// Push all changes in one batch
|
|
270
340
|
try {
|
|
271
|
-
const result = await (0, api_js_1.deployToLive)(changes, 'push');
|
|
341
|
+
const result = await (0, api_js_1.deployToLive)(changes, args.versionName || 'push');
|
|
272
342
|
// Force refresh cache after mutations
|
|
273
343
|
const newNames = await forceRefreshAndGetNames();
|
|
274
344
|
let content = `**Summary:**\n`;
|
|
@@ -303,25 +373,24 @@ async function handlePushPrompts(args) {
|
|
|
303
373
|
return formatError(error);
|
|
304
374
|
}
|
|
305
375
|
}
|
|
306
|
-
const
|
|
376
|
+
const AddPromptSchema = zod_1.z.object({
|
|
307
377
|
prompts: zod_1.z
|
|
308
378
|
.array(zod_1.z.object({
|
|
309
379
|
name: zod_1.z.string().describe('Prompt name'),
|
|
310
380
|
content: zod_1.z.string().describe('Prompt content'),
|
|
311
381
|
}))
|
|
312
382
|
.optional()
|
|
313
|
-
.describe('Prompts to
|
|
383
|
+
.describe('Prompts to add/update - overwrites if exists, adds if new'),
|
|
314
384
|
filePaths: zod_1.z
|
|
315
385
|
.array(zod_1.z.string())
|
|
316
386
|
.optional()
|
|
317
|
-
.describe('File paths to .promptl files -
|
|
318
|
-
|
|
319
|
-
.
|
|
387
|
+
.describe('File paths to .promptl files - overwrites if exists, adds if new'),
|
|
388
|
+
versionName: zod_1.z
|
|
389
|
+
.string()
|
|
320
390
|
.optional()
|
|
321
|
-
.
|
|
322
|
-
.describe('If true, update existing prompts with same name (still no deletions)'),
|
|
391
|
+
.describe('Optional version name (like git commit: feat/add-auth, fix/typo). Describes what changed.'),
|
|
323
392
|
});
|
|
324
|
-
async function
|
|
393
|
+
async function handleAddPrompt(args) {
|
|
325
394
|
try {
|
|
326
395
|
// Build prompts from either direct input or file paths
|
|
327
396
|
let prompts = [];
|
|
@@ -337,9 +406,9 @@ async function handleAppendPrompts(args) {
|
|
|
337
406
|
if (prompts.length === 0) {
|
|
338
407
|
return formatError(new Error('No prompts provided. Use either prompts array or filePaths.'));
|
|
339
408
|
}
|
|
340
|
-
// PRE-VALIDATE ALL PROMPTS BEFORE
|
|
341
|
-
// If ANY prompt fails validation, return errors and
|
|
342
|
-
logger.info(`Validating ${prompts.length} prompt(s) before
|
|
409
|
+
// PRE-VALIDATE ALL PROMPTS BEFORE ADDING
|
|
410
|
+
// If ANY prompt fails validation, return errors and add NOTHING
|
|
411
|
+
logger.info(`Validating ${prompts.length} prompt(s) before add...`);
|
|
343
412
|
const validation = await validateAllPrompts(prompts);
|
|
344
413
|
if (!validation.valid) {
|
|
345
414
|
logger.warn(`Validation failed for ${validation.errors.length} prompt(s)`);
|
|
@@ -349,26 +418,20 @@ async function handleAppendPrompts(args) {
|
|
|
349
418
|
// Get existing prompts
|
|
350
419
|
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
351
420
|
const existingMap = new Map(existingDocs.map((d) => [d.path, d]));
|
|
352
|
-
// Build changes -
|
|
421
|
+
// Build changes - ALWAYS overwrite if exists, add if new, NEVER delete
|
|
353
422
|
const changes = [];
|
|
354
|
-
const skipped = [];
|
|
355
423
|
for (const prompt of prompts) {
|
|
356
424
|
const existingDoc = existingMap.get(prompt.name);
|
|
357
425
|
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);
|
|
426
|
+
// Only include if content is different
|
|
427
|
+
if (existingDoc.content !== prompt.content) {
|
|
428
|
+
changes.push({
|
|
429
|
+
path: prompt.name,
|
|
430
|
+
content: prompt.content,
|
|
431
|
+
status: 'modified',
|
|
432
|
+
});
|
|
371
433
|
}
|
|
434
|
+
// If same content, skip silently (unchanged)
|
|
372
435
|
}
|
|
373
436
|
else {
|
|
374
437
|
// New prompt
|
|
@@ -382,28 +445,19 @@ async function handleAppendPrompts(args) {
|
|
|
382
445
|
// Summarize
|
|
383
446
|
const added = changes.filter((c) => c.status === 'added');
|
|
384
447
|
const modified = changes.filter((c) => c.status === 'modified');
|
|
385
|
-
if (changes.length === 0
|
|
448
|
+
if (changes.length === 0) {
|
|
386
449
|
const newNames = await forceRefreshAndGetNames();
|
|
387
450
|
return formatSuccess('No Changes Needed', `All ${prompts.length} prompt(s) are already up to date.\n\n` +
|
|
388
451
|
`**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`);
|
|
389
452
|
}
|
|
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
453
|
// Push all changes in one batch
|
|
397
454
|
try {
|
|
398
|
-
const result = await (0, api_js_1.deployToLive)(changes, '
|
|
455
|
+
const result = await (0, api_js_1.deployToLive)(changes, args.versionName || 'add');
|
|
399
456
|
// Force refresh cache after mutations
|
|
400
457
|
const newNames = await forceRefreshAndGetNames();
|
|
401
458
|
let content = `**Summary:**\n`;
|
|
402
459
|
content += `- Added: ${added.length}\n`;
|
|
403
460
|
content += `- Updated: ${modified.length}\n`;
|
|
404
|
-
if (skipped.length > 0) {
|
|
405
|
-
content += `- Skipped: ${skipped.length} (use overwrite=true)\n`;
|
|
406
|
-
}
|
|
407
461
|
content += `- Documents processed: ${result.documentsProcessed}\n\n`;
|
|
408
462
|
if (added.length > 0) {
|
|
409
463
|
content += `### Added\n${added.map((c) => `- \`${c.path}\``).join('\n')}\n\n`;
|
|
@@ -411,11 +465,8 @@ async function handleAppendPrompts(args) {
|
|
|
411
465
|
if (modified.length > 0) {
|
|
412
466
|
content += `### Updated\n${modified.map((c) => `- \`${c.path}\``).join('\n')}\n\n`;
|
|
413
467
|
}
|
|
414
|
-
if (skipped.length > 0) {
|
|
415
|
-
content += `### Skipped (already exist)\n${skipped.map(n => `- \`${n}\``).join('\n')}\n\n`;
|
|
416
|
-
}
|
|
417
468
|
content += `---\n**Current LIVE prompts (${newNames.length}):** ${newNames.map(n => `\`${n}\``).join(', ')}`;
|
|
418
|
-
return formatSuccess('Prompts
|
|
469
|
+
return formatSuccess('Prompts Added to LIVE', content);
|
|
419
470
|
}
|
|
420
471
|
catch (error) {
|
|
421
472
|
// Detailed error from API
|
|
@@ -472,93 +523,13 @@ async function handlePullPrompts(args) {
|
|
|
472
523
|
content += `**Written:** ${written.length} file(s)\n\n`;
|
|
473
524
|
content += `### Files\n\n`;
|
|
474
525
|
content += written.map((f) => `- \`${f}\``).join('\n');
|
|
475
|
-
content += `\n\n**Tip:** Edit files locally, then use \`
|
|
526
|
+
content += `\n\n**Tip:** Edit files locally, then use \`add_prompt\` with \`filePaths\` to push changes.`;
|
|
476
527
|
return formatSuccess('Prompts Pulled from LIVE', content);
|
|
477
528
|
}
|
|
478
529
|
catch (error) {
|
|
479
530
|
return formatError(error);
|
|
480
531
|
}
|
|
481
532
|
}
|
|
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
533
|
const DocsSchema = zod_1.z.object({
|
|
563
534
|
action: zod_1.z
|
|
564
535
|
.enum(['help', 'get', 'find'])
|
|
@@ -614,37 +585,34 @@ async function registerTools(server) {
|
|
|
614
585
|
description: 'Get full prompt content by name',
|
|
615
586
|
inputSchema: GetPromptSchema,
|
|
616
587
|
}, handleGetPrompt);
|
|
588
|
+
// Build dynamic description with prompt names and their variables
|
|
589
|
+
const runDesc = await buildRunPromptDescription();
|
|
617
590
|
server.registerTool('run_prompt', {
|
|
618
591
|
title: 'Run Prompt',
|
|
619
|
-
description:
|
|
592
|
+
description: runDesc,
|
|
620
593
|
inputSchema: RunPromptSchema,
|
|
621
594
|
}, handleRunPrompt);
|
|
622
595
|
server.registerTool('push_prompts', {
|
|
623
|
-
title: 'Push Prompts',
|
|
624
|
-
description: 'Replace ALL prompts in LIVE.
|
|
596
|
+
title: 'Push Prompts (FULL SYNC)',
|
|
597
|
+
description: 'FULL SYNC: Replace ALL prompts in LIVE. Deletes remote prompts not in your list. Use for initialization or complete sync.',
|
|
625
598
|
inputSchema: PushPromptsSchema,
|
|
626
599
|
}, 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
600
|
server.registerTool('pull_prompts', {
|
|
633
|
-
title: 'Pull Prompts',
|
|
634
|
-
description: 'Download all prompts from LIVE to local ./prompts/*.promptl files',
|
|
601
|
+
title: 'Pull Prompts (FULL SYNC)',
|
|
602
|
+
description: 'FULL SYNC: Download all prompts from LIVE to local ./prompts/*.promptl files. Deletes existing local files first.',
|
|
635
603
|
inputSchema: PullPromptsSchema,
|
|
636
604
|
}, handlePullPrompts);
|
|
637
605
|
// Build dynamic description with available prompts
|
|
638
|
-
const
|
|
639
|
-
server.registerTool('
|
|
640
|
-
title: '
|
|
641
|
-
description:
|
|
642
|
-
inputSchema:
|
|
643
|
-
},
|
|
606
|
+
const addDesc = await buildAddPromptDescription();
|
|
607
|
+
server.registerTool('add_prompt', {
|
|
608
|
+
title: 'Add/Update Prompt',
|
|
609
|
+
description: addDesc,
|
|
610
|
+
inputSchema: AddPromptSchema,
|
|
611
|
+
}, handleAddPrompt);
|
|
644
612
|
server.registerTool('docs', {
|
|
645
613
|
title: 'Documentation',
|
|
646
614
|
description: 'Get documentation. Actions: help (overview), get (topic), find (search)',
|
|
647
615
|
inputSchema: DocsSchema,
|
|
648
616
|
}, handleDocs);
|
|
649
|
-
logger.info(`Registered
|
|
617
|
+
logger.info(`Registered 7 MCP tools (${cachedPromptNames.length} prompts cached)`);
|
|
650
618
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latitude-mcp-server",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.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>
|