snow-flow 3.5.10 ā 3.5.12
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/mcp/servicenow-local-development-mcp.js +91 -52
- package/dist/utils/artifact-local-sync.js +33 -16
- package/dist/utils/mcp-timeout-config.d.ts +39 -0
- package/dist/utils/mcp-timeout-config.js +123 -0
- package/dist/utils/servicenow-client.js +1 -7
- package/dist/utils/smart-field-fetcher.js +18 -14
- package/package.json +2 -2
|
@@ -190,44 +190,56 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
190
190
|
}));
|
|
191
191
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
192
192
|
const { name, arguments: args } = request.params;
|
|
193
|
+
// Add timeout protection for MCP tool execution
|
|
194
|
+
const TOOL_TIMEOUT = 10000; // 10 seconds max per tool call
|
|
193
195
|
try {
|
|
194
196
|
this.logger.info(`š§ Executing tool: ${name}`, args);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
197
|
+
// Create timeout promise
|
|
198
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
199
|
+
setTimeout(() => reject(new Error(`Tool ${name} timed out after ${TOOL_TIMEOUT / 1000}s`)), TOOL_TIMEOUT);
|
|
200
|
+
});
|
|
201
|
+
// Execute tool with timeout protection
|
|
202
|
+
const toolPromise = (async () => {
|
|
203
|
+
let result;
|
|
204
|
+
switch (name) {
|
|
205
|
+
case 'snow_pull_artifact':
|
|
206
|
+
result = await this.pullArtifact(args);
|
|
207
|
+
break;
|
|
208
|
+
case 'snow_push_artifact':
|
|
209
|
+
result = await this.pushArtifact(args);
|
|
210
|
+
break;
|
|
211
|
+
case 'snow_validate_artifact_coherence':
|
|
212
|
+
result = await this.validateArtifactCoherence(args);
|
|
213
|
+
break;
|
|
214
|
+
case 'snow_sync_status':
|
|
215
|
+
result = await this.getSyncStatus(args);
|
|
216
|
+
break;
|
|
217
|
+
case 'snow_list_supported_artifacts':
|
|
218
|
+
result = await this.listSupportedArtifacts(args);
|
|
219
|
+
break;
|
|
220
|
+
case 'snow_sync_cleanup':
|
|
221
|
+
result = await this.syncCleanup(args);
|
|
222
|
+
break;
|
|
223
|
+
case 'snow_convert_to_es5':
|
|
224
|
+
result = await this.convertToES5(args);
|
|
225
|
+
break;
|
|
226
|
+
case 'snow_debug_widget_fetch':
|
|
227
|
+
result = await this.debugWidgetFetch(args);
|
|
228
|
+
break;
|
|
229
|
+
// Legacy compatibility
|
|
230
|
+
case 'snow_pull_widget':
|
|
231
|
+
result = await this.pullWidget(args);
|
|
232
|
+
break;
|
|
233
|
+
case 'snow_push_widget':
|
|
234
|
+
result = await this.pushWidget(args);
|
|
235
|
+
break;
|
|
236
|
+
default:
|
|
237
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
})();
|
|
241
|
+
// Race between tool execution and timeout
|
|
242
|
+
const result = await Promise.race([toolPromise, timeoutPromise]);
|
|
231
243
|
this.logger.info(`ā
Tool ${name} completed successfully`);
|
|
232
244
|
return {
|
|
233
245
|
content: result.content
|
|
@@ -249,16 +261,25 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
249
261
|
}
|
|
250
262
|
async pullArtifact(args) {
|
|
251
263
|
const { sys_id, table } = args;
|
|
264
|
+
// Add timeout for pull operations
|
|
265
|
+
const PULL_TIMEOUT = 15000; // 15 seconds for pull operations
|
|
252
266
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
267
|
+
const pullPromise = (async () => {
|
|
268
|
+
let artifact;
|
|
269
|
+
if (table) {
|
|
270
|
+
// Use specified table
|
|
271
|
+
artifact = await this.syncManager.pullArtifact(table, sys_id);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Auto-detect table
|
|
275
|
+
artifact = await this.syncManager.pullArtifactBySysId(sys_id);
|
|
276
|
+
}
|
|
277
|
+
return artifact;
|
|
278
|
+
})();
|
|
279
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
280
|
+
setTimeout(() => reject(new Error(`Pull operation timed out after ${PULL_TIMEOUT / 1000}s`)), PULL_TIMEOUT);
|
|
281
|
+
});
|
|
282
|
+
const artifact = await Promise.race([pullPromise, timeoutPromise]);
|
|
262
283
|
return {
|
|
263
284
|
content: [
|
|
264
285
|
{
|
|
@@ -431,8 +452,14 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
431
452
|
}
|
|
432
453
|
async debugWidgetFetch(args) {
|
|
433
454
|
const { sys_id } = args;
|
|
455
|
+
// Debug operations get extra time
|
|
456
|
+
const DEBUG_TIMEOUT = 20000; // 20 seconds for debug operations
|
|
434
457
|
try {
|
|
435
|
-
const
|
|
458
|
+
const debugPromise = this.syncManager['smartFetcher'].debugFetchWidget(sys_id);
|
|
459
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
460
|
+
setTimeout(() => reject(new Error(`Debug operation timed out after ${DEBUG_TIMEOUT / 1000}s`)), DEBUG_TIMEOUT);
|
|
461
|
+
});
|
|
462
|
+
const debugResults = await Promise.race([debugPromise, timeoutPromise]);
|
|
436
463
|
let summaryText = `š Debug Results for Widget ${sys_id}\n\n`;
|
|
437
464
|
// Check which methods worked
|
|
438
465
|
const methods = ['searchRecords', 'getRecord', 'searchRecordsWithFields'];
|
|
@@ -490,13 +517,25 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
490
517
|
}
|
|
491
518
|
}
|
|
492
519
|
exports.ServiceNowLocalDevelopmentMCP = ServiceNowLocalDevelopmentMCP;
|
|
493
|
-
// Start the server
|
|
520
|
+
// Start the server with timeout protection
|
|
494
521
|
async function main() {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
522
|
+
try {
|
|
523
|
+
const mcpServer = new ServiceNowLocalDevelopmentMCP();
|
|
524
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
525
|
+
// Add timeout for server initialization
|
|
526
|
+
const INIT_TIMEOUT = 5000; // 5 seconds to start
|
|
527
|
+
const connectPromise = mcpServer.server.connect(transport);
|
|
528
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
529
|
+
setTimeout(() => reject(new Error('Server initialization timeout')), INIT_TIMEOUT);
|
|
530
|
+
});
|
|
531
|
+
await Promise.race([connectPromise, timeoutPromise]);
|
|
532
|
+
console.error('š ServiceNow Local Development MCP Server started');
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error('ā Server failed to start within timeout:', error);
|
|
536
|
+
// Still allow server to run even if initial connection takes time
|
|
537
|
+
console.error('ā³ Server may still be initializing...');
|
|
538
|
+
}
|
|
500
539
|
}
|
|
501
540
|
if (require.main === module) {
|
|
502
541
|
main().catch((error) => {
|
|
@@ -46,6 +46,7 @@ exports.ArtifactLocalSync = void 0;
|
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const smart_field_fetcher_js_1 = require("./smart-field-fetcher.js");
|
|
49
|
+
const mcp_timeout_config_js_1 = require("./mcp-timeout-config.js");
|
|
49
50
|
const artifact_registry_1 = require("./artifact-sync/artifact-registry");
|
|
50
51
|
class ArtifactLocalSync {
|
|
51
52
|
constructor(client, customBaseDir) {
|
|
@@ -126,22 +127,34 @@ class ArtifactLocalSync {
|
|
|
126
127
|
throw new Error(`Unsupported artifact type: ${tableName}. Supported types: ${Object.keys(artifact_registry_1.ARTIFACT_REGISTRY).join(', ')}`);
|
|
127
128
|
}
|
|
128
129
|
console.log(`\nš Pulling ${config.displayName} (${sys_id}) to local files...`);
|
|
130
|
+
// Get timeout configuration
|
|
131
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
129
132
|
// Use smart fetcher for known types, otherwise direct query
|
|
130
133
|
let artifactData;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
// Fetch with timeout protection
|
|
135
|
+
try {
|
|
136
|
+
if (tableName === 'sp_widget') {
|
|
137
|
+
artifactData = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.smartFetcher.fetchWidget(sys_id), timeoutConfig.pullToolTimeout, `Fetch widget ${sys_id}`);
|
|
138
|
+
}
|
|
139
|
+
else if (tableName === 'sys_hub_flow') {
|
|
140
|
+
artifactData = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.smartFetcher.fetchFlow(sys_id), timeoutConfig.pullToolTimeout, `Fetch flow ${sys_id}`);
|
|
141
|
+
}
|
|
142
|
+
else if (tableName === 'sys_script') {
|
|
143
|
+
artifactData = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.smartFetcher.fetchBusinessRule(sys_id), timeoutConfig.pullToolTimeout, `Fetch business rule ${sys_id}`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Generic fetch for other types
|
|
147
|
+
const allFields = config.fieldMappings.map(fm => fm.serviceNowField);
|
|
148
|
+
const response = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecords(tableName, `sys_id=${sys_id}`, 1), timeoutConfig.queryToolTimeout, `Query ${tableName} ${sys_id}`);
|
|
149
|
+
artifactData = response.result?.[0];
|
|
150
|
+
}
|
|
139
151
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (error instanceof Error && error.message.includes('timed out')) {
|
|
154
|
+
console.error(`ā±ļø Fetch timed out - ServiceNow may be slow or artifact is very large`);
|
|
155
|
+
console.error(`š” Try using snow_debug_widget_fetch to diagnose the issue`);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
145
158
|
}
|
|
146
159
|
if (!artifactData) {
|
|
147
160
|
throw new Error(`Artifact not found: ${tableName}/${sys_id}`);
|
|
@@ -301,10 +314,11 @@ class ArtifactLocalSync {
|
|
|
301
314
|
console.log(`\nā Continue with deployment anyway? (Issues might cause runtime errors)`);
|
|
302
315
|
// In real implementation, prompt for confirmation
|
|
303
316
|
}
|
|
304
|
-
// Update in ServiceNow
|
|
317
|
+
// Update in ServiceNow with timeout protection
|
|
305
318
|
try {
|
|
306
319
|
console.log(`\nš¤ Updating ${config.displayName} in ServiceNow...`);
|
|
307
|
-
|
|
320
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
321
|
+
await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.updateRecord(config.tableName, sys_id, updates), timeoutConfig.pushToolTimeout, `Update ${config.displayName} ${sys_id}`);
|
|
308
322
|
artifact.syncStatus = 'synced';
|
|
309
323
|
artifact.lastSyncedAt = new Date();
|
|
310
324
|
console.log(`ā
${config.displayName} successfully updated in ServiceNow!`);
|
|
@@ -575,9 +589,12 @@ snow-flow sync status ${widget.sys_id}
|
|
|
575
589
|
async pullArtifactBySysId(sys_id) {
|
|
576
590
|
// Try to detect table by querying common tables
|
|
577
591
|
const tables = Object.keys(artifact_registry_1.ARTIFACT_REGISTRY);
|
|
592
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
578
593
|
for (const table of tables) {
|
|
579
594
|
try {
|
|
580
|
-
|
|
595
|
+
// Quick query with short timeout
|
|
596
|
+
const response = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecords(table, `sys_id=${sys_id}`, 1), 3000, // 3 second timeout for detection
|
|
597
|
+
`Detect table for ${sys_id}`);
|
|
581
598
|
if (response.result?.[0]) {
|
|
582
599
|
console.log(`š Found artifact in table: ${table}`);
|
|
583
600
|
return this.pullArtifact(table, sys_id);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Timeout Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides fast, reliable timeout settings for MCP server operations
|
|
5
|
+
* to prevent Claude from timing out during API calls.
|
|
6
|
+
*/
|
|
7
|
+
export interface MCPTimeoutConfig {
|
|
8
|
+
serverInitTimeout: number;
|
|
9
|
+
defaultToolTimeout: number;
|
|
10
|
+
queryToolTimeout: number;
|
|
11
|
+
pullToolTimeout: number;
|
|
12
|
+
pushToolTimeout: number;
|
|
13
|
+
debugToolTimeout: number;
|
|
14
|
+
scriptToolTimeout: number;
|
|
15
|
+
apiCallTimeout: number;
|
|
16
|
+
apiRetryDelay: number;
|
|
17
|
+
apiMaxRetries: number;
|
|
18
|
+
healthCheckInterval: number;
|
|
19
|
+
healthCheckTimeout: number;
|
|
20
|
+
maxResponseSize: number;
|
|
21
|
+
chunkDelay: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get MCP timeout configuration based on environment
|
|
25
|
+
*/
|
|
26
|
+
export declare function getMCPTimeoutConfig(): MCPTimeoutConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Wrap a promise with timeout protection
|
|
29
|
+
*/
|
|
30
|
+
export declare function withMCPTimeout<T>(promise: Promise<T>, timeoutMs: number, operationName?: string): Promise<T>;
|
|
31
|
+
/**
|
|
32
|
+
* Execute with retry logic and timeout
|
|
33
|
+
*/
|
|
34
|
+
export declare function withMCPRetry<T>(fn: () => Promise<T>, config?: Partial<MCPTimeoutConfig>, operationName?: string): Promise<T>;
|
|
35
|
+
/**
|
|
36
|
+
* Quick health check with timeout
|
|
37
|
+
*/
|
|
38
|
+
export declare function quickHealthCheck(checkFn: () => Promise<boolean>, timeoutMs?: number): Promise<boolean>;
|
|
39
|
+
//# sourceMappingURL=mcp-timeout-config.d.ts.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server Timeout Configuration
|
|
4
|
+
*
|
|
5
|
+
* Provides fast, reliable timeout settings for MCP server operations
|
|
6
|
+
* to prevent Claude from timing out during API calls.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getMCPTimeoutConfig = getMCPTimeoutConfig;
|
|
10
|
+
exports.withMCPTimeout = withMCPTimeout;
|
|
11
|
+
exports.withMCPRetry = withMCPRetry;
|
|
12
|
+
exports.quickHealthCheck = quickHealthCheck;
|
|
13
|
+
/**
|
|
14
|
+
* Get MCP timeout configuration based on environment
|
|
15
|
+
*/
|
|
16
|
+
function getMCPTimeoutConfig() {
|
|
17
|
+
const isDebug = process.env.DEBUG === 'true';
|
|
18
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
19
|
+
// Balanced timeouts - long enough for real work, short enough to prevent hanging
|
|
20
|
+
const config = {
|
|
21
|
+
// Server should start reasonably quickly
|
|
22
|
+
serverInitTimeout: parseInt(process.env.MCP_SERVER_INIT_TIMEOUT || '10000'), // 10s for server init
|
|
23
|
+
// Tool timeouts - balanced for real operations
|
|
24
|
+
defaultToolTimeout: parseInt(process.env.MCP_DEFAULT_TOOL_TIMEOUT || '30000'), // 30s default
|
|
25
|
+
queryToolTimeout: parseInt(process.env.MCP_QUERY_TIMEOUT || '15000'), // 15s for queries
|
|
26
|
+
pullToolTimeout: parseInt(process.env.MCP_PULL_TIMEOUT || '60000'), // 60s for pulls (large widgets)
|
|
27
|
+
pushToolTimeout: parseInt(process.env.MCP_PUSH_TIMEOUT || '45000'), // 45s for pushes
|
|
28
|
+
debugToolTimeout: parseInt(process.env.MCP_DEBUG_TIMEOUT || '30000'), // 30s for debug
|
|
29
|
+
scriptToolTimeout: parseInt(process.env.MCP_SCRIPT_TIMEOUT || '120000'), // 2 min for scripts
|
|
30
|
+
// API calls get reasonable time
|
|
31
|
+
apiCallTimeout: parseInt(process.env.MCP_API_TIMEOUT || '15000'), // 15s per API call
|
|
32
|
+
apiRetryDelay: parseInt(process.env.MCP_API_RETRY_DELAY || '2000'), // 2s between retries
|
|
33
|
+
apiMaxRetries: parseInt(process.env.MCP_API_MAX_RETRIES || '3'), // 3 retries for resilience
|
|
34
|
+
// Health checks
|
|
35
|
+
healthCheckInterval: parseInt(process.env.MCP_HEALTH_INTERVAL || '30000'), // Every 30s
|
|
36
|
+
healthCheckTimeout: parseInt(process.env.MCP_HEALTH_TIMEOUT || '2000'), // 2s timeout
|
|
37
|
+
// Response optimization
|
|
38
|
+
maxResponseSize: parseInt(process.env.MCP_MAX_RESPONSE_SIZE || '100000'), // 100KB chunks
|
|
39
|
+
chunkDelay: parseInt(process.env.MCP_CHUNK_DELAY || '100'), // 100ms between chunks
|
|
40
|
+
};
|
|
41
|
+
// In debug mode, allow longer timeouts
|
|
42
|
+
if (isDebug) {
|
|
43
|
+
config.defaultToolTimeout *= 2;
|
|
44
|
+
config.queryToolTimeout *= 2;
|
|
45
|
+
config.pullToolTimeout *= 2;
|
|
46
|
+
config.pushToolTimeout *= 2;
|
|
47
|
+
config.debugToolTimeout *= 2;
|
|
48
|
+
config.scriptToolTimeout *= 2;
|
|
49
|
+
}
|
|
50
|
+
// In production, ensure fast responses
|
|
51
|
+
if (isProduction) {
|
|
52
|
+
config.apiMaxRetries = 1; // Only 1 retry in production
|
|
53
|
+
config.apiRetryDelay = 500; // Faster retry
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Wrap a promise with timeout protection
|
|
59
|
+
*/
|
|
60
|
+
async function withMCPTimeout(promise, timeoutMs, operationName = 'Operation') {
|
|
61
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
reject(new Error(`${operationName} timed out after ${timeoutMs / 1000}s`));
|
|
64
|
+
}, timeoutMs);
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof Error && error.message.includes('timed out')) {
|
|
71
|
+
// Log timeout for debugging
|
|
72
|
+
console.error(`ā±ļø MCP Timeout: ${operationName} exceeded ${timeoutMs}ms`);
|
|
73
|
+
// Add context about what to do
|
|
74
|
+
error.message += ' - Consider increasing timeout or optimizing the operation';
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Execute with retry logic and timeout
|
|
81
|
+
*/
|
|
82
|
+
async function withMCPRetry(fn, config = {}, operationName = 'Operation') {
|
|
83
|
+
const fullConfig = { ...getMCPTimeoutConfig(), ...config };
|
|
84
|
+
let lastError = null;
|
|
85
|
+
for (let attempt = 0; attempt <= fullConfig.apiMaxRetries; attempt++) {
|
|
86
|
+
try {
|
|
87
|
+
// Try with timeout
|
|
88
|
+
const result = await withMCPTimeout(fn(), fullConfig.defaultToolTimeout, `${operationName} (attempt ${attempt + 1})`);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
lastError = error;
|
|
93
|
+
// Don't retry on non-retryable errors
|
|
94
|
+
if (error instanceof Error) {
|
|
95
|
+
if (error.message.includes('Authentication') ||
|
|
96
|
+
error.message.includes('Permission') ||
|
|
97
|
+
error.message.includes('Not found')) {
|
|
98
|
+
throw error; // Don't retry auth/permission errors
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// If we have retries left, wait and try again
|
|
102
|
+
if (attempt < fullConfig.apiMaxRetries) {
|
|
103
|
+
console.log(`ā ļø ${operationName} failed, retrying in ${fullConfig.apiRetryDelay / 1000}s...`);
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, fullConfig.apiRetryDelay));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// All retries exhausted
|
|
109
|
+
throw lastError || new Error(`${operationName} failed after ${fullConfig.apiMaxRetries} retries`);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Quick health check with timeout
|
|
113
|
+
*/
|
|
114
|
+
async function quickHealthCheck(checkFn, timeoutMs = 2000) {
|
|
115
|
+
try {
|
|
116
|
+
return await withMCPTimeout(checkFn(), timeoutMs, 'Health check');
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Health check failed or timed out
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=mcp-timeout-config.js.map
|
|
@@ -86,8 +86,6 @@ class ServiceNowClient {
|
|
|
86
86
|
// š§ CRITICAL FIX: Add makeRequest method to Axios instance to fix phantom calls
|
|
87
87
|
// Some code expects makeRequest to exist on this.client (the Axios instance)
|
|
88
88
|
this.client.makeRequest = async (config) => {
|
|
89
|
-
this.logger.debug('š§ AXIOS makeRequest called! Config:', config);
|
|
90
|
-
this.logger.debug('š§ Routing to appropriate HTTP method...');
|
|
91
89
|
// Route to the appropriate Axios method based on the request config
|
|
92
90
|
const method = (config.method || 'GET').toLowerCase();
|
|
93
91
|
const url = config.url || config.endpoint;
|
|
@@ -2888,17 +2886,13 @@ try {
|
|
|
2888
2886
|
* This method provides compatibility for code that expects makeRequest
|
|
2889
2887
|
*/
|
|
2890
2888
|
async makeRequest(config) {
|
|
2891
|
-
|
|
2892
|
-
this.logger.info('š§ TEMP FIX: makeRequest called with config:', config);
|
|
2893
|
-
this.logger.info('š§ This instance constructor:', this.constructor.name);
|
|
2894
|
-
this.logger.info('š§ This instance methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(this)));
|
|
2889
|
+
// Debug logging removed - was causing noise in production
|
|
2895
2890
|
try {
|
|
2896
2891
|
await this.ensureAuthenticated();
|
|
2897
2892
|
// Route the request to the appropriate HTTP method
|
|
2898
2893
|
const method = (config.method || 'GET').toLowerCase();
|
|
2899
2894
|
const url = config.url || config.endpoint;
|
|
2900
2895
|
const data = config.data || config.body;
|
|
2901
|
-
this.logger.info(`š§ Routing ${method.toUpperCase()} request to: ${url}`);
|
|
2902
2896
|
switch (method) {
|
|
2903
2897
|
case 'get':
|
|
2904
2898
|
return await this.get(url, config.params);
|
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.SmartFieldFetcher = void 0;
|
|
10
10
|
exports.createFetchStrategyHint = createFetchStrategyHint;
|
|
11
11
|
const artifact_registry_js_1 = require("./artifact-sync/artifact-registry.js");
|
|
12
|
+
const mcp_timeout_config_js_1 = require("./mcp-timeout-config.js");
|
|
12
13
|
// Widget field groups with relationship context - 20K per field for better context
|
|
13
14
|
const WIDGET_FIELD_GROUPS = [
|
|
14
15
|
{
|
|
@@ -160,9 +161,10 @@ class SmartFieldFetcher {
|
|
|
160
161
|
};
|
|
161
162
|
// Try to fetch all fields first, then fall back to individual fields if too large
|
|
162
163
|
console.log(`š¦ Attempting to fetch complete widget data...`);
|
|
164
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
163
165
|
try {
|
|
164
|
-
// Try to get all fields at once
|
|
165
|
-
const response = await this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1);
|
|
166
|
+
// Try to get all fields at once with timeout protection
|
|
167
|
+
const response = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1), timeoutConfig.queryToolTimeout, `Fetch complete widget ${sys_id}`);
|
|
166
168
|
if (response && response.success && response.data && response.data.result && response.data.result.length > 0) {
|
|
167
169
|
const widgetData = response.data.result[0];
|
|
168
170
|
console.log(`ā
Successfully fetched complete widget`);
|
|
@@ -192,7 +194,7 @@ class SmartFieldFetcher {
|
|
|
192
194
|
console.log(`š Switching to more robust fetching approach...`);
|
|
193
195
|
// First, get the widget with minimal fields to ensure it exists
|
|
194
196
|
try {
|
|
195
|
-
const basicResponse = await this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1);
|
|
197
|
+
const basicResponse = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1), timeoutConfig.queryToolTimeout, `Basic widget fetch ${sys_id}`);
|
|
196
198
|
if (!basicResponse || !basicResponse.success || !basicResponse.data || !basicResponse.data.result || basicResponse.data.result.length === 0) {
|
|
197
199
|
throw new Error(`Widget with sys_id ${sys_id} not found`);
|
|
198
200
|
}
|
|
@@ -227,8 +229,9 @@ class SmartFieldFetcher {
|
|
|
227
229
|
// If critical fields are missing, try alternative approach
|
|
228
230
|
if (!widgetData.script && !widgetData.client_script && !widgetData.template) {
|
|
229
231
|
console.log(`ā ļø Critical fields missing, trying direct API call...`);
|
|
230
|
-
// Try using getRecord instead of searchRecords
|
|
231
|
-
const directResponse = await this.client.getRecord('sp_widget', sys_id)
|
|
232
|
+
// Try using getRecord instead of searchRecords with short timeout
|
|
233
|
+
const directResponse = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.getRecord('sp_widget', sys_id), 5000, // 5 second timeout for direct fetch
|
|
234
|
+
`Direct widget fetch ${sys_id}`);
|
|
232
235
|
if (directResponse && directResponse.success && directResponse.data && directResponse.data.result) {
|
|
233
236
|
const directData = directResponse.data.result;
|
|
234
237
|
// Merge any new data we got
|
|
@@ -360,10 +363,11 @@ class SmartFieldFetcher {
|
|
|
360
363
|
async debugFetchWidget(sys_id) {
|
|
361
364
|
console.log(`\nš DEBUG: Testing different fetch approaches for widget ${sys_id}\n`);
|
|
362
365
|
const results = {};
|
|
363
|
-
|
|
366
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
367
|
+
// Test 1: Basic searchRecords with timeout
|
|
364
368
|
try {
|
|
365
|
-
console.log(`Test 1: searchRecords...`);
|
|
366
|
-
const test1 = await this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1);
|
|
369
|
+
console.log(`Test 1: searchRecords (${timeoutConfig.queryToolTimeout / 1000}s timeout)...`);
|
|
370
|
+
const test1 = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecords('sp_widget', `sys_id=${sys_id}`, 1), timeoutConfig.queryToolTimeout, 'searchRecords test');
|
|
367
371
|
console.log(` - Response structure: success=${test1?.success}, has data=${!!test1?.data}, has result=${!!test1?.data?.result}`);
|
|
368
372
|
if (test1?.data?.result?.[0]) {
|
|
369
373
|
const widget = test1.data.result[0];
|
|
@@ -377,10 +381,10 @@ class SmartFieldFetcher {
|
|
|
377
381
|
catch (e) {
|
|
378
382
|
console.log(` ā Error: ${e.message}`);
|
|
379
383
|
}
|
|
380
|
-
// Test 2: getRecord
|
|
384
|
+
// Test 2: getRecord with timeout
|
|
381
385
|
try {
|
|
382
|
-
console.log(`Test 2: getRecord...`);
|
|
383
|
-
const test2 = await this.client.getRecord('sp_widget', sys_id);
|
|
386
|
+
console.log(`Test 2: getRecord (${timeoutConfig.queryToolTimeout / 1000}s timeout)...`);
|
|
387
|
+
const test2 = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.getRecord('sp_widget', sys_id), timeoutConfig.queryToolTimeout, 'getRecord test');
|
|
384
388
|
console.log(` - Response structure: success=${test2?.success}, has data=${!!test2?.data}, has result=${!!test2?.data?.result}`);
|
|
385
389
|
if (test2?.data?.result) {
|
|
386
390
|
const widget = test2.data.result;
|
|
@@ -394,10 +398,10 @@ class SmartFieldFetcher {
|
|
|
394
398
|
catch (e) {
|
|
395
399
|
console.log(` ā Error: ${e.message}`);
|
|
396
400
|
}
|
|
397
|
-
// Test 3: searchRecordsWithFields
|
|
401
|
+
// Test 3: searchRecordsWithFields with timeout
|
|
398
402
|
try {
|
|
399
|
-
console.log(`Test 3: searchRecordsWithFields
|
|
400
|
-
const test3 = await this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, ['name', 'script', 'client_script', 'template', 'css'], 1);
|
|
403
|
+
console.log(`Test 3: searchRecordsWithFields (${timeoutConfig.queryToolTimeout / 1000}s timeout)...`);
|
|
404
|
+
const test3 = await (0, mcp_timeout_config_js_1.withMCPTimeout)(this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, ['name', 'script', 'client_script', 'template', 'css'], 1), timeoutConfig.queryToolTimeout, 'searchRecordsWithFields test');
|
|
401
405
|
console.log(` - Response structure: success=${test3?.success}, has data=${!!test3?.data}, has result=${!!test3?.data?.result}`);
|
|
402
406
|
if (test3?.data?.result?.[0]) {
|
|
403
407
|
const widget = test3.data.result[0];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "3.5.
|
|
4
|
-
"description": "ServiceNow development framework with MCP server integration. Executes background scripts using ES5 JavaScript only (ServiceNow Rhino engine requirement). Provides 18 MCP servers including local development sync for editing ServiceNow artifacts with Claude Code native tools, widget deployment with coherence validation, table operations, script execution, machine learning, advanced features, and comprehensive platform management.",
|
|
3
|
+
"version": "3.5.12",
|
|
4
|
+
"description": "ServiceNow development framework with MCP server integration and balanced timeout protection. Features intelligent timeout configuration for long operations while preventing Claude API timeouts. Debug logging cleaned up for production use. Executes background scripts using ES5 JavaScript only (ServiceNow Rhino engine requirement). Provides 18 MCP servers including local development sync for editing ServiceNow artifacts with Claude Code native tools, widget deployment with coherence validation, table operations, script execution, machine learning, advanced features, and comprehensive platform management.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|