latitude-mcp-server 2.2.6 → 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.
package/dist/api.d.ts CHANGED
@@ -52,10 +52,6 @@ interface PushResponse {
52
52
  * This is the CLI-style push that sends all changes at once
53
53
  */
54
54
  export declare function pushChanges(versionUuid: string, changes: DocumentChange[]): Promise<PushResponse>;
55
- /**
56
- * Compute hash of content for diff comparison
57
- */
58
- export declare function hashContent(content: string): string;
59
55
  /**
60
56
  * Compute diff between incoming prompts and existing prompts
61
57
  * Returns only the changes that need to be made
@@ -65,6 +61,24 @@ export declare function computeDiff(incoming: Array<{
65
61
  content: string;
66
62
  }>, existing: Document[]): DocumentChange[];
67
63
  export declare function runDocument(path: string, parameters?: Record<string, unknown>, versionUuid?: string): Promise<RunResult>;
64
+ export interface ValidationIssue {
65
+ type: 'error' | 'warning';
66
+ code: string;
67
+ message: string;
68
+ rootCause: string;
69
+ suggestion: string;
70
+ location?: {
71
+ line: number;
72
+ column: number;
73
+ };
74
+ codeFrame?: string;
75
+ }
76
+ /**
77
+ * Pre-validate PromptL content using the official promptl-ai library.
78
+ * Returns detailed, actionable error messages with code frames.
79
+ * EXPORTED for use by tools.ts to pre-validate before pushing.
80
+ */
81
+ export declare function validatePromptLContent(content: string, path: string): Promise<ValidationIssue[]>;
68
82
  /**
69
83
  * Deploy changes to LIVE version using the proper workflow:
70
84
  * 1. Create a draft version
package/dist/api.js CHANGED
@@ -24,9 +24,9 @@ exports.getDocument = getDocument;
24
24
  exports.createOrUpdateDocument = createOrUpdateDocument;
25
25
  exports.deleteDocument = deleteDocument;
26
26
  exports.pushChanges = pushChanges;
27
- exports.hashContent = hashContent;
28
27
  exports.computeDiff = computeDiff;
29
28
  exports.runDocument = runDocument;
29
+ exports.validatePromptLContent = validatePromptLContent;
30
30
  exports.deployToLive = deployToLive;
31
31
  exports.getPromptNames = getPromptNames;
32
32
  const logger_util_js_1 = require("./utils/logger.util.js");
@@ -66,9 +66,6 @@ async function request(endpoint, options = {}) {
66
66
  const body = options.body && method === 'POST'
67
67
  ? { ...options.body, __internal: { source: 'api' } }
68
68
  : options.body;
69
- if (body) {
70
- logger.debug(`Request body: ${JSON.stringify(body, null, 2)}`);
71
- }
72
69
  const response = await fetch(url, {
73
70
  method,
74
71
  headers: {
@@ -332,19 +329,6 @@ async function pushChanges(versionUuid, changes) {
332
329
  body: { changes: apiChanges },
333
330
  });
334
331
  }
335
- /**
336
- * Compute hash of content for diff comparison
337
- */
338
- function hashContent(content) {
339
- // Simple hash - in production you might want crypto
340
- let hash = 0;
341
- for (let i = 0; i < content.length; i++) {
342
- const char = content.charCodeAt(i);
343
- hash = ((hash << 5) - hash) + char;
344
- hash = hash & hash; // Convert to 32bit integer
345
- }
346
- return hash.toString(16);
347
- }
348
332
  /**
349
333
  * Compute diff between incoming prompts and existing prompts
350
334
  * Returns only the changes that need to be made
@@ -446,6 +430,7 @@ const ERROR_SUGGESTIONS = {
446
430
  /**
447
431
  * Pre-validate PromptL content using the official promptl-ai library.
448
432
  * Returns detailed, actionable error messages with code frames.
433
+ * EXPORTED for use by tools.ts to pre-validate before pushing.
449
434
  */
450
435
  async function validatePromptLContent(content, path) {
451
436
  const issues = [];
@@ -704,7 +689,6 @@ async function deployToLive(changes, _versionName) {
704
689
  logger.info(`Draft created: ${draft.uuid}`);
705
690
  // Step 2: Push all changes to the draft in ONE batch
706
691
  logger.info(`Pushing ${actualChanges.length} change(s) to draft...`);
707
- logger.debug(`Changes payload: ${JSON.stringify(actualChanges, null, 2)}`);
708
692
  const pushResult = await pushChanges(draft.uuid, actualChanges);
709
693
  logger.info(`Push complete: ${pushResult.documentsProcessed} documents processed`);
710
694
  // Step 3: Publish the draft to make it LIVE
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.6",
3
+ "version": "3.0.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",