snow-flow 10.0.21 → 10.0.23
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 +1 -1
- package/src/cli/cmd/tui/component/dialog-auth.tsx +109 -32
- package/src/project/agents-template.txt +6 -0
- package/src/servicenow/servicenow-mcp-unified/shared/tool-registry.ts +2 -0
- package/src/servicenow/servicenow-mcp-unified/tools/index.ts +1 -1
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/index.ts +1 -3
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_plugin_manage.ts +295 -0
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_activate_plugin.ts +0 -47
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_custom_plugin.ts +0 -46
- package/src/servicenow/servicenow-mcp-unified/tools/plugins/snow_list_plugins.ts +0 -52
package/package.json
CHANGED
|
@@ -1284,6 +1284,7 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1284
1284
|
| "code"
|
|
1285
1285
|
| "verifying-enterprise"
|
|
1286
1286
|
| "checking-portal-sn"
|
|
1287
|
+
| "select-sn-instance"
|
|
1287
1288
|
| "sn-method"
|
|
1288
1289
|
| "sn-instance"
|
|
1289
1290
|
| "sn-oauth-clientid"
|
|
@@ -1309,6 +1310,18 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1309
1310
|
clientSecret?: string
|
|
1310
1311
|
} | null>(null)
|
|
1311
1312
|
|
|
1313
|
+
// ServiceNow instances from enterprise portal (for multi-instance selection)
|
|
1314
|
+
const [snInstances, setSnInstances] = createSignal<
|
|
1315
|
+
Array<{
|
|
1316
|
+
id: number
|
|
1317
|
+
instanceName: string
|
|
1318
|
+
instanceUrl: string
|
|
1319
|
+
environmentType: string
|
|
1320
|
+
isDefault: boolean
|
|
1321
|
+
enabled: boolean
|
|
1322
|
+
}>
|
|
1323
|
+
>([])
|
|
1324
|
+
|
|
1312
1325
|
// ServiceNow state
|
|
1313
1326
|
const [snMethod, setSnMethod] = createSignal<"oauth" | "basic">("oauth")
|
|
1314
1327
|
const [snInstance, setSnInstance] = createSignal("")
|
|
@@ -1369,6 +1382,9 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1369
1382
|
setSessionId("")
|
|
1370
1383
|
setAuthCode("")
|
|
1371
1384
|
setTimeout(() => subdomainInput?.focus(), 10)
|
|
1385
|
+
} else if (currentStep === "select-sn-instance") {
|
|
1386
|
+
// Can't re-do enterprise auth, go back to main auth menu
|
|
1387
|
+
dialog.replace(() => <DialogAuth />)
|
|
1372
1388
|
} else if (currentStep === "sn-method") {
|
|
1373
1389
|
// Can't go back to enterprise flow, go to main menu
|
|
1374
1390
|
dialog.replace(() => <DialogAuth />)
|
|
@@ -1520,28 +1536,42 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1520
1536
|
duration: 3000,
|
|
1521
1537
|
})
|
|
1522
1538
|
|
|
1523
|
-
// Check if enterprise portal has ServiceNow
|
|
1539
|
+
// Check if enterprise portal has ServiceNow instances
|
|
1524
1540
|
setStep("checking-portal-sn")
|
|
1525
|
-
const
|
|
1541
|
+
const instances = await fetchPortalSnInstances(portalUrl, data.token)
|
|
1526
1542
|
|
|
1527
|
-
if (
|
|
1528
|
-
//
|
|
1529
|
-
|
|
1543
|
+
if (instances.length === 0) {
|
|
1544
|
+
// No instances — manual setup
|
|
1545
|
+
toast.show({
|
|
1546
|
+
variant: "info",
|
|
1547
|
+
message: "No ServiceNow instances found on portal. Please configure manually.",
|
|
1548
|
+
duration: 3000,
|
|
1549
|
+
})
|
|
1550
|
+
setStep("sn-method")
|
|
1551
|
+
} else if (instances.length === 1) {
|
|
1552
|
+
// Single instance — fetch creds and proceed
|
|
1530
1553
|
toast.show({
|
|
1531
1554
|
variant: "info",
|
|
1532
|
-
message:
|
|
1555
|
+
message: `ServiceNow instance found: ${instances[0].instanceName}`,
|
|
1533
1556
|
duration: 3000,
|
|
1534
1557
|
})
|
|
1535
|
-
|
|
1536
|
-
|
|
1558
|
+
const creds = await fetchPortalSnInstanceById(portalUrl, data.token, instances[0].id)
|
|
1559
|
+
if (creds) {
|
|
1560
|
+
setPortalSnCredentials(creds)
|
|
1561
|
+
await startBothMcpServersWithPortalCreds(portalUrl, data.token, creds, data.user)
|
|
1562
|
+
} else {
|
|
1563
|
+
toast.show({ variant: "error", message: "Failed to fetch instance credentials.", duration: 3000 })
|
|
1564
|
+
setStep("sn-method")
|
|
1565
|
+
}
|
|
1537
1566
|
} else {
|
|
1538
|
-
//
|
|
1567
|
+
// Multiple instances — show selection
|
|
1568
|
+
setSnInstances(instances)
|
|
1539
1569
|
toast.show({
|
|
1540
1570
|
variant: "info",
|
|
1541
|
-
message:
|
|
1571
|
+
message: `${instances.length} ServiceNow instances found. Please select one.`,
|
|
1542
1572
|
duration: 3000,
|
|
1543
1573
|
})
|
|
1544
|
-
setStep("sn-
|
|
1574
|
+
setStep("select-sn-instance")
|
|
1545
1575
|
}
|
|
1546
1576
|
} catch (e) {
|
|
1547
1577
|
toast.show({ variant: "error", message: e instanceof Error ? e.message : "Verification failed" })
|
|
@@ -1552,38 +1582,58 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1552
1582
|
|
|
1553
1583
|
// === Portal ServiceNow Credentials ===
|
|
1554
1584
|
|
|
1555
|
-
const
|
|
1585
|
+
const fetchPortalSnInstances = async (
|
|
1556
1586
|
portalUrl: string,
|
|
1557
|
-
token: string
|
|
1558
|
-
): Promise<
|
|
1587
|
+
token: string,
|
|
1588
|
+
): Promise<
|
|
1589
|
+
Array<{
|
|
1590
|
+
id: number
|
|
1591
|
+
instanceName: string
|
|
1592
|
+
instanceUrl: string
|
|
1593
|
+
environmentType: string
|
|
1594
|
+
isDefault: boolean
|
|
1595
|
+
enabled: boolean
|
|
1596
|
+
}>
|
|
1597
|
+
> => {
|
|
1559
1598
|
try {
|
|
1560
|
-
const response = await fetch(`${portalUrl}/api/user-credentials/servicenow/
|
|
1599
|
+
const response = await fetch(`${portalUrl}/api/user-credentials/servicenow/instances`, {
|
|
1561
1600
|
method: "GET",
|
|
1562
1601
|
headers: {
|
|
1563
1602
|
Authorization: `Bearer ${token}`,
|
|
1564
1603
|
Accept: "application/json",
|
|
1565
1604
|
},
|
|
1566
1605
|
})
|
|
1567
|
-
|
|
1568
|
-
if (!response.ok) {
|
|
1569
|
-
return null
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1606
|
+
if (!response.ok) return []
|
|
1572
1607
|
const data = await response.json()
|
|
1608
|
+
if (!data.success || !Array.isArray(data.instances)) return []
|
|
1609
|
+
return data.instances.filter((i: any) => i.enabled)
|
|
1610
|
+
} catch {
|
|
1611
|
+
return []
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1573
1614
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1615
|
+
const fetchPortalSnInstanceById = async (
|
|
1616
|
+
portalUrl: string,
|
|
1617
|
+
token: string,
|
|
1618
|
+
instanceId: number,
|
|
1619
|
+
): Promise<{ instanceUrl: string; clientId: string; clientSecret: string } | null> => {
|
|
1620
|
+
try {
|
|
1621
|
+
const response = await fetch(`${portalUrl}/api/user-credentials/servicenow/instances/${instanceId}`, {
|
|
1622
|
+
method: "GET",
|
|
1623
|
+
headers: {
|
|
1624
|
+
Authorization: `Bearer ${token}`,
|
|
1625
|
+
Accept: "application/json",
|
|
1626
|
+
},
|
|
1627
|
+
})
|
|
1628
|
+
if (!response.ok) return null
|
|
1629
|
+
const data = await response.json()
|
|
1630
|
+
if (!data.success || !data.instance) return null
|
|
1631
|
+
const inst = data.instance
|
|
1632
|
+
if (!inst.instanceUrl || !inst.clientId || !inst.clientSecret) return null
|
|
1583
1633
|
return {
|
|
1584
|
-
instanceUrl:
|
|
1585
|
-
clientId:
|
|
1586
|
-
clientSecret:
|
|
1634
|
+
instanceUrl: inst.instanceUrl,
|
|
1635
|
+
clientId: inst.clientId,
|
|
1636
|
+
clientSecret: inst.clientSecret,
|
|
1587
1637
|
}
|
|
1588
1638
|
} catch {
|
|
1589
1639
|
return null
|
|
@@ -1938,6 +1988,33 @@ function DialogAuthEnterpriseCombined() {
|
|
|
1938
1988
|
</box>
|
|
1939
1989
|
</Show>
|
|
1940
1990
|
|
|
1991
|
+
{/* Step 4c: Select ServiceNow instance */}
|
|
1992
|
+
<Show when={step() === "select-sn-instance"}>
|
|
1993
|
+
<DialogSelect
|
|
1994
|
+
title="Select ServiceNow Instance"
|
|
1995
|
+
options={snInstances().map((inst) => ({
|
|
1996
|
+
title: inst.instanceName,
|
|
1997
|
+
value: String(inst.id),
|
|
1998
|
+
description: inst.instanceUrl,
|
|
1999
|
+
footer: inst.environmentType + (inst.isDefault ? " (default)" : ""),
|
|
2000
|
+
category: "ServiceNow Instances",
|
|
2001
|
+
onSelect: async () => {
|
|
2002
|
+
setStep("completing")
|
|
2003
|
+
const portalUrl = `https://${subdomain().trim().toLowerCase()}.snow-flow.dev`
|
|
2004
|
+
const token = enterpriseData().token!
|
|
2005
|
+
const creds = await fetchPortalSnInstanceById(portalUrl, token, inst.id)
|
|
2006
|
+
if (creds) {
|
|
2007
|
+
setPortalSnCredentials(creds)
|
|
2008
|
+
await startBothMcpServersWithPortalCreds(portalUrl, token, creds, enterpriseData().user)
|
|
2009
|
+
} else {
|
|
2010
|
+
toast.show({ variant: "error", message: "Failed to fetch instance credentials.", duration: 3000 })
|
|
2011
|
+
setStep("sn-method")
|
|
2012
|
+
}
|
|
2013
|
+
},
|
|
2014
|
+
}))}
|
|
2015
|
+
/>
|
|
2016
|
+
</Show>
|
|
2017
|
+
|
|
1941
2018
|
{/* Step 5: Choose ServiceNow method */}
|
|
1942
2019
|
<Show when={step() === "sn-method"}>
|
|
1943
2020
|
<box gap={1}>
|
|
@@ -125,6 +125,12 @@ await activity_start({
|
|
|
125
125
|
updateSetUrl: "https://instance.service-now.com/sys_update_set.do?sys_id=abc123..."
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
await activity_update({
|
|
129
|
+
activityId: activityId,
|
|
130
|
+
status: "in_progress", // or "review", "failed", "cancelled"
|
|
131
|
+
summary: "Progress update: completed widget creation, starting business rule"
|
|
132
|
+
});
|
|
133
|
+
|
|
128
134
|
await activity_add_artifact({
|
|
129
135
|
activityId: activityId,
|
|
130
136
|
artifactType: "widget",
|
|
@@ -82,6 +82,7 @@ import * as integrationTools from '../tools/integration/index.js';
|
|
|
82
82
|
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
|
+
import * as pluginsTools from '../tools/plugins/index.js';
|
|
85
86
|
|
|
86
87
|
// ES Module compatible __dirname
|
|
87
88
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -148,6 +149,7 @@ const STATIC_TOOL_MODULES: Record<string, any> = {
|
|
|
148
149
|
'platform': platformTools,
|
|
149
150
|
'devops': devopsTools,
|
|
150
151
|
'virtual-agent': virtualAgentTools,
|
|
152
|
+
'plugins': pluginsTools,
|
|
151
153
|
};
|
|
152
154
|
|
|
153
155
|
export class ToolRegistry {
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export { toolDefinition as
|
|
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';
|