latitude-mcp-server 1.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.
Files changed (51) hide show
  1. package/.releaserc.json +34 -0
  2. package/README.md +687 -0
  3. package/dist/cli/index.d.ts +7 -0
  4. package/dist/cli/index.js +43 -0
  5. package/dist/cli/latitude.cli.d.ts +10 -0
  6. package/dist/cli/latitude.cli.js +286 -0
  7. package/dist/controllers/latitude.controller.d.ts +115 -0
  8. package/dist/controllers/latitude.controller.js +287 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +166 -0
  11. package/dist/resources/latitude.resource.d.ts +12 -0
  12. package/dist/resources/latitude.resource.js +145 -0
  13. package/dist/services/vendor.latitude.service.d.ts +49 -0
  14. package/dist/services/vendor.latitude.service.js +294 -0
  15. package/dist/tools/latitude.tool.d.ts +6 -0
  16. package/dist/tools/latitude.tool.js +517 -0
  17. package/dist/types/common.types.d.ts +20 -0
  18. package/dist/types/common.types.js +7 -0
  19. package/dist/types/latitude.types.d.ts +487 -0
  20. package/dist/types/latitude.types.js +311 -0
  21. package/dist/utils/cli.test.util.d.ts +34 -0
  22. package/dist/utils/cli.test.util.js +143 -0
  23. package/dist/utils/config.util.d.ts +43 -0
  24. package/dist/utils/config.util.js +145 -0
  25. package/dist/utils/config.util.test.d.ts +1 -0
  26. package/dist/utils/constants.util.d.ts +26 -0
  27. package/dist/utils/constants.util.js +29 -0
  28. package/dist/utils/error-handler.util.d.ts +54 -0
  29. package/dist/utils/error-handler.util.js +202 -0
  30. package/dist/utils/error-handler.util.test.d.ts +1 -0
  31. package/dist/utils/error.util.d.ts +73 -0
  32. package/dist/utils/error.util.js +174 -0
  33. package/dist/utils/error.util.test.d.ts +1 -0
  34. package/dist/utils/formatter.util.d.ts +36 -0
  35. package/dist/utils/formatter.util.js +116 -0
  36. package/dist/utils/jest.setup.d.ts +5 -0
  37. package/dist/utils/jest.setup.js +36 -0
  38. package/dist/utils/jq.util.d.ts +34 -0
  39. package/dist/utils/jq.util.js +87 -0
  40. package/dist/utils/logger.util.d.ts +78 -0
  41. package/dist/utils/logger.util.js +344 -0
  42. package/dist/utils/toon.util.d.ts +15 -0
  43. package/dist/utils/toon.util.js +65 -0
  44. package/dist/utils/transport.util.d.ts +49 -0
  45. package/dist/utils/transport.util.js +162 -0
  46. package/eslint.config.mjs +46 -0
  47. package/openapi.json +12592 -0
  48. package/package.json +118 -0
  49. package/scripts/ensure-executable.js +38 -0
  50. package/scripts/package.json +3 -0
  51. package/scripts/update-version.js +204 -0
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CreateLogInputSchema = exports.StopConversationInputSchema = exports.GetConversationInputSchema = exports.ChatInputSchema = exports.PushChangesInputSchema = exports.RunPromptInputSchema = exports.PushPromptFromFileInputSchema = exports.PushPromptInputSchema = exports.GetPromptInputSchema = exports.ListPromptsInputSchema = exports.PublishVersionInputSchema = exports.CreateVersionInputSchema = exports.GetVersionInputSchema = exports.ListVersionsInputSchema = exports.CreateProjectInputSchema = exports.ListProjectsInputSchema = exports.LatitudeErrorSchema = exports.StreamEventSchema = exports.StreamEventTypeEnum = exports.CreateLogRequestSchema = exports.ChatRequestSchema = exports.ConversationSchema = exports.MessageSchema = exports.ContentPartSchema = exports.FileContentSchema = exports.ImageContentSchema = exports.TextContentSchema = exports.MessageRoleEnum = exports.RunRequestSchema = exports.RunSourceEnum = exports.PushChangesSchema = exports.DocumentChangeSchema = exports.ChangeStatusEnum = exports.DocumentListSchema = exports.DocumentSchema = exports.ParameterTypeSchema = exports.ProviderEnum = exports.VersionListSchema = exports.VersionSchema = exports.ProjectListSchema = exports.ProjectSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Latitude API Types and Zod Schemas
7
+ *
8
+ * Based on OpenAPI spec from openapi.json
9
+ * API Version: 1.0.2
10
+ * Base URL: https://gateway.latitude.so
11
+ */
12
+ // ============================================================================
13
+ // Project Types
14
+ // ============================================================================
15
+ exports.ProjectSchema = zod_1.z.object({
16
+ id: zod_1.z.number(),
17
+ name: zod_1.z.string(),
18
+ workspaceId: zod_1.z.number(),
19
+ createdAt: zod_1.z.string(),
20
+ updatedAt: zod_1.z.string(),
21
+ lastEditedAt: zod_1.z.string().optional(),
22
+ deletedAt: zod_1.z.string().nullable().optional(),
23
+ });
24
+ exports.ProjectListSchema = zod_1.z.array(exports.ProjectSchema);
25
+ // ============================================================================
26
+ // Version Types
27
+ // ============================================================================
28
+ exports.VersionSchema = zod_1.z.object({
29
+ id: zod_1.z.number(),
30
+ uuid: zod_1.z.string(),
31
+ title: zod_1.z.string(),
32
+ description: zod_1.z.string().nullable(),
33
+ projectId: zod_1.z.number(),
34
+ version: zod_1.z.number().nullable(),
35
+ userId: zod_1.z.string(),
36
+ mergedAt: zod_1.z.string().nullable(),
37
+ createdAt: zod_1.z.string(),
38
+ updatedAt: zod_1.z.string(),
39
+ deletedAt: zod_1.z.string().nullable(),
40
+ status: zod_1.z.string().optional(),
41
+ message: zod_1.z.string().optional(),
42
+ authorName: zod_1.z.string().nullable().optional(),
43
+ authorEmail: zod_1.z.string().nullable().optional(),
44
+ authorId: zod_1.z.number().nullable().optional(),
45
+ parentCommitUuid: zod_1.z.string().nullable().optional(),
46
+ });
47
+ exports.VersionListSchema = zod_1.z.array(exports.VersionSchema);
48
+ // ============================================================================
49
+ // Document/Prompt Types
50
+ // ============================================================================
51
+ exports.ProviderEnum = zod_1.z.enum([
52
+ 'openai',
53
+ 'anthropic',
54
+ 'groq',
55
+ 'mistral',
56
+ 'azure',
57
+ 'google',
58
+ 'google_vertex',
59
+ 'anthropic_vertex',
60
+ 'custom',
61
+ 'xai',
62
+ 'amazon_bedrock',
63
+ 'deepseek',
64
+ 'perplexity',
65
+ ]);
66
+ exports.ParameterTypeSchema = zod_1.z.object({
67
+ type: zod_1.z.enum(['text', 'image', 'file']),
68
+ });
69
+ exports.DocumentSchema = zod_1.z.object({
70
+ versionUuid: zod_1.z.string(),
71
+ uuid: zod_1.z.string(),
72
+ path: zod_1.z.string(),
73
+ content: zod_1.z.string(),
74
+ contentHash: zod_1.z.string().optional(),
75
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
76
+ parameters: zod_1.z.record(zod_1.z.string(), exports.ParameterTypeSchema).optional(),
77
+ provider: exports.ProviderEnum.optional(),
78
+ });
79
+ exports.DocumentListSchema = zod_1.z.array(exports.DocumentSchema);
80
+ // ============================================================================
81
+ // Document Change Types (for Push)
82
+ // ============================================================================
83
+ exports.ChangeStatusEnum = zod_1.z.enum(['added', 'modified', 'deleted', 'unchanged']);
84
+ exports.DocumentChangeSchema = zod_1.z.object({
85
+ path: zod_1.z.string(),
86
+ content: zod_1.z.string(),
87
+ status: exports.ChangeStatusEnum.default('modified'),
88
+ contentHash: zod_1.z.string().optional(),
89
+ });
90
+ exports.PushChangesSchema = zod_1.z.object({
91
+ changes: zod_1.z.array(exports.DocumentChangeSchema),
92
+ });
93
+ // ============================================================================
94
+ // Run/Execute Types
95
+ // ============================================================================
96
+ exports.RunSourceEnum = zod_1.z.enum([
97
+ 'api',
98
+ 'agent_as_tool',
99
+ 'copilot',
100
+ 'email_trigger',
101
+ 'evaluation',
102
+ 'experiment',
103
+ 'integration_trigger',
104
+ 'playground',
105
+ 'scheduled_trigger',
106
+ 'shared_prompt',
107
+ 'user',
108
+ ]);
109
+ exports.RunRequestSchema = zod_1.z.object({
110
+ path: zod_1.z.string(),
111
+ stream: zod_1.z.boolean().default(false),
112
+ customIdentifier: zod_1.z.string().optional(),
113
+ parameters: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
114
+ tools: zod_1.z.array(zod_1.z.string()).optional(),
115
+ userMessage: zod_1.z.string().optional(),
116
+ background: zod_1.z.boolean().optional(),
117
+ __internal: zod_1.z
118
+ .object({
119
+ source: exports.RunSourceEnum.optional(),
120
+ })
121
+ .optional(),
122
+ });
123
+ // ============================================================================
124
+ // Conversation Types
125
+ // ============================================================================
126
+ exports.MessageRoleEnum = zod_1.z.enum(['system', 'user', 'assistant', 'tool']);
127
+ exports.TextContentSchema = zod_1.z.object({
128
+ type: zod_1.z.literal('text'),
129
+ text: zod_1.z.string(),
130
+ });
131
+ exports.ImageContentSchema = zod_1.z.object({
132
+ type: zod_1.z.literal('image'),
133
+ image: zod_1.z.string(),
134
+ mimeType: zod_1.z.string().optional(),
135
+ });
136
+ exports.FileContentSchema = zod_1.z.object({
137
+ type: zod_1.z.literal('file'),
138
+ file: zod_1.z.string(),
139
+ mimeType: zod_1.z.string(),
140
+ });
141
+ exports.ContentPartSchema = zod_1.z.union([
142
+ exports.TextContentSchema,
143
+ exports.ImageContentSchema,
144
+ exports.FileContentSchema,
145
+ ]);
146
+ exports.MessageSchema = zod_1.z.object({
147
+ role: exports.MessageRoleEnum,
148
+ content: zod_1.z.union([zod_1.z.string(), zod_1.z.array(exports.ContentPartSchema)]),
149
+ name: zod_1.z.string().optional(),
150
+ });
151
+ exports.ConversationSchema = zod_1.z.object({
152
+ uuid: zod_1.z.string(),
153
+ messages: zod_1.z.array(exports.MessageSchema).optional(),
154
+ createdAt: zod_1.z.string().optional(),
155
+ updatedAt: zod_1.z.string().optional(),
156
+ });
157
+ // ============================================================================
158
+ // Chat Types
159
+ // ============================================================================
160
+ exports.ChatRequestSchema = zod_1.z.object({
161
+ messages: zod_1.z.array(exports.MessageSchema),
162
+ stream: zod_1.z.boolean().default(false),
163
+ });
164
+ // ============================================================================
165
+ // Log Types
166
+ // ============================================================================
167
+ exports.CreateLogRequestSchema = zod_1.z.object({
168
+ path: zod_1.z.string(),
169
+ messages: zod_1.z.array(exports.MessageSchema),
170
+ });
171
+ // ============================================================================
172
+ // Streaming Event Types
173
+ // ============================================================================
174
+ exports.StreamEventTypeEnum = zod_1.z.enum([
175
+ 'text-delta',
176
+ 'step-complete',
177
+ 'provider-event',
178
+ 'tool-call-started',
179
+ 'tool-call',
180
+ 'chain-complete',
181
+ 'chain-error',
182
+ ]);
183
+ exports.StreamEventSchema = zod_1.z.object({
184
+ event: exports.StreamEventTypeEnum,
185
+ data: zod_1.z.any(),
186
+ });
187
+ // ============================================================================
188
+ // Error Types
189
+ // ============================================================================
190
+ exports.LatitudeErrorSchema = zod_1.z.object({
191
+ name: zod_1.z.string(),
192
+ errorCode: zod_1.z.string(),
193
+ message: zod_1.z.string(),
194
+ details: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
195
+ });
196
+ // ============================================================================
197
+ // Tool Input Schemas (for MCP tools)
198
+ // ============================================================================
199
+ // Project tools
200
+ exports.ListProjectsInputSchema = zod_1.z.object({});
201
+ exports.CreateProjectInputSchema = zod_1.z.object({
202
+ name: zod_1.z.string().describe('Project name'),
203
+ });
204
+ // Version tools
205
+ exports.ListVersionsInputSchema = zod_1.z.object({
206
+ projectId: zod_1.z.string().describe('Project ID'),
207
+ });
208
+ exports.GetVersionInputSchema = zod_1.z.object({
209
+ projectId: zod_1.z.string().describe('Project ID'),
210
+ versionUuid: zod_1.z.string().describe('Version UUID'),
211
+ });
212
+ exports.CreateVersionInputSchema = zod_1.z.object({
213
+ projectId: zod_1.z.string().describe('Project ID'),
214
+ name: zod_1.z.string().describe('Version/commit name'),
215
+ });
216
+ exports.PublishVersionInputSchema = zod_1.z.object({
217
+ projectId: zod_1.z.string().describe('Project ID'),
218
+ versionUuid: zod_1.z.string().describe('Version UUID to publish'),
219
+ title: zod_1.z.string().optional().describe('Publication title'),
220
+ description: zod_1.z.string().optional().describe('Publication description'),
221
+ });
222
+ // Document/Prompt tools
223
+ exports.ListPromptsInputSchema = zod_1.z.object({
224
+ projectId: zod_1.z.string().describe('Project ID'),
225
+ versionUuid: zod_1.z
226
+ .string()
227
+ .default('live')
228
+ .describe("Version UUID or 'live' for published version"),
229
+ });
230
+ exports.GetPromptInputSchema = zod_1.z.object({
231
+ projectId: zod_1.z.string().describe('Project ID'),
232
+ versionUuid: zod_1.z.string().default('live').describe("Version UUID or 'live'"),
233
+ path: zod_1.z
234
+ .string()
235
+ .describe("Prompt path (e.g., '/my-prompt' or 'folder/prompt')"),
236
+ });
237
+ exports.PushPromptInputSchema = zod_1.z.object({
238
+ projectId: zod_1.z.string().describe('Project ID'),
239
+ versionUuid: zod_1.z
240
+ .string()
241
+ .describe("Target version UUID (must be draft, not 'live')"),
242
+ path: zod_1.z.string().describe('Prompt path'),
243
+ content: zod_1.z.string().describe('Prompt content in PromptL format'),
244
+ force: zod_1.z.boolean().default(false).describe('Force overwrite if exists'),
245
+ });
246
+ exports.PushPromptFromFileInputSchema = zod_1.z.object({
247
+ projectId: zod_1.z.string().describe('Project ID'),
248
+ versionUuid: zod_1.z
249
+ .string()
250
+ .describe("Target version UUID (must be draft, not 'live')"),
251
+ filePath: zod_1.z
252
+ .string()
253
+ .describe('Absolute path to the prompt file (e.g., /path/to/my-prompt.md)'),
254
+ promptPath: zod_1.z
255
+ .string()
256
+ .optional()
257
+ .describe("Optional: Prompt path in Latitude. If omitted, derived from filename (e.g., 'my-prompt.md' → 'my-prompt')"),
258
+ force: zod_1.z.boolean().default(false).describe('Force overwrite if exists'),
259
+ });
260
+ exports.RunPromptInputSchema = zod_1.z.object({
261
+ projectId: zod_1.z.string().describe('Project ID'),
262
+ versionUuid: zod_1.z.string().default('live').describe("Version UUID or 'live'"),
263
+ path: zod_1.z.string().describe('Prompt path to run'),
264
+ parameters: zod_1.z
265
+ .record(zod_1.z.string(), zod_1.z.unknown())
266
+ .optional()
267
+ .describe('Prompt parameters as key-value pairs'),
268
+ stream: zod_1.z.boolean().default(false).describe('Enable streaming response'),
269
+ tools: zod_1.z.array(zod_1.z.string()).optional().describe('Tool names to enable'),
270
+ userMessage: zod_1.z.string().optional().describe('Additional user message'),
271
+ });
272
+ exports.PushChangesInputSchema = zod_1.z.object({
273
+ projectId: zod_1.z.string().describe('Project ID'),
274
+ versionUuid: zod_1.z.string().describe('Target version UUID'),
275
+ changes: zod_1.z
276
+ .array(zod_1.z.object({
277
+ path: zod_1.z.string().describe('Document path'),
278
+ content: zod_1.z.string().describe('Document content'),
279
+ status: zod_1.z
280
+ .enum(['added', 'modified', 'deleted'])
281
+ .default('modified')
282
+ .describe('Change status'),
283
+ }))
284
+ .describe('Array of document changes'),
285
+ });
286
+ // Conversation tools
287
+ exports.ChatInputSchema = zod_1.z.object({
288
+ conversationUuid: zod_1.z
289
+ .string()
290
+ .describe('Conversation UUID from previous run'),
291
+ message: zod_1.z.string().describe('User message to send'),
292
+ stream: zod_1.z.boolean().default(false).describe('Enable streaming response'),
293
+ });
294
+ exports.GetConversationInputSchema = zod_1.z.object({
295
+ conversationUuid: zod_1.z.string().describe('Conversation UUID'),
296
+ });
297
+ exports.StopConversationInputSchema = zod_1.z.object({
298
+ conversationUuid: zod_1.z.string().describe('Conversation UUID to stop'),
299
+ });
300
+ // Log tools
301
+ exports.CreateLogInputSchema = zod_1.z.object({
302
+ projectId: zod_1.z.string().describe('Project ID'),
303
+ versionUuid: zod_1.z.string().describe('Version UUID'),
304
+ path: zod_1.z.string().describe('Prompt path'),
305
+ messages: zod_1.z
306
+ .array(zod_1.z.object({
307
+ role: zod_1.z.enum(['system', 'user', 'assistant']),
308
+ content: zod_1.z.string(),
309
+ }))
310
+ .describe('Conversation messages to log'),
311
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Utility for testing CLI commands with real execution
3
+ */
4
+ export declare class CliTestUtil {
5
+ /**
6
+ * Executes a CLI command and returns the result
7
+ *
8
+ * @param args - CLI arguments to pass to the command
9
+ * @param options - Test options
10
+ * @returns Promise with stdout, stderr, and exit code
11
+ */
12
+ static runCommand(args: string[], options?: {
13
+ timeoutMs?: number;
14
+ env?: Record<string, string>;
15
+ }): Promise<{
16
+ stdout: string;
17
+ stderr: string;
18
+ exitCode: number;
19
+ }>;
20
+ /**
21
+ * Validates that stdout contains expected strings/patterns
22
+ */
23
+ static validateOutputContains(output: string, expectedPatterns: (string | RegExp)[]): void;
24
+ /**
25
+ * Validates TOON output format
26
+ * TOON uses "key: value" syntax for objects
27
+ */
28
+ static validateToonOutput(output: string): void;
29
+ /**
30
+ * Validates Markdown output format
31
+ * @deprecated Use validateToonOutput for new code - output is now TOON by default
32
+ */
33
+ static validateMarkdownOutput(output: string): void;
34
+ }
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliTestUtil = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const path_1 = require("path");
6
+ /**
7
+ * Utility for testing CLI commands with real execution
8
+ */
9
+ class CliTestUtil {
10
+ /**
11
+ * Executes a CLI command and returns the result
12
+ *
13
+ * @param args - CLI arguments to pass to the command
14
+ * @param options - Test options
15
+ * @returns Promise with stdout, stderr, and exit code
16
+ */
17
+ static async runCommand(args, options = {}) {
18
+ // Default timeout of 30 seconds
19
+ const timeoutMs = options.timeoutMs || 30000;
20
+ // CLI execution path - points to the built CLI script
21
+ const cliPath = (0, path_1.join)(process.cwd(), 'dist', 'index.js');
22
+ // Log what command we're about to run
23
+ console.log(`Running CLI command: node ${cliPath} ${args.join(' ')}`);
24
+ return new Promise((resolve, reject) => {
25
+ // Set up timeout handler
26
+ const timeoutId = setTimeout(() => {
27
+ child.kill();
28
+ reject(new Error(`CLI command timed out after ${timeoutMs}ms`));
29
+ }, timeoutMs);
30
+ // Capture stdout and stderr
31
+ let stdout = '';
32
+ let stderr = '';
33
+ // Spawn the process with given arguments and enhanced environment
34
+ const child = (0, child_process_1.spawn)('node', [cliPath, ...args], {
35
+ env: {
36
+ ...process.env,
37
+ ...options.env,
38
+ DEBUG: 'true', // Enable debug logging
39
+ NODE_ENV: 'test', // Ensure tests are detected
40
+ },
41
+ });
42
+ // Collect stdout data
43
+ child.stdout.on('data', (data) => {
44
+ const chunk = data.toString();
45
+ stdout += chunk;
46
+ console.log(`STDOUT chunk: ${chunk.substring(0, 50)}...`);
47
+ });
48
+ // Collect stderr data
49
+ child.stderr.on('data', (data) => {
50
+ const chunk = data.toString();
51
+ stderr += chunk;
52
+ console.log(`STDERR chunk: ${chunk.substring(0, 50)}...`);
53
+ });
54
+ // Handle process completion
55
+ child.on('close', (exitCode) => {
56
+ clearTimeout(timeoutId);
57
+ console.log(`Command completed with exit code: ${exitCode}`);
58
+ console.log(`Total STDOUT length: ${stdout.length} chars`);
59
+ // Get the non-debug output for debugging purposes
60
+ const nonDebugOutput = stdout
61
+ .split('\n')
62
+ .filter((line) => !line.match(/^\[\d{2}:\d{2}:\d{2}\]/))
63
+ .join('\n');
64
+ console.log(`Non-debug output length: ${nonDebugOutput.length} chars`);
65
+ console.log(`STDOUT excerpt: ${stdout.substring(0, 100)}...`);
66
+ console.log(`Filtered excerpt: ${nonDebugOutput.substring(0, 100)}...`);
67
+ resolve({
68
+ stdout,
69
+ stderr,
70
+ exitCode: exitCode ?? 0,
71
+ });
72
+ });
73
+ // Handle process errors
74
+ child.on('error', (err) => {
75
+ clearTimeout(timeoutId);
76
+ console.error(`Command error: ${err.message}`);
77
+ reject(err);
78
+ });
79
+ });
80
+ }
81
+ /**
82
+ * Validates that stdout contains expected strings/patterns
83
+ */
84
+ static validateOutputContains(output, expectedPatterns) {
85
+ // Filter out debug log lines for cleaner validation
86
+ const cleanOutput = output
87
+ .split('\n')
88
+ .filter((line) => !line.match(/^\[\d{2}:\d{2}:\d{2}\]/))
89
+ .join('\n');
90
+ console.log('==== Cleaned output for validation ====');
91
+ console.log(cleanOutput);
92
+ console.log('=======================================');
93
+ for (const pattern of expectedPatterns) {
94
+ if (typeof pattern === 'string') {
95
+ expect(cleanOutput).toContain(pattern);
96
+ }
97
+ else {
98
+ expect(cleanOutput).toMatch(pattern);
99
+ }
100
+ }
101
+ }
102
+ /**
103
+ * Validates TOON output format
104
+ * TOON uses "key: value" syntax for objects
105
+ */
106
+ static validateToonOutput(output) {
107
+ // Filter out debug log lines for cleaner validation
108
+ const cleanOutput = output
109
+ .split('\n')
110
+ .filter((line) => !line.match(/^\[\d{2}:\d{2}:\d{2}\]/))
111
+ .join('\n');
112
+ // TOON format characteristics:
113
+ // - Key-value pairs in "key: value" format
114
+ // - No curly braces for objects
115
+ // - Arrays use [count]{fields}: notation
116
+ const toonPatterns = [
117
+ /^\w+:\s*.+/m, // key: value pattern
118
+ ];
119
+ expect(toonPatterns.some((pattern) => pattern.test(cleanOutput))).toBe(true);
120
+ }
121
+ /**
122
+ * Validates Markdown output format
123
+ * @deprecated Use validateToonOutput for new code - output is now TOON by default
124
+ */
125
+ static validateMarkdownOutput(output) {
126
+ // Filter out debug log lines for cleaner validation
127
+ const cleanOutput = output
128
+ .split('\n')
129
+ .filter((line) => !line.match(/^\[\d{2}:\d{2}:\d{2}\]/))
130
+ .join('\n');
131
+ // Check for Markdown heading
132
+ expect(cleanOutput).toMatch(/^#\s.+/m);
133
+ // Check for markdown formatting elements like bold text, lists, etc.
134
+ const markdownElements = [
135
+ /\*\*.+\*\*/, // Bold text
136
+ /-\s.+/, // List items
137
+ /\|.+\|.+\|/, // Table rows
138
+ /\[.+\]\(.+\)/, // Links
139
+ ];
140
+ expect(markdownElements.some((pattern) => pattern.test(cleanOutput))).toBe(true);
141
+ }
142
+ }
143
+ exports.CliTestUtil = CliTestUtil;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Configuration loader that handles multiple sources with priority:
3
+ * 1. Direct ENV pass (process.env)
4
+ * 2. .env file in project root
5
+ * 3. Global config file at $HOME/.mcp/configs.json
6
+ */
7
+ declare class ConfigLoader {
8
+ private packageName;
9
+ private configLoaded;
10
+ /**
11
+ * Create a new ConfigLoader instance
12
+ * @param packageName The package name to use for global config lookup
13
+ */
14
+ constructor(packageName: string);
15
+ /**
16
+ * Load configuration from all sources with proper priority
17
+ */
18
+ load(): void;
19
+ /**
20
+ * Load configuration from .env file in project root
21
+ */
22
+ private loadFromEnvFile;
23
+ /**
24
+ * Load configuration from global config file at $HOME/.mcp/configs.json
25
+ */
26
+ private loadFromGlobalConfig;
27
+ /**
28
+ * Get a configuration value
29
+ * @param key The configuration key
30
+ * @param defaultValue The default value if the key is not found
31
+ * @returns The configuration value or the default value
32
+ */
33
+ get(key: string, defaultValue?: string): string | undefined;
34
+ /**
35
+ * Get a boolean configuration value
36
+ * @param key The configuration key
37
+ * @param defaultValue The default value if the key is not found
38
+ * @returns The boolean configuration value or the default value
39
+ */
40
+ getBoolean(key: string, defaultValue?: boolean): boolean;
41
+ }
42
+ export declare const config: ConfigLoader;
43
+ export {};
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.config = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const logger_util_js_1 = require("./logger.util.js");
10
+ const dotenv_1 = __importDefault(require("dotenv"));
11
+ const os_1 = __importDefault(require("os"));
12
+ /**
13
+ * Configuration loader that handles multiple sources with priority:
14
+ * 1. Direct ENV pass (process.env)
15
+ * 2. .env file in project root
16
+ * 3. Global config file at $HOME/.mcp/configs.json
17
+ */
18
+ class ConfigLoader {
19
+ /**
20
+ * Create a new ConfigLoader instance
21
+ * @param packageName The package name to use for global config lookup
22
+ */
23
+ constructor(packageName) {
24
+ this.configLoaded = false;
25
+ this.packageName = packageName;
26
+ }
27
+ /**
28
+ * Load configuration from all sources with proper priority
29
+ */
30
+ load() {
31
+ const methodLogger = logger_util_js_1.Logger.forContext('utils/config.util.ts', 'load');
32
+ if (this.configLoaded) {
33
+ methodLogger.debug('Configuration already loaded, skipping');
34
+ return;
35
+ }
36
+ methodLogger.debug('Loading configuration...');
37
+ // Priority 3: Load from global config file
38
+ this.loadFromGlobalConfig();
39
+ // Priority 2: Load from .env file
40
+ this.loadFromEnvFile();
41
+ // Priority 1: Direct ENV pass is already in process.env
42
+ // No need to do anything as it already has highest priority
43
+ this.configLoaded = true;
44
+ methodLogger.debug('Configuration loaded successfully');
45
+ }
46
+ /**
47
+ * Load configuration from .env file in project root
48
+ */
49
+ loadFromEnvFile() {
50
+ const methodLogger = logger_util_js_1.Logger.forContext('utils/config.util.ts', 'loadFromEnvFile');
51
+ try {
52
+ const result = dotenv_1.default.config({
53
+ // Suppress dotenv output in test environment
54
+ debug: process.env.NODE_ENV !== 'test' &&
55
+ process.env.DEBUG === 'true',
56
+ // Suppress promotional messages in test environment (dotenv v17+)
57
+ quiet: process.env.NODE_ENV === 'test',
58
+ });
59
+ if (result.error) {
60
+ methodLogger.debug('No .env file found or error reading it');
61
+ return;
62
+ }
63
+ methodLogger.debug('Loaded configuration from .env file');
64
+ }
65
+ catch (error) {
66
+ methodLogger.error('Error loading .env file', error);
67
+ }
68
+ }
69
+ /**
70
+ * Load configuration from global config file at $HOME/.mcp/configs.json
71
+ */
72
+ loadFromGlobalConfig() {
73
+ const methodLogger = logger_util_js_1.Logger.forContext('utils/config.util.ts', 'loadFromGlobalConfig');
74
+ try {
75
+ const homedir = os_1.default.homedir();
76
+ const globalConfigPath = path_1.default.join(homedir, '.mcp', 'configs.json');
77
+ if (!fs_1.default.existsSync(globalConfigPath)) {
78
+ methodLogger.debug('Global config file not found');
79
+ return;
80
+ }
81
+ const configContent = fs_1.default.readFileSync(globalConfigPath, 'utf8');
82
+ const config = JSON.parse(configContent);
83
+ // Determine the potential keys for the current package
84
+ const shortKey = 'latitude'; // Project-specific short key
85
+ const fullPackageName = this.packageName; // e.g., '@anthropic/latitude-mcp-server'
86
+ const unscopedPackageName = fullPackageName.split('/')[1] || fullPackageName; // e.g., 'latitude-mcp-server'
87
+ const potentialKeys = [
88
+ shortKey,
89
+ fullPackageName,
90
+ unscopedPackageName,
91
+ ];
92
+ let foundConfigSection = null;
93
+ let usedKey = null;
94
+ for (const key of potentialKeys) {
95
+ if (config[key] &&
96
+ typeof config[key] === 'object' &&
97
+ config[key].environments) {
98
+ foundConfigSection = config[key];
99
+ usedKey = key;
100
+ methodLogger.debug(`Found configuration using key: ${key}`);
101
+ break; // Stop once found
102
+ }
103
+ }
104
+ if (!foundConfigSection || !foundConfigSection.environments) {
105
+ methodLogger.debug(`No configuration found for ${this.packageName} using keys: ${potentialKeys.join(', ')}`);
106
+ return;
107
+ }
108
+ const environments = foundConfigSection.environments;
109
+ for (const [key, value] of Object.entries(environments)) {
110
+ // Only set if not already defined in process.env
111
+ if (process.env[key] === undefined) {
112
+ process.env[key] = String(value);
113
+ }
114
+ }
115
+ methodLogger.debug(`Loaded configuration from global config file using key: ${usedKey}`);
116
+ }
117
+ catch (error) {
118
+ methodLogger.error('Error loading global config file', error);
119
+ }
120
+ }
121
+ /**
122
+ * Get a configuration value
123
+ * @param key The configuration key
124
+ * @param defaultValue The default value if the key is not found
125
+ * @returns The configuration value or the default value
126
+ */
127
+ get(key, defaultValue) {
128
+ return process.env[key] || defaultValue;
129
+ }
130
+ /**
131
+ * Get a boolean configuration value
132
+ * @param key The configuration key
133
+ * @param defaultValue The default value if the key is not found
134
+ * @returns The boolean configuration value or the default value
135
+ */
136
+ getBoolean(key, defaultValue = false) {
137
+ const value = this.get(key);
138
+ if (value === undefined) {
139
+ return defaultValue;
140
+ }
141
+ return value.toLowerCase() === 'true';
142
+ }
143
+ }
144
+ // Create and export a singleton instance with the package name from package.json
145
+ exports.config = new ConfigLoader('@anthropic/latitude-mcp-server');
@@ -0,0 +1 @@
1
+ export {};