latitude-mcp-server 2.2.5 → 2.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +18 -0
- package/dist/api.js +25 -24
- package/dist/tools.js +76 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -65,6 +65,24 @@ export declare function computeDiff(incoming: Array<{
|
|
|
65
65
|
content: string;
|
|
66
66
|
}>, existing: Document[]): DocumentChange[];
|
|
67
67
|
export declare function runDocument(path: string, parameters?: Record<string, unknown>, versionUuid?: string): Promise<RunResult>;
|
|
68
|
+
export interface ValidationIssue {
|
|
69
|
+
type: 'error' | 'warning';
|
|
70
|
+
code: string;
|
|
71
|
+
message: string;
|
|
72
|
+
rootCause: string;
|
|
73
|
+
suggestion: string;
|
|
74
|
+
location?: {
|
|
75
|
+
line: number;
|
|
76
|
+
column: number;
|
|
77
|
+
};
|
|
78
|
+
codeFrame?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Pre-validate PromptL content using the official promptl-ai library.
|
|
82
|
+
* Returns detailed, actionable error messages with code frames.
|
|
83
|
+
* EXPORTED for use by tools.ts to pre-validate before pushing.
|
|
84
|
+
*/
|
|
85
|
+
export declare function validatePromptLContent(content: string, path: string): Promise<ValidationIssue[]>;
|
|
68
86
|
/**
|
|
69
87
|
* Deploy changes to LIVE version using the proper workflow:
|
|
70
88
|
* 1. Create a draft version
|
package/dist/api.js
CHANGED
|
@@ -27,6 +27,7 @@ exports.pushChanges = pushChanges;
|
|
|
27
27
|
exports.hashContent = hashContent;
|
|
28
28
|
exports.computeDiff = computeDiff;
|
|
29
29
|
exports.runDocument = runDocument;
|
|
30
|
+
exports.validatePromptLContent = validatePromptLContent;
|
|
30
31
|
exports.deployToLive = deployToLive;
|
|
31
32
|
exports.getPromptNames = getPromptNames;
|
|
32
33
|
const logger_util_js_1 = require("./utils/logger.util.js");
|
|
@@ -443,16 +444,10 @@ const ERROR_SUGGESTIONS = {
|
|
|
443
444
|
suggestion: 'Move the <tool-call> tag inside an <assistant> block.',
|
|
444
445
|
},
|
|
445
446
|
};
|
|
446
|
-
/**
|
|
447
|
-
* Get snippet from content around a position
|
|
448
|
-
*/
|
|
449
|
-
function getSnippet(content, startIndex, endIndex) {
|
|
450
|
-
const snippet = content.substring(startIndex, Math.min(endIndex, startIndex + 100));
|
|
451
|
-
return snippet.split('\n')[0] || snippet;
|
|
452
|
-
}
|
|
453
447
|
/**
|
|
454
448
|
* Pre-validate PromptL content using the official promptl-ai library.
|
|
455
|
-
* Returns detailed, actionable error messages.
|
|
449
|
+
* Returns detailed, actionable error messages with code frames.
|
|
450
|
+
* EXPORTED for use by tools.ts to pre-validate before pushing.
|
|
456
451
|
*/
|
|
457
452
|
async function validatePromptLContent(content, path) {
|
|
458
453
|
const issues = [];
|
|
@@ -465,9 +460,6 @@ async function validatePromptLContent(content, path) {
|
|
|
465
460
|
});
|
|
466
461
|
// Convert CompileErrors to our ValidationIssue format
|
|
467
462
|
for (const compileError of result.errors) {
|
|
468
|
-
// CompileError has start?.line (Position) and startIndex (number)
|
|
469
|
-
const lineNumber = compileError.start?.line;
|
|
470
|
-
const snippet = getSnippet(content, compileError.startIndex, compileError.endIndex);
|
|
471
463
|
// Get human-readable suggestion based on error code
|
|
472
464
|
const suggestionInfo = ERROR_SUGGESTIONS[compileError.code] || {
|
|
473
465
|
rootCause: compileError.message,
|
|
@@ -475,36 +467,43 @@ async function validatePromptLContent(content, path) {
|
|
|
475
467
|
};
|
|
476
468
|
issues.push({
|
|
477
469
|
type: 'error',
|
|
470
|
+
code: compileError.code,
|
|
478
471
|
message: compileError.message,
|
|
479
472
|
rootCause: suggestionInfo.rootCause,
|
|
480
473
|
suggestion: suggestionInfo.suggestion,
|
|
481
|
-
|
|
482
|
-
|
|
474
|
+
location: compileError.start ? {
|
|
475
|
+
line: compileError.start.line,
|
|
476
|
+
column: compileError.start.column,
|
|
477
|
+
} : undefined,
|
|
478
|
+
codeFrame: compileError.frame, // The beautiful formatted code frame!
|
|
483
479
|
});
|
|
484
480
|
}
|
|
485
481
|
}
|
|
486
482
|
catch (err) {
|
|
487
483
|
// Handle parse errors (thrown, not accumulated)
|
|
488
484
|
if (err instanceof promptl_ai_1.CompileError) {
|
|
489
|
-
const lineNumber = err.start?.line;
|
|
490
|
-
const snippet = getSnippet(content, err.startIndex, err.endIndex);
|
|
491
485
|
const suggestionInfo = ERROR_SUGGESTIONS[err.code] || {
|
|
492
486
|
rootCause: err.message,
|
|
493
487
|
suggestion: 'Fix the syntax error at the indicated location.',
|
|
494
488
|
};
|
|
495
489
|
issues.push({
|
|
496
490
|
type: 'error',
|
|
491
|
+
code: err.code,
|
|
497
492
|
message: err.message,
|
|
498
493
|
rootCause: suggestionInfo.rootCause,
|
|
499
494
|
suggestion: suggestionInfo.suggestion,
|
|
500
|
-
|
|
501
|
-
|
|
495
|
+
location: err.start ? {
|
|
496
|
+
line: err.start.line,
|
|
497
|
+
column: err.start.column,
|
|
498
|
+
} : undefined,
|
|
499
|
+
codeFrame: err.frame,
|
|
502
500
|
});
|
|
503
501
|
}
|
|
504
502
|
else {
|
|
505
503
|
// Unknown error - still report it
|
|
506
504
|
issues.push({
|
|
507
505
|
type: 'error',
|
|
506
|
+
code: 'unknown-error',
|
|
508
507
|
message: err instanceof Error ? err.message : 'Unknown validation error',
|
|
509
508
|
rootCause: 'An unexpected error occurred during validation.',
|
|
510
509
|
suggestion: 'Check the prompt content for syntax errors.',
|
|
@@ -533,10 +532,11 @@ async function identifyFailingDocuments(changes) {
|
|
|
533
532
|
failed.push({
|
|
534
533
|
path: change.path,
|
|
535
534
|
error: mainError.message,
|
|
535
|
+
code: mainError.code,
|
|
536
536
|
rootCause: mainError.rootCause,
|
|
537
537
|
suggestion: mainError.suggestion,
|
|
538
|
-
|
|
539
|
-
|
|
538
|
+
location: mainError.location,
|
|
539
|
+
codeFrame: mainError.codeFrame,
|
|
540
540
|
});
|
|
541
541
|
}
|
|
542
542
|
}
|
|
@@ -601,9 +601,9 @@ async function testSingleDocument(change) {
|
|
|
601
601
|
return {
|
|
602
602
|
path: change.path,
|
|
603
603
|
error: errorMsg,
|
|
604
|
+
code: 'api-validation-error',
|
|
604
605
|
rootCause,
|
|
605
606
|
suggestion,
|
|
606
|
-
snippet: change.content?.substring(0, 300),
|
|
607
607
|
};
|
|
608
608
|
}
|
|
609
609
|
}
|
|
@@ -731,13 +731,14 @@ async function deployToLive(changes, _versionName) {
|
|
|
731
731
|
const errorLines = [];
|
|
732
732
|
for (const doc of failedDocs) {
|
|
733
733
|
errorLines.push(`\n## ❌ ${doc.path}`);
|
|
734
|
+
errorLines.push(`**Error Code:** \`${doc.code}\``);
|
|
734
735
|
errorLines.push(`**Error:** ${doc.error}`);
|
|
735
736
|
errorLines.push(`**Root Cause:** ${doc.rootCause}`);
|
|
736
|
-
if (doc.
|
|
737
|
-
errorLines.push(`**Location:** Line ${doc.
|
|
737
|
+
if (doc.location) {
|
|
738
|
+
errorLines.push(`**Location:** Line ${doc.location.line}, Column ${doc.location.column}`);
|
|
738
739
|
}
|
|
739
|
-
if (doc.
|
|
740
|
-
errorLines.push(`**
|
|
740
|
+
if (doc.codeFrame) {
|
|
741
|
+
errorLines.push(`**Code Context:**\n\`\`\`\n${doc.codeFrame}\n\`\`\``);
|
|
741
742
|
}
|
|
742
743
|
errorLines.push(`**Fix:** ${doc.suggestion}`);
|
|
743
744
|
}
|
package/dist/tools.js
CHANGED
|
@@ -106,6 +106,55 @@ function formatAvailablePrompts(names) {
|
|
|
106
106
|
const formatted = names.map(n => `\`${n}\``).join(', ');
|
|
107
107
|
return `\n\n**Available prompts (${names.length}):** ${formatted}`;
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate all prompts BEFORE pushing.
|
|
111
|
+
* If ANY prompt fails validation, returns all errors and NOTHING is pushed.
|
|
112
|
+
*/
|
|
113
|
+
async function validateAllPrompts(prompts) {
|
|
114
|
+
const errors = [];
|
|
115
|
+
for (const prompt of prompts) {
|
|
116
|
+
const issues = await (0, api_js_1.validatePromptLContent)(prompt.content, prompt.name);
|
|
117
|
+
const errorIssues = issues.filter(i => i.type === 'error');
|
|
118
|
+
if (errorIssues.length > 0) {
|
|
119
|
+
errors.push({ name: prompt.name, issues: errorIssues });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
valid: errors.length === 0,
|
|
124
|
+
errors,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Format validation errors into a detailed MCP-friendly error message.
|
|
129
|
+
*/
|
|
130
|
+
function formatValidationErrors(errors) {
|
|
131
|
+
const lines = [
|
|
132
|
+
`## ❌ Validation Failed - No Changes Made\n`,
|
|
133
|
+
`**${errors.length} prompt(s) have errors.** Fix all errors before pushing.\n`,
|
|
134
|
+
];
|
|
135
|
+
for (const { name, issues } of errors) {
|
|
136
|
+
for (const issue of issues) {
|
|
137
|
+
lines.push(`### ${name}`);
|
|
138
|
+
lines.push(`**Error Code:** \`${issue.code}\``);
|
|
139
|
+
lines.push(`**Error:** ${issue.message}`);
|
|
140
|
+
lines.push(`**Root Cause:** ${issue.rootCause}`);
|
|
141
|
+
if (issue.location) {
|
|
142
|
+
lines.push(`**Location:** Line ${issue.location.line}, Column ${issue.location.column}`);
|
|
143
|
+
}
|
|
144
|
+
if (issue.codeFrame) {
|
|
145
|
+
lines.push(`**Code Context:**\n\`\`\`\n${issue.codeFrame}\n\`\`\``);
|
|
146
|
+
}
|
|
147
|
+
lines.push(`**Fix:** ${issue.suggestion}`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
lines.push(`---`);
|
|
152
|
+
lines.push(`**Action Required:** Fix the errors above, then retry.`);
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
155
|
+
isError: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
109
158
|
// ============================================================================
|
|
110
159
|
// Tool Handlers
|
|
111
160
|
// ============================================================================
|
|
@@ -194,6 +243,15 @@ async function handlePushPrompts(args) {
|
|
|
194
243
|
if (prompts.length === 0) {
|
|
195
244
|
return formatError(new Error('No prompts provided. Use either prompts array or filePaths.'));
|
|
196
245
|
}
|
|
246
|
+
// PRE-VALIDATE ALL PROMPTS BEFORE PUSHING
|
|
247
|
+
// If ANY prompt fails validation, return errors and push NOTHING
|
|
248
|
+
logger.info(`Validating ${prompts.length} prompt(s) before push...`);
|
|
249
|
+
const validation = await validateAllPrompts(prompts);
|
|
250
|
+
if (!validation.valid) {
|
|
251
|
+
logger.warn(`Validation failed for ${validation.errors.length} prompt(s)`);
|
|
252
|
+
return formatValidationErrors(validation.errors);
|
|
253
|
+
}
|
|
254
|
+
logger.info(`All ${prompts.length} prompt(s) passed validation`);
|
|
197
255
|
// Get existing prompts for diff computation
|
|
198
256
|
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
199
257
|
// Compute diff - this determines what needs to be added, modified, or deleted
|
|
@@ -279,6 +337,15 @@ async function handleAppendPrompts(args) {
|
|
|
279
337
|
if (prompts.length === 0) {
|
|
280
338
|
return formatError(new Error('No prompts provided. Use either prompts array or filePaths.'));
|
|
281
339
|
}
|
|
340
|
+
// PRE-VALIDATE ALL PROMPTS BEFORE APPENDING
|
|
341
|
+
// If ANY prompt fails validation, return errors and append NOTHING
|
|
342
|
+
logger.info(`Validating ${prompts.length} prompt(s) before append...`);
|
|
343
|
+
const validation = await validateAllPrompts(prompts);
|
|
344
|
+
if (!validation.valid) {
|
|
345
|
+
logger.warn(`Validation failed for ${validation.errors.length} prompt(s)`);
|
|
346
|
+
return formatValidationErrors(validation.errors);
|
|
347
|
+
}
|
|
348
|
+
logger.info(`All ${prompts.length} prompt(s) passed validation`);
|
|
282
349
|
// Get existing prompts
|
|
283
350
|
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
284
351
|
const existingMap = new Map(existingDocs.map((d) => [d.path, d]));
|
|
@@ -454,6 +521,15 @@ async function handleReplacePrompt(args) {
|
|
|
454
521
|
if (!content) {
|
|
455
522
|
return formatError(new Error('Prompt content is required. Provide either `content` or `filePath`.'));
|
|
456
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`);
|
|
457
533
|
// Check if prompt exists
|
|
458
534
|
const existingDocs = await (0, api_js_1.listDocuments)('live');
|
|
459
535
|
const exists = existingDocs.some((d) => d.path === name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latitude-mcp-server",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
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",
|