snow-flow 10.0.198 → 10.0.199
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,25 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* snow_order_catalog_item - Order catalog item
|
|
3
3
|
*
|
|
4
|
-
* Orders a catalog item programmatically, creating a request (RITM) with
|
|
4
|
+
* Orders a catalog item programmatically, creating a request (RITM) with
|
|
5
|
+
* specified variable values.
|
|
6
|
+
*
|
|
7
|
+
* Operation order (avoids race condition with flows):
|
|
8
|
+
* 1. Fetch variable definitions (item_option_new)
|
|
9
|
+
* 2. Create sc_request
|
|
10
|
+
* 3. Pre-create sc_item_option records in parallel (no RITM needed yet)
|
|
11
|
+
* 4. Short delay to ensure DB commit
|
|
12
|
+
* 5. Create sc_req_item (RITM) — flow triggers here with variables ready
|
|
13
|
+
* 6. Create sc_item_option_mtom links in parallel
|
|
14
|
+
* 7. Two PATCHs to trigger Business Rules that associate the flow context
|
|
15
|
+
*
|
|
16
|
+
* Based on contribution by @frodoyoraul (PR #83).
|
|
5
17
|
*/
|
|
6
18
|
|
|
7
19
|
import { MCPToolDefinition, ServiceNowContext, ToolResult } from "../../shared/types.js"
|
|
8
20
|
import { getAuthenticatedClient } from "../../shared/auth.js"
|
|
9
21
|
import { createSuccessResult, createErrorResult } from "../../shared/error-handler.js"
|
|
10
22
|
|
|
23
|
+
// Delay between sc_item_option creation and RITM insert to ensure DB commit
|
|
24
|
+
const VARIABLE_COMMIT_DELAY_MS = 1000
|
|
25
|
+
|
|
11
26
|
export const toolDefinition: MCPToolDefinition = {
|
|
12
27
|
name: "snow_order_catalog_item",
|
|
13
28
|
description: "Orders a catalog item programmatically, creating a request (RITM) with specified variable values.",
|
|
14
|
-
// Metadata for tool discovery (not sent to LLM)
|
|
15
29
|
category: "itsm",
|
|
16
30
|
subcategory: "service-catalog",
|
|
17
31
|
use_cases: ["ordering", "automation", "ritm"],
|
|
18
32
|
complexity: "intermediate",
|
|
19
33
|
frequency: "high",
|
|
20
34
|
|
|
21
|
-
// Permission enforcement
|
|
22
|
-
// Classification: WRITE - Write operation based on name pattern
|
|
23
35
|
permission: "write",
|
|
24
36
|
allowedRoles: ["developer", "admin"],
|
|
25
37
|
inputSchema: {
|
|
@@ -36,46 +48,89 @@ export const toolDefinition: MCPToolDefinition = {
|
|
|
36
48
|
},
|
|
37
49
|
}
|
|
38
50
|
|
|
51
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
52
|
+
|
|
39
53
|
export async function execute(args: any, context: ServiceNowContext): Promise<ToolResult> {
|
|
40
54
|
const { cat_item, requested_for, variables, quantity = 1, delivery_address, special_instructions } = args
|
|
41
55
|
|
|
42
56
|
try {
|
|
43
57
|
const client = await getAuthenticatedClient(context)
|
|
44
58
|
|
|
45
|
-
//
|
|
59
|
+
// 1. Fetch variable definitions before creating the RITM
|
|
60
|
+
const varDefsResponse = await client.get("/api/now/table/item_option_new", {
|
|
61
|
+
params: {
|
|
62
|
+
sysparm_query: "cat_item=" + cat_item,
|
|
63
|
+
sysparm_fields: "sys_id,name",
|
|
64
|
+
sysparm_limit: 100,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
const varDefs = varDefsResponse.data.result || []
|
|
68
|
+
const varMap = new Map<string, string>()
|
|
69
|
+
varDefs.forEach((def: any) => {
|
|
70
|
+
if (def.name) varMap.set(def.name, def.sys_id)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// 2. Create sc_request
|
|
46
74
|
const requestData: any = {
|
|
47
75
|
requested_for,
|
|
48
76
|
opened_by: requested_for,
|
|
49
77
|
}
|
|
50
|
-
|
|
51
78
|
if (special_instructions) requestData.special_instructions = special_instructions
|
|
52
79
|
|
|
53
80
|
const requestResponse = await client.post("/api/now/table/sc_request", requestData)
|
|
54
81
|
const requestId = requestResponse.data.result.sys_id
|
|
55
82
|
|
|
56
|
-
//
|
|
83
|
+
// 3. Pre-create sc_item_option records in parallel (no RITM needed yet)
|
|
84
|
+
const varEntries = variables ? Object.entries(variables).filter(([name]) => varMap.has(name)) : []
|
|
85
|
+
const skippedVars = variables ? Object.keys(variables).filter((name) => !varMap.has(name)) : []
|
|
86
|
+
|
|
87
|
+
const optionSysIds: Array<{ optionSysId: string }> = await Promise.all(
|
|
88
|
+
varEntries.map(async ([varName, varValue]) => {
|
|
89
|
+
const defSysId = varMap.get(varName)!
|
|
90
|
+
const optResp = await client.post("/api/now/table/sc_item_option", {
|
|
91
|
+
item_option_new: defSysId,
|
|
92
|
+
value: varValue,
|
|
93
|
+
})
|
|
94
|
+
return { optionSysId: optResp.data.result.sys_id }
|
|
95
|
+
}),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// 4. Wait for sc_item_option records to be committed in DB
|
|
99
|
+
if (optionSysIds.length > 0) {
|
|
100
|
+
await sleep(VARIABLE_COMMIT_DELAY_MS)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 5. Create sc_req_item (RITM) — flow triggers here with variables already ready
|
|
57
104
|
const ritmData: any = {
|
|
58
105
|
request: requestId,
|
|
59
106
|
cat_item,
|
|
60
107
|
requested_for,
|
|
61
108
|
quantity,
|
|
62
109
|
}
|
|
63
|
-
|
|
64
110
|
if (delivery_address) ritmData.delivery_address = delivery_address
|
|
65
111
|
|
|
66
112
|
const ritmResponse = await client.post("/api/now/table/sc_req_item", ritmData)
|
|
67
113
|
const ritmId = ritmResponse.data.result.sys_id
|
|
68
114
|
const ritmNumber = ritmResponse.data.result.number
|
|
69
115
|
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
// 6. Create sc_item_option_mtom links in parallel
|
|
117
|
+
await Promise.all(
|
|
118
|
+
optionSysIds.map(({ optionSysId }) =>
|
|
119
|
+
client.post("/api/now/table/sc_item_option_mtom", {
|
|
74
120
|
request_item: ritmId,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
121
|
+
sc_item_option: optionSysId,
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// 7. Two PATCHs to trigger Business Rules that associate the flow context.
|
|
127
|
+
// sys_mod_count is auto-incremented by ServiceNow (the value we send is
|
|
128
|
+
// ignored), but the PATCH itself is a real update that fires BR triggers.
|
|
129
|
+
// Two updates are needed: the first associates the flow, the second
|
|
130
|
+
// ensures the flow context is fully propagated.
|
|
131
|
+
if (optionSysIds.length > 0) {
|
|
132
|
+
await client.patch("/api/now/table/sc_req_item/" + ritmId, { sys_mod_count: "1" })
|
|
133
|
+
await client.patch("/api/now/table/sc_req_item/" + ritmId, { sys_mod_count: "1" })
|
|
79
134
|
}
|
|
80
135
|
|
|
81
136
|
return createSuccessResult(
|
|
@@ -85,6 +140,8 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
85
140
|
ritm_id: ritmId,
|
|
86
141
|
ritm_number: ritmNumber,
|
|
87
142
|
quantity,
|
|
143
|
+
variables_processed: varEntries.length,
|
|
144
|
+
variables_skipped: skippedVars.length > 0 ? skippedVars : undefined,
|
|
88
145
|
},
|
|
89
146
|
{
|
|
90
147
|
operation: "order_catalog_item",
|
|
@@ -97,5 +154,5 @@ export async function execute(args: any, context: ServiceNowContext): Promise<To
|
|
|
97
154
|
}
|
|
98
155
|
}
|
|
99
156
|
|
|
100
|
-
export const version = "
|
|
101
|
-
export const author = "Snow-Flow
|
|
157
|
+
export const version = "2.0.0"
|
|
158
|
+
export const author = "Snow-Flow"
|