snow-flow 3.5.5 → 3.5.8

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.
@@ -8,5 +8,20 @@
8
8
  *
9
9
  * THIS IS THE KEY TO POWERFUL SERVICENOW DEVELOPMENT!
10
10
  */
11
- export {};
11
+ import { EnhancedBaseMCPServer } from './shared/enhanced-base-mcp-server.js';
12
+ export declare class ServiceNowLocalDevelopmentMCP extends EnhancedBaseMCPServer {
13
+ private syncManager;
14
+ constructor();
15
+ private setupSyncManager;
16
+ private setupHandlers;
17
+ private pullArtifact;
18
+ private pushArtifact;
19
+ private getSyncStatus;
20
+ private listSupportedArtifacts;
21
+ private validateArtifactCoherence;
22
+ private syncCleanup;
23
+ private convertToES5;
24
+ private pullWidget;
25
+ private pushWidget;
26
+ }
12
27
  //# sourceMappingURL=servicenow-local-development-mcp.d.ts.map
@@ -10,6 +10,7 @@
10
10
  * THIS IS THE KEY TO POWERFUL SERVICENOW DEVELOPMENT!
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ServiceNowLocalDevelopmentMCP = void 0;
13
14
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
14
15
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
15
16
  const enhanced_base_mcp_server_js_1 = require("./shared/enhanced-base-mcp-server.js");
@@ -211,7 +212,9 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
211
212
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
212
213
  }
213
214
  this.logger.info(`✅ Tool ${name} completed successfully`);
214
- return result;
215
+ return {
216
+ content: result.content
217
+ };
215
218
  }
216
219
  catch (error) {
217
220
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -417,11 +420,13 @@ class ServiceNowLocalDevelopmentMCP extends enhanced_base_mcp_server_js_1.Enhanc
417
420
  return this.pushArtifact(args);
418
421
  }
419
422
  }
423
+ exports.ServiceNowLocalDevelopmentMCP = ServiceNowLocalDevelopmentMCP;
420
424
  // Start the server
421
425
  async function main() {
422
- const server = new ServiceNowLocalDevelopmentMCP();
426
+ const mcpServer = new ServiceNowLocalDevelopmentMCP();
423
427
  const transport = new stdio_js_1.StdioServerTransport();
424
- await server.server.connect(transport);
428
+ // Access server through a public method
429
+ await mcpServer.server.connect(transport);
425
430
  console.error('🚀 ServiceNow Local Development MCP Server started');
426
431
  }
427
432
  if (require.main === module) {
@@ -4,18 +4,19 @@
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
5
5
  import { ServiceNowClientWithTracking } from '../../utils/servicenow-client-with-tracking.js';
6
6
  import { MCPLogger } from './mcp-logger.js';
7
- import { SnowOAuth } from '../../utils/snow-oauth.js';
7
+ import { ServiceNowOAuth } from '../../utils/snow-oauth.js';
8
8
  export interface MCPToolResult {
9
9
  content: Array<{
10
10
  type: string;
11
11
  text: string;
12
12
  }>;
13
+ [key: string]: unknown;
13
14
  }
14
15
  export declare abstract class EnhancedBaseMCPServer {
15
16
  protected server: Server;
16
17
  protected client: ServiceNowClientWithTracking;
17
18
  protected logger: MCPLogger;
18
- protected oauth: SnowOAuth;
19
+ protected oauth: ServiceNowOAuth;
19
20
  protected isAuthenticated: boolean;
20
21
  constructor(name: string, version?: string);
21
22
  /**
@@ -19,7 +19,7 @@ class EnhancedBaseMCPServer {
19
19
  // Create enhanced client with tracking
20
20
  this.client = new servicenow_client_with_tracking_js_1.ServiceNowClientWithTracking(this.logger);
21
21
  // Initialize OAuth
22
- this.oauth = new snow_oauth_js_1.SnowOAuth();
22
+ this.oauth = new snow_oauth_js_1.ServiceNowOAuth();
23
23
  // Create server with capabilities
24
24
  this.server = new index_js_1.Server({
25
25
  name,
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * THIS IS THE BRIDGE BETWEEN SERVICENOW AND CLAUDE CODE!
9
9
  */
10
- import { ServiceNowClient } from './servicenow-client';
10
+ import { ServiceNowClient } from './servicenow-client.js';
11
11
  import { ArtifactTypeConfig, FieldMapping, ValidationResult } from './artifact-sync/artifact-registry';
12
12
  export interface LocalArtifact {
13
13
  sys_id: string;
@@ -46,13 +46,13 @@ exports.ArtifactLocalSync = void 0;
46
46
  const fs = __importStar(require("fs"));
47
47
  const path = __importStar(require("path"));
48
48
  const os = __importStar(require("os"));
49
- const smart_field_fetcher_1 = require("./smart-field-fetcher");
49
+ const smart_field_fetcher_js_1 = require("./smart-field-fetcher.js");
50
50
  const artifact_registry_1 = require("./artifact-sync/artifact-registry");
51
51
  class ArtifactLocalSync {
52
52
  constructor(client) {
53
53
  this.artifacts = new Map();
54
54
  this.client = client;
55
- this.smartFetcher = new smart_field_fetcher_1.SmartFieldFetcher(client);
55
+ this.smartFetcher = new smart_field_fetcher_js_1.SmartFieldFetcher(client);
56
56
  // Create base directory for Snow-Flow artifacts
57
57
  this.baseDir = path.join(os.tmpdir(), 'snow-flow-artifacts');
58
58
  if (!fs.existsSync(this.baseDir)) {
@@ -84,11 +84,7 @@ class ArtifactLocalSync {
84
84
  else {
85
85
  // Generic fetch for other types
86
86
  const allFields = config.fieldMappings.map(fm => fm.serviceNowField);
87
- const response = await this.client.query(tableName, {
88
- query: `sys_id=${sys_id}`,
89
- fields: allFields.concat(['sys_id', 'sys_created_on', 'sys_updated_on']),
90
- limit: 1
91
- });
87
+ const response = await this.client.searchRecords(tableName, `sys_id=${sys_id}`, 1);
92
88
  artifactData = response.result?.[0];
93
89
  }
94
90
  if (!artifactData) {
@@ -320,7 +316,7 @@ class ArtifactLocalSync {
320
316
  // Update in ServiceNow
321
317
  try {
322
318
  console.log(`\n📤 Updating ${config.displayName} in ServiceNow...`);
323
- await this.client.update(config.tableName, sys_id, updates);
319
+ await this.client.updateRecord(config.tableName, sys_id, updates);
324
320
  artifact.syncStatus = 'synced';
325
321
  artifact.lastSyncedAt = new Date();
326
322
  console.log(`✅ ${config.displayName} successfully updated in ServiceNow!`);
@@ -382,7 +378,7 @@ class ArtifactLocalSync {
382
378
  // Update in ServiceNow
383
379
  try {
384
380
  console.log(`\n📤 Updating widget in ServiceNow...`);
385
- await this.client.update('sp_widget', sys_id, updates);
381
+ await this.client.updateRecord('sp_widget', sys_id, updates);
386
382
  artifact.syncStatus = 'synced';
387
383
  artifact.lastSyncedAt = new Date();
388
384
  console.log(`✅ Widget successfully updated in ServiceNow!`);
@@ -647,11 +643,7 @@ snow-flow sync status ${widget.sys_id}
647
643
  const tables = Object.keys(artifact_registry_1.ARTIFACT_REGISTRY);
648
644
  for (const table of tables) {
649
645
  try {
650
- const response = await this.client.query(table, {
651
- query: `sys_id=${sys_id}`,
652
- fields: ['sys_id'],
653
- limit: 1
654
- });
646
+ const response = await this.client.searchRecords(table, `sys_id=${sys_id}`, 1);
655
647
  if (response.result?.[0]) {
656
648
  console.log(`🎆 Found artifact in table: ${table}`);
657
649
  return this.pullArtifact(table, sys_id);
@@ -10,6 +10,10 @@ export declare class ServiceNowClientWithTracking extends ServiceNowClient {
10
10
  * Override makeRequest to add tracking
11
11
  */
12
12
  makeRequest(config: any): Promise<any>;
13
+ /**
14
+ * Override searchRecordsWithFields to add tracking
15
+ */
16
+ searchRecordsWithFields(table: string, query: string, fields: string[], limit?: number): Promise<any>;
13
17
  /**
14
18
  * Override searchRecords to add tracking
15
19
  */
@@ -36,6 +36,27 @@ class ServiceNowClientWithTracking extends servicenow_client_js_1.ServiceNowClie
36
36
  throw error;
37
37
  }
38
38
  }
39
+ /**
40
+ * Override searchRecordsWithFields to add tracking
41
+ */
42
+ async searchRecordsWithFields(table, query, fields, limit = 10) {
43
+ this.mcpLogger.progress(`Searching ${table} with specific fields: ${fields.join(', ')}`);
44
+ try {
45
+ const result = await super.searchRecordsWithFields(table, query, fields, limit);
46
+ // Estimate tokens based on fields requested
47
+ const fieldCount = fields.length;
48
+ const estimatedTokens = Math.min(fieldCount * 100, 1000); // Rough estimate
49
+ this.mcpLogger.trackTokens(estimatedTokens, 0);
50
+ if (result?.data?.result?.length > 0) {
51
+ this.mcpLogger.info(`Found ${result.data.result.length} records with ${fieldCount} fields`);
52
+ }
53
+ return result;
54
+ }
55
+ catch (error) {
56
+ this.mcpLogger.error('Search with fields failed', error);
57
+ throw error;
58
+ }
59
+ }
39
60
  /**
40
61
  * Override searchRecords to add tracking
41
62
  */
@@ -106,7 +127,7 @@ class ServiceNowClientWithTracking extends servicenow_client_js_1.ServiceNowClie
106
127
  async getRecord(table, sysId, fields) {
107
128
  this.mcpLogger.progress(`Fetching ${table} record: ${sysId}`);
108
129
  try {
109
- const result = await super.getRecord(table, sysId, fields);
130
+ const result = await super.getRecord(table, sysId);
110
131
  // Estimate tokens
111
132
  if (result?.data?.result) {
112
133
  const responseSize = JSON.stringify(result.data.result).length;
@@ -156,6 +156,10 @@ export declare class ServiceNowClient {
156
156
  * Get multiple records from a table
157
157
  */
158
158
  getRecords(table: string, params?: any): Promise<ServiceNowAPIResponse<any[]>>;
159
+ /**
160
+ * Search records with specific fields
161
+ */
162
+ searchRecordsWithFields(table: string, query: string, fields: string[], limit?: number): Promise<ServiceNowAPIResponse<any>>;
159
163
  /**
160
164
  * Search records in a table using encoded query
161
165
  */
@@ -1039,6 +1039,33 @@ class ServiceNowClient {
1039
1039
  };
1040
1040
  }
1041
1041
  }
1042
+ /**
1043
+ * Search records with specific fields
1044
+ */
1045
+ async searchRecordsWithFields(table, query, fields, limit = 10) {
1046
+ try {
1047
+ await this.ensureAuthenticated();
1048
+ const url = `/api/now/table/${table}`;
1049
+ const params = {
1050
+ sysparm_query: query,
1051
+ sysparm_limit: limit.toString(),
1052
+ sysparm_fields: fields.join(',')
1053
+ };
1054
+ const response = await this.client.get(url, { params });
1055
+ return {
1056
+ success: true,
1057
+ data: response.data
1058
+ };
1059
+ }
1060
+ catch (error) {
1061
+ console.error('API Error:', error);
1062
+ return {
1063
+ success: false,
1064
+ error: error instanceof Error ? error.message : String(error),
1065
+ data: null
1066
+ };
1067
+ }
1068
+ }
1042
1069
  /**
1043
1070
  * Search records in a table using encoded query
1044
1071
  */
@@ -4,7 +4,7 @@
4
4
  * Intelligently fetches large artifacts by splitting fields into chunks
5
5
  * while maintaining context relationships between fields.
6
6
  */
7
- import { ServiceNowClient } from './servicenow-client';
7
+ import { ServiceNowClient } from './servicenow-client.js';
8
8
  export interface FetchStrategy {
9
9
  table: string;
10
10
  sys_id: string;
@@ -8,7 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.SmartFieldFetcher = void 0;
10
10
  exports.createFetchStrategyHint = createFetchStrategyHint;
11
- const artifact_registry_1 = require("./artifact-sync/artifact-registry");
11
+ const artifact_registry_js_1 = require("./artifact-sync/artifact-registry.js");
12
12
  // Widget field groups with relationship context - 20K per field for better context
13
13
  const WIDGET_FIELD_GROUPS = [
14
14
  {
@@ -112,7 +112,7 @@ class SmartFieldFetcher {
112
112
  async fetchArtifact(table, sys_id) {
113
113
  console.log(`\n🔍 Smart fetching ${table}: ${sys_id}`);
114
114
  const fieldGroups = this.getFieldGroups(table);
115
- const config = (0, artifact_registry_1.getArtifactConfig)(table);
115
+ const config = (0, artifact_registry_js_1.getArtifactConfig)(table);
116
116
  const results = {
117
117
  _fetch_strategy: 'smart_chunked',
118
118
  _context_hint: config ? `${config.displayName} - fields fetched in groups to respect token limits` : 'Fields fetched separately but are related',
@@ -122,14 +122,10 @@ class SmartFieldFetcher {
122
122
  for (const group of fieldGroups) {
123
123
  console.log(`📦 Fetching ${group.groupName}: ${group.description}`);
124
124
  try {
125
- const response = await this.client.query(table, {
126
- query: `sys_id=${sys_id}`,
127
- fields: group.fields,
128
- limit: 1
129
- });
130
- if (response.result?.[0]) {
125
+ const response = await this.client.getRecord(table, sys_id);
126
+ if (response) {
131
127
  results._field_groups[group.groupName] = {
132
- data: response.result[0],
128
+ data: response,
133
129
  description: group.description,
134
130
  fields: group.fields
135
131
  };
@@ -156,38 +152,103 @@ class SmartFieldFetcher {
156
152
  * (Wrapper for backward compatibility)
157
153
  */
158
154
  async fetchWidget(sys_id) {
159
- return this.fetchArtifact('sp_widget', sys_id);
160
155
  console.log(`\n🔍 Smart fetching widget: ${sys_id}`);
161
156
  const results = {
162
157
  _fetch_strategy: 'smart_chunked',
163
158
  _context_hint: 'Widget fields fetched separately but are interconnected: template references {{data.x}} from server script, calls methods from client script, and uses CSS classes',
164
159
  _field_groups: {}
165
160
  };
166
- // Fetch each field group
167
- for (const group of WIDGET_FIELD_GROUPS) {
168
- console.log(`📦 Fetching ${group.groupName}: ${group.description}`);
169
- try {
170
- const response = await this.client.query('sp_widget', {
171
- query: `sys_id=${sys_id}`,
172
- fields: group.fields,
173
- limit: 1
174
- });
175
- if (response.result?.[0]) {
161
+ // Try to fetch all fields first, then fall back to individual fields if too large
162
+ console.log(`📦 Attempting to fetch complete widget data...`);
163
+ 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];
168
+ console.log(`✅ Successfully fetched complete widget`);
169
+ // Organize into field groups for better context
170
+ for (const group of WIDGET_FIELD_GROUPS) {
171
+ const groupData = {};
172
+ for (const fieldName of group.fields) {
173
+ if (widgetData[fieldName] !== undefined) {
174
+ groupData[fieldName] = widgetData[fieldName];
175
+ }
176
+ }
176
177
  results._field_groups[group.groupName] = {
177
- data: response.result[0],
178
+ data: groupData,
178
179
  description: group.description,
179
180
  fields: group.fields
180
181
  };
181
- // Add to flat structure for easy access
182
- Object.assign(results, response.result[0]);
183
182
  }
183
+ // Add complete widget data to flat structure
184
+ Object.assign(results, widgetData);
184
185
  }
185
- catch (error) {
186
- console.log(`⚠️ Failed to fetch ${group.groupName}: ${error.message}`);
187
- // If group fails due to size, fetch fields individually
188
- if (error.message?.includes('exceeds maximum allowed tokens')) {
189
- results._field_groups[group.groupName] = await this.fetchFieldsIndividually('sp_widget', sys_id, group.fields, group.description);
186
+ else {
187
+ throw new Error('Widget not found');
188
+ }
189
+ }
190
+ catch (error) {
191
+ console.log(`⚠️ Complete fetch failed: ${error.message}`);
192
+ console.log(`🔄 Switching to field-by-field fetching...`);
193
+ // Fall back to fetching fields per group or individually
194
+ for (const group of WIDGET_FIELD_GROUPS) {
195
+ console.log(`📦 Fetching ${group.groupName}: ${group.description}`);
196
+ try {
197
+ // Try to fetch all fields in this group at once
198
+ const groupResponse = await this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, group.fields, 1);
199
+ if (groupResponse && groupResponse.success && groupResponse.data && groupResponse.data.result && groupResponse.data.result.length > 0) {
200
+ const groupData = groupResponse.data.result[0];
201
+ console.log(` ✅ Successfully fetched ${group.groupName}`);
202
+ results._field_groups[group.groupName] = {
203
+ data: groupData,
204
+ description: group.description,
205
+ fields: group.fields
206
+ };
207
+ // Add to flat structure
208
+ Object.assign(results, groupData);
209
+ }
210
+ else {
211
+ throw new Error('No data returned for group');
212
+ }
190
213
  }
214
+ catch (groupError) {
215
+ console.log(` ⚠️ Group ${group.groupName} failed, fetching fields individually...`);
216
+ // If group fails, fetch fields one by one
217
+ const groupData = {};
218
+ for (const fieldName of group.fields) {
219
+ console.log(` 📄 Fetching field: ${fieldName}`);
220
+ try {
221
+ // Fetch just this single field
222
+ const fieldResponse = await this.client.searchRecordsWithFields('sp_widget', `sys_id=${sys_id}`, [fieldName], 1);
223
+ if (fieldResponse && fieldResponse.success && fieldResponse.data && fieldResponse.data.result && fieldResponse.data.result.length > 0) {
224
+ const fieldValue = fieldResponse.data.result[0][fieldName];
225
+ if (fieldValue !== undefined && fieldValue !== null) {
226
+ groupData[fieldName] = fieldValue;
227
+ results[fieldName] = fieldValue; // Also add to flat structure
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
+ }
240
+ }
241
+ results._field_groups[group.groupName] = {
242
+ data: groupData,
243
+ description: group.description,
244
+ fields: group.fields,
245
+ _fetched_individually: true
246
+ };
247
+ }
248
+ }
249
+ // Make sure we have at least the sys_id
250
+ if (!results.sys_id) {
251
+ results.sys_id = sys_id;
191
252
  }
192
253
  }
193
254
  // Add coherence validation hints
@@ -222,13 +283,9 @@ class SmartFieldFetcher {
222
283
  for (const field of fields) {
223
284
  try {
224
285
  console.log(` 📄 Fetching field: ${field}`);
225
- const response = await this.client.query(table, {
226
- query: `sys_id=${sys_id}`,
227
- fields: [field],
228
- limit: 1
229
- });
230
- if (response.result?.[0]) {
231
- result.data[field] = response.result[0][field];
286
+ const response = await this.client.getRecord(table, sys_id);
287
+ if (response) {
288
+ result.data[field] = response[field];
232
289
  result._field_status[field] = 'success';
233
290
  }
234
291
  }
@@ -284,11 +341,7 @@ class SmartFieldFetcher {
284
341
  // Use CONTAINS operator for field search
285
342
  const query = `${field}CONTAINS${searchTerm}${additionalQuery ? '^' + additionalQuery : ''}`;
286
343
  try {
287
- const response = await this.client.query(table, {
288
- query: query,
289
- fields: ['sys_id', 'name', field.substring(0, 50)], // Get preview of field
290
- limit: 10
291
- });
344
+ const response = await this.client.searchRecords(table, query, 10);
292
345
  console.log(` ✅ Found ${response.result?.length || 0} matches`);
293
346
  return response.result || [];
294
347
  }
@@ -311,7 +364,7 @@ class SmartFieldFetcher {
311
364
  return BUSINESS_RULE_FIELD_GROUPS;
312
365
  }
313
366
  // Try to get from artifact registry
314
- const config = (0, artifact_registry_1.getArtifactConfig)(table);
367
+ const config = (0, artifact_registry_js_1.getArtifactConfig)(table);
315
368
  if (config) {
316
369
  return this.createFieldGroupsFromRegistry(config);
317
370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-flow",
3
- "version": "3.5.5",
3
+ "version": "3.5.8",
4
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.",
5
5
  "main": "dist/index.js",
6
6
  "type": "commonjs",