latitude-mcp-server 2.2.4 → 2.2.6
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.js +114 -81
- package/package.json +2 -1
package/dist/api.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.deployToLive = deployToLive;
|
|
|
31
31
|
exports.getPromptNames = getPromptNames;
|
|
32
32
|
const logger_util_js_1 = require("./utils/logger.util.js");
|
|
33
33
|
const config_util_js_1 = require("./utils/config.util.js");
|
|
34
|
+
const promptl_ai_1 = require("promptl-ai");
|
|
34
35
|
const logger = logger_util_js_1.Logger.forContext('api.ts');
|
|
35
36
|
const DEFAULT_BASE_URL = 'https://gateway.latitude.so';
|
|
36
37
|
const API_VERSION = 'v3';
|
|
@@ -398,84 +399,114 @@ async function runDocument(path, parameters, versionUuid = 'live') {
|
|
|
398
399
|
});
|
|
399
400
|
}
|
|
400
401
|
/**
|
|
401
|
-
*
|
|
402
|
-
* Returns detailed, actionable error messages.
|
|
402
|
+
* Error code to human-readable fix suggestion mapping
|
|
403
403
|
*/
|
|
404
|
-
|
|
404
|
+
const ERROR_SUGGESTIONS = {
|
|
405
|
+
'message-tag-inside-message': {
|
|
406
|
+
rootCause: 'Message/role tags (<system>, <user>, <assistant>, <tool>) cannot be nested inside each other.',
|
|
407
|
+
suggestion: 'Move the nested tag outside its parent. If showing an example, use a code block (```yaml) instead of actual role tags.',
|
|
408
|
+
},
|
|
409
|
+
'content-tag-inside-content': {
|
|
410
|
+
rootCause: 'Content tags (<text>, <image>, <file>, <tool-call>) must be directly inside message tags.',
|
|
411
|
+
suggestion: 'Restructure so content tags are direct children of message tags, not nested in other content.',
|
|
412
|
+
},
|
|
413
|
+
'step-tag-inside-step': {
|
|
414
|
+
rootCause: 'Step/response tags cannot be nested inside each other.',
|
|
415
|
+
suggestion: 'Move the <response> tag outside its parent <response> tag.',
|
|
416
|
+
},
|
|
417
|
+
'config-not-found': {
|
|
418
|
+
rootCause: 'PromptL files require a YAML configuration section at the top.',
|
|
419
|
+
suggestion: 'Add config at the beginning:\n---\nprovider: openai\nmodel: gpt-4\n---',
|
|
420
|
+
},
|
|
421
|
+
'config-already-declared': {
|
|
422
|
+
rootCause: 'Only one configuration section is allowed per file.',
|
|
423
|
+
suggestion: 'Remove the duplicate --- config --- section.',
|
|
424
|
+
},
|
|
425
|
+
'invalid-config': {
|
|
426
|
+
rootCause: 'The YAML configuration has syntax or validation errors.',
|
|
427
|
+
suggestion: 'Check YAML syntax. Required fields: model. Optional: provider, temperature, schema.',
|
|
428
|
+
},
|
|
429
|
+
'unclosed-block': {
|
|
430
|
+
rootCause: 'A tag or block was opened but never closed.',
|
|
431
|
+
suggestion: 'Add the missing closing tag. Check for typos in tag names.',
|
|
432
|
+
},
|
|
433
|
+
'unexpected-eof': {
|
|
434
|
+
rootCause: 'The file ended unexpectedly, likely due to unclosed tags or blocks.',
|
|
435
|
+
suggestion: 'Ensure all opened tags ({#if}, {#each}, <system>, etc.) are properly closed.',
|
|
436
|
+
},
|
|
437
|
+
'variable-not-defined': {
|
|
438
|
+
rootCause: 'A variable is used but not provided in parameters.',
|
|
439
|
+
suggestion: 'Either pass this variable when calling the prompt, or define it with {#let}.',
|
|
440
|
+
},
|
|
441
|
+
'invalid-tool-call-placement': {
|
|
442
|
+
rootCause: 'Tool calls (<tool-call>) can only appear inside <assistant> messages.',
|
|
443
|
+
suggestion: 'Move the <tool-call> tag inside an <assistant> block.',
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* Pre-validate PromptL content using the official promptl-ai library.
|
|
448
|
+
* Returns detailed, actionable error messages with code frames.
|
|
449
|
+
*/
|
|
450
|
+
async function validatePromptLContent(content, path) {
|
|
405
451
|
const issues = [];
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
rootCause: 'PromptL files must start with YAML frontmatter (---).',
|
|
413
|
-
suggestion: 'Add frontmatter at the beginning:\n---\nprovider: YourProvider\nmodel: your-model\n---',
|
|
452
|
+
try {
|
|
453
|
+
// Use official promptl-ai scan function for validation
|
|
454
|
+
const result = await (0, promptl_ai_1.scan)({
|
|
455
|
+
prompt: content,
|
|
456
|
+
fullPath: path,
|
|
457
|
+
requireConfig: false, // Don't require config for flexibility
|
|
414
458
|
});
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
roleStack.push({ tag, line: lineNum });
|
|
437
|
-
}
|
|
438
|
-
// Check for closing role tags
|
|
439
|
-
const closeMatches = line.matchAll(/<\/(system|user|assistant|tool)>/gi);
|
|
440
|
-
for (const match of closeMatches) {
|
|
441
|
-
const tag = match[1].toLowerCase();
|
|
442
|
-
if (roleStack.length > 0 && roleStack[roleStack.length - 1].tag === tag) {
|
|
443
|
-
roleStack.pop();
|
|
444
|
-
}
|
|
459
|
+
// Convert CompileErrors to our ValidationIssue format
|
|
460
|
+
for (const compileError of result.errors) {
|
|
461
|
+
// Get human-readable suggestion based on error code
|
|
462
|
+
const suggestionInfo = ERROR_SUGGESTIONS[compileError.code] || {
|
|
463
|
+
rootCause: compileError.message,
|
|
464
|
+
suggestion: 'Review the PromptL documentation for correct syntax.',
|
|
465
|
+
};
|
|
466
|
+
issues.push({
|
|
467
|
+
type: 'error',
|
|
468
|
+
code: compileError.code,
|
|
469
|
+
message: compileError.message,
|
|
470
|
+
rootCause: suggestionInfo.rootCause,
|
|
471
|
+
suggestion: suggestionInfo.suggestion,
|
|
472
|
+
location: compileError.start ? {
|
|
473
|
+
line: compileError.start.line,
|
|
474
|
+
column: compileError.start.column,
|
|
475
|
+
} : undefined,
|
|
476
|
+
codeFrame: compileError.frame, // The beautiful formatted code frame!
|
|
477
|
+
});
|
|
445
478
|
}
|
|
446
479
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
lineNumber: unclosed.line,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
// Check frontmatter has required fields
|
|
458
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
459
|
-
if (frontmatterMatch) {
|
|
460
|
-
const frontmatter = frontmatterMatch[1];
|
|
461
|
-
if (!frontmatter.includes('model:') && !frontmatter.includes('model :')) {
|
|
480
|
+
catch (err) {
|
|
481
|
+
// Handle parse errors (thrown, not accumulated)
|
|
482
|
+
if (err instanceof promptl_ai_1.CompileError) {
|
|
483
|
+
const suggestionInfo = ERROR_SUGGESTIONS[err.code] || {
|
|
484
|
+
rootCause: err.message,
|
|
485
|
+
suggestion: 'Fix the syntax error at the indicated location.',
|
|
486
|
+
};
|
|
462
487
|
issues.push({
|
|
463
488
|
type: 'error',
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
489
|
+
code: err.code,
|
|
490
|
+
message: err.message,
|
|
491
|
+
rootCause: suggestionInfo.rootCause,
|
|
492
|
+
suggestion: suggestionInfo.suggestion,
|
|
493
|
+
location: err.start ? {
|
|
494
|
+
line: err.start.line,
|
|
495
|
+
column: err.start.column,
|
|
496
|
+
} : undefined,
|
|
497
|
+
codeFrame: err.frame,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// Unknown error - still report it
|
|
502
|
+
issues.push({
|
|
503
|
+
type: 'error',
|
|
504
|
+
code: 'unknown-error',
|
|
505
|
+
message: err instanceof Error ? err.message : 'Unknown validation error',
|
|
506
|
+
rootCause: 'An unexpected error occurred during validation.',
|
|
507
|
+
suggestion: 'Check the prompt content for syntax errors.',
|
|
467
508
|
});
|
|
468
509
|
}
|
|
469
|
-
}
|
|
470
|
-
// Check for empty content after frontmatter
|
|
471
|
-
const contentAfterFrontmatter = content.replace(/^---[\s\S]*?---/, '').trim();
|
|
472
|
-
if (!contentAfterFrontmatter) {
|
|
473
|
-
issues.push({
|
|
474
|
-
type: 'warning',
|
|
475
|
-
message: 'Empty prompt content',
|
|
476
|
-
rootCause: 'The prompt has no content after the frontmatter.',
|
|
477
|
-
suggestion: 'Add prompt content using role tags:\n<system>\nYour system prompt here\n</system>',
|
|
478
|
-
});
|
|
479
510
|
}
|
|
480
511
|
return issues;
|
|
481
512
|
}
|
|
@@ -492,17 +523,18 @@ async function identifyFailingDocuments(changes) {
|
|
|
492
523
|
for (const change of nonDeleteChanges) {
|
|
493
524
|
if (!change.content)
|
|
494
525
|
continue;
|
|
495
|
-
const localIssues = validatePromptLContent(change.content, change.path);
|
|
496
|
-
const errors = localIssues.filter(i => i.type === 'error');
|
|
526
|
+
const localIssues = await validatePromptLContent(change.content, change.path);
|
|
527
|
+
const errors = localIssues.filter((i) => i.type === 'error');
|
|
497
528
|
if (errors.length > 0) {
|
|
498
529
|
const mainError = errors[0];
|
|
499
530
|
failed.push({
|
|
500
531
|
path: change.path,
|
|
501
532
|
error: mainError.message,
|
|
533
|
+
code: mainError.code,
|
|
502
534
|
rootCause: mainError.rootCause,
|
|
503
535
|
suggestion: mainError.suggestion,
|
|
504
|
-
|
|
505
|
-
|
|
536
|
+
location: mainError.location,
|
|
537
|
+
codeFrame: mainError.codeFrame,
|
|
506
538
|
});
|
|
507
539
|
}
|
|
508
540
|
}
|
|
@@ -556,20 +588,20 @@ async function testSingleDocument(change) {
|
|
|
556
588
|
}
|
|
557
589
|
// Run local validation to give more context
|
|
558
590
|
if (change.content) {
|
|
559
|
-
const localIssues = validatePromptLContent(change.content, change.path);
|
|
591
|
+
const localIssues = await validatePromptLContent(change.content, change.path);
|
|
560
592
|
if (localIssues.length > 0) {
|
|
561
|
-
const warnings = localIssues.filter(i => i.type === 'warning');
|
|
593
|
+
const warnings = localIssues.filter((i) => i.type === 'warning');
|
|
562
594
|
if (warnings.length > 0) {
|
|
563
|
-
suggestion += `\n\nAdditional observations:\n${warnings.map(w => `- ${w.message}: ${w.suggestion}`).join('\n')}`;
|
|
595
|
+
suggestion += `\n\nAdditional observations:\n${warnings.map((w) => `- ${w.message}: ${w.suggestion}`).join('\n')}`;
|
|
564
596
|
}
|
|
565
597
|
}
|
|
566
598
|
}
|
|
567
599
|
return {
|
|
568
600
|
path: change.path,
|
|
569
601
|
error: errorMsg,
|
|
602
|
+
code: 'api-validation-error',
|
|
570
603
|
rootCause,
|
|
571
604
|
suggestion,
|
|
572
|
-
snippet: change.content?.substring(0, 300),
|
|
573
605
|
};
|
|
574
606
|
}
|
|
575
607
|
}
|
|
@@ -697,13 +729,14 @@ async function deployToLive(changes, _versionName) {
|
|
|
697
729
|
const errorLines = [];
|
|
698
730
|
for (const doc of failedDocs) {
|
|
699
731
|
errorLines.push(`\n## ❌ ${doc.path}`);
|
|
732
|
+
errorLines.push(`**Error Code:** \`${doc.code}\``);
|
|
700
733
|
errorLines.push(`**Error:** ${doc.error}`);
|
|
701
734
|
errorLines.push(`**Root Cause:** ${doc.rootCause}`);
|
|
702
|
-
if (doc.
|
|
703
|
-
errorLines.push(`**Location:** Line ${doc.
|
|
735
|
+
if (doc.location) {
|
|
736
|
+
errorLines.push(`**Location:** Line ${doc.location.line}, Column ${doc.location.column}`);
|
|
704
737
|
}
|
|
705
|
-
if (doc.
|
|
706
|
-
errorLines.push(`**
|
|
738
|
+
if (doc.codeFrame) {
|
|
739
|
+
errorLines.push(`**Code Context:**\n\`\`\`\n${doc.codeFrame}\n\`\`\``);
|
|
707
740
|
}
|
|
708
741
|
errorLines.push(`**Fix:** ${doc.suggestion}`);
|
|
709
742
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "latitude-mcp-server",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
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",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
76
76
|
"dotenv": "^17.2.3",
|
|
77
|
+
"promptl-ai": "^0.9.4",
|
|
77
78
|
"zod": "^4.1.13"
|
|
78
79
|
},
|
|
79
80
|
"publishConfig": {
|