snow-flow 1.1.67 → 1.1.69

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.
@@ -694,6 +694,157 @@ class ServiceNowOperationsMCP {
694
694
  }
695
695
  }
696
696
  }
697
+ },
698
+ // User and Group Management Tools
699
+ {
700
+ name: 'snow_create_user_group',
701
+ description: 'Create a new user group in ServiceNow',
702
+ inputSchema: {
703
+ type: 'object',
704
+ properties: {
705
+ name: {
706
+ type: 'string',
707
+ description: 'Group name (e.g., "Need Approval")'
708
+ },
709
+ description: {
710
+ type: 'string',
711
+ description: 'Group description'
712
+ },
713
+ email: {
714
+ type: 'string',
715
+ description: 'Group email address (optional)'
716
+ },
717
+ manager: {
718
+ type: 'string',
719
+ description: 'Group manager sys_id or user_name (optional)'
720
+ },
721
+ parent: {
722
+ type: 'string',
723
+ description: 'Parent group sys_id (optional)'
724
+ },
725
+ type: {
726
+ type: 'string',
727
+ description: 'Group type (optional)',
728
+ enum: ['', 'catalog', 'change', 'incident', 'problem', 'request']
729
+ },
730
+ active: {
731
+ type: 'boolean',
732
+ description: 'Is group active',
733
+ default: true
734
+ }
735
+ },
736
+ required: ['name']
737
+ }
738
+ },
739
+ {
740
+ name: 'snow_create_user',
741
+ description: 'Create a new user in ServiceNow',
742
+ inputSchema: {
743
+ type: 'object',
744
+ properties: {
745
+ user_name: {
746
+ type: 'string',
747
+ description: 'Unique username for login'
748
+ },
749
+ first_name: {
750
+ type: 'string',
751
+ description: 'User first name'
752
+ },
753
+ last_name: {
754
+ type: 'string',
755
+ description: 'User last name'
756
+ },
757
+ email: {
758
+ type: 'string',
759
+ description: 'User email address'
760
+ },
761
+ department: {
762
+ type: 'string',
763
+ description: 'Department name or sys_id (optional)'
764
+ },
765
+ manager: {
766
+ type: 'string',
767
+ description: 'Manager sys_id or user_name (optional)'
768
+ },
769
+ phone: {
770
+ type: 'string',
771
+ description: 'Phone number (optional)'
772
+ },
773
+ title: {
774
+ type: 'string',
775
+ description: 'Job title (optional)'
776
+ },
777
+ location: {
778
+ type: 'string',
779
+ description: 'Location name or sys_id (optional)'
780
+ },
781
+ active: {
782
+ type: 'boolean',
783
+ description: 'Is user active',
784
+ default: true
785
+ },
786
+ password: {
787
+ type: 'string',
788
+ description: 'Initial password (will require change on first login)'
789
+ }
790
+ },
791
+ required: ['user_name', 'first_name', 'last_name', 'email']
792
+ }
793
+ },
794
+ {
795
+ name: 'snow_assign_user_to_group',
796
+ description: 'Add a user to a group in ServiceNow',
797
+ inputSchema: {
798
+ type: 'object',
799
+ properties: {
800
+ user: {
801
+ type: 'string',
802
+ description: 'User sys_id or user_name'
803
+ },
804
+ group: {
805
+ type: 'string',
806
+ description: 'Group sys_id or name'
807
+ }
808
+ },
809
+ required: ['user', 'group']
810
+ }
811
+ },
812
+ {
813
+ name: 'snow_remove_user_from_group',
814
+ description: 'Remove a user from a group in ServiceNow',
815
+ inputSchema: {
816
+ type: 'object',
817
+ properties: {
818
+ user: {
819
+ type: 'string',
820
+ description: 'User sys_id or user_name'
821
+ },
822
+ group: {
823
+ type: 'string',
824
+ description: 'Group sys_id or name'
825
+ }
826
+ },
827
+ required: ['user', 'group']
828
+ }
829
+ },
830
+ {
831
+ name: 'snow_list_group_members',
832
+ description: 'List all members of a group',
833
+ inputSchema: {
834
+ type: 'object',
835
+ properties: {
836
+ group: {
837
+ type: 'string',
838
+ description: 'Group sys_id or name'
839
+ },
840
+ active_only: {
841
+ type: 'boolean',
842
+ description: 'Only show active users',
843
+ default: true
844
+ }
845
+ },
846
+ required: ['group']
847
+ }
697
848
  }
698
849
  ],
699
850
  };
@@ -735,6 +886,16 @@ class ServiceNowOperationsMCP {
735
886
  return await this.handleLinkCatalogToFlow(args);
736
887
  case 'snow_cleanup_test_artifacts':
737
888
  return await this.handleCleanupTestArtifacts(args);
889
+ case 'snow_create_user_group':
890
+ return await this.handleCreateUserGroup(args);
891
+ case 'snow_create_user':
892
+ return await this.handleCreateUser(args);
893
+ case 'snow_assign_user_to_group':
894
+ return await this.handleAssignUserToGroup(args);
895
+ case 'snow_remove_user_from_group':
896
+ return await this.handleRemoveUserFromGroup(args);
897
+ case 'snow_list_group_members':
898
+ return await this.handleListGroupMembers(args);
738
899
  default:
739
900
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool ${name} not found`);
740
901
  }
@@ -2898,6 +3059,401 @@ ${JSON.stringify(testResults, null, 2)}
2898
3059
  // This would need to get the instance URL from credentials
2899
3060
  return 'https://instance.service-now.com';
2900
3061
  }
3062
+ async handleCreateUserGroup(args) {
3063
+ const { name, description, email, manager, parent, type, active = true } = args;
3064
+ logger_js_1.logger.info(`Creating user group: ${name}`);
3065
+ try {
3066
+ // Check if group already exists
3067
+ const existingGroup = await this.client.get('/api/now/table/sys_user_group', {
3068
+ sysparm_query: `name=${name}`,
3069
+ sysparm_limit: 1
3070
+ });
3071
+ if (existingGroup.result?.length > 0) {
3072
+ return {
3073
+ content: [{
3074
+ type: 'text',
3075
+ text: `✅ Group "${name}" already exists!\n\nSys ID: ${existingGroup.result[0].sys_id}\nManager: ${existingGroup.result[0].manager?.display_value || 'None'}\nType: ${existingGroup.result[0].type || 'Standard'}\nActive: ${existingGroup.result[0].active}\n\nNo action taken.`
3076
+ }]
3077
+ };
3078
+ }
3079
+ // Prepare group data
3080
+ const groupData = {
3081
+ name,
3082
+ active: active.toString()
3083
+ };
3084
+ if (description)
3085
+ groupData.description = description;
3086
+ if (email)
3087
+ groupData.email = email;
3088
+ if (type)
3089
+ groupData.type = type;
3090
+ // Handle manager lookup
3091
+ if (manager) {
3092
+ const managerUser = await this.findUserBySysIdOrUsername(manager);
3093
+ if (managerUser) {
3094
+ groupData.manager = managerUser.sys_id;
3095
+ }
3096
+ else {
3097
+ logger_js_1.logger.warn(`Manager "${manager}" not found, creating group without manager`);
3098
+ }
3099
+ }
3100
+ // Handle parent group lookup
3101
+ if (parent) {
3102
+ const parentGroup = await this.findGroupBySysIdOrName(parent);
3103
+ if (parentGroup) {
3104
+ groupData.parent = parentGroup.sys_id;
3105
+ }
3106
+ else {
3107
+ logger_js_1.logger.warn(`Parent group "${parent}" not found, creating group without parent`);
3108
+ }
3109
+ }
3110
+ // Create the group
3111
+ const result = await this.client.post('/api/now/table/sys_user_group', groupData);
3112
+ if (result.result) {
3113
+ const groupUrl = `${process.env.SNOW_INSTANCE ? `https://${process.env.SNOW_INSTANCE.replace(/\/$/, '')}.service-now.com` : ''}/sys_user_group.do?sys_id=${result.result.sys_id}`;
3114
+ return {
3115
+ content: [{
3116
+ type: 'text',
3117
+ text: `✅ **User Group Created Successfully!**\n\n**Group Name:** ${result.result.name}\n**Sys ID:** ${result.result.sys_id}\n**Type:** ${result.result.type || 'Standard'}\n**Active:** ${result.result.active}\n${result.result.manager ? `**Manager:** ${result.result.manager.display_value}` : ''}\n${result.result.parent ? `**Parent Group:** ${result.result.parent.display_value}` : ''}\n\n**View Group:** ${groupUrl}\n\nYou can now assign users to this group using \`snow_assign_user_to_group\`.`
3118
+ }]
3119
+ };
3120
+ }
3121
+ else {
3122
+ throw new Error('Failed to create group - no result returned');
3123
+ }
3124
+ }
3125
+ catch (error) {
3126
+ return {
3127
+ content: [{
3128
+ type: 'text',
3129
+ text: `❌ **Failed to create user group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to create groups (admin or user_admin role).`
3130
+ }]
3131
+ };
3132
+ }
3133
+ }
3134
+ async handleCreateUser(args) {
3135
+ const { user_name, first_name, last_name, email, department, manager, phone, title, location, active = true, password } = args;
3136
+ logger_js_1.logger.info(`Creating user: ${user_name}`);
3137
+ try {
3138
+ // Check if user already exists
3139
+ const existingUser = await this.client.get('/api/now/table/sys_user', {
3140
+ sysparm_query: `user_name=${user_name}`,
3141
+ sysparm_limit: 1
3142
+ });
3143
+ if (existingUser.result?.length > 0) {
3144
+ return {
3145
+ content: [{
3146
+ type: 'text',
3147
+ text: `✅ User "${user_name}" already exists!\n\nSys ID: ${existingUser.result[0].sys_id}\nName: ${existingUser.result[0].first_name} ${existingUser.result[0].last_name}\nEmail: ${existingUser.result[0].email}\nActive: ${existingUser.result[0].active}\n\nNo action taken.`
3148
+ }]
3149
+ };
3150
+ }
3151
+ // Prepare user data
3152
+ const userData = {
3153
+ user_name,
3154
+ first_name,
3155
+ last_name,
3156
+ email,
3157
+ active: active.toString()
3158
+ };
3159
+ if (phone)
3160
+ userData.phone = phone;
3161
+ if (title)
3162
+ userData.title = title;
3163
+ if (password)
3164
+ userData.user_password = password;
3165
+ // Handle manager lookup
3166
+ if (manager) {
3167
+ const managerUser = await this.findUserBySysIdOrUsername(manager);
3168
+ if (managerUser) {
3169
+ userData.manager = managerUser.sys_id;
3170
+ }
3171
+ else {
3172
+ logger_js_1.logger.warn(`Manager "${manager}" not found, creating user without manager`);
3173
+ }
3174
+ }
3175
+ // Handle department lookup
3176
+ if (department) {
3177
+ const dept = await this.findDepartment(department);
3178
+ if (dept) {
3179
+ userData.department = dept.sys_id;
3180
+ }
3181
+ else {
3182
+ logger_js_1.logger.warn(`Department "${department}" not found, creating user without department`);
3183
+ }
3184
+ }
3185
+ // Handle location lookup
3186
+ if (location) {
3187
+ const loc = await this.findLocation(location);
3188
+ if (loc) {
3189
+ userData.location = loc.sys_id;
3190
+ }
3191
+ else {
3192
+ logger_js_1.logger.warn(`Location "${location}" not found, creating user without location`);
3193
+ }
3194
+ }
3195
+ // Create the user
3196
+ const result = await this.client.post('/api/now/table/sys_user', userData);
3197
+ if (result.result) {
3198
+ const userUrl = `${process.env.SNOW_INSTANCE ? `https://${process.env.SNOW_INSTANCE.replace(/\/$/, '')}.service-now.com` : ''}/sys_user.do?sys_id=${result.result.sys_id}`;
3199
+ return {
3200
+ content: [{
3201
+ type: 'text',
3202
+ text: `✅ **User Created Successfully!**\n\n**Username:** ${result.result.user_name}\n**Name:** ${result.result.first_name} ${result.result.last_name}\n**Email:** ${result.result.email}\n**Sys ID:** ${result.result.sys_id}\n**Active:** ${result.result.active}\n${result.result.manager ? `**Manager:** ${result.result.manager.display_value}` : ''}\n${result.result.department ? `**Department:** ${result.result.department.display_value}` : ''}\n${password ? '\n⚠️ **Note:** User will be required to change password on first login.' : ''}\n\n**View User:** ${userUrl}\n\nYou can now assign this user to groups using \`snow_assign_user_to_group\`.`
3203
+ }]
3204
+ };
3205
+ }
3206
+ else {
3207
+ throw new Error('Failed to create user - no result returned');
3208
+ }
3209
+ }
3210
+ catch (error) {
3211
+ return {
3212
+ content: [{
3213
+ type: 'text',
3214
+ text: `❌ **Failed to create user**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to create users (admin or user_admin role).`
3215
+ }]
3216
+ };
3217
+ }
3218
+ }
3219
+ async handleAssignUserToGroup(args) {
3220
+ const { user, group } = args;
3221
+ logger_js_1.logger.info(`Assigning user ${user} to group ${group}`);
3222
+ try {
3223
+ // Find the user
3224
+ const userRecord = await this.findUserBySysIdOrUsername(user);
3225
+ if (!userRecord) {
3226
+ return {
3227
+ content: [{
3228
+ type: 'text',
3229
+ text: `❌ User "${user}" not found. Please check the username or sys_id.`
3230
+ }]
3231
+ };
3232
+ }
3233
+ // Find the group
3234
+ const groupRecord = await this.findGroupBySysIdOrName(group);
3235
+ if (!groupRecord) {
3236
+ return {
3237
+ content: [{
3238
+ type: 'text',
3239
+ text: `❌ Group "${group}" not found. Please check the group name or sys_id.\n\nTip: You can create a new group using \`snow_create_user_group\`.`
3240
+ }]
3241
+ };
3242
+ }
3243
+ // Check if user is already in group
3244
+ const existingMembership = await this.client.get('/api/now/table/sys_user_grmember', {
3245
+ sysparm_query: `user=${userRecord.sys_id}^group=${groupRecord.sys_id}`,
3246
+ sysparm_limit: 1
3247
+ });
3248
+ if (existingMembership.result?.length > 0) {
3249
+ return {
3250
+ content: [{
3251
+ type: 'text',
3252
+ text: `ℹ️ User "${userRecord.user_name}" is already a member of group "${groupRecord.name}".\n\nNo action taken.`
3253
+ }]
3254
+ };
3255
+ }
3256
+ // Add user to group
3257
+ const result = await this.client.post('/api/now/table/sys_user_grmember', {
3258
+ user: userRecord.sys_id,
3259
+ group: groupRecord.sys_id
3260
+ });
3261
+ if (result.result) {
3262
+ return {
3263
+ content: [{
3264
+ type: 'text',
3265
+ text: `✅ **User Added to Group Successfully!**\n\n**User:** ${userRecord.first_name} ${userRecord.last_name} (${userRecord.user_name})\n**Group:** ${groupRecord.name}\n**Membership Sys ID:** ${result.result.sys_id}\n\nThe user now has all permissions associated with the "${groupRecord.name}" group.`
3266
+ }]
3267
+ };
3268
+ }
3269
+ else {
3270
+ throw new Error('Failed to add user to group - no result returned');
3271
+ }
3272
+ }
3273
+ catch (error) {
3274
+ return {
3275
+ content: [{
3276
+ type: 'text',
3277
+ text: `❌ **Failed to assign user to group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to manage group membership.`
3278
+ }]
3279
+ };
3280
+ }
3281
+ }
3282
+ async handleRemoveUserFromGroup(args) {
3283
+ const { user, group } = args;
3284
+ logger_js_1.logger.info(`Removing user ${user} from group ${group}`);
3285
+ try {
3286
+ // Find the user
3287
+ const userRecord = await this.findUserBySysIdOrUsername(user);
3288
+ if (!userRecord) {
3289
+ return {
3290
+ content: [{
3291
+ type: 'text',
3292
+ text: `❌ User "${user}" not found. Please check the username or sys_id.`
3293
+ }]
3294
+ };
3295
+ }
3296
+ // Find the group
3297
+ const groupRecord = await this.findGroupBySysIdOrName(group);
3298
+ if (!groupRecord) {
3299
+ return {
3300
+ content: [{
3301
+ type: 'text',
3302
+ text: `❌ Group "${group}" not found. Please check the group name or sys_id.`
3303
+ }]
3304
+ };
3305
+ }
3306
+ // Find the membership record
3307
+ const membership = await this.client.get('/api/now/table/sys_user_grmember', {
3308
+ sysparm_query: `user=${userRecord.sys_id}^group=${groupRecord.sys_id}`,
3309
+ sysparm_limit: 1
3310
+ });
3311
+ if (!membership.result || membership.result.length === 0) {
3312
+ return {
3313
+ content: [{
3314
+ type: 'text',
3315
+ text: `ℹ️ User "${userRecord.user_name}" is not a member of group "${groupRecord.name}".\n\nNo action taken.`
3316
+ }]
3317
+ };
3318
+ }
3319
+ // Remove user from group
3320
+ await this.client.delete(`/api/now/table/sys_user_grmember/${membership.result[0].sys_id}`);
3321
+ return {
3322
+ content: [{
3323
+ type: 'text',
3324
+ text: `✅ **User Removed from Group Successfully!**\n\n**User:** ${userRecord.first_name} ${userRecord.last_name} (${userRecord.user_name})\n**Group:** ${groupRecord.name}\n\nThe user no longer has permissions associated with the "${groupRecord.name}" group.`
3325
+ }]
3326
+ };
3327
+ }
3328
+ catch (error) {
3329
+ return {
3330
+ content: [{
3331
+ type: 'text',
3332
+ text: `❌ **Failed to remove user from group**\n\nError: ${error instanceof Error ? error.message : String(error)}\n\nTip: Ensure you have the necessary permissions to manage group membership.`
3333
+ }]
3334
+ };
3335
+ }
3336
+ }
3337
+ async handleListGroupMembers(args) {
3338
+ const { group, active_only = true } = args;
3339
+ logger_js_1.logger.info(`Listing members of group: ${group}`);
3340
+ try {
3341
+ // Find the group
3342
+ const groupRecord = await this.findGroupBySysIdOrName(group);
3343
+ if (!groupRecord) {
3344
+ return {
3345
+ content: [{
3346
+ type: 'text',
3347
+ text: `❌ Group "${group}" not found. Please check the group name or sys_id.`
3348
+ }]
3349
+ };
3350
+ }
3351
+ // Get group members
3352
+ let query = `group=${groupRecord.sys_id}`;
3353
+ if (active_only) {
3354
+ query += '^user.active=true';
3355
+ }
3356
+ const members = await this.client.get('/api/now/table/sys_user_grmember', {
3357
+ sysparm_query: query,
3358
+ sysparm_fields: 'user.user_name,user.first_name,user.last_name,user.email,user.title,user.active,user.sys_id',
3359
+ sysparm_limit: 100
3360
+ });
3361
+ if (!members.result || members.result.length === 0) {
3362
+ return {
3363
+ content: [{
3364
+ type: 'text',
3365
+ text: `ℹ️ **Group "${groupRecord.name}" has no members.**\n\nYou can add users to this group using \`snow_assign_user_to_group\`.`
3366
+ }]
3367
+ };
3368
+ }
3369
+ // Format member list
3370
+ let response = `👥 **Members of "${groupRecord.name}" Group**\n\n`;
3371
+ response += `**Total Members:** ${members.result.length}${active_only ? ' (active only)' : ''}\n\n`;
3372
+ members.result.forEach((member, index) => {
3373
+ const u = member.user;
3374
+ response += `${index + 1}. **${u.first_name} ${u.last_name}** (${u.user_name})\n`;
3375
+ response += ` - Email: ${u.email}\n`;
3376
+ if (u.title)
3377
+ response += ` - Title: ${u.title}\n`;
3378
+ response += ` - Active: ${u.active}\n`;
3379
+ response += ` - Sys ID: ${u.sys_id}\n\n`;
3380
+ });
3381
+ if (members.result.length === 100) {
3382
+ response += `\n⚠️ **Note:** Results limited to 100 members. The group may have additional members.`;
3383
+ }
3384
+ return {
3385
+ content: [{
3386
+ type: 'text',
3387
+ text: response
3388
+ }]
3389
+ };
3390
+ }
3391
+ catch (error) {
3392
+ return {
3393
+ content: [{
3394
+ type: 'text',
3395
+ text: `❌ **Failed to list group members**\n\nError: ${error instanceof Error ? error.message : String(error)}`
3396
+ }]
3397
+ };
3398
+ }
3399
+ }
3400
+ // Helper methods
3401
+ async findUserBySysIdOrUsername(identifier) {
3402
+ // First try as sys_id
3403
+ if (identifier.match(/^[a-f0-9]{32}$/)) {
3404
+ const result = await this.client.get(`/api/now/table/sys_user/${identifier}`);
3405
+ if (result.result)
3406
+ return result.result;
3407
+ }
3408
+ // Then try as username
3409
+ const result = await this.client.get('/api/now/table/sys_user', {
3410
+ sysparm_query: `user_name=${identifier}`,
3411
+ sysparm_limit: 1
3412
+ });
3413
+ return result.result?.[0] || null;
3414
+ }
3415
+ async findGroupBySysIdOrName(identifier) {
3416
+ // First try as sys_id
3417
+ if (identifier.match(/^[a-f0-9]{32}$/)) {
3418
+ const result = await this.client.get(`/api/now/table/sys_user_group/${identifier}`);
3419
+ if (result.result)
3420
+ return result.result;
3421
+ }
3422
+ // Then try as name
3423
+ const result = await this.client.get('/api/now/table/sys_user_group', {
3424
+ sysparm_query: `name=${identifier}`,
3425
+ sysparm_limit: 1
3426
+ });
3427
+ return result.result?.[0] || null;
3428
+ }
3429
+ async findDepartment(identifier) {
3430
+ // First try as sys_id
3431
+ if (identifier.match(/^[a-f0-9]{32}$/)) {
3432
+ const result = await this.client.get(`/api/now/table/cmn_department/${identifier}`);
3433
+ if (result.result)
3434
+ return result.result;
3435
+ }
3436
+ // Then try as name
3437
+ const result = await this.client.get('/api/now/table/cmn_department', {
3438
+ sysparm_query: `name=${identifier}`,
3439
+ sysparm_limit: 1
3440
+ });
3441
+ return result.result?.[0] || null;
3442
+ }
3443
+ async findLocation(identifier) {
3444
+ // First try as sys_id
3445
+ if (identifier.match(/^[a-f0-9]{32}$/)) {
3446
+ const result = await this.client.get(`/api/now/table/cmn_location/${identifier}`);
3447
+ if (result.result)
3448
+ return result.result;
3449
+ }
3450
+ // Then try as name
3451
+ const result = await this.client.get('/api/now/table/cmn_location', {
3452
+ sysparm_query: `name=${identifier}`,
3453
+ sysparm_limit: 1
3454
+ });
3455
+ return result.result?.[0] || null;
3456
+ }
2901
3457
  async run() {
2902
3458
  const transport = new stdio_js_1.StdioServerTransport();
2903
3459
  await this.server.connect(transport);