latitude-mcp-server 2.2.1 → 2.2.3

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
@@ -33,6 +33,7 @@ export declare class LatitudeApiError extends Error {
33
33
  */
34
34
  export declare function getProjectId(): string;
35
35
  export declare function listVersions(): Promise<Version[]>;
36
+ export declare function getVersion(versionUuid: string): Promise<Version>;
36
37
  export declare function createVersion(name: string): Promise<Version>;
37
38
  export declare function publishVersion(versionUuid: string, title?: string): Promise<Version>;
38
39
  export declare function listDocuments(versionUuid?: string): Promise<Document[]>;
@@ -72,6 +73,6 @@ export declare function runDocument(path: string, parameters?: Record<string, un
72
73
  *
73
74
  * This is the same workflow the CLI uses, ensuring proper validation.
74
75
  */
75
- export declare function deployToLive(changes: DocumentChange[], versionName?: string): Promise<DeployResult>;
76
+ export declare function deployToLive(changes: DocumentChange[], _versionName?: string): Promise<DeployResult>;
76
77
  export declare function getPromptNames(): Promise<string[]>;
77
78
  export {};
package/dist/api.js CHANGED
@@ -16,6 +16,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.LatitudeApiError = void 0;
17
17
  exports.getProjectId = getProjectId;
18
18
  exports.listVersions = listVersions;
19
+ exports.getVersion = getVersion;
19
20
  exports.createVersion = createVersion;
20
21
  exports.publishVersion = publishVersion;
21
22
  exports.listDocuments = listDocuments;
@@ -60,6 +61,13 @@ async function request(endpoint, options = {}) {
60
61
  const timeoutId = setTimeout(() => controller.abort(), options.timeout || API_TIMEOUT_MS);
61
62
  logger.debug(`API ${method} ${endpoint}`);
62
63
  try {
64
+ // Add __internal: { source: 'api' } to all POST requests (required by Latitude API)
65
+ const body = options.body && method === 'POST'
66
+ ? { ...options.body, __internal: { source: 'api' } }
67
+ : options.body;
68
+ if (body) {
69
+ logger.debug(`Request body: ${JSON.stringify(body, null, 2)}`);
70
+ }
63
71
  const response = await fetch(url, {
64
72
  method,
65
73
  headers: {
@@ -67,7 +75,7 @@ async function request(endpoint, options = {}) {
67
75
  Accept: 'application/json',
68
76
  Authorization: `Bearer ${apiKey}`,
69
77
  },
70
- body: options.body ? JSON.stringify(options.body) : undefined,
78
+ body: body ? JSON.stringify(body) : undefined,
71
79
  signal: controller.signal,
72
80
  });
73
81
  clearTimeout(timeoutId);
@@ -264,6 +272,10 @@ async function listVersions() {
264
272
  const projectId = getProjectId();
265
273
  return request(`/projects/${projectId}/versions`);
266
274
  }
275
+ async function getVersion(versionUuid) {
276
+ const projectId = getProjectId();
277
+ return request(`/projects/${projectId}/versions/${versionUuid}`);
278
+ }
267
279
  async function createVersion(name) {
268
280
  const projectId = getProjectId();
269
281
  return request(`/projects/${projectId}/versions`, {
@@ -386,27 +398,38 @@ async function runDocument(path, parameters, versionUuid = 'live') {
386
398
  });
387
399
  }
388
400
  /**
389
- * Format timestamp for version names: "14 Jan 2025 - 13:11"
390
- * Optionally prepend action prefix like "push cover-letter"
401
+ * Identify which documents fail validation by testing each one individually.
402
+ * Used when a batch publish fails to provide actionable error messages.
391
403
  */
392
- function formatVersionName(prefix) {
393
- const now = new Date();
394
- const options = {
395
- timeZone: 'America/Los_Angeles',
396
- day: 'numeric',
397
- month: 'short',
398
- year: 'numeric',
399
- hour: '2-digit',
400
- minute: '2-digit',
401
- hour12: false,
402
- };
403
- const formatted = now.toLocaleString('en-US', options);
404
- // "Jan 14, 2025, 13:11" "14 Jan 2025 - 13:11"
405
- const match = formatted.match(/(\w+)\s+(\d+),\s+(\d+),\s+(\d+:\d+)/);
406
- const timestamp = match
407
- ? `${match[2]} ${match[1]} ${match[3]} - ${match[4]}`
408
- : now.toISOString().replace(/[:.]/g, '-');
409
- return prefix ? `${prefix} (${timestamp})` : timestamp;
404
+ async function identifyFailingDocuments(changes) {
405
+ const failed = [];
406
+ for (const change of changes) {
407
+ if (change.status === 'deleted')
408
+ continue; // Deletes don't have content validation
409
+ try {
410
+ // Create a test draft for this single document
411
+ const testDraft = await createVersion(`validation-test-${change.path}`);
412
+ // Push just this document
413
+ await pushChanges(testDraft.uuid, [change]);
414
+ // Try to publish - this will fail if document has validation errors
415
+ await publishVersion(testDraft.uuid);
416
+ // If we get here, document is valid - the draft was published but that's ok
417
+ logger.debug(`Document ${change.path} validated successfully`);
418
+ }
419
+ catch (error) {
420
+ // This document failed - capture the error
421
+ const errorMsg = error instanceof LatitudeApiError
422
+ ? error.message
423
+ : (error instanceof Error ? error.message : 'Unknown validation error');
424
+ failed.push({
425
+ path: change.path,
426
+ error: errorMsg,
427
+ content: change.content?.substring(0, 200) + (change.content && change.content.length > 200 ? '...' : ''),
428
+ });
429
+ logger.debug(`Document ${change.path} failed validation: ${errorMsg}`);
430
+ }
431
+ }
432
+ return failed;
410
433
  }
411
434
  /**
412
435
  * Create a synthetic Version object for no-op deploys.
@@ -432,7 +455,7 @@ function createNoOpVersion() {
432
455
  *
433
456
  * This is the same workflow the CLI uses, ensuring proper validation.
434
457
  */
435
- async function deployToLive(changes, versionName) {
458
+ async function deployToLive(changes, _versionName) {
436
459
  if (changes.length === 0) {
437
460
  logger.info('No changes to deploy');
438
461
  return {
@@ -459,27 +482,46 @@ async function deployToLive(changes, versionName) {
459
482
  const added = actualChanges.filter((c) => c.status === 'added').map((c) => c.path);
460
483
  const modified = actualChanges.filter((c) => c.status === 'modified').map((c) => c.path);
461
484
  const deleted = actualChanges.filter((c) => c.status === 'deleted').map((c) => c.path);
462
- const name = formatVersionName(versionName);
463
485
  logger.info(`Deploying to LIVE: ${added.length} added, ${modified.length} modified, ${deleted.length} deleted`);
464
486
  // Step 1: Create a new draft version
465
- logger.info(`Creating draft version: ${name}`);
466
- const draft = await createVersion(name);
487
+ const draftName = `MCP deploy ${new Date().toISOString()}`;
488
+ logger.info(`Creating draft version: ${draftName}`);
489
+ const draft = await createVersion(draftName);
467
490
  logger.info(`Draft created: ${draft.uuid}`);
468
491
  // Step 2: Push all changes to the draft in ONE batch
469
492
  logger.info(`Pushing ${actualChanges.length} change(s) to draft...`);
493
+ logger.debug(`Changes payload: ${JSON.stringify(actualChanges, null, 2)}`);
470
494
  const pushResult = await pushChanges(draft.uuid, actualChanges);
471
495
  logger.info(`Push complete: ${pushResult.documentsProcessed} documents processed`);
472
496
  // Step 3: Publish the draft to make it LIVE
473
497
  logger.info(`Publishing draft ${draft.uuid} to LIVE...`);
474
- const published = await publishVersion(draft.uuid, name);
475
- logger.info(`Published successfully! Version is now LIVE: ${published.uuid}`);
476
- return {
477
- version: published,
478
- documentsProcessed: pushResult.documentsProcessed,
479
- added,
480
- modified,
481
- deleted,
482
- };
498
+ try {
499
+ const published = await publishVersion(draft.uuid, draftName);
500
+ logger.info(`Published successfully! Version is now LIVE: ${published.uuid}`);
501
+ return {
502
+ version: published,
503
+ documentsProcessed: pushResult.documentsProcessed,
504
+ added,
505
+ modified,
506
+ deleted,
507
+ };
508
+ }
509
+ catch (publishError) {
510
+ // Publish failed - identify which document(s) have validation errors
511
+ logger.warn('Batch publish failed, identifying problematic documents...');
512
+ const failedDocs = await identifyFailingDocuments(actualChanges);
513
+ if (failedDocs.length > 0) {
514
+ const errorDetails = failedDocs.map(f => `- ${f.path}: ${f.error}`).join('\n');
515
+ throw new LatitudeApiError({
516
+ name: 'DocumentValidationError',
517
+ errorCode: 'DOCUMENT_VALIDATION_FAILED',
518
+ message: `${failedDocs.length} document(s) failed validation:\n${errorDetails}`,
519
+ details: { failedDocuments: failedDocs },
520
+ }, 422);
521
+ }
522
+ // Re-throw original error if we couldn't identify specific failures
523
+ throw publishError;
524
+ }
483
525
  }
484
526
  async function getPromptNames() {
485
527
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latitude-mcp-server",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
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",