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.
- package/.releaserc.json +34 -0
- package/README.md +687 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.js +43 -0
- package/dist/cli/latitude.cli.d.ts +10 -0
- package/dist/cli/latitude.cli.js +286 -0
- package/dist/controllers/latitude.controller.d.ts +115 -0
- package/dist/controllers/latitude.controller.js +287 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +166 -0
- package/dist/resources/latitude.resource.d.ts +12 -0
- package/dist/resources/latitude.resource.js +145 -0
- package/dist/services/vendor.latitude.service.d.ts +49 -0
- package/dist/services/vendor.latitude.service.js +294 -0
- package/dist/tools/latitude.tool.d.ts +6 -0
- package/dist/tools/latitude.tool.js +517 -0
- package/dist/types/common.types.d.ts +20 -0
- package/dist/types/common.types.js +7 -0
- package/dist/types/latitude.types.d.ts +487 -0
- package/dist/types/latitude.types.js +311 -0
- package/dist/utils/cli.test.util.d.ts +34 -0
- package/dist/utils/cli.test.util.js +143 -0
- package/dist/utils/config.util.d.ts +43 -0
- package/dist/utils/config.util.js +145 -0
- package/dist/utils/config.util.test.d.ts +1 -0
- package/dist/utils/constants.util.d.ts +26 -0
- package/dist/utils/constants.util.js +29 -0
- package/dist/utils/error-handler.util.d.ts +54 -0
- package/dist/utils/error-handler.util.js +202 -0
- package/dist/utils/error-handler.util.test.d.ts +1 -0
- package/dist/utils/error.util.d.ts +73 -0
- package/dist/utils/error.util.js +174 -0
- package/dist/utils/error.util.test.d.ts +1 -0
- package/dist/utils/formatter.util.d.ts +36 -0
- package/dist/utils/formatter.util.js +116 -0
- package/dist/utils/jest.setup.d.ts +5 -0
- package/dist/utils/jest.setup.js +36 -0
- package/dist/utils/jq.util.d.ts +34 -0
- package/dist/utils/jq.util.js +87 -0
- package/dist/utils/logger.util.d.ts +78 -0
- package/dist/utils/logger.util.js +344 -0
- package/dist/utils/toon.util.d.ts +15 -0
- package/dist/utils/toon.util.js +65 -0
- package/dist/utils/transport.util.d.ts +49 -0
- package/dist/utils/transport.util.js +162 -0
- package/eslint.config.mjs +46 -0
- package/openapi.json +12592 -0
- package/package.json +118 -0
- package/scripts/ensure-executable.js +38 -0
- package/scripts/package.json +3 -0
- package/scripts/update-version.js +204 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Logger = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
/**
|
|
42
|
+
* Format a timestamp for logging
|
|
43
|
+
* @returns Formatted timestamp [HH:MM:SS]
|
|
44
|
+
*/
|
|
45
|
+
function getTimestamp() {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
return `[${now.toISOString().split('T')[1].split('.')[0]}]`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Safely convert object to string with size limits
|
|
51
|
+
* @param obj Object to stringify
|
|
52
|
+
* @param maxLength Maximum length of the resulting string
|
|
53
|
+
* @returns Safely stringified object
|
|
54
|
+
*/
|
|
55
|
+
function safeStringify(obj, maxLength = 1000) {
|
|
56
|
+
try {
|
|
57
|
+
const str = JSON.stringify(obj);
|
|
58
|
+
if (str.length <= maxLength) {
|
|
59
|
+
return str;
|
|
60
|
+
}
|
|
61
|
+
return `${str.substring(0, maxLength)}... (truncated, ${str.length} chars total)`;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return '[Object cannot be stringified]';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract essential values from larger objects for logging
|
|
69
|
+
* @param obj The object to extract values from
|
|
70
|
+
* @param keys Keys to extract (if available)
|
|
71
|
+
* @returns Object containing only the specified keys
|
|
72
|
+
*/
|
|
73
|
+
function extractEssentialValues(obj, keys) {
|
|
74
|
+
const result = {};
|
|
75
|
+
keys.forEach((key) => {
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
77
|
+
result[key] = obj[key];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Format source path consistently using the standardized format:
|
|
84
|
+
* [module/file.ts@function] or [module/file.ts]
|
|
85
|
+
*
|
|
86
|
+
* @param filePath File path (with or without src/ prefix)
|
|
87
|
+
* @param functionName Optional function name
|
|
88
|
+
* @returns Formatted source path according to standard pattern
|
|
89
|
+
*/
|
|
90
|
+
function formatSourcePath(filePath, functionName) {
|
|
91
|
+
// Always strip 'src/' prefix for consistency
|
|
92
|
+
const normalizedPath = filePath.replace(/^src\//, '');
|
|
93
|
+
return functionName
|
|
94
|
+
? `[${normalizedPath}@${functionName}]`
|
|
95
|
+
: `[${normalizedPath}]`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if debug logging is enabled for a specific module
|
|
99
|
+
*
|
|
100
|
+
* This function parses the DEBUG environment variable to determine if a specific
|
|
101
|
+
* module should have debug logging enabled. The DEBUG variable can be:
|
|
102
|
+
* - 'true' or '1': Enable all debug logging
|
|
103
|
+
* - Comma-separated list of modules: Enable debug only for those modules
|
|
104
|
+
* - Module patterns with wildcards: e.g., 'controllers/*' enables all controllers
|
|
105
|
+
*
|
|
106
|
+
* Examples:
|
|
107
|
+
* - DEBUG=true
|
|
108
|
+
* - DEBUG=controllers/*,services/aws.sso.auth.service.ts
|
|
109
|
+
* - DEBUG=transport,utils/formatter*
|
|
110
|
+
*
|
|
111
|
+
* @param modulePath The module path to check against DEBUG patterns
|
|
112
|
+
* @returns true if debug is enabled for this module, false otherwise
|
|
113
|
+
*/
|
|
114
|
+
function isDebugEnabledForModule(modulePath) {
|
|
115
|
+
const debugEnv = process.env.DEBUG;
|
|
116
|
+
if (!debugEnv) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// If debug is set to true or 1, enable all debug logging
|
|
120
|
+
if (debugEnv === 'true' || debugEnv === '1') {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
// Parse comma-separated debug patterns
|
|
124
|
+
const debugPatterns = debugEnv.split(',').map((p) => p.trim());
|
|
125
|
+
// Check if the module matches any pattern
|
|
126
|
+
return debugPatterns.some((pattern) => {
|
|
127
|
+
// Convert glob-like patterns to regex
|
|
128
|
+
// * matches anything within a path segment
|
|
129
|
+
// ** matches across path segments
|
|
130
|
+
const regexPattern = pattern
|
|
131
|
+
.replace(/\*/g, '.*') // Convert * to regex .*
|
|
132
|
+
.replace(/\?/g, '.'); // Convert ? to regex .
|
|
133
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
134
|
+
return (regex.test(modulePath) ||
|
|
135
|
+
// Check for pattern matches without the 'src/' prefix
|
|
136
|
+
regex.test(modulePath.replace(/^src\//, '')));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Generate a unique session ID for this process
|
|
140
|
+
const SESSION_ID = crypto.randomUUID();
|
|
141
|
+
// Get the package name from environment variables or default to 'mcp-server'
|
|
142
|
+
const getPkgName = () => {
|
|
143
|
+
try {
|
|
144
|
+
// Try to get it from package.json first if available
|
|
145
|
+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
146
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
147
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
148
|
+
if (packageJson.name) {
|
|
149
|
+
// Extract the last part of the name if it's scoped
|
|
150
|
+
const match = packageJson.name.match(/(@[\w-]+\/)?(.+)/);
|
|
151
|
+
return match ? match[2] : packageJson.name;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Silently fail and use default
|
|
157
|
+
}
|
|
158
|
+
// Fallback to environment variable or default
|
|
159
|
+
return process.env.PACKAGE_NAME || 'mcp-server';
|
|
160
|
+
};
|
|
161
|
+
// MCP logs directory setup
|
|
162
|
+
const HOME_DIR = os.homedir();
|
|
163
|
+
const MCP_DATA_DIR = path.join(HOME_DIR, '.mcp', 'data');
|
|
164
|
+
const CLI_NAME = getPkgName();
|
|
165
|
+
// Ensure the MCP data directory exists
|
|
166
|
+
if (!fs.existsSync(MCP_DATA_DIR)) {
|
|
167
|
+
fs.mkdirSync(MCP_DATA_DIR, { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
// Create the log file path with session ID
|
|
170
|
+
const LOG_FILENAME = `${CLI_NAME}.${SESSION_ID}.log`;
|
|
171
|
+
const LOG_FILEPATH = path.join(MCP_DATA_DIR, LOG_FILENAME);
|
|
172
|
+
// Write initial log header
|
|
173
|
+
fs.writeFileSync(LOG_FILEPATH, `# ${CLI_NAME} Log Session\n` +
|
|
174
|
+
`Session ID: ${SESSION_ID}\n` +
|
|
175
|
+
`Started: ${new Date().toISOString()}\n` +
|
|
176
|
+
`Process ID: ${process.pid}\n` +
|
|
177
|
+
`Working Directory: ${process.cwd()}\n` +
|
|
178
|
+
`Command: ${process.argv.join(' ')}\n\n` +
|
|
179
|
+
`## Log Entries\n\n`, 'utf8');
|
|
180
|
+
// Logger singleton to track initialization
|
|
181
|
+
let isLoggerInitialized = false;
|
|
182
|
+
/**
|
|
183
|
+
* Logger class for consistent logging across the application.
|
|
184
|
+
*
|
|
185
|
+
* RECOMMENDED USAGE:
|
|
186
|
+
*
|
|
187
|
+
* 1. Create a file-level logger using the static forContext method:
|
|
188
|
+
* ```
|
|
189
|
+
* const logger = Logger.forContext('controllers/myController.ts');
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* 2. For method-specific logging, create a method logger:
|
|
193
|
+
* ```
|
|
194
|
+
* const methodLogger = Logger.forContext('controllers/myController.ts', 'myMethod');
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* 3. Avoid using raw string prefixes in log messages. Instead, use contextualized loggers.
|
|
198
|
+
*
|
|
199
|
+
* 4. For debugging objects, use the debugResponse method to log only essential properties.
|
|
200
|
+
*
|
|
201
|
+
* 5. Set DEBUG environment variable to control which modules show debug logs:
|
|
202
|
+
* - DEBUG=true (enable all debug logs)
|
|
203
|
+
* - DEBUG=controllers/*,services/* (enable for specific module groups)
|
|
204
|
+
* - DEBUG=transport,utils/formatter* (enable specific modules, supports wildcards)
|
|
205
|
+
*/
|
|
206
|
+
class Logger {
|
|
207
|
+
constructor(context, modulePath = '') {
|
|
208
|
+
this.context = context;
|
|
209
|
+
this.modulePath = modulePath;
|
|
210
|
+
// Log initialization message only once
|
|
211
|
+
if (!isLoggerInitialized) {
|
|
212
|
+
this.info(`Logger initialized with session ID: ${Logger.sessionId}`);
|
|
213
|
+
this.info(`Logs will be saved to: ${Logger.logFilePath}`);
|
|
214
|
+
isLoggerInitialized = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a contextualized logger for a specific file or component.
|
|
219
|
+
* This is the preferred method for creating loggers.
|
|
220
|
+
*
|
|
221
|
+
* @param filePath The file path (e.g., 'controllers/aws.sso.auth.controller.ts')
|
|
222
|
+
* @param functionName Optional function name for more specific context
|
|
223
|
+
* @returns A new Logger instance with the specified context
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // File-level logger
|
|
227
|
+
* const logger = Logger.forContext('controllers/myController.ts');
|
|
228
|
+
*
|
|
229
|
+
* // Method-level logger
|
|
230
|
+
* const methodLogger = Logger.forContext('controllers/myController.ts', 'myMethod');
|
|
231
|
+
*/
|
|
232
|
+
static forContext(filePath, functionName) {
|
|
233
|
+
return new Logger(formatSourcePath(filePath, functionName), filePath);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Create a method level logger from a context logger
|
|
237
|
+
* @param method Method name
|
|
238
|
+
* @returns A new logger with the method context
|
|
239
|
+
*/
|
|
240
|
+
forMethod(method) {
|
|
241
|
+
return Logger.forContext(this.modulePath, method);
|
|
242
|
+
}
|
|
243
|
+
_formatMessage(message) {
|
|
244
|
+
return this.context ? `${this.context} ${message}` : message;
|
|
245
|
+
}
|
|
246
|
+
_formatArgs(args) {
|
|
247
|
+
// If the first argument is an object and not an Error, safely stringify it
|
|
248
|
+
if (args.length > 0 &&
|
|
249
|
+
typeof args[0] === 'object' &&
|
|
250
|
+
args[0] !== null &&
|
|
251
|
+
!(args[0] instanceof Error)) {
|
|
252
|
+
args[0] = safeStringify(args[0]);
|
|
253
|
+
}
|
|
254
|
+
return args;
|
|
255
|
+
}
|
|
256
|
+
_log(level, message, ...args) {
|
|
257
|
+
// Skip debug messages if not enabled for this module
|
|
258
|
+
if (level === 'debug' && !isDebugEnabledForModule(this.modulePath)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const timestamp = getTimestamp();
|
|
262
|
+
const prefix = `${timestamp} [${level.toUpperCase()}]`;
|
|
263
|
+
let logMessage = `${prefix} ${this._formatMessage(message)}`;
|
|
264
|
+
const formattedArgs = this._formatArgs(args);
|
|
265
|
+
if (formattedArgs.length > 0) {
|
|
266
|
+
// Handle errors specifically
|
|
267
|
+
if (formattedArgs[0] instanceof Error) {
|
|
268
|
+
const error = formattedArgs[0];
|
|
269
|
+
logMessage += ` Error: ${error.message}`;
|
|
270
|
+
if (error.stack) {
|
|
271
|
+
logMessage += `\n${error.stack}`;
|
|
272
|
+
}
|
|
273
|
+
// If there are more args, add them after the error
|
|
274
|
+
if (formattedArgs.length > 1) {
|
|
275
|
+
logMessage += ` ${formattedArgs
|
|
276
|
+
.slice(1)
|
|
277
|
+
.map((arg) => typeof arg === 'string' ? arg : safeStringify(arg))
|
|
278
|
+
.join(' ')}`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
logMessage += ` ${formattedArgs
|
|
283
|
+
.map((arg) => typeof arg === 'string' ? arg : safeStringify(arg))
|
|
284
|
+
.join(' ')}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Write to log file
|
|
288
|
+
try {
|
|
289
|
+
fs.appendFileSync(Logger.logFilePath, `${logMessage}\n`, 'utf8');
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
// If we can't write to the log file, log the error to console
|
|
293
|
+
console.error(`Failed to write to log file: ${err}`);
|
|
294
|
+
}
|
|
295
|
+
// Only output to console if not in test environment or if debug is enabled
|
|
296
|
+
if (process.env.NODE_ENV !== 'test' || process.env.DEBUG === 'true') {
|
|
297
|
+
if (process.env.NODE_ENV === 'test') {
|
|
298
|
+
console[level](logMessage);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
console.error(logMessage);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
info(message, ...args) {
|
|
306
|
+
this._log('info', message, ...args);
|
|
307
|
+
}
|
|
308
|
+
warn(message, ...args) {
|
|
309
|
+
this._log('warn', message, ...args);
|
|
310
|
+
}
|
|
311
|
+
error(message, ...args) {
|
|
312
|
+
this._log('error', message, ...args);
|
|
313
|
+
}
|
|
314
|
+
debug(message, ...args) {
|
|
315
|
+
this._log('debug', message, ...args);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Log essential information about an API response
|
|
319
|
+
* @param message Log message
|
|
320
|
+
* @param response API response object
|
|
321
|
+
* @param essentialKeys Keys to extract from the response
|
|
322
|
+
*/
|
|
323
|
+
debugResponse(message, response, essentialKeys) {
|
|
324
|
+
const essentialInfo = extractEssentialValues(response, essentialKeys);
|
|
325
|
+
this.debug(message, essentialInfo);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get the current session ID
|
|
329
|
+
* @returns The UUID for the current logging session
|
|
330
|
+
*/
|
|
331
|
+
static getSessionId() {
|
|
332
|
+
return Logger.sessionId;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get the current log file path
|
|
336
|
+
* @returns The path to the current log file
|
|
337
|
+
*/
|
|
338
|
+
static getLogFilePath() {
|
|
339
|
+
return Logger.logFilePath;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
exports.Logger = Logger;
|
|
343
|
+
Logger.sessionId = SESSION_ID;
|
|
344
|
+
Logger.logFilePath = LOG_FILEPATH;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert data to TOON format with JSON fallback.
|
|
3
|
+
*
|
|
4
|
+
* TOON (Token-Oriented Object Notation) is 30-60% more token-efficient than JSON
|
|
5
|
+
* for tabular data, making it ideal for LLM responses.
|
|
6
|
+
*
|
|
7
|
+
* @param data - The data to convert
|
|
8
|
+
* @param jsonFallback - JSON string to return if TOON encoding fails
|
|
9
|
+
* @returns TOON formatted string, or JSON fallback on failure
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const json = JSON.stringify(data, null, 2);
|
|
13
|
+
* const output = await toToonOrJson(data, json);
|
|
14
|
+
*/
|
|
15
|
+
export declare function toToonOrJson(data: unknown, jsonFallback: string): Promise<string>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toToonOrJson = toToonOrJson;
|
|
4
|
+
const logger_util_js_1 = require("./logger.util.js");
|
|
5
|
+
const logger = logger_util_js_1.Logger.forContext('utils/toon.util.ts');
|
|
6
|
+
/**
|
|
7
|
+
* Cached TOON encoder to avoid repeated dynamic imports
|
|
8
|
+
*/
|
|
9
|
+
let toonEncode = null;
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically loads the TOON encoder module.
|
|
12
|
+
* Uses dynamic import because @toon-format/toon is an ESM-only package.
|
|
13
|
+
*
|
|
14
|
+
* @returns Promise resolving to the encode function or null if loading fails
|
|
15
|
+
*/
|
|
16
|
+
async function loadToonEncoder() {
|
|
17
|
+
const methodLogger = logger.forMethod('loadToonEncoder');
|
|
18
|
+
// Return cached encoder if available
|
|
19
|
+
if (toonEncode) {
|
|
20
|
+
return toonEncode;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
methodLogger.debug('Loading TOON encoder module...');
|
|
24
|
+
// Dynamic import for ESM module in CommonJS project
|
|
25
|
+
const toon = await import('@toon-format/toon');
|
|
26
|
+
toonEncode = toon.encode;
|
|
27
|
+
methodLogger.debug('TOON encoder loaded successfully');
|
|
28
|
+
return toonEncode;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
methodLogger.error('Failed to load TOON encoder', error);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convert data to TOON format with JSON fallback.
|
|
37
|
+
*
|
|
38
|
+
* TOON (Token-Oriented Object Notation) is 30-60% more token-efficient than JSON
|
|
39
|
+
* for tabular data, making it ideal for LLM responses.
|
|
40
|
+
*
|
|
41
|
+
* @param data - The data to convert
|
|
42
|
+
* @param jsonFallback - JSON string to return if TOON encoding fails
|
|
43
|
+
* @returns TOON formatted string, or JSON fallback on failure
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const json = JSON.stringify(data, null, 2);
|
|
47
|
+
* const output = await toToonOrJson(data, json);
|
|
48
|
+
*/
|
|
49
|
+
async function toToonOrJson(data, jsonFallback) {
|
|
50
|
+
const methodLogger = logger.forMethod('toToonOrJson');
|
|
51
|
+
try {
|
|
52
|
+
const encode = await loadToonEncoder();
|
|
53
|
+
if (!encode) {
|
|
54
|
+
methodLogger.debug('TOON encoder not available, using JSON fallback');
|
|
55
|
+
return jsonFallback;
|
|
56
|
+
}
|
|
57
|
+
const result = encode(data, { indent: 2 });
|
|
58
|
+
methodLogger.debug('Successfully converted to TOON format');
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
methodLogger.error('TOON encoding failed, using JSON fallback', error);
|
|
63
|
+
return jsonFallback;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for IP API credentials.
|
|
3
|
+
* Note: API token is optional for the free tier.
|
|
4
|
+
*/
|
|
5
|
+
export interface IpApiCredentials {
|
|
6
|
+
apiToken?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Interface for HTTP request options
|
|
10
|
+
*/
|
|
11
|
+
export interface RequestOptions {
|
|
12
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Retrieves IP API credentials from configuration.
|
|
18
|
+
* Specifically checks for IPAPI_API_TOKEN.
|
|
19
|
+
* @returns IpApiCredentials object containing the API token if found.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getIpApiCredentials(): IpApiCredentials;
|
|
22
|
+
/**
|
|
23
|
+
* Fetches data specifically from the ip-api.com endpoint.
|
|
24
|
+
* Handles URL construction, authentication (if token provided), and query parameters.
|
|
25
|
+
* Relies on the generic fetchApi function for the actual HTTP request.
|
|
26
|
+
*
|
|
27
|
+
* @param path The specific IP address or path component (e.g., "8.8.8.8"). Empty string for current IP.
|
|
28
|
+
* @param options Additional options like HTTP method, headers, body, and ip-api specific params.
|
|
29
|
+
* @param options.useHttps - Use HTTPS (requires paid plan for ip-api.com). Defaults to false.
|
|
30
|
+
* @param options.fields - Specific fields to request from ip-api.com.
|
|
31
|
+
* @param options.lang - Language code for response data.
|
|
32
|
+
* @returns The response data parsed as type T.
|
|
33
|
+
* @throws {McpError} If the request fails, including network errors, API errors, or parsing issues.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fetchIpApi<T>(path: string, options?: RequestOptions & {
|
|
36
|
+
useHttps?: boolean;
|
|
37
|
+
fields?: string[];
|
|
38
|
+
lang?: string;
|
|
39
|
+
}): Promise<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Generic and reusable function to fetch data from any API endpoint.
|
|
42
|
+
* Handles standard HTTP request setup, response checking, basic error handling, and logging.
|
|
43
|
+
*
|
|
44
|
+
* @param url The full URL to fetch data from.
|
|
45
|
+
* @param options Request options including method, headers, and body.
|
|
46
|
+
* @returns The response data parsed as type T.
|
|
47
|
+
* @throws {McpError} If the request fails, including network errors, non-OK HTTP status, or JSON parsing issues.
|
|
48
|
+
*/
|
|
49
|
+
export declare function fetchApi<T>(url: string, options?: RequestOptions): Promise<T>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getIpApiCredentials = getIpApiCredentials;
|
|
4
|
+
exports.fetchIpApi = fetchIpApi;
|
|
5
|
+
exports.fetchApi = fetchApi;
|
|
6
|
+
const logger_util_js_1 = require("./logger.util.js");
|
|
7
|
+
const config_util_js_1 = require("./config.util.js");
|
|
8
|
+
const error_util_js_1 = require("./error.util.js");
|
|
9
|
+
// Create a contextualized logger for this file
|
|
10
|
+
const transportLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts');
|
|
11
|
+
// Log transport utility initialization
|
|
12
|
+
transportLogger.debug('Transport utility initialized');
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves IP API credentials from configuration.
|
|
15
|
+
* Specifically checks for IPAPI_API_TOKEN.
|
|
16
|
+
* @returns IpApiCredentials object containing the API token if found.
|
|
17
|
+
*/
|
|
18
|
+
function getIpApiCredentials() {
|
|
19
|
+
const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'getIpApiCredentials');
|
|
20
|
+
const apiToken = config_util_js_1.config.get('IPAPI_API_TOKEN');
|
|
21
|
+
if (!apiToken) {
|
|
22
|
+
methodLogger.debug('No IP API token found (IPAPI_API_TOKEN). Using free tier.');
|
|
23
|
+
return {}; // Return empty object if no token
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
methodLogger.debug('Using IP API token from configuration.');
|
|
27
|
+
return { apiToken };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fetches data specifically from the ip-api.com endpoint.
|
|
32
|
+
* Handles URL construction, authentication (if token provided), and query parameters.
|
|
33
|
+
* Relies on the generic fetchApi function for the actual HTTP request.
|
|
34
|
+
*
|
|
35
|
+
* @param path The specific IP address or path component (e.g., "8.8.8.8"). Empty string for current IP.
|
|
36
|
+
* @param options Additional options like HTTP method, headers, body, and ip-api specific params.
|
|
37
|
+
* @param options.useHttps - Use HTTPS (requires paid plan for ip-api.com). Defaults to false.
|
|
38
|
+
* @param options.fields - Specific fields to request from ip-api.com.
|
|
39
|
+
* @param options.lang - Language code for response data.
|
|
40
|
+
* @returns The response data parsed as type T.
|
|
41
|
+
* @throws {McpError} If the request fails, including network errors, API errors, or parsing issues.
|
|
42
|
+
*/
|
|
43
|
+
async function fetchIpApi(path, options = {}) {
|
|
44
|
+
const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'fetchIpApi');
|
|
45
|
+
// Get credentials (token might be undefined)
|
|
46
|
+
const credentials = getIpApiCredentials();
|
|
47
|
+
// Determine protocol based on options
|
|
48
|
+
const protocol = options.useHttps ? 'https' : 'http';
|
|
49
|
+
const baseUrl = `${protocol}://ip-api.com/json`;
|
|
50
|
+
// Format path for URL
|
|
51
|
+
const normalizedPath = path ? `/${path}` : '';
|
|
52
|
+
let url = `${baseUrl}${normalizedPath}`;
|
|
53
|
+
// Build query parameters
|
|
54
|
+
const queryParams = new URLSearchParams();
|
|
55
|
+
// Add API token if present
|
|
56
|
+
if (credentials.apiToken) {
|
|
57
|
+
queryParams.set('key', credentials.apiToken);
|
|
58
|
+
methodLogger.debug('API token added to query parameters.');
|
|
59
|
+
}
|
|
60
|
+
// Add fields parameter
|
|
61
|
+
if (options.fields?.length) {
|
|
62
|
+
queryParams.set('fields', options.fields.join(','));
|
|
63
|
+
methodLogger.debug(`Requesting fields: ${options.fields.join(',')}`);
|
|
64
|
+
}
|
|
65
|
+
// Add language parameter
|
|
66
|
+
if (options.lang) {
|
|
67
|
+
queryParams.set('lang', options.lang);
|
|
68
|
+
methodLogger.debug(`Requesting language: ${options.lang}`);
|
|
69
|
+
}
|
|
70
|
+
// Append query string if needed
|
|
71
|
+
const queryString = queryParams.toString();
|
|
72
|
+
if (queryString) {
|
|
73
|
+
url += `?${queryString}`;
|
|
74
|
+
}
|
|
75
|
+
methodLogger.debug(`Constructed URL: ${url}`);
|
|
76
|
+
// Delegate the actual fetch call to the generic fetchApi
|
|
77
|
+
return fetchApi(url, {
|
|
78
|
+
method: options.method,
|
|
79
|
+
headers: options.headers,
|
|
80
|
+
body: options.body,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Generic and reusable function to fetch data from any API endpoint.
|
|
85
|
+
* Handles standard HTTP request setup, response checking, basic error handling, and logging.
|
|
86
|
+
*
|
|
87
|
+
* @param url The full URL to fetch data from.
|
|
88
|
+
* @param options Request options including method, headers, and body.
|
|
89
|
+
* @returns The response data parsed as type T.
|
|
90
|
+
* @throws {McpError} If the request fails, including network errors, non-OK HTTP status, or JSON parsing issues.
|
|
91
|
+
*/
|
|
92
|
+
async function fetchApi(url, options = {}) {
|
|
93
|
+
const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'fetchApi');
|
|
94
|
+
// Prepare standard request options
|
|
95
|
+
const requestOptions = {
|
|
96
|
+
method: options.method || 'GET',
|
|
97
|
+
headers: {
|
|
98
|
+
// Standard headers, allow overrides via options.headers
|
|
99
|
+
'Content-Type': 'application/json',
|
|
100
|
+
Accept: 'application/json',
|
|
101
|
+
...options.headers,
|
|
102
|
+
},
|
|
103
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
104
|
+
};
|
|
105
|
+
methodLogger.debug(`Executing API call: ${requestOptions.method} ${url}`);
|
|
106
|
+
const startTime = performance.now(); // Track performance
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url, requestOptions);
|
|
109
|
+
const endTime = performance.now();
|
|
110
|
+
const duration = (endTime - startTime).toFixed(2);
|
|
111
|
+
methodLogger.debug(`API call completed in ${duration}ms with status: ${response.status} ${response.statusText}`, { url, status: response.status });
|
|
112
|
+
// Check if the response status is OK (2xx)
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const errorText = await response.text(); // Get error body for context
|
|
115
|
+
methodLogger.error(`API error response (${response.status}):`, errorText);
|
|
116
|
+
// Classify standard HTTP errors
|
|
117
|
+
if (response.status === 401) {
|
|
118
|
+
// Use createAuthInvalidError for consistency, even if ip-api uses keys
|
|
119
|
+
throw (0, error_util_js_1.createAuthInvalidError)('Authentication failed. Check API token if required.');
|
|
120
|
+
}
|
|
121
|
+
else if (response.status === 403) {
|
|
122
|
+
// Use createAuthInvalidError or a more specific permission error if needed
|
|
123
|
+
throw (0, error_util_js_1.createAuthInvalidError)('Permission denied for the requested resource.');
|
|
124
|
+
}
|
|
125
|
+
else if (response.status === 404) {
|
|
126
|
+
throw (0, error_util_js_1.createApiError)('Resource not found at the specified URL.', response.status, errorText);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Generic API error for other non-2xx statuses
|
|
130
|
+
throw (0, error_util_js_1.createApiError)(`API request failed with status ${response.status}: ${response.statusText}`, response.status, errorText);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Attempt to parse the response body as JSON
|
|
134
|
+
try {
|
|
135
|
+
const responseData = await response.json();
|
|
136
|
+
methodLogger.debug('Response body successfully parsed as JSON.');
|
|
137
|
+
return responseData;
|
|
138
|
+
}
|
|
139
|
+
catch (parseError) {
|
|
140
|
+
methodLogger.error('Failed to parse API response JSON:', parseError);
|
|
141
|
+
// Throw a specific error for JSON parsing failure
|
|
142
|
+
throw (0, error_util_js_1.createApiError)(`Failed to parse API response JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`, response.status, // Include original status for context
|
|
143
|
+
parseError);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const endTime = performance.now();
|
|
148
|
+
const duration = (endTime - startTime).toFixed(2);
|
|
149
|
+
methodLogger.error(`API call failed after ${duration}ms for ${url}:`, error);
|
|
150
|
+
// Rethrow if it's already an McpError (e.g., from status checks or parsing)
|
|
151
|
+
if (error instanceof error_util_js_1.McpError) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
// Handle potential network errors (TypeError in fetch)
|
|
155
|
+
if (error instanceof TypeError) {
|
|
156
|
+
throw (0, error_util_js_1.createApiError)(`Network error during API call: ${error.message}`, undefined, // No specific HTTP status for network errors
|
|
157
|
+
error);
|
|
158
|
+
}
|
|
159
|
+
// Wrap any other unexpected errors
|
|
160
|
+
throw (0, error_util_js_1.createUnexpectedError)(`Unexpected error during API call: ${error instanceof Error ? error.message : String(error)}`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
import prettierPlugin from 'eslint-plugin-prettier';
|
|
4
|
+
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
5
|
+
|
|
6
|
+
export default tseslint.config(
|
|
7
|
+
{
|
|
8
|
+
ignores: ['node_modules/**', 'dist/**', 'examples/**'],
|
|
9
|
+
},
|
|
10
|
+
eslint.configs.recommended,
|
|
11
|
+
...tseslint.configs.recommended,
|
|
12
|
+
{
|
|
13
|
+
plugins: {
|
|
14
|
+
prettier: prettierPlugin,
|
|
15
|
+
},
|
|
16
|
+
rules: {
|
|
17
|
+
'prettier/prettier': 'error',
|
|
18
|
+
indent: ['error', 'tab', { SwitchCase: 1 }],
|
|
19
|
+
'@typescript-eslint/no-unused-vars': [
|
|
20
|
+
'error',
|
|
21
|
+
{ argsIgnorePattern: '^_' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
languageOptions: {
|
|
25
|
+
parserOptions: {
|
|
26
|
+
ecmaVersion: 'latest',
|
|
27
|
+
sourceType: 'module',
|
|
28
|
+
},
|
|
29
|
+
globals: {
|
|
30
|
+
node: 'readonly',
|
|
31
|
+
jest: 'readonly',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
// Special rules for test files
|
|
36
|
+
{
|
|
37
|
+
files: ['**/*.test.ts'],
|
|
38
|
+
rules: {
|
|
39
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
40
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
41
|
+
'@typescript-eslint/no-unsafe-function-type': 'off',
|
|
42
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
eslintConfigPrettier,
|
|
46
|
+
);
|