snow-flow 10.0.1-dev.424 → 10.0.1-dev.426

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.1-dev.424",
3
+ "version": "10.0.1-dev.426",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -135,6 +135,12 @@ await activity_start({
135
135
  updateSetUrl: "https://instance.service-now.com/sys_update_set.do?sys_id=abc123..."
136
136
  });
137
137
 
138
+ await activity_update({
139
+ activityId: activityId,
140
+ status: "in_progress", // or "review", "failed", "cancelled"
141
+ summary: "Progress update: completed widget creation, starting business rule"
142
+ });
143
+
138
144
  await activity_add_artifact({
139
145
  activityId: activityId,
140
146
  artifactType: "widget",
@@ -83,6 +83,7 @@ import * as platformTools from '../tools/platform/index.js';
83
83
  import * as devopsTools from '../tools/devops/index.js';
84
84
  import * as virtualAgentTools from '../tools/virtual-agent/index.js';
85
85
  import * as flowDesignerTools from '../tools/flow-designer/index.js';
86
+ import * as pluginsTools from '../tools/plugins/index.js';
86
87
 
87
88
  // ES Module compatible __dirname
88
89
  const __filename = fileURLToPath(import.meta.url);
@@ -150,6 +151,7 @@ const STATIC_TOOL_MODULES: Record<string, any> = {
150
151
  'devops': devopsTools,
151
152
  'virtual-agent': virtualAgentTools,
152
153
  'flow-designer': flowDesignerTools,
154
+ 'plugins': pluginsTools,
153
155
  };
154
156
 
155
157
  export class ToolRegistry {
@@ -4,10 +4,10 @@
4
4
  * Create, list, get, update, activate, deactivate, delete and publish
5
5
  * Flow Designer flows and subflows.
6
6
  *
7
- * For create/create_subflow: uses a Scheduled Job (sysauto_script) that
8
- * runs GlideRecord server-side, ensuring sys_hub_flow_version records are
9
- * created, flow_definition is set, and sn_fd engine registration is
10
- * attempted. Falls back to Table API if the scheduled job fails.
7
+ * For create/create_subflow: uses the ProcessFlow REST API
8
+ * (/api/now/processflow/flow) the same endpoint the Flow Designer UI
9
+ * uses. This creates engine-registered flows that can be opened in Flow
10
+ * Designer without "corrupted flow" errors. Falls back to Table API.
11
11
  *
12
12
  * All other actions (list, get, update, activate, etc.) use the Table API.
13
13
  */
@@ -36,6 +36,94 @@ function escForScript(s: string): string {
36
36
  return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '');
37
37
  }
38
38
 
39
+ /**
40
+ * Create a flow via the ProcessFlow REST API — the same endpoint the Flow Designer UI uses.
41
+ *
42
+ * Uses /api/now/processflow/flow to create engine-registered flows, then
43
+ * /api/now/processflow/versioning/create_version for versioning.
44
+ *
45
+ * This is the only method that produces flows openable in Flow Designer
46
+ * without "corrupted flow" errors, because it sets:
47
+ * - flowEngineVersion
48
+ * - label_cache / attributes
49
+ * - flowCatalogVariableModelId
50
+ * - proper internal_name
51
+ */
52
+ async function createFlowViaProcessFlowAPI(
53
+ client: any,
54
+ params: {
55
+ name: string;
56
+ description: string;
57
+ isSubflow: boolean;
58
+ runAs: string;
59
+ shouldActivate: boolean;
60
+ }
61
+ ): Promise<{
62
+ success: boolean;
63
+ flowSysId?: string;
64
+ versionCreated?: boolean;
65
+ flowData?: any;
66
+ error?: string;
67
+ }> {
68
+ try {
69
+ // Step 1: Create flow via ProcessFlow API (same as Flow Designer UI)
70
+ var flowResp = await client.post(
71
+ '/api/now/processflow/flow',
72
+ {
73
+ access: 'public',
74
+ description: params.description || '',
75
+ flowPriority: 'MEDIUM',
76
+ name: params.name,
77
+ protection: '',
78
+ runAs: params.runAs || 'user',
79
+ runWithRoles: { value: '', displayValue: '' },
80
+ scope: 'global',
81
+ scopeDisplayName: '',
82
+ scopeName: '',
83
+ security: { can_read: true, can_write: true },
84
+ status: 'draft',
85
+ type: params.isSubflow ? 'subflow' : 'flow',
86
+ userHasRolesAssignedToFlow: true,
87
+ active: false,
88
+ deleted: false
89
+ },
90
+ {
91
+ params: {
92
+ param_only_properties: 'true',
93
+ sysparm_transaction_scope: 'global'
94
+ }
95
+ }
96
+ );
97
+
98
+ var flowResult = flowResp.data?.result?.data;
99
+ if (!flowResult?.id) {
100
+ var errDetail = flowResp.data?.result?.errorMessage || 'no flow id returned';
101
+ return { success: false, error: 'ProcessFlow API: ' + errDetail };
102
+ }
103
+
104
+ var flowSysId = flowResult.id;
105
+
106
+ // Step 2: Create version via ProcessFlow versioning API
107
+ var versionCreated = false;
108
+ try {
109
+ await client.post(
110
+ '/api/now/processflow/versioning/create_version',
111
+ { item_sys_id: flowSysId, type: 'Autosave', annotation: '', favorite: false },
112
+ { params: { sysparm_transaction_scope: 'global' } }
113
+ );
114
+ versionCreated = true;
115
+ } catch (_) {
116
+ // Non-fatal: flow was created, version creation is best-effort
117
+ }
118
+
119
+ return { success: true, flowSysId, versionCreated, flowData: flowResult };
120
+ } catch (e: any) {
121
+ var msg = e.message || '';
122
+ try { msg += ' — ' + JSON.stringify(e.response?.data || '').substring(0, 200); } catch (_) {}
123
+ return { success: false, error: 'ProcessFlow API: ' + msg };
124
+ }
125
+ }
126
+
39
127
  /**
40
128
  * Create a flow via a ServiceNow Scheduled Job (sysauto_script).
41
129
  *
@@ -1405,7 +1493,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1405
1493
  switch (action) {
1406
1494
 
1407
1495
  // ────────────────────────────────────────────────────────────────
1408
- // CREATE (Scripted REST API → Table API fallback)
1496
+ // CREATE (ProcessFlow API → Table API fallback)
1409
1497
  // ────────────────────────────────────────────────────────────────
1410
1498
  case 'create':
1411
1499
  case 'create_subflow': {
@@ -1468,7 +1556,7 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1468
1556
  delete flowDefinition.trigger;
1469
1557
  }
1470
1558
 
1471
- // ── Pipeline: Scheduled Job (primary) → Table API (fallback) ──
1559
+ // ── Pipeline: ProcessFlow API (primary) → Table API (fallback) ──
1472
1560
  var flowSysId: string | null = null;
1473
1561
  var usedMethod = 'table_api';
1474
1562
  var versionCreated = false;
@@ -1477,82 +1565,119 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1477
1565
  var actionsCreated = 0;
1478
1566
  var varsCreated = 0;
1479
1567
 
1480
- // Diagnostics: track every step for debugging "flow cannot be found" issues
1568
+ // Diagnostics
1481
1569
  var diagnostics: any = {
1570
+ processflow_api: null,
1482
1571
  table_api_used: false,
1483
1572
  version_created: false,
1484
1573
  version_method: null,
1485
1574
  post_verify: null
1486
1575
  };
1487
1576
 
1488
- // ── Scheduled Job (primary — server-side GlideRecord) ───────
1489
- // This runs inside ServiceNow as a system job, so it CAN set
1490
- // computed fields like latest_version that Table API cannot.
1491
- // Also attempts sn_fd engine registration server-side.
1577
+ // ── ProcessFlow API (primary — same REST endpoint as Flow Designer UI) ──
1578
+ // Uses /api/now/processflow/flow to create engine-registered flows,
1579
+ // then /api/now/processflow/versioning/create_version for versioning.
1492
1580
  try {
1493
- var scheduledResult = await createFlowViaScheduledJob(client, {
1581
+ var pfResult = await createFlowViaProcessFlowAPI(client, {
1494
1582
  name: flowName,
1495
1583
  description: flowDescription,
1496
- internalName: sanitizeInternalName(flowName),
1497
1584
  isSubflow: isSubflow,
1498
- category: flowCategory,
1499
1585
  runAs: flowRunAs,
1500
- shouldActivate: shouldActivate,
1501
- triggerType: triggerType,
1502
- triggerTable: flowTable,
1503
- triggerCondition: triggerCondition,
1504
- activities: activitiesArg.map(function (act: any, idx: number) {
1505
- return { name: act.name, type: act.type || 'script', inputs: act.inputs || {} };
1506
- }),
1507
- inputs: inputsArg,
1508
- outputs: outputsArg,
1509
- flowDefinition: flowDefinition
1586
+ shouldActivate: shouldActivate
1510
1587
  });
1511
- diagnostics.scheduled_job = {
1512
- success: scheduledResult.success,
1513
- tierUsed: scheduledResult.tierUsed,
1514
- latestVersionSet: scheduledResult.latestVersionSet,
1515
- latestVersionValue: scheduledResult.latestVersionValue,
1516
- steps: scheduledResult.steps,
1517
- error: scheduledResult.error
1588
+ diagnostics.processflow_api = {
1589
+ success: pfResult.success,
1590
+ versionCreated: pfResult.versionCreated,
1591
+ error: pfResult.error
1518
1592
  };
1519
- if (scheduledResult.success && scheduledResult.flowSysId) {
1520
- flowSysId = scheduledResult.flowSysId;
1521
- usedMethod = 'scheduled_job (' + (scheduledResult.tierUsed || 'unknown') + ')';
1522
- versionCreated = !!scheduledResult.versionSysId;
1593
+ if (pfResult.success && pfResult.flowSysId) {
1594
+ flowSysId = pfResult.flowSysId;
1595
+ usedMethod = 'processflow_api';
1596
+ versionCreated = !!pfResult.versionCreated;
1523
1597
  diagnostics.version_created = versionCreated;
1524
- diagnostics.version_method = 'scheduled_job';
1525
- diagnostics.latest_version_auto_set = scheduledResult.latestVersionSet;
1526
- // Extract engine registration results from scheduled job
1527
- if (scheduledResult.steps?.engine) {
1528
- diagnostics.engine_registration = scheduledResult.steps.engine;
1529
- }
1530
- // Extract trigger/action/variable results from scheduled job
1531
- if (scheduledResult.steps?.trigger) {
1532
- triggerCreated = !!scheduledResult.steps.trigger.success;
1533
- if (!scheduledResult.steps.trigger.success && scheduledResult.steps.trigger.error) {
1534
- factoryWarnings.push('Trigger: ' + scheduledResult.steps.trigger.error);
1598
+ diagnostics.version_method = 'processflow_api';
1599
+ }
1600
+ } catch (pfErr: any) {
1601
+ diagnostics.processflow_api = { error: pfErr.message || 'unknown' };
1602
+ factoryWarnings.push('ProcessFlow API failed: ' + (pfErr.message || pfErr));
1603
+ }
1604
+
1605
+ // ── Triggers, actions, variables for ProcessFlow-created flows ──
1606
+ // ProcessFlow API creates empty flows; add components via Table API
1607
+ if (flowSysId && usedMethod === 'processflow_api') {
1608
+ if (!isSubflow && triggerType !== 'manual') {
1609
+ try {
1610
+ var pfTrigDefId: string | null = null;
1611
+ var pfExactNames: Record<string, string[]> = {
1612
+ 'record_created': ['sn_fd.trigger.record_created', 'global.sn_fd.trigger.record_created'],
1613
+ 'record_updated': ['sn_fd.trigger.record_updated', 'global.sn_fd.trigger.record_updated'],
1614
+ 'scheduled': ['sn_fd.trigger.scheduled', 'global.sn_fd.trigger.scheduled']
1615
+ };
1616
+ var pfCands = pfExactNames[triggerType] || [];
1617
+ for (var pfi = 0; pfi < pfCands.length && !pfTrigDefId; pfi++) {
1618
+ var pfExResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
1619
+ params: { sysparm_query: 'internal_name=' + pfCands[pfi], sysparm_fields: 'sys_id', sysparm_limit: 1 }
1620
+ });
1621
+ pfTrigDefId = pfExResp.data.result?.[0]?.sys_id || null;
1535
1622
  }
1623
+ if (!pfTrigDefId) {
1624
+ var pfPfxResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
1625
+ params: { sysparm_query: 'internal_nameSTARTSWITHsn_fd.trigger', sysparm_fields: 'sys_id,internal_name', sysparm_limit: 10 }
1626
+ });
1627
+ var pfPfxResults = pfPfxResp.data.result || [];
1628
+ for (var pfpi = 0; pfpi < pfPfxResults.length && !pfTrigDefId; pfpi++) {
1629
+ if ((pfPfxResults[pfpi].internal_name || '').indexOf(triggerType.replace('record_', '')) > -1) {
1630
+ pfTrigDefId = pfPfxResults[pfpi].sys_id;
1631
+ }
1632
+ }
1633
+ }
1634
+ var pfTrigData: any = { flow: flowSysId, name: triggerType, order: 0, active: true };
1635
+ if (pfTrigDefId) pfTrigData.action_type = pfTrigDefId;
1636
+ if (flowTable) pfTrigData.table = flowTable;
1637
+ if (triggerCondition) pfTrigData.condition = triggerCondition;
1638
+ await client.post('/api/now/table/sys_hub_trigger_instance', pfTrigData);
1639
+ triggerCreated = true;
1640
+ } catch (_) { /* best-effort */ }
1641
+ }
1642
+ for (var pfai = 0; pfai < activitiesArg.length; pfai++) {
1643
+ try {
1644
+ var pfAct = activitiesArg[pfai];
1645
+ var pfActQ = 'internal_nameLIKE' + (pfAct.type || 'script') + '^ORnameLIKE' + (pfAct.type || 'script');
1646
+ var pfActDefResp = await client.get('/api/now/table/sys_hub_action_type_definition', {
1647
+ params: { sysparm_query: pfActQ, sysparm_fields: 'sys_id', sysparm_limit: 1 }
1648
+ });
1649
+ var pfActDef = pfActDefResp.data.result?.[0]?.sys_id;
1650
+ var pfInstData: any = { flow: flowSysId, name: pfAct.name, order: (pfai + 1) * 100, active: true };
1651
+ if (pfActDef) pfInstData.action_type = pfActDef;
1652
+ await client.post('/api/now/table/sys_hub_action_instance', pfInstData);
1653
+ actionsCreated++;
1654
+ } catch (_) { /* best-effort */ }
1655
+ }
1656
+ if (isSubflow) {
1657
+ for (var pfvi = 0; pfvi < inputsArg.length; pfvi++) {
1658
+ try {
1659
+ var pfInp = inputsArg[pfvi];
1660
+ await client.post('/api/now/table/sys_hub_flow_variable', {
1661
+ flow: flowSysId, name: pfInp.name, label: pfInp.label || pfInp.name,
1662
+ type: pfInp.type || 'string', mandatory: pfInp.mandatory || false,
1663
+ default_value: pfInp.default_value || '', variable_type: 'input'
1664
+ });
1665
+ varsCreated++;
1666
+ } catch (_) {}
1536
1667
  }
1537
- if (scheduledResult.steps?.actions) {
1538
- actionsCreated = scheduledResult.steps.actions.created || 0;
1539
- }
1540
- if (scheduledResult.steps?.variables) {
1541
- varsCreated = scheduledResult.steps.variables.created || 0;
1668
+ for (var pfvo = 0; pfvo < outputsArg.length; pfvo++) {
1669
+ try {
1670
+ var pfOut = outputsArg[pfvo];
1671
+ await client.post('/api/now/table/sys_hub_flow_variable', {
1672
+ flow: flowSysId, name: pfOut.name, label: pfOut.label || pfOut.name,
1673
+ type: pfOut.type || 'string', variable_type: 'output'
1674
+ });
1675
+ varsCreated++;
1676
+ } catch (_) {}
1542
1677
  }
1543
1678
  }
1544
- } catch (schedErr: any) {
1545
- diagnostics.scheduled_job = { error: schedErr.message || 'unknown' };
1546
- factoryWarnings.push('Scheduled job failed: ' + (schedErr.message || schedErr));
1547
1679
  }
1548
1680
 
1549
- // BR compile + engine REST registration skipped:
1550
- // - BR: FlowDesigner unavailable in all contexts, FlowAPI.compile returns error,
1551
- // all Script Includes (FlowDesignerScriptable etc.) don't exist on PDI instances
1552
- // - REST: all sn_fd endpoints return 400 "Requested URI does not represent any resource"
1553
- // Both add ~20s combined and provide zero value. Flow records are functional
1554
- // (listable, queryable, deletable) but cannot be opened in Flow Designer UI.
1555
-
1556
1681
  // ── Table API fallback (last resort) ─────────────────────────
1557
1682
  if (!flowSysId) {
1558
1683
  diagnostics.table_api_used = true;
@@ -1815,11 +1940,9 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1815
1940
  }
1816
1941
 
1817
1942
  // ── Build summary ───────────────────────────────────────────
1818
- var methodLabel = usedMethod.startsWith('scheduled_job')
1819
- ? 'Scheduled Job (server-side GlideRecord)'
1820
- : usedMethod === 'scripted_rest_api'
1821
- ? 'Scripted REST API (GlideRecord)'
1822
- : 'Table API' + (factoryWarnings.length > 0 ? ' (fallback)' : '');
1943
+ var methodLabel = usedMethod === 'processflow_api'
1944
+ ? 'ProcessFlow API (Flow Designer engine)'
1945
+ : 'Table API' + (factoryWarnings.length > 0 ? ' (fallback)' : '');
1823
1946
 
1824
1947
  var createSummary = summary()
1825
1948
  .success('Created ' + (isSubflow ? 'subflow' : 'flow') + ': ' + flowName)
@@ -1855,17 +1978,13 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
1855
1978
 
1856
1979
  // Diagnostics section
1857
1980
  createSummary.blank().line('Diagnostics:');
1858
- if (diagnostics.scheduled_job) {
1859
- var sj = diagnostics.scheduled_job;
1860
- createSummary.indented('Scheduled job: ' + (sj.success ? 'success' : 'failed') + (sj.tierUsed ? ' (' + sj.tierUsed + ')' : ''));
1861
- if (sj.error) createSummary.indented(' Error: ' + sj.error);
1981
+ if (diagnostics.processflow_api) {
1982
+ var pf = diagnostics.processflow_api;
1983
+ createSummary.indented('ProcessFlow API: ' + (pf.success ? 'success' : 'failed') + (pf.versionCreated ? ' (version created)' : ''));
1984
+ if (pf.error) createSummary.indented(' Error: ' + pf.error);
1862
1985
  }
1863
- createSummary.indented('Table API used: ' + diagnostics.table_api_used);
1986
+ createSummary.indented('Table API fallback used: ' + diagnostics.table_api_used);
1864
1987
  createSummary.indented('Version created: ' + diagnostics.version_created + (diagnostics.version_method ? ' (' + diagnostics.version_method + ')' : ''));
1865
- createSummary.indented('Engine compile: skipped (not available on this instance — FlowDesigner unavailable, REST 400s, no Script Includes)');
1866
- if (diagnostics.latest_version_auto_set !== undefined) {
1867
- createSummary.indented('latest_version: ' + (diagnostics.latest_version_auto_set ? 'set' : 'null'));
1868
- }
1869
1988
  if (diagnostics.post_verify) {
1870
1989
  if (diagnostics.post_verify.error) {
1871
1990
  createSummary.indented('Post-verify: error — ' + diagnostics.post_verify.error);
@@ -2251,5 +2370,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
2251
2370
  }
2252
2371
  }
2253
2372
 
2254
- export const version = '5.0.0';
2373
+ export const version = '6.0.0';
2255
2374
  export const author = 'Snow-Flow Team';
@@ -166,7 +166,7 @@ export * from './mappers/index.js';
166
166
  // Decoders (1 tool)
167
167
  export * from './decoders/index.js';
168
168
 
169
- // Plugins (3 tools)
169
+ // Plugins (1 tool - unified plugin management)
170
170
  export * from './plugins/index.js';
171
171
 
172
172
  // Addons (3 tools)
@@ -1,3 +1 @@
1
- export { toolDefinition as snow_custom_plugin_def, execute as snow_custom_plugin_exec } from './snow_custom_plugin.js';
2
- export { toolDefinition as snow_activate_plugin_def, execute as snow_activate_plugin_exec } from './snow_activate_plugin.js';
3
- export { toolDefinition as snow_list_plugins_def, execute as snow_list_plugins_exec } from './snow_list_plugins.js';
1
+ export { toolDefinition as snow_plugin_manage_def, execute as snow_plugin_manage_exec } from './snow_plugin_manage.js'
@@ -0,0 +1,295 @@
1
+ /**
2
+ * snow_plugin_manage - Unified Plugin Management
3
+ *
4
+ * Single tool for all ServiceNow plugin operations:
5
+ * list, check, activate, deactivate.
6
+ */
7
+
8
+ import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js'
9
+ import { getAuthenticatedClient } from '../../shared/auth.js'
10
+ import { createSuccessResult, createErrorResult } from '../../shared/error-handler.js'
11
+
12
+ export const toolDefinition: MCPToolDefinition = {
13
+ name: 'snow_plugin_manage',
14
+ description: `Manage ServiceNow plugins: list, check status, activate, or deactivate.
15
+
16
+ Actions:
17
+ • list — Search and list plugins. Filter by name/ID, show only active.
18
+ • check — Get detailed info on a specific plugin (status, version, dependencies).
19
+ • activate — Activate a plugin (admin only). Uses CICD API with table fallback.
20
+ • deactivate — Deactivate a plugin (admin only). Uses CICD API with table fallback.
21
+
22
+ Examples:
23
+ • { action: "list", search: "incident" }
24
+ • { action: "check", plugin_id: "com.snc.incident" }
25
+ • { action: "activate", plugin_id: "com.snc.incident" }`,
26
+ category: 'advanced',
27
+ subcategory: 'administration',
28
+ use_cases: ['plugin-discovery', 'plugin-management', 'plugin-status', 'activation', 'deactivation'],
29
+ complexity: 'intermediate',
30
+ frequency: 'low',
31
+ permission: 'write',
32
+ allowedRoles: ['developer', 'admin'],
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ action: {
37
+ type: 'string',
38
+ description: 'Operation to perform',
39
+ enum: ['list', 'check', 'activate', 'deactivate'],
40
+ },
41
+ plugin_id: {
42
+ type: 'string',
43
+ description: '[check/activate/deactivate] Plugin identifier (e.g. "com.snc.incident") or sys_id',
44
+ },
45
+ search: {
46
+ type: 'string',
47
+ description: '[list] Filter by plugin name or ID (e.g. "incident", "com.snc")',
48
+ },
49
+ active_only: {
50
+ type: 'boolean',
51
+ description: '[list] Only show active plugins',
52
+ default: false,
53
+ },
54
+ limit: {
55
+ type: 'number',
56
+ description: '[list] Max results to return (default 50)',
57
+ default: 50,
58
+ },
59
+ },
60
+ required: ['action'],
61
+ },
62
+ }
63
+
64
+ export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
65
+ const { action } = args
66
+ try {
67
+ switch (action) {
68
+ case 'list':
69
+ return await executeList(args, context)
70
+ case 'check':
71
+ return await executeCheck(args, context)
72
+ case 'activate':
73
+ return await executeActivate(args, context)
74
+ case 'deactivate':
75
+ return await executeDeactivate(args, context)
76
+ default:
77
+ return createErrorResult(
78
+ 'Unknown action: ' + action + '. Valid actions: list, check, activate, deactivate'
79
+ )
80
+ }
81
+ } catch (error: any) {
82
+ return createErrorResult(error.message)
83
+ }
84
+ }
85
+
86
+ // ==================== LIST ====================
87
+ async function executeList(args: any, context: ServiceNowContext): Promise<ToolResult> {
88
+ const { search, active_only = false, limit = 50 } = args
89
+ const client = await getAuthenticatedClient(context)
90
+
91
+ const queryParts: string[] = []
92
+ if (search) {
93
+ queryParts.push('nameLIKE' + search + '^ORidLIKE' + search)
94
+ }
95
+ if (active_only) {
96
+ queryParts.push('active=true')
97
+ }
98
+
99
+ const response = await client.get('/api/now/table/v_plugin', {
100
+ params: {
101
+ sysparm_query: queryParts.join('^'),
102
+ sysparm_fields: 'sys_id,id,name,active,version,description',
103
+ sysparm_limit: limit,
104
+ sysparm_display_value: 'true',
105
+ },
106
+ })
107
+
108
+ const plugins = response.data.result || []
109
+ const activeCount = plugins.filter((p: any) => p.active === 'true' || p.active === true).length
110
+ const inactiveCount = plugins.length - activeCount
111
+
112
+ const summary =
113
+ 'Found ' +
114
+ plugins.length +
115
+ ' plugin(s)' +
116
+ (search ? ' matching "' + search + '"' : '') +
117
+ '. Active: ' +
118
+ activeCount +
119
+ ', Inactive: ' +
120
+ inactiveCount +
121
+ '.'
122
+
123
+ return createSuccessResult(
124
+ { action: 'list', plugins, count: plugins.length },
125
+ { active_count: activeCount, inactive_count: inactiveCount },
126
+ summary
127
+ )
128
+ }
129
+
130
+ // ==================== CHECK ====================
131
+ async function executeCheck(args: any, context: ServiceNowContext): Promise<ToolResult> {
132
+ const { plugin_id } = args
133
+ if (!plugin_id) return createErrorResult('plugin_id is required for check action')
134
+
135
+ const client = await getAuthenticatedClient(context)
136
+
137
+ const response = await client.get('/api/now/table/v_plugin', {
138
+ params: {
139
+ sysparm_query: 'id=' + plugin_id + '^ORsys_id=' + plugin_id,
140
+ sysparm_fields: 'sys_id,id,name,active,version,description,parent,optional,licensable',
141
+ sysparm_limit: 1,
142
+ sysparm_display_value: 'true',
143
+ },
144
+ })
145
+
146
+ const results = response.data.result || []
147
+ if (results.length === 0) {
148
+ return createErrorResult('Plugin not found: ' + plugin_id)
149
+ }
150
+
151
+ const plugin = results[0]
152
+ const active = plugin.active === 'true' || plugin.active === true
153
+ const summary =
154
+ 'Plugin "' +
155
+ plugin.name +
156
+ '" (' +
157
+ plugin.id +
158
+ ') is ' +
159
+ (active ? 'ACTIVE' : 'INACTIVE') +
160
+ '. Version: ' +
161
+ (plugin.version || 'unknown') +
162
+ '.'
163
+
164
+ return createSuccessResult(
165
+ { action: 'check', plugin },
166
+ { status: active ? 'active' : 'inactive' },
167
+ summary
168
+ )
169
+ }
170
+
171
+ // ==================== ACTIVATE ====================
172
+ async function executeActivate(args: any, context: ServiceNowContext): Promise<ToolResult> {
173
+ const { plugin_id } = args
174
+ if (!plugin_id) return createErrorResult('plugin_id is required for activate action')
175
+
176
+ const client = await getAuthenticatedClient(context)
177
+ const plugin = await lookupPlugin(client, plugin_id)
178
+ if (!plugin) return createErrorResult('Plugin not found: ' + plugin_id)
179
+
180
+ if (plugin.active === 'true' || plugin.active === true) {
181
+ return createSuccessResult(
182
+ { action: 'activate', plugin, activated: false, already_active: true },
183
+ {},
184
+ 'Plugin "' + plugin.name + '" (' + plugin.id + ') is already active.'
185
+ )
186
+ }
187
+
188
+ const { success, method, error } = await togglePlugin(client, plugin, 'activate')
189
+ if (!success) return createErrorResult(error!)
190
+
191
+ const verified = await verifyPluginState(client, plugin.sys_id, true)
192
+ const summary = verified
193
+ ? 'Plugin "' + plugin.name + '" (' + plugin.id + ') activated successfully via ' + method + '.'
194
+ : 'Activation request sent for "' + plugin.name + '" via ' + method + '. May take a moment to fully activate.'
195
+
196
+ return createSuccessResult({ action: 'activate', plugin, activated: true, verified, method }, {}, summary)
197
+ }
198
+
199
+ // ==================== DEACTIVATE ====================
200
+ async function executeDeactivate(args: any, context: ServiceNowContext): Promise<ToolResult> {
201
+ const { plugin_id } = args
202
+ if (!plugin_id) return createErrorResult('plugin_id is required for deactivate action')
203
+
204
+ const client = await getAuthenticatedClient(context)
205
+ const plugin = await lookupPlugin(client, plugin_id)
206
+ if (!plugin) return createErrorResult('Plugin not found: ' + plugin_id)
207
+
208
+ if (plugin.active !== 'true' && plugin.active !== true) {
209
+ return createSuccessResult(
210
+ { action: 'deactivate', plugin, deactivated: false, already_inactive: true },
211
+ {},
212
+ 'Plugin "' + plugin.name + '" (' + plugin.id + ') is already inactive.'
213
+ )
214
+ }
215
+
216
+ const { success, method, error } = await togglePlugin(client, plugin, 'deactivate')
217
+ if (!success) return createErrorResult(error!)
218
+
219
+ const verified = await verifyPluginState(client, plugin.sys_id, false)
220
+ const summary = verified
221
+ ? 'Plugin "' + plugin.name + '" (' + plugin.id + ') deactivated successfully via ' + method + '.'
222
+ : 'Deactivation request sent for "' + plugin.name + '" via ' + method + '. May take a moment to fully deactivate.'
223
+
224
+ return createSuccessResult({ action: 'deactivate', plugin, deactivated: true, verified, method }, {}, summary)
225
+ }
226
+
227
+ // ==================== HELPERS ====================
228
+
229
+ async function lookupPlugin(client: any, pluginId: string): Promise<any | null> {
230
+ const response = await client.get('/api/now/table/v_plugin', {
231
+ params: {
232
+ sysparm_query: 'id=' + pluginId + '^ORsys_id=' + pluginId,
233
+ sysparm_fields: 'sys_id,id,name,active',
234
+ sysparm_limit: 1,
235
+ },
236
+ })
237
+ const results = response.data.result || []
238
+ return results.length > 0 ? results[0] : null
239
+ }
240
+
241
+ async function togglePlugin(
242
+ client: any,
243
+ plugin: any,
244
+ operation: 'activate' | 'deactivate'
245
+ ): Promise<{ success: boolean; method: string; error?: string }> {
246
+ // Try CICD Plugin API first
247
+ try {
248
+ await client.post('/api/sn_cicd/plugin/' + encodeURIComponent(plugin.id) + '/' + operation)
249
+ return { success: true, method: 'cicd_api' }
250
+ } catch (cicdError: any) {
251
+ const status = cicdError.response ? cicdError.response.status : 0
252
+ if (status === 404 || status === 400) {
253
+ // Fallback to sys_plugins PATCH
254
+ try {
255
+ const activeValue = operation === 'activate' ? 'true' : 'false'
256
+ await client.patch('/api/now/table/sys_plugins/' + plugin.sys_id, { active: activeValue })
257
+ return { success: true, method: 'table_api' }
258
+ } catch (patchError: any) {
259
+ return {
260
+ success: false,
261
+ method: '',
262
+ error:
263
+ 'Failed to ' +
264
+ operation +
265
+ ' plugin via both CICD API and Table API. CICD: ' +
266
+ (cicdError.message || 'unknown') +
267
+ '. Table API: ' +
268
+ (patchError.message || 'unknown'),
269
+ }
270
+ }
271
+ }
272
+ return { success: false, method: '', error: 'CICD Plugin API error: ' + (cicdError.message || 'unknown') }
273
+ }
274
+ }
275
+
276
+ async function verifyPluginState(client: any, sysId: string, expectActive: boolean): Promise<boolean> {
277
+ try {
278
+ const response = await client.get('/api/now/table/v_plugin', {
279
+ params: {
280
+ sysparm_query: 'sys_id=' + sysId,
281
+ sysparm_fields: 'active',
282
+ sysparm_limit: 1,
283
+ },
284
+ })
285
+ const results = response.data.result || []
286
+ if (results.length === 0) return false
287
+ const isActive = results[0].active === 'true' || results[0].active === true
288
+ return expectActive ? isActive : !isActive
289
+ } catch {
290
+ return false
291
+ }
292
+ }
293
+
294
+ export const version = '1.0.0'
295
+ export const author = 'Snow-Flow'
@@ -1,47 +0,0 @@
1
- /**
2
- * snow_activate_plugin
3
- */
4
-
5
- import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
6
- import { getAuthenticatedClient } from '../../shared/auth.js';
7
- import { createSuccessResult, createErrorResult } from '../../shared/error-handler.js';
8
-
9
- export const toolDefinition: MCPToolDefinition = {
10
- name: 'snow_activate_plugin',
11
- description: 'Activate ServiceNow plugin',
12
- // Metadata for tool discovery (not sent to LLM)
13
- category: 'advanced',
14
- subcategory: 'administration',
15
- use_cases: ['plugin-management', 'activation', 'configuration'],
16
- complexity: 'intermediate',
17
- frequency: 'low',
18
-
19
- // Permission enforcement
20
- // Classification: WRITE - Write operation based on name pattern
21
- permission: 'write',
22
- allowedRoles: ['developer', 'admin'],
23
- inputSchema: {
24
- type: 'object',
25
- properties: {
26
- plugin_id: { type: 'string', description: 'Plugin ID' }
27
- },
28
- required: ['plugin_id']
29
- }
30
- };
31
-
32
- export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
33
- const { plugin_id } = args;
34
- try {
35
- const client = await getAuthenticatedClient(context);
36
- const response = await client.post('/api/now/table/v_plugin', {
37
- id: plugin_id,
38
- active: true
39
- });
40
- return createSuccessResult({ activated: true, plugin: response.data.result });
41
- } catch (error: any) {
42
- return createErrorResult(error.message);
43
- }
44
- }
45
-
46
- export const version = '1.0.0';
47
- export const author = 'Snow-Flow SDK Migration';
@@ -1,46 +0,0 @@
1
- /**
2
- * snow_custom_plugin
3
- */
4
-
5
- import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
6
- import { createSuccessResult, createErrorResult } from '../../shared/error-handler.js';
7
-
8
- export const toolDefinition: MCPToolDefinition = {
9
- name: 'snow_custom_plugin',
10
- description: 'Execute custom plugin logic',
11
- // Metadata for tool discovery (not sent to LLM)
12
- category: 'advanced',
13
- subcategory: 'administration',
14
- use_cases: ['plugin-customization', 'extensions', 'custom-logic'],
15
- complexity: 'advanced',
16
- frequency: 'low',
17
-
18
- // Permission enforcement
19
- // Classification: WRITE - Plugin function - executes custom plugin which can modify data
20
- permission: 'write',
21
- allowedRoles: ['developer', 'admin'],
22
- inputSchema: {
23
- type: 'object',
24
- properties: {
25
- plugin_name: { type: 'string', description: 'Plugin name' },
26
- parameters: { type: 'object', description: 'Plugin parameters' }
27
- },
28
- required: ['plugin_name']
29
- }
30
- };
31
-
32
- export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
33
- const { plugin_name, parameters = {} } = args;
34
- try {
35
- return createSuccessResult({
36
- executed: true,
37
- plugin_name,
38
- parameters
39
- });
40
- } catch (error: any) {
41
- return createErrorResult(error.message);
42
- }
43
- }
44
-
45
- export const version = '1.0.0';
46
- export const author = 'Snow-Flow SDK Migration';
@@ -1,52 +0,0 @@
1
- /**
2
- * snow_list_plugins
3
- */
4
-
5
- import { MCPToolDefinition, ServiceNowContext, ToolResult } from '../../shared/types.js';
6
- import { getAuthenticatedClient } from '../../shared/auth.js';
7
- import { createSuccessResult, createErrorResult } from '../../shared/error-handler.js';
8
-
9
- export const toolDefinition: MCPToolDefinition = {
10
- name: 'snow_list_plugins',
11
- description: 'List installed plugins',
12
- // Metadata for tool discovery (not sent to LLM)
13
- category: 'advanced',
14
- subcategory: 'administration',
15
- use_cases: ['plugin-discovery', 'plugin-management', 'administration'],
16
- complexity: 'beginner',
17
- frequency: 'low',
18
-
19
- // Permission enforcement
20
- // Classification: READ - Read-only operation based on name pattern
21
- permission: 'read',
22
- allowedRoles: ['developer', 'stakeholder', 'admin'],
23
- inputSchema: {
24
- type: 'object',
25
- properties: {
26
- active_only: { type: 'boolean', default: false }
27
- }
28
- }
29
- };
30
-
31
- export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
32
- const { active_only = false } = args;
33
- try {
34
- const client = await getAuthenticatedClient(context);
35
- const query = active_only ? 'active=true' : '';
36
- const response = await client.get('/api/now/table/v_plugin', {
37
- params: {
38
- sysparm_query: query,
39
- sysparm_limit: 100
40
- }
41
- });
42
- return createSuccessResult({
43
- plugins: response.data.result,
44
- count: response.data.result.length
45
- });
46
- } catch (error: any) {
47
- return createErrorResult(error.message);
48
- }
49
- }
50
-
51
- export const version = '1.0.0';
52
- export const author = 'Snow-Flow SDK Migration';