snow-flow 3.3.7 → 3.3.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.
package/.mcp.json.template
CHANGED
|
@@ -131,6 +131,18 @@
|
|
|
131
131
|
"SNOW_CLIENT_ID": "{{SNOW_CLIENT_ID}}",
|
|
132
132
|
"SNOW_CLIENT_SECRET": "{{SNOW_CLIENT_SECRET}}"
|
|
133
133
|
}
|
|
134
|
+
},
|
|
135
|
+
"servicenow-system-properties": {
|
|
136
|
+
"command": "node",
|
|
137
|
+
"args": [
|
|
138
|
+
"{{PROJECT_ROOT}}/dist/mcp/servicenow-system-properties-mcp.js"
|
|
139
|
+
],
|
|
140
|
+
"description": "System property management via official ServiceNow APIs: get/set/list/delete properties, bulk operations, import/export JSON, validate values, search, categories, audit history - all using standard Table API on sys_properties",
|
|
141
|
+
"env": {
|
|
142
|
+
"SNOW_INSTANCE": "{{SNOW_INSTANCE}}",
|
|
143
|
+
"SNOW_CLIENT_ID": "{{SNOW_CLIENT_ID}}",
|
|
144
|
+
"SNOW_CLIENT_SECRET": "{{SNOW_CLIENT_SECRET}}"
|
|
145
|
+
}
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
}
|
package/dist/dynamic-version.js
CHANGED
|
@@ -36,7 +36,7 @@ function getDynamicVersion() {
|
|
|
36
36
|
console.warn('Warning: Could not read version from package.json:', error);
|
|
37
37
|
}
|
|
38
38
|
// Fallback to hardcoded version
|
|
39
|
-
return '3.3.
|
|
39
|
+
return '3.3.8';
|
|
40
40
|
}
|
|
41
41
|
// Export a constant that uses the dynamic version
|
|
42
42
|
exports.VERSION = getDynamicVersion();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceNow System Properties MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive system property management through official ServiceNow APIs
|
|
5
|
+
* Uses the standard Table API on sys_properties table
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* ServiceNow System Properties MCP Server
|
|
9
|
+
* Manages system properties through official ServiceNow REST APIs
|
|
10
|
+
*/
|
|
11
|
+
export declare class ServiceNowSystemPropertiesMCP {
|
|
12
|
+
private server;
|
|
13
|
+
private client;
|
|
14
|
+
private propertyCache;
|
|
15
|
+
constructor();
|
|
16
|
+
private setupHandlers;
|
|
17
|
+
private setupTools;
|
|
18
|
+
/**
|
|
19
|
+
* Get a system property value
|
|
20
|
+
*/
|
|
21
|
+
private getProperty;
|
|
22
|
+
/**
|
|
23
|
+
* Set or create a system property
|
|
24
|
+
*/
|
|
25
|
+
private setProperty;
|
|
26
|
+
/**
|
|
27
|
+
* List system properties
|
|
28
|
+
*/
|
|
29
|
+
private listProperties;
|
|
30
|
+
/**
|
|
31
|
+
* Delete a system property
|
|
32
|
+
*/
|
|
33
|
+
private deleteProperty;
|
|
34
|
+
/**
|
|
35
|
+
* Search properties
|
|
36
|
+
*/
|
|
37
|
+
private searchProperties;
|
|
38
|
+
/**
|
|
39
|
+
* Bulk get properties
|
|
40
|
+
*/
|
|
41
|
+
private bulkGetProperties;
|
|
42
|
+
/**
|
|
43
|
+
* Bulk set properties
|
|
44
|
+
*/
|
|
45
|
+
private bulkSetProperties;
|
|
46
|
+
/**
|
|
47
|
+
* Export properties
|
|
48
|
+
*/
|
|
49
|
+
private exportProperties;
|
|
50
|
+
/**
|
|
51
|
+
* Import properties
|
|
52
|
+
*/
|
|
53
|
+
private importProperties;
|
|
54
|
+
/**
|
|
55
|
+
* Validate property value
|
|
56
|
+
*/
|
|
57
|
+
private validateProperty;
|
|
58
|
+
/**
|
|
59
|
+
* Get property categories
|
|
60
|
+
*/
|
|
61
|
+
private getCategories;
|
|
62
|
+
/**
|
|
63
|
+
* Get property audit history
|
|
64
|
+
*/
|
|
65
|
+
private getPropertyHistory;
|
|
66
|
+
start(): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=servicenow-system-properties-mcp.d.ts.map
|
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ServiceNow System Properties MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Provides comprehensive system property management through official ServiceNow APIs
|
|
6
|
+
* Uses the standard Table API on sys_properties table
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ServiceNowSystemPropertiesMCP = void 0;
|
|
10
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
11
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
12
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
13
|
+
const servicenow_client_js_1 = require("../utils/servicenow-client.js");
|
|
14
|
+
const logger_js_1 = require("../utils/logger.js");
|
|
15
|
+
const logger = new logger_js_1.Logger('ServiceNowSystemProperties');
|
|
16
|
+
/**
|
|
17
|
+
* ServiceNow System Properties MCP Server
|
|
18
|
+
* Manages system properties through official ServiceNow REST APIs
|
|
19
|
+
*/
|
|
20
|
+
class ServiceNowSystemPropertiesMCP {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.propertyCache = new Map();
|
|
23
|
+
this.server = new index_js_1.Server({
|
|
24
|
+
name: 'servicenow-system-properties',
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
}, {
|
|
27
|
+
capabilities: {
|
|
28
|
+
tools: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
this.client = new servicenow_client_js_1.ServiceNowClient();
|
|
32
|
+
this.setupHandlers();
|
|
33
|
+
this.setupTools();
|
|
34
|
+
}
|
|
35
|
+
setupHandlers() {
|
|
36
|
+
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: 'snow_property_get',
|
|
40
|
+
description: 'Get a system property value by name',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
name: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Property name (e.g., glide.servlet.uri)'
|
|
47
|
+
},
|
|
48
|
+
include_metadata: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
description: 'Include full property metadata',
|
|
51
|
+
default: false
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
required: ['name']
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'snow_property_set',
|
|
59
|
+
description: 'Set or update a system property value',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
name: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Property name'
|
|
66
|
+
},
|
|
67
|
+
value: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Property value'
|
|
70
|
+
},
|
|
71
|
+
description: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Property description (optional)'
|
|
74
|
+
},
|
|
75
|
+
type: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'Property type (string, boolean, integer, etc.)',
|
|
78
|
+
default: 'string'
|
|
79
|
+
},
|
|
80
|
+
choices: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
description: 'Comma-separated list of valid choices (optional)'
|
|
83
|
+
},
|
|
84
|
+
is_private: {
|
|
85
|
+
type: 'boolean',
|
|
86
|
+
description: 'Mark property as private',
|
|
87
|
+
default: false
|
|
88
|
+
},
|
|
89
|
+
suffix: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description: 'Property suffix/scope (optional)'
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: ['name', 'value']
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'snow_property_list',
|
|
99
|
+
description: 'List system properties with optional filtering',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
pattern: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Name pattern to filter (e.g., glide.* for all glide properties)'
|
|
106
|
+
},
|
|
107
|
+
category: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Property category filter'
|
|
110
|
+
},
|
|
111
|
+
is_private: {
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
description: 'Filter by private properties'
|
|
114
|
+
},
|
|
115
|
+
limit: {
|
|
116
|
+
type: 'number',
|
|
117
|
+
description: 'Maximum number of properties to return',
|
|
118
|
+
default: 100
|
|
119
|
+
},
|
|
120
|
+
include_values: {
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
description: 'Include property values in response',
|
|
123
|
+
default: true
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'snow_property_delete',
|
|
130
|
+
description: 'Delete a system property',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
name: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Property name to delete'
|
|
137
|
+
},
|
|
138
|
+
confirm: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
description: 'Confirmation flag (must be true)',
|
|
141
|
+
default: false
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
required: ['name', 'confirm']
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'snow_property_search',
|
|
149
|
+
description: 'Search properties by name or value content',
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: {
|
|
153
|
+
search_term: {
|
|
154
|
+
type: 'string',
|
|
155
|
+
description: 'Search term to find in property names or values'
|
|
156
|
+
},
|
|
157
|
+
search_in: {
|
|
158
|
+
type: 'string',
|
|
159
|
+
description: 'Where to search: name, value, description, or all',
|
|
160
|
+
default: 'all'
|
|
161
|
+
},
|
|
162
|
+
limit: {
|
|
163
|
+
type: 'number',
|
|
164
|
+
description: 'Maximum results',
|
|
165
|
+
default: 50
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
required: ['search_term']
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'snow_property_bulk_get',
|
|
173
|
+
description: 'Get multiple properties at once',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
names: {
|
|
178
|
+
type: 'array',
|
|
179
|
+
items: { type: 'string' },
|
|
180
|
+
description: 'Array of property names to retrieve'
|
|
181
|
+
},
|
|
182
|
+
include_metadata: {
|
|
183
|
+
type: 'boolean',
|
|
184
|
+
description: 'Include full metadata for each property',
|
|
185
|
+
default: false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
required: ['names']
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'snow_property_bulk_set',
|
|
193
|
+
description: 'Set multiple properties at once',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
properties: {
|
|
198
|
+
type: 'array',
|
|
199
|
+
items: {
|
|
200
|
+
type: 'object',
|
|
201
|
+
properties: {
|
|
202
|
+
name: { type: 'string' },
|
|
203
|
+
value: { type: 'string' },
|
|
204
|
+
description: { type: 'string' },
|
|
205
|
+
type: { type: 'string' }
|
|
206
|
+
},
|
|
207
|
+
required: ['name', 'value']
|
|
208
|
+
},
|
|
209
|
+
description: 'Array of properties to set'
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
required: ['properties']
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'snow_property_export',
|
|
217
|
+
description: 'Export system properties to JSON format',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
pattern: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Pattern to filter properties (e.g., glide.*)'
|
|
224
|
+
},
|
|
225
|
+
include_system: {
|
|
226
|
+
type: 'boolean',
|
|
227
|
+
description: 'Include system properties',
|
|
228
|
+
default: false
|
|
229
|
+
},
|
|
230
|
+
include_private: {
|
|
231
|
+
type: 'boolean',
|
|
232
|
+
description: 'Include private properties',
|
|
233
|
+
default: false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'snow_property_import',
|
|
240
|
+
description: 'Import system properties from JSON',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: {
|
|
244
|
+
properties: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
description: 'JSON object with property names as keys'
|
|
247
|
+
},
|
|
248
|
+
overwrite: {
|
|
249
|
+
type: 'boolean',
|
|
250
|
+
description: 'Overwrite existing properties',
|
|
251
|
+
default: false
|
|
252
|
+
},
|
|
253
|
+
dry_run: {
|
|
254
|
+
type: 'boolean',
|
|
255
|
+
description: 'Preview changes without applying',
|
|
256
|
+
default: false
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
required: ['properties']
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'snow_property_validate',
|
|
264
|
+
description: 'Validate property value against its type and constraints',
|
|
265
|
+
inputSchema: {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: {
|
|
268
|
+
name: {
|
|
269
|
+
type: 'string',
|
|
270
|
+
description: 'Property name'
|
|
271
|
+
},
|
|
272
|
+
value: {
|
|
273
|
+
type: 'string',
|
|
274
|
+
description: 'Value to validate'
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
required: ['name', 'value']
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'snow_property_categories',
|
|
282
|
+
description: 'List all property categories',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
include_counts: {
|
|
287
|
+
type: 'boolean',
|
|
288
|
+
description: 'Include count of properties per category',
|
|
289
|
+
default: true
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'snow_property_history',
|
|
296
|
+
description: 'Get audit history for a property',
|
|
297
|
+
inputSchema: {
|
|
298
|
+
type: 'object',
|
|
299
|
+
properties: {
|
|
300
|
+
name: {
|
|
301
|
+
type: 'string',
|
|
302
|
+
description: 'Property name'
|
|
303
|
+
},
|
|
304
|
+
limit: {
|
|
305
|
+
type: 'number',
|
|
306
|
+
description: 'Number of history records',
|
|
307
|
+
default: 10
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
required: ['name']
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
setupTools() {
|
|
317
|
+
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
318
|
+
const { name, arguments: args } = request.params;
|
|
319
|
+
try {
|
|
320
|
+
// Ensure authentication
|
|
321
|
+
const isAuthenticated = await this.client.isAuthenticated();
|
|
322
|
+
if (!isAuthenticated) {
|
|
323
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Not authenticated. Please run "snow-flow auth login" first.');
|
|
324
|
+
}
|
|
325
|
+
switch (name) {
|
|
326
|
+
case 'snow_property_get':
|
|
327
|
+
return await this.getProperty(args);
|
|
328
|
+
case 'snow_property_set':
|
|
329
|
+
return await this.setProperty(args);
|
|
330
|
+
case 'snow_property_list':
|
|
331
|
+
return await this.listProperties(args);
|
|
332
|
+
case 'snow_property_delete':
|
|
333
|
+
return await this.deleteProperty(args);
|
|
334
|
+
case 'snow_property_search':
|
|
335
|
+
return await this.searchProperties(args);
|
|
336
|
+
case 'snow_property_bulk_get':
|
|
337
|
+
return await this.bulkGetProperties(args);
|
|
338
|
+
case 'snow_property_bulk_set':
|
|
339
|
+
return await this.bulkSetProperties(args);
|
|
340
|
+
case 'snow_property_export':
|
|
341
|
+
return await this.exportProperties(args);
|
|
342
|
+
case 'snow_property_import':
|
|
343
|
+
return await this.importProperties(args);
|
|
344
|
+
case 'snow_property_validate':
|
|
345
|
+
return await this.validateProperty(args);
|
|
346
|
+
case 'snow_property_categories':
|
|
347
|
+
return await this.getCategories(args);
|
|
348
|
+
case 'snow_property_history':
|
|
349
|
+
return await this.getPropertyHistory(args);
|
|
350
|
+
default:
|
|
351
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
logger.error(`Tool execution failed: ${name}`, error);
|
|
356
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, error instanceof Error ? error.message : String(error));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Get a system property value
|
|
362
|
+
*/
|
|
363
|
+
async getProperty(args) {
|
|
364
|
+
const { name, include_metadata = false } = args;
|
|
365
|
+
logger.info(`Getting property: ${name}`);
|
|
366
|
+
try {
|
|
367
|
+
const response = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
368
|
+
if (!response.success || !response.data?.result?.length) {
|
|
369
|
+
return {
|
|
370
|
+
content: [{
|
|
371
|
+
type: 'text',
|
|
372
|
+
text: `❌ Property not found: ${name}`
|
|
373
|
+
}]
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const property = response.data.result[0];
|
|
377
|
+
// Cache the property
|
|
378
|
+
this.propertyCache.set(name, property);
|
|
379
|
+
if (include_metadata) {
|
|
380
|
+
return {
|
|
381
|
+
content: [{
|
|
382
|
+
type: 'text',
|
|
383
|
+
text: `📋 **Property: ${name}**
|
|
384
|
+
|
|
385
|
+
**Value:** ${property.value || '(empty)'}
|
|
386
|
+
**Type:** ${property.type || 'string'}
|
|
387
|
+
**Description:** ${property.description || 'No description'}
|
|
388
|
+
**Suffix:** ${property.suffix || 'global'}
|
|
389
|
+
**Private:** ${property.is_private === 'true' ? 'Yes' : 'No'}
|
|
390
|
+
**Choices:** ${property.choices || 'None'}
|
|
391
|
+
**sys_id:** ${property.sys_id}
|
|
392
|
+
|
|
393
|
+
✅ Property retrieved successfully`
|
|
394
|
+
}]
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
return {
|
|
399
|
+
content: [{
|
|
400
|
+
type: 'text',
|
|
401
|
+
text: property.value || ''
|
|
402
|
+
}]
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
logger.error('Failed to get property:', error);
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Set or create a system property
|
|
413
|
+
*/
|
|
414
|
+
async setProperty(args) {
|
|
415
|
+
const { name, value, description, type = 'string', choices, is_private = false, suffix } = args;
|
|
416
|
+
logger.info(`Setting property: ${name} = ${value}`);
|
|
417
|
+
try {
|
|
418
|
+
// Check if property exists
|
|
419
|
+
const existing = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
420
|
+
let result;
|
|
421
|
+
if (existing.success && existing.data?.result?.length > 0) {
|
|
422
|
+
// Update existing property
|
|
423
|
+
const sys_id = existing.data.result[0].sys_id;
|
|
424
|
+
result = await this.client.updateRecord('sys_properties', sys_id, {
|
|
425
|
+
value,
|
|
426
|
+
...(description && { description }),
|
|
427
|
+
...(type && { type }),
|
|
428
|
+
...(choices && { choices }),
|
|
429
|
+
...(suffix && { suffix }),
|
|
430
|
+
is_private: is_private ? 'true' : 'false'
|
|
431
|
+
});
|
|
432
|
+
logger.info(`Updated property: ${name}`);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// Create new property
|
|
436
|
+
result = await this.client.createRecord('sys_properties', {
|
|
437
|
+
name,
|
|
438
|
+
value,
|
|
439
|
+
description: description || `Created by Snow-Flow`,
|
|
440
|
+
type,
|
|
441
|
+
choices: choices || '',
|
|
442
|
+
is_private: is_private ? 'true' : 'false',
|
|
443
|
+
suffix: suffix || 'global'
|
|
444
|
+
});
|
|
445
|
+
logger.info(`Created new property: ${name}`);
|
|
446
|
+
}
|
|
447
|
+
if (!result.success) {
|
|
448
|
+
throw new Error(`Failed to set property: ${result.error}`);
|
|
449
|
+
}
|
|
450
|
+
// Clear cache
|
|
451
|
+
this.propertyCache.delete(name);
|
|
452
|
+
return {
|
|
453
|
+
content: [{
|
|
454
|
+
type: 'text',
|
|
455
|
+
text: `✅ Property set successfully!
|
|
456
|
+
|
|
457
|
+
**Name:** ${name}
|
|
458
|
+
**Value:** ${value}
|
|
459
|
+
**Type:** ${type}
|
|
460
|
+
${description ? `**Description:** ${description}` : ''}
|
|
461
|
+
${choices ? `**Choices:** ${choices}` : ''}
|
|
462
|
+
**Private:** ${is_private ? 'Yes' : 'No'}
|
|
463
|
+
|
|
464
|
+
💡 Changes take effect immediately in ServiceNow`
|
|
465
|
+
}]
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
logger.error('Failed to set property:', error);
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* List system properties
|
|
475
|
+
*/
|
|
476
|
+
async listProperties(args) {
|
|
477
|
+
const { pattern, category, is_private, limit = 100, include_values = true } = args;
|
|
478
|
+
logger.info('Listing properties', { pattern, category, limit });
|
|
479
|
+
try {
|
|
480
|
+
let query = '';
|
|
481
|
+
const conditions = [];
|
|
482
|
+
if (pattern) {
|
|
483
|
+
if (pattern.includes('*')) {
|
|
484
|
+
// Convert wildcard to LIKE query
|
|
485
|
+
const likePattern = pattern.replace(/\*/g, '');
|
|
486
|
+
conditions.push(`nameLIKE${likePattern}`);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
conditions.push(`name=${pattern}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (category) {
|
|
493
|
+
conditions.push(`suffix=${category}`);
|
|
494
|
+
}
|
|
495
|
+
if (is_private !== undefined) {
|
|
496
|
+
conditions.push(`is_private=${is_private ? 'true' : 'false'}`);
|
|
497
|
+
}
|
|
498
|
+
query = conditions.join('^');
|
|
499
|
+
const response = await this.client.searchRecords('sys_properties', query, limit);
|
|
500
|
+
if (!response.success || !response.data?.result) {
|
|
501
|
+
throw new Error('Failed to list properties');
|
|
502
|
+
}
|
|
503
|
+
const properties = response.data.result;
|
|
504
|
+
// Group by category/suffix
|
|
505
|
+
const grouped = {};
|
|
506
|
+
for (const prop of properties) {
|
|
507
|
+
const category = prop.suffix || 'global';
|
|
508
|
+
if (!grouped[category])
|
|
509
|
+
grouped[category] = [];
|
|
510
|
+
grouped[category].push(prop);
|
|
511
|
+
}
|
|
512
|
+
let output = `📋 **System Properties** (Found: ${properties.length})\n\n`;
|
|
513
|
+
for (const [cat, props] of Object.entries(grouped)) {
|
|
514
|
+
output += `**Category: ${cat}**\n`;
|
|
515
|
+
for (const prop of props) {
|
|
516
|
+
if (include_values) {
|
|
517
|
+
output += `• ${prop.name} = "${prop.value || ''}"\n`;
|
|
518
|
+
if (prop.description) {
|
|
519
|
+
output += ` ↳ ${prop.description}\n`;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
output += `• ${prop.name}\n`;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
output += '\n';
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
content: [{
|
|
530
|
+
type: 'text',
|
|
531
|
+
text: output
|
|
532
|
+
}]
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
logger.error('Failed to list properties:', error);
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Delete a system property
|
|
542
|
+
*/
|
|
543
|
+
async deleteProperty(args) {
|
|
544
|
+
const { name, confirm } = args;
|
|
545
|
+
if (!confirm) {
|
|
546
|
+
return {
|
|
547
|
+
content: [{
|
|
548
|
+
type: 'text',
|
|
549
|
+
text: `⚠️ Deletion requires confirmation. Set confirm: true to proceed.
|
|
550
|
+
|
|
551
|
+
**Property to delete:** ${name}
|
|
552
|
+
|
|
553
|
+
⚠️ WARNING: Deleting system properties can affect ServiceNow functionality!`
|
|
554
|
+
}]
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
logger.info(`Deleting property: ${name}`);
|
|
558
|
+
try {
|
|
559
|
+
// Find the property
|
|
560
|
+
const response = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
561
|
+
if (!response.success || !response.data?.result?.length) {
|
|
562
|
+
return {
|
|
563
|
+
content: [{
|
|
564
|
+
type: 'text',
|
|
565
|
+
text: `❌ Property not found: ${name}`
|
|
566
|
+
}]
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const sys_id = response.data.result[0].sys_id;
|
|
570
|
+
const result = await this.client.deleteRecord('sys_properties', sys_id);
|
|
571
|
+
if (!result.success) {
|
|
572
|
+
throw new Error(`Failed to delete property: ${result.error}`);
|
|
573
|
+
}
|
|
574
|
+
// Clear cache
|
|
575
|
+
this.propertyCache.delete(name);
|
|
576
|
+
return {
|
|
577
|
+
content: [{
|
|
578
|
+
type: 'text',
|
|
579
|
+
text: `✅ Property deleted successfully: ${name}
|
|
580
|
+
|
|
581
|
+
⚠️ Note: Some properties may be recreated by ServiceNow on next access with default values.`
|
|
582
|
+
}]
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
logger.error('Failed to delete property:', error);
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Search properties
|
|
592
|
+
*/
|
|
593
|
+
async searchProperties(args) {
|
|
594
|
+
const { search_term, search_in = 'all', limit = 50 } = args;
|
|
595
|
+
logger.info(`Searching properties for: ${search_term}`);
|
|
596
|
+
try {
|
|
597
|
+
let query = '';
|
|
598
|
+
switch (search_in) {
|
|
599
|
+
case 'name':
|
|
600
|
+
query = `nameLIKE${search_term}`;
|
|
601
|
+
break;
|
|
602
|
+
case 'value':
|
|
603
|
+
query = `valueLIKE${search_term}`;
|
|
604
|
+
break;
|
|
605
|
+
case 'description':
|
|
606
|
+
query = `descriptionLIKE${search_term}`;
|
|
607
|
+
break;
|
|
608
|
+
case 'all':
|
|
609
|
+
default:
|
|
610
|
+
query = `nameLIKE${search_term}^ORvalueLIKE${search_term}^ORdescriptionLIKE${search_term}`;
|
|
611
|
+
}
|
|
612
|
+
const response = await this.client.searchRecords('sys_properties', query, limit);
|
|
613
|
+
if (!response.success || !response.data?.result) {
|
|
614
|
+
throw new Error('Search failed');
|
|
615
|
+
}
|
|
616
|
+
const results = response.data.result;
|
|
617
|
+
if (results.length === 0) {
|
|
618
|
+
return {
|
|
619
|
+
content: [{
|
|
620
|
+
type: 'text',
|
|
621
|
+
text: `No properties found matching: "${search_term}"`
|
|
622
|
+
}]
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
let output = `🔍 **Search Results** (Found: ${results.length})\n`;
|
|
626
|
+
output += `Search term: "${search_term}" in ${search_in}\n\n`;
|
|
627
|
+
for (const prop of results) {
|
|
628
|
+
output += `**${prop.name}**\n`;
|
|
629
|
+
output += `• Value: ${prop.value || '(empty)'}\n`;
|
|
630
|
+
if (prop.description) {
|
|
631
|
+
output += `• Description: ${prop.description}\n`;
|
|
632
|
+
}
|
|
633
|
+
output += '\n';
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
content: [{
|
|
637
|
+
type: 'text',
|
|
638
|
+
text: output
|
|
639
|
+
}]
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
logger.error('Search failed:', error);
|
|
644
|
+
throw error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Bulk get properties
|
|
649
|
+
*/
|
|
650
|
+
async bulkGetProperties(args) {
|
|
651
|
+
const { names, include_metadata = false } = args;
|
|
652
|
+
logger.info(`Bulk getting ${names.length} properties`);
|
|
653
|
+
const results = {};
|
|
654
|
+
const errors = [];
|
|
655
|
+
for (const name of names) {
|
|
656
|
+
try {
|
|
657
|
+
// Check cache first
|
|
658
|
+
if (this.propertyCache.has(name)) {
|
|
659
|
+
results[name] = this.propertyCache.get(name);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const response = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
663
|
+
if (response.success && response.data?.result?.length > 0) {
|
|
664
|
+
const prop = response.data.result[0];
|
|
665
|
+
results[name] = include_metadata ? prop : prop.value;
|
|
666
|
+
this.propertyCache.set(name, prop);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
results[name] = null;
|
|
670
|
+
errors.push(name);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
logger.error(`Failed to get property ${name}:`, error);
|
|
675
|
+
results[name] = null;
|
|
676
|
+
errors.push(name);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
let output = `📋 **Bulk Property Retrieval**\n\n`;
|
|
680
|
+
if (include_metadata) {
|
|
681
|
+
output += JSON.stringify(results, null, 2);
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
for (const [name, value] of Object.entries(results)) {
|
|
685
|
+
output += `• ${name} = ${value !== null ? `"${value}"` : 'NOT FOUND'}\n`;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (errors.length > 0) {
|
|
689
|
+
output += `\n⚠️ Properties not found: ${errors.join(', ')}`;
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
content: [{
|
|
693
|
+
type: 'text',
|
|
694
|
+
text: output
|
|
695
|
+
}]
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Bulk set properties
|
|
700
|
+
*/
|
|
701
|
+
async bulkSetProperties(args) {
|
|
702
|
+
const { properties } = args;
|
|
703
|
+
logger.info(`Bulk setting ${properties.length} properties`);
|
|
704
|
+
const results = {
|
|
705
|
+
created: [],
|
|
706
|
+
updated: [],
|
|
707
|
+
failed: []
|
|
708
|
+
};
|
|
709
|
+
for (const prop of properties) {
|
|
710
|
+
try {
|
|
711
|
+
// Check if exists
|
|
712
|
+
const existing = await this.client.searchRecords('sys_properties', `name=${prop.name}`, 1);
|
|
713
|
+
let result;
|
|
714
|
+
if (existing.success && existing.data?.result?.length > 0) {
|
|
715
|
+
// Update
|
|
716
|
+
const sys_id = existing.data.result[0].sys_id;
|
|
717
|
+
result = await this.client.updateRecord('sys_properties', sys_id, {
|
|
718
|
+
value: prop.value,
|
|
719
|
+
...(prop.description && { description: prop.description }),
|
|
720
|
+
...(prop.type && { type: prop.type })
|
|
721
|
+
});
|
|
722
|
+
if (result.success) {
|
|
723
|
+
results.updated.push(prop.name);
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
results.failed.push(`${prop.name}: ${result.error}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
// Create
|
|
731
|
+
result = await this.client.createRecord('sys_properties', {
|
|
732
|
+
name: prop.name,
|
|
733
|
+
value: prop.value,
|
|
734
|
+
description: prop.description || `Created by Snow-Flow bulk operation`,
|
|
735
|
+
type: prop.type || 'string'
|
|
736
|
+
});
|
|
737
|
+
if (result.success) {
|
|
738
|
+
results.created.push(prop.name);
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
results.failed.push(`${prop.name}: ${result.error}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
// Clear cache
|
|
745
|
+
this.propertyCache.delete(prop.name);
|
|
746
|
+
}
|
|
747
|
+
catch (error) {
|
|
748
|
+
logger.error(`Failed to set property ${prop.name}:`, error);
|
|
749
|
+
results.failed.push(`${prop.name}: ${error}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
content: [{
|
|
754
|
+
type: 'text',
|
|
755
|
+
text: `📦 **Bulk Property Update Results**
|
|
756
|
+
|
|
757
|
+
✅ **Created:** ${results.created.length}
|
|
758
|
+
${results.created.map(n => `• ${n}`).join('\n')}
|
|
759
|
+
|
|
760
|
+
🔄 **Updated:** ${results.updated.length}
|
|
761
|
+
${results.updated.map(n => `• ${n}`).join('\n')}
|
|
762
|
+
|
|
763
|
+
${results.failed.length > 0 ? `❌ **Failed:** ${results.failed.length}\n${results.failed.map(f => `• ${f}`).join('\n')}` : ''}
|
|
764
|
+
|
|
765
|
+
Total processed: ${properties.length}`
|
|
766
|
+
}]
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Export properties
|
|
771
|
+
*/
|
|
772
|
+
async exportProperties(args) {
|
|
773
|
+
const { pattern, include_system = false, include_private = false } = args;
|
|
774
|
+
logger.info('Exporting properties', { pattern, include_system, include_private });
|
|
775
|
+
try {
|
|
776
|
+
let query = '';
|
|
777
|
+
const conditions = [];
|
|
778
|
+
if (pattern) {
|
|
779
|
+
if (pattern.includes('*')) {
|
|
780
|
+
const likePattern = pattern.replace(/\*/g, '');
|
|
781
|
+
conditions.push(`nameLIKE${likePattern}`);
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
conditions.push(`name=${pattern}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (!include_system) {
|
|
788
|
+
conditions.push(`name!=glide.*^name!=sys.*`);
|
|
789
|
+
}
|
|
790
|
+
if (!include_private) {
|
|
791
|
+
conditions.push(`is_private=false`);
|
|
792
|
+
}
|
|
793
|
+
query = conditions.join('^');
|
|
794
|
+
const response = await this.client.searchRecords('sys_properties', query, 1000);
|
|
795
|
+
if (!response.success || !response.data?.result) {
|
|
796
|
+
throw new Error('Export failed');
|
|
797
|
+
}
|
|
798
|
+
const properties = response.data.result;
|
|
799
|
+
const exportData = {};
|
|
800
|
+
for (const prop of properties) {
|
|
801
|
+
exportData[prop.name] = {
|
|
802
|
+
value: prop.value,
|
|
803
|
+
type: prop.type || 'string',
|
|
804
|
+
description: prop.description || '',
|
|
805
|
+
suffix: prop.suffix || 'global',
|
|
806
|
+
is_private: prop.is_private === 'true',
|
|
807
|
+
choices: prop.choices || ''
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
return {
|
|
811
|
+
content: [{
|
|
812
|
+
type: 'text',
|
|
813
|
+
text: `📤 **Properties Export** (${properties.length} properties)
|
|
814
|
+
|
|
815
|
+
\`\`\`json
|
|
816
|
+
${JSON.stringify(exportData, null, 2)}
|
|
817
|
+
\`\`\`
|
|
818
|
+
|
|
819
|
+
✅ Export complete. You can save this JSON for backup or migration.`
|
|
820
|
+
}]
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
logger.error('Export failed:', error);
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Import properties
|
|
830
|
+
*/
|
|
831
|
+
async importProperties(args) {
|
|
832
|
+
const { properties, overwrite = false, dry_run = false } = args;
|
|
833
|
+
logger.info('Importing properties', { count: Object.keys(properties).length, overwrite, dry_run });
|
|
834
|
+
const results = {
|
|
835
|
+
would_create: [],
|
|
836
|
+
would_update: [],
|
|
837
|
+
would_skip: [],
|
|
838
|
+
created: [],
|
|
839
|
+
updated: [],
|
|
840
|
+
skipped: [],
|
|
841
|
+
failed: []
|
|
842
|
+
};
|
|
843
|
+
for (const [name, data] of Object.entries(properties)) {
|
|
844
|
+
try {
|
|
845
|
+
// Check if exists
|
|
846
|
+
const existing = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
847
|
+
const exists = existing.success && existing.data?.result?.length > 0;
|
|
848
|
+
if (dry_run) {
|
|
849
|
+
if (exists && overwrite) {
|
|
850
|
+
results.would_update.push(name);
|
|
851
|
+
}
|
|
852
|
+
else if (exists && !overwrite) {
|
|
853
|
+
results.would_skip.push(name);
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
results.would_create.push(name);
|
|
857
|
+
}
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (exists && !overwrite) {
|
|
861
|
+
results.skipped.push(name);
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
const propertyData = typeof data === 'object' ? data : { value: data };
|
|
865
|
+
if (exists) {
|
|
866
|
+
// Update
|
|
867
|
+
const sys_id = existing.data.result[0].sys_id;
|
|
868
|
+
const result = await this.client.updateRecord('sys_properties', sys_id, {
|
|
869
|
+
value: propertyData.value,
|
|
870
|
+
...(propertyData.description && { description: propertyData.description }),
|
|
871
|
+
...(propertyData.type && { type: propertyData.type }),
|
|
872
|
+
...(propertyData.suffix && { suffix: propertyData.suffix }),
|
|
873
|
+
...(propertyData.choices && { choices: propertyData.choices }),
|
|
874
|
+
...(propertyData.is_private !== undefined && { is_private: propertyData.is_private ? 'true' : 'false' })
|
|
875
|
+
});
|
|
876
|
+
if (result.success) {
|
|
877
|
+
results.updated.push(name);
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
results.failed.push(`${name}: ${result.error}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
// Create
|
|
885
|
+
const result = await this.client.createRecord('sys_properties', {
|
|
886
|
+
name,
|
|
887
|
+
value: propertyData.value,
|
|
888
|
+
description: propertyData.description || `Imported by Snow-Flow`,
|
|
889
|
+
type: propertyData.type || 'string',
|
|
890
|
+
suffix: propertyData.suffix || 'global',
|
|
891
|
+
choices: propertyData.choices || '',
|
|
892
|
+
is_private: propertyData.is_private ? 'true' : 'false'
|
|
893
|
+
});
|
|
894
|
+
if (result.success) {
|
|
895
|
+
results.created.push(name);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
results.failed.push(`${name}: ${result.error}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
// Clear cache
|
|
902
|
+
this.propertyCache.delete(name);
|
|
903
|
+
}
|
|
904
|
+
catch (error) {
|
|
905
|
+
logger.error(`Failed to import property ${name}:`, error);
|
|
906
|
+
results.failed.push(`${name}: ${error}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (dry_run) {
|
|
910
|
+
return {
|
|
911
|
+
content: [{
|
|
912
|
+
type: 'text',
|
|
913
|
+
text: `🔍 **Import Preview (Dry Run)**
|
|
914
|
+
|
|
915
|
+
Would create: ${results.would_create.length}
|
|
916
|
+
${results.would_create.slice(0, 10).map(n => `• ${n}`).join('\n')}${results.would_create.length > 10 ? `\n... and ${results.would_create.length - 10} more` : ''}
|
|
917
|
+
|
|
918
|
+
Would update: ${results.would_update.length}
|
|
919
|
+
${results.would_update.slice(0, 10).map(n => `• ${n}`).join('\n')}${results.would_update.length > 10 ? `\n... and ${results.would_update.length - 10} more` : ''}
|
|
920
|
+
|
|
921
|
+
Would skip: ${results.would_skip.length}
|
|
922
|
+
${results.would_skip.slice(0, 10).map(n => `• ${n}`).join('\n')}${results.would_skip.length > 10 ? `\n... and ${results.would_skip.length - 10} more` : ''}
|
|
923
|
+
|
|
924
|
+
✅ Run with dry_run: false to apply changes`
|
|
925
|
+
}]
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
content: [{
|
|
930
|
+
type: 'text',
|
|
931
|
+
text: `📥 **Import Results**
|
|
932
|
+
|
|
933
|
+
✅ Created: ${results.created.length}
|
|
934
|
+
🔄 Updated: ${results.updated.length}
|
|
935
|
+
⏭️ Skipped: ${results.skipped.length}
|
|
936
|
+
${results.failed.length > 0 ? `❌ Failed: ${results.failed.length}\n${results.failed.join('\n')}` : ''}
|
|
937
|
+
|
|
938
|
+
Total processed: ${Object.keys(properties).length}`
|
|
939
|
+
}]
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Validate property value
|
|
944
|
+
*/
|
|
945
|
+
async validateProperty(args) {
|
|
946
|
+
const { name, value } = args;
|
|
947
|
+
logger.info(`Validating property: ${name} = ${value}`);
|
|
948
|
+
try {
|
|
949
|
+
// Get property metadata
|
|
950
|
+
const response = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
951
|
+
if (!response.success || !response.data?.result?.length) {
|
|
952
|
+
return {
|
|
953
|
+
content: [{
|
|
954
|
+
type: 'text',
|
|
955
|
+
text: `❌ Property not found: ${name}. Cannot validate.`
|
|
956
|
+
}]
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const property = response.data.result[0];
|
|
960
|
+
const validationResults = [];
|
|
961
|
+
let isValid = true;
|
|
962
|
+
// Type validation
|
|
963
|
+
if (property.type) {
|
|
964
|
+
switch (property.type) {
|
|
965
|
+
case 'boolean':
|
|
966
|
+
if (!['true', 'false', '1', '0'].includes(value.toLowerCase())) {
|
|
967
|
+
validationResults.push('❌ Value must be true/false');
|
|
968
|
+
isValid = false;
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
validationResults.push('✅ Valid boolean value');
|
|
972
|
+
}
|
|
973
|
+
break;
|
|
974
|
+
case 'integer':
|
|
975
|
+
if (!/^-?\d+$/.test(value)) {
|
|
976
|
+
validationResults.push('❌ Value must be an integer');
|
|
977
|
+
isValid = false;
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
validationResults.push('✅ Valid integer value');
|
|
981
|
+
}
|
|
982
|
+
break;
|
|
983
|
+
case 'float':
|
|
984
|
+
case 'decimal':
|
|
985
|
+
if (!/^-?\d*\.?\d+$/.test(value)) {
|
|
986
|
+
validationResults.push('❌ Value must be a number');
|
|
987
|
+
isValid = false;
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
validationResults.push('✅ Valid numeric value');
|
|
991
|
+
}
|
|
992
|
+
break;
|
|
993
|
+
case 'string':
|
|
994
|
+
default:
|
|
995
|
+
validationResults.push('✅ Valid string value');
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
// Choices validation
|
|
999
|
+
if (property.choices) {
|
|
1000
|
+
const validChoices = property.choices.split(',').map((c) => c.trim());
|
|
1001
|
+
if (!validChoices.includes(value)) {
|
|
1002
|
+
validationResults.push(`❌ Value must be one of: ${validChoices.join(', ')}`);
|
|
1003
|
+
isValid = false;
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
validationResults.push('✅ Valid choice');
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return {
|
|
1010
|
+
content: [{
|
|
1011
|
+
type: 'text',
|
|
1012
|
+
text: `🔍 **Property Validation: ${name}**
|
|
1013
|
+
|
|
1014
|
+
**Current Value:** ${property.value}
|
|
1015
|
+
**New Value:** ${value}
|
|
1016
|
+
**Type:** ${property.type || 'string'}
|
|
1017
|
+
${property.choices ? `**Valid Choices:** ${property.choices}` : ''}
|
|
1018
|
+
|
|
1019
|
+
**Validation Results:**
|
|
1020
|
+
${validationResults.join('\n')}
|
|
1021
|
+
|
|
1022
|
+
**Overall:** ${isValid ? '✅ VALID' : '❌ INVALID'}`
|
|
1023
|
+
}]
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
catch (error) {
|
|
1027
|
+
logger.error('Validation failed:', error);
|
|
1028
|
+
throw error;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Get property categories
|
|
1033
|
+
*/
|
|
1034
|
+
async getCategories(args) {
|
|
1035
|
+
const { include_counts = true } = args;
|
|
1036
|
+
logger.info('Getting property categories');
|
|
1037
|
+
try {
|
|
1038
|
+
// Get distinct suffixes (categories)
|
|
1039
|
+
const response = await this.client.searchRecords('sys_properties', '', 1000);
|
|
1040
|
+
if (!response.success || !response.data?.result) {
|
|
1041
|
+
throw new Error('Failed to get categories');
|
|
1042
|
+
}
|
|
1043
|
+
const categories = {};
|
|
1044
|
+
for (const prop of response.data.result) {
|
|
1045
|
+
const category = prop.suffix || 'global';
|
|
1046
|
+
categories[category] = (categories[category] || 0) + 1;
|
|
1047
|
+
}
|
|
1048
|
+
const sorted = Object.entries(categories).sort((a, b) => b[1] - a[1]);
|
|
1049
|
+
let output = `📂 **Property Categories**\n\n`;
|
|
1050
|
+
for (const [category, count] of sorted) {
|
|
1051
|
+
if (include_counts) {
|
|
1052
|
+
output += `• **${category}** (${count} properties)\n`;
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
output += `• ${category}\n`;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
output += `\n📊 Total categories: ${sorted.length}`;
|
|
1059
|
+
output += `\n📋 Total properties: ${response.data.result.length}`;
|
|
1060
|
+
return {
|
|
1061
|
+
content: [{
|
|
1062
|
+
type: 'text',
|
|
1063
|
+
text: output
|
|
1064
|
+
}]
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
logger.error('Failed to get categories:', error);
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Get property audit history
|
|
1074
|
+
*/
|
|
1075
|
+
async getPropertyHistory(args) {
|
|
1076
|
+
const { name, limit = 10 } = args;
|
|
1077
|
+
logger.info(`Getting history for property: ${name}`);
|
|
1078
|
+
try {
|
|
1079
|
+
// First, get the property to get its sys_id
|
|
1080
|
+
const propResponse = await this.client.searchRecords('sys_properties', `name=${name}`, 1);
|
|
1081
|
+
if (!propResponse.success || !propResponse.data?.result?.length) {
|
|
1082
|
+
return {
|
|
1083
|
+
content: [{
|
|
1084
|
+
type: 'text',
|
|
1085
|
+
text: `❌ Property not found: ${name}`
|
|
1086
|
+
}]
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
const sys_id = propResponse.data.result[0].sys_id;
|
|
1090
|
+
// Get audit history
|
|
1091
|
+
const auditResponse = await this.client.searchRecords('sys_audit', `documentkey=${sys_id}^tablename=sys_properties`, limit);
|
|
1092
|
+
if (!auditResponse.success || !auditResponse.data?.result?.length) {
|
|
1093
|
+
return {
|
|
1094
|
+
content: [{
|
|
1095
|
+
type: 'text',
|
|
1096
|
+
text: `📜 No audit history found for property: ${name}`
|
|
1097
|
+
}]
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
let output = `📜 **Audit History: ${name}**\n\n`;
|
|
1101
|
+
for (const audit of auditResponse.data.result) {
|
|
1102
|
+
output += `**${audit.sys_created_on}**\n`;
|
|
1103
|
+
output += `• User: ${audit.sys_created_by}\n`;
|
|
1104
|
+
output += `• Field: ${audit.fieldname}\n`;
|
|
1105
|
+
output += `• Old: ${audit.oldvalue || '(empty)'}\n`;
|
|
1106
|
+
output += `• New: ${audit.newvalue || '(empty)'}\n`;
|
|
1107
|
+
output += '\n';
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
content: [{
|
|
1111
|
+
type: 'text',
|
|
1112
|
+
text: output
|
|
1113
|
+
}]
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
catch (error) {
|
|
1117
|
+
logger.error('Failed to get history:', error);
|
|
1118
|
+
// Audit might not be available
|
|
1119
|
+
return {
|
|
1120
|
+
content: [{
|
|
1121
|
+
type: 'text',
|
|
1122
|
+
text: `⚠️ Audit history not available for this property or table.
|
|
1123
|
+
|
|
1124
|
+
Note: Audit history requires sys_audit to be enabled for sys_properties table.`
|
|
1125
|
+
}]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async start() {
|
|
1130
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
1131
|
+
await this.server.connect(transport);
|
|
1132
|
+
logger.info('ServiceNow System Properties MCP Server started');
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
exports.ServiceNowSystemPropertiesMCP = ServiceNowSystemPropertiesMCP;
|
|
1136
|
+
// Start the server
|
|
1137
|
+
const server = new ServiceNowSystemPropertiesMCP();
|
|
1138
|
+
server.start().catch((error) => {
|
|
1139
|
+
console.error('Failed to start ServiceNow System Properties MCP:', error);
|
|
1140
|
+
process.exit(1);
|
|
1141
|
+
});
|
|
1142
|
+
//# sourceMappingURL=servicenow-system-properties-mcp.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snow-flow",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "Snow-Flow v3.3.
|
|
3
|
+
"version": "3.3.8",
|
|
4
|
+
"description": "Snow-Flow v3.3.8: SYSTEM PROPERTIES MCP - Complete system property management via official ServiceNow APIs! 12 new tools: get, set, list, delete, search, bulk operations, import/export, validation, categories, and audit history. All using standard Table API on sys_properties - no hacks, 100% official! Manage configurations, feature flags, and system settings programmatically. Now 192+ MCP tools across 18 specialized servers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"mcp:clean": "node scripts/cleanup-mcp-servers.js && npm run build",
|
|
28
28
|
"mcp:start": "node scripts/start-mcp-proper.js",
|
|
29
29
|
"mcp:start-proper": "node scripts/start-mcp-proper.js",
|
|
30
|
+
"mcp:sysprops": "node scripts/start-sysprops-mcp.js",
|
|
30
31
|
"test:integration": "npm run build && node dist/tests/integration-test.js",
|
|
31
32
|
"postbuild-disabled": "npm run setup-mcp",
|
|
32
33
|
"postinstall": "node scripts/postinstall.js",
|