snow-flow 3.5.9 ā 3.5.11
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.d.ts +1 -0
- package/dist/mcp/servicenow-local-development-mcp.js +156 -48
- package/dist/utils/artifact-local-sync.js +40 -18
- package/dist/utils/mcp-timeout-config.d.ts +39 -0
- package/dist/utils/mcp-timeout-config.js +123 -0
- package/dist/utils/servicenow-client-with-tracking.js +1 -1
- package/dist/utils/smart-field-fetcher.d.ts +4 -0
- package/dist/utils/smart-field-fetcher.js +123 -52
- package/package.json +2 -2
|
@@ -143,6 +143,20 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
143
143
|
required: ['code']
|
|
144
144
|
}
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: 'snow_debug_widget_fetch',
|
|
148
|
+
description: 'Debug widget fetching to diagnose API issues',
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
sys_id: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'Widget sys_id to debug'
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
required: ['sys_id']
|
|
158
|
+
}
|
|
159
|
+
},
|
|
146
160
|
// Legacy compatibility tools
|
|
147
161
|
{
|
|
148
162
|
name: 'snow_pull_widget',
|
|
@@ -176,41 +190,56 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
176
190
|
}));
|
|
177
191
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
178
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
|
|
179
195
|
try {
|
|
180
196
|
this.logger.info(`š§ Executing tool: ${name}`, args);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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]);
|
|
214
243
|
this.logger.info(`ā
Tool ${name} completed successfully`);
|
|
215
244
|
return {
|
|
216
245
|
content: result.content
|
|
@@ -232,16 +261,25 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
232
261
|
}
|
|
233
262
|
async pullArtifact(args) {
|
|
234
263
|
const { sys_id, table } = args;
|
|
264
|
+
// Add timeout for pull operations
|
|
265
|
+
const PULL_TIMEOUT = 15000; // 15 seconds for pull operations
|
|
235
266
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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]);
|
|
245
283
|
return {
|
|
246
284
|
content: [
|
|
247
285
|
{
|
|
@@ -412,6 +450,64 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
412
450
|
]
|
|
413
451
|
};
|
|
414
452
|
}
|
|
453
|
+
async debugWidgetFetch(args) {
|
|
454
|
+
const { sys_id } = args;
|
|
455
|
+
// Debug operations get extra time
|
|
456
|
+
const DEBUG_TIMEOUT = 20000; // 20 seconds for debug operations
|
|
457
|
+
try {
|
|
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]);
|
|
463
|
+
let summaryText = `š Debug Results for Widget ${sys_id}\n\n`;
|
|
464
|
+
// Check which methods worked
|
|
465
|
+
const methods = ['searchRecords', 'getRecord', 'searchRecordsWithFields'];
|
|
466
|
+
for (const method of methods) {
|
|
467
|
+
if (debugResults[method]) {
|
|
468
|
+
const widget = debugResults[method];
|
|
469
|
+
summaryText += `ā
${method}:\n`;
|
|
470
|
+
summaryText += ` - Fields: ${Object.keys(widget).length}\n`;
|
|
471
|
+
summaryText += ` - Has script: ${!!widget.script}\n`;
|
|
472
|
+
summaryText += ` - Has client_script: ${!!widget.client_script}\n`;
|
|
473
|
+
summaryText += ` - Has template: ${!!widget.template}\n`;
|
|
474
|
+
summaryText += ` - Script size: ${widget.script?.length || 0} chars\n`;
|
|
475
|
+
summaryText += ` - Client script size: ${widget.client_script?.length || 0} chars\n`;
|
|
476
|
+
summaryText += ` - Template size: ${widget.template?.length || 0} chars\n\n`;
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
summaryText += `ā ${method}: Failed\n\n`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Recommend best approach
|
|
483
|
+
const workingMethods = methods.filter(m => debugResults[m]);
|
|
484
|
+
if (workingMethods.length > 0) {
|
|
485
|
+
summaryText += `\nš Recommendation: Use ${workingMethods[0]} for fetching this widget.`;
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
summaryText += `\nā ļø All fetch methods failed. There may be an authentication or permission issue.`;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: 'text',
|
|
494
|
+
text: summaryText
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
catch (error) {
|
|
500
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
501
|
+
return {
|
|
502
|
+
content: [
|
|
503
|
+
{
|
|
504
|
+
type: 'text',
|
|
505
|
+
text: `ā Debug failed: ${errorMessage}`
|
|
506
|
+
}
|
|
507
|
+
]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
415
511
|
// Legacy compatibility methods
|
|
416
512
|
async pullWidget(args) {
|
|
417
513
|
return this.pullArtifact({ ...args, table: 'sp_widget' });
|
|
@@ -421,13 +517,25 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
|
|
|
421
517
|
}
|
|
422
518
|
}
|
|
423
519
|
exports.ServiceNowLocalDevelopmentMCP = ServiceNowLocalDevelopmentMCP;
|
|
424
|
-
// Start the server
|
|
520
|
+
// Start the server with timeout protection
|
|
425
521
|
async function main() {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
}
|
|
431
539
|
}
|
|
432
540
|
if (require.main === module) {
|
|
433
541
|
main().catch((error) => {
|
|
@@ -46,30 +46,36 @@ 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) {
|
|
52
53
|
this.artifacts = new Map();
|
|
53
54
|
this.client = client;
|
|
54
55
|
this.smartFetcher = new smart_field_fetcher_js_1.SmartFieldFetcher(client);
|
|
56
|
+
// Version check for debugging
|
|
57
|
+
console.log(`š§ ArtifactLocalSync v3.5.10 initializing...`);
|
|
55
58
|
// Use custom directory, environment variable, or default to current project's servicenow folder
|
|
56
59
|
if (customBaseDir) {
|
|
57
60
|
this.baseDir = customBaseDir;
|
|
61
|
+
console.log(` š Using custom directory: ${customBaseDir}`);
|
|
58
62
|
}
|
|
59
63
|
else if (process.env.SNOW_FLOW_ARTIFACTS_DIR) {
|
|
60
64
|
this.baseDir = process.env.SNOW_FLOW_ARTIFACTS_DIR;
|
|
65
|
+
console.log(` š Using environment variable directory: ${this.baseDir}`);
|
|
61
66
|
}
|
|
62
67
|
else {
|
|
63
68
|
// Default to 'servicenow' folder in current working directory
|
|
64
69
|
this.baseDir = path.join(process.cwd(), 'servicenow');
|
|
70
|
+
console.log(` š Using project directory: ${this.baseDir}`);
|
|
65
71
|
}
|
|
66
72
|
// Create base directory if it doesn't exist
|
|
67
73
|
if (!fs.existsSync(this.baseDir)) {
|
|
68
74
|
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
69
|
-
console.log(
|
|
75
|
+
console.log(` ā
Created ServiceNow artifacts directory`);
|
|
70
76
|
}
|
|
71
77
|
else {
|
|
72
|
-
console.log(
|
|
78
|
+
console.log(` ā
Directory exists`);
|
|
73
79
|
}
|
|
74
80
|
// Create .gitignore if it doesn't exist to optionally exclude from version control
|
|
75
81
|
const gitignorePath = path.join(this.baseDir, '.gitignore');
|
|
@@ -121,22 +127,34 @@ class ArtifactLocalSync {
|
|
|
121
127
|
throw new Error(`Unsupported artifact type: ${tableName}. Supported types: ${Object.keys(artifact_registry_1.ARTIFACT_REGISTRY).join(', ')}`);
|
|
122
128
|
}
|
|
123
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)();
|
|
124
132
|
// Use smart fetcher for known types, otherwise direct query
|
|
125
133
|
let artifactData;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
}
|
|
134
151
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
158
|
}
|
|
141
159
|
if (!artifactData) {
|
|
142
160
|
throw new Error(`Artifact not found: ${tableName}/${sys_id}`);
|
|
@@ -296,10 +314,11 @@ class ArtifactLocalSync {
|
|
|
296
314
|
console.log(`\nā Continue with deployment anyway? (Issues might cause runtime errors)`);
|
|
297
315
|
// In real implementation, prompt for confirmation
|
|
298
316
|
}
|
|
299
|
-
// Update in ServiceNow
|
|
317
|
+
// Update in ServiceNow with timeout protection
|
|
300
318
|
try {
|
|
301
319
|
console.log(`\nš¤ Updating ${config.displayName} in ServiceNow...`);
|
|
302
|
-
|
|
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}`);
|
|
303
322
|
artifact.syncStatus = 'synced';
|
|
304
323
|
artifact.lastSyncedAt = new Date();
|
|
305
324
|
console.log(`ā
${config.displayName} successfully updated in ServiceNow!`);
|
|
@@ -570,9 +589,12 @@ snow-flow sync status ${widget.sys_id}
|
|
|
570
589
|
async pullArtifactBySysId(sys_id) {
|
|
571
590
|
// Try to detect table by querying common tables
|
|
572
591
|
const tables = Object.keys(artifact_registry_1.ARTIFACT_REGISTRY);
|
|
592
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
573
593
|
for (const table of tables) {
|
|
574
594
|
try {
|
|
575
|
-
|
|
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}`);
|
|
576
598
|
if (response.result?.[0]) {
|
|
577
599
|
console.log(`š Found artifact in table: ${table}`);
|
|
578
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
|
+
// Fast timeouts to prevent Claude from timing out
|
|
20
|
+
const config = {
|
|
21
|
+
// Server should start quickly (3s max)
|
|
22
|
+
serverInitTimeout: parseInt(process.env.MCP_SERVER_INIT_TIMEOUT || '3000'),
|
|
23
|
+
// Tool timeouts - keep them short to fail fast
|
|
24
|
+
defaultToolTimeout: parseInt(process.env.MCP_DEFAULT_TOOL_TIMEOUT || '8000'), // 8s default
|
|
25
|
+
queryToolTimeout: parseInt(process.env.MCP_QUERY_TIMEOUT || '5000'), // 5s for queries
|
|
26
|
+
pullToolTimeout: parseInt(process.env.MCP_PULL_TIMEOUT || '10000'), // 10s for pulls
|
|
27
|
+
pushToolTimeout: parseInt(process.env.MCP_PUSH_TIMEOUT || '10000'), // 10s for pushes
|
|
28
|
+
debugToolTimeout: parseInt(process.env.MCP_DEBUG_TIMEOUT || '15000'), // 15s for debug
|
|
29
|
+
scriptToolTimeout: parseInt(process.env.MCP_SCRIPT_TIMEOUT || '20000'), // 20s for scripts
|
|
30
|
+
// API calls should be fast
|
|
31
|
+
apiCallTimeout: parseInt(process.env.MCP_API_TIMEOUT || '5000'), // 5s per API call
|
|
32
|
+
apiRetryDelay: parseInt(process.env.MCP_API_RETRY_DELAY || '1000'), // 1s between retries
|
|
33
|
+
apiMaxRetries: parseInt(process.env.MCP_API_MAX_RETRIES || '2'), // Only 2 retries
|
|
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
|
|
@@ -46,7 +46,7 @@ class ServiceNowClientWithTracking extends servicenow_client_js_1.ServiceNowClie
|
|
|
46
46
|
// Estimate tokens based on fields requested
|
|
47
47
|
const fieldCount = fields.length;
|
|
48
48
|
const estimatedTokens = Math.min(fieldCount * 100, 1000); // Rough estimate
|
|
49
|
-
this.mcpLogger.
|
|
49
|
+
this.mcpLogger.addTokens(estimatedTokens, 0);
|
|
50
50
|
if (result?.data?.result?.length > 0) {
|
|
51
51
|
this.mcpLogger.info(`Found ${result.data.result.length} records with ${fieldCount} fields`);
|
|
52
52
|
}
|
|
@@ -52,6 +52,10 @@ export declare class SmartFieldFetcher {
|
|
|
52
52
|
* Search within fields using GlideRecord queries
|
|
53
53
|
*/
|
|
54
54
|
searchInField(table: string, field: string, searchTerm: string, additionalQuery?: string): Promise<any[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Debug method to directly test API calls
|
|
57
|
+
*/
|
|
58
|
+
debugFetchWidget(sys_id: string): Promise<any>;
|
|
55
59
|
/**
|
|
56
60
|
* Get smart fetch strategy based on table - NOW USES ARTIFACT REGISTRY!
|
|
57
61
|
*/
|
|
@@ -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,11 +161,12 @@ 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
|
-
if (response && response.result && response.result.length > 0) {
|
|
167
|
-
const widgetData = response.result[0];
|
|
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}`);
|
|
168
|
+
if (response && response.success && response.data && response.data.result && response.data.result.length > 0) {
|
|
169
|
+
const widgetData = response.data.result[0];
|
|
168
170
|
console.log(`ā
Successfully fetched complete widget`);
|
|
169
171
|
// Organize into field groups for better context
|
|
170
172
|
for (const group of WIDGET_FIELD_GROUPS) {
|
|
@@ -184,68 +186,73 @@ class SmartFieldFetcher {
|
|
|
184
186
|
Object.assign(results, widgetData);
|
|
185
187
|
}
|
|
186
188
|
else {
|
|
187
|
-
throw new Error('Widget not found');
|
|
189
|
+
throw new Error('Widget not found or no data returned');
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
catch (error) {
|
|
191
193
|
console.log(`ā ļø Complete fetch failed: ${error.message}`);
|
|
192
|
-
console.log(`š Switching to
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
194
|
+
console.log(`š Switching to more robust fetching approach...`);
|
|
195
|
+
// First, get the widget with minimal fields to ensure it exists
|
|
196
|
+
try {
|
|
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}`);
|
|
198
|
+
if (!basicResponse || !basicResponse.success || !basicResponse.data || !basicResponse.data.result || basicResponse.data.result.length === 0) {
|
|
199
|
+
throw new Error(`Widget with sys_id ${sys_id} not found`);
|
|
200
|
+
}
|
|
201
|
+
// Widget exists, now get it via getRecord which might handle large fields better
|
|
202
|
+
const widgetData = basicResponse.data.result[0];
|
|
203
|
+
// Store what we got
|
|
204
|
+
if (widgetData) {
|
|
205
|
+
// Add all available fields to results
|
|
206
|
+
Object.assign(results, widgetData);
|
|
207
|
+
// Organize into field groups
|
|
208
|
+
for (const group of WIDGET_FIELD_GROUPS) {
|
|
209
|
+
const groupData = {};
|
|
210
|
+
for (const fieldName of group.fields) {
|
|
211
|
+
if (widgetData[fieldName] !== undefined) {
|
|
212
|
+
groupData[fieldName] = widgetData[fieldName];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
202
215
|
results._field_groups[group.groupName] = {
|
|
203
216
|
data: groupData,
|
|
204
217
|
description: group.description,
|
|
205
218
|
fields: group.fields
|
|
206
219
|
};
|
|
207
|
-
// Add to flat structure
|
|
208
|
-
Object.assign(results, groupData);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
throw new Error('No data returned for group');
|
|
212
220
|
}
|
|
221
|
+
// Log what we got
|
|
222
|
+
console.log(`š Retrieved widget fields:`);
|
|
223
|
+
console.log(` - name: ${widgetData.name || 'N/A'}`);
|
|
224
|
+
console.log(` - template: ${widgetData.template ? widgetData.template.length + ' chars' : 'empty'}`);
|
|
225
|
+
console.log(` - script: ${widgetData.script ? widgetData.script.length + ' chars' : 'empty'}`);
|
|
226
|
+
console.log(` - client_script: ${widgetData.client_script ? widgetData.client_script.length + ' chars' : 'empty'}`);
|
|
227
|
+
console.log(` - css: ${widgetData.css ? widgetData.css.length + ' chars' : 'empty'}`);
|
|
213
228
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.log(` ā
Got ${fieldName} (${typeof fieldValue === 'string' ? fieldValue.length : 0} chars)`);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
groupData[fieldName] = '';
|
|
232
|
-
console.log(` ā ļø ${fieldName} is empty`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch (fieldError) {
|
|
237
|
-
console.log(` ā Failed to fetch ${fieldName}: ${fieldError.message}`);
|
|
238
|
-
groupData[fieldName] = ''; // Empty string for failed fields
|
|
239
|
-
}
|
|
229
|
+
// If critical fields are missing, try alternative approach
|
|
230
|
+
if (!widgetData.script && !widgetData.client_script && !widgetData.template) {
|
|
231
|
+
console.log(`ā ļø Critical fields missing, trying direct API call...`);
|
|
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}`);
|
|
235
|
+
if (directResponse && directResponse.success && directResponse.data && directResponse.data.result) {
|
|
236
|
+
const directData = directResponse.data.result;
|
|
237
|
+
// Merge any new data we got
|
|
238
|
+
Object.assign(results, directData);
|
|
239
|
+
console.log(`š Direct API retrieved:`);
|
|
240
|
+
console.log(` - template: ${directData.template ? directData.template.length + ' chars' : 'empty'}`);
|
|
241
|
+
console.log(` - script: ${directData.script ? directData.script.length + ' chars' : 'empty'}`);
|
|
242
|
+
console.log(` - client_script: ${directData.client_script ? directData.client_script.length + ' chars' : 'empty'}`);
|
|
240
243
|
}
|
|
241
|
-
results._field_groups[group.groupName] = {
|
|
242
|
-
data: groupData,
|
|
243
|
-
description: group.description,
|
|
244
|
-
fields: group.fields,
|
|
245
|
-
_fetched_individually: true
|
|
246
|
-
};
|
|
247
244
|
}
|
|
248
245
|
}
|
|
246
|
+
catch (fallbackError) {
|
|
247
|
+
console.log(`ā All fetch attempts failed: ${fallbackError.message}`);
|
|
248
|
+
// At least return basic structure with sys_id
|
|
249
|
+
results.sys_id = sys_id;
|
|
250
|
+
results.name = 'Unknown Widget';
|
|
251
|
+
results.template = '';
|
|
252
|
+
results.script = '';
|
|
253
|
+
results.client_script = '';
|
|
254
|
+
results.css = '';
|
|
255
|
+
}
|
|
249
256
|
// Make sure we have at least the sys_id
|
|
250
257
|
if (!results.sys_id) {
|
|
251
258
|
results.sys_id = sys_id;
|
|
@@ -350,6 +357,70 @@ class SmartFieldFetcher {
|
|
|
350
357
|
return [];
|
|
351
358
|
}
|
|
352
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Debug method to directly test API calls
|
|
362
|
+
*/
|
|
363
|
+
async debugFetchWidget(sys_id) {
|
|
364
|
+
console.log(`\nš DEBUG: Testing different fetch approaches for widget ${sys_id}\n`);
|
|
365
|
+
const results = {};
|
|
366
|
+
const timeoutConfig = (0, mcp_timeout_config_js_1.getMCPTimeoutConfig)();
|
|
367
|
+
// Test 1: Basic searchRecords with timeout
|
|
368
|
+
try {
|
|
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');
|
|
371
|
+
console.log(` - Response structure: success=${test1?.success}, has data=${!!test1?.data}, has result=${!!test1?.data?.result}`);
|
|
372
|
+
if (test1?.data?.result?.[0]) {
|
|
373
|
+
const widget = test1.data.result[0];
|
|
374
|
+
console.log(` - Fields received: ${Object.keys(widget).join(', ')}`);
|
|
375
|
+
console.log(` - Script length: ${widget.script?.length || 0}`);
|
|
376
|
+
console.log(` - Client script length: ${widget.client_script?.length || 0}`);
|
|
377
|
+
console.log(` - Template length: ${widget.template?.length || 0}`);
|
|
378
|
+
results.searchRecords = widget;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
console.log(` ā Error: ${e.message}`);
|
|
383
|
+
}
|
|
384
|
+
// Test 2: getRecord with timeout
|
|
385
|
+
try {
|
|
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');
|
|
388
|
+
console.log(` - Response structure: success=${test2?.success}, has data=${!!test2?.data}, has result=${!!test2?.data?.result}`);
|
|
389
|
+
if (test2?.data?.result) {
|
|
390
|
+
const widget = test2.data.result;
|
|
391
|
+
console.log(` - Fields received: ${Object.keys(widget).join(', ')}`);
|
|
392
|
+
console.log(` - Script length: ${widget.script?.length || 0}`);
|
|
393
|
+
console.log(` - Client script length: ${widget.client_script?.length || 0}`);
|
|
394
|
+
console.log(` - Template length: ${widget.template?.length || 0}`);
|
|
395
|
+
results.getRecord = widget;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
console.log(` ā Error: ${e.message}`);
|
|
400
|
+
}
|
|
401
|
+
// Test 3: searchRecordsWithFields with timeout
|
|
402
|
+
try {
|
|
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');
|
|
405
|
+
console.log(` - Response structure: success=${test3?.success}, has data=${!!test3?.data}, has result=${!!test3?.data?.result}`);
|
|
406
|
+
if (test3?.data?.result?.[0]) {
|
|
407
|
+
const widget = test3.data.result[0];
|
|
408
|
+
console.log(` - Fields received: ${Object.keys(widget).join(', ')}`);
|
|
409
|
+
console.log(` - Script length: ${widget.script?.length || 0}`);
|
|
410
|
+
console.log(` - Client script length: ${widget.client_script?.length || 0}`);
|
|
411
|
+
console.log(` - Template length: ${widget.template?.length || 0}`);
|
|
412
|
+
results.searchRecordsWithFields = widget;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
console.log(` ā Error: ${e.message}`);
|
|
417
|
+
}
|
|
418
|
+
console.log(`\nš Debug Results Summary:`);
|
|
419
|
+
console.log(` - searchRecords got: ${results.searchRecords ? Object.keys(results.searchRecords).length + ' fields' : 'failed'}`);
|
|
420
|
+
console.log(` - getRecord got: ${results.getRecord ? Object.keys(results.getRecord).length + ' fields' : 'failed'}`);
|
|
421
|
+
console.log(` - searchRecordsWithFields got: ${results.searchRecordsWithFields ? Object.keys(results.searchRecordsWithFields).length + ' fields' : 'failed'}`);
|
|
422
|
+
return results;
|
|
423
|
+
}
|
|
353
424
|
/**
|
|
354
425
|
* Get smart fetch strategy based on table - NOW USES ARTIFACT REGISTRY!
|
|
355
426
|
*/
|
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.11",
|
|
4
|
+
"description": "ServiceNow development framework with MCP server integration and fast response times. Features timeout-protected MCP operations to prevent Claude API timeouts. 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": {
|