@sonisoft/now-sdk-ext-mcp 1.0.0

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.
Files changed (102) hide show
  1. package/README.md +393 -0
  2. package/dist/common/connection.d.ts +42 -0
  3. package/dist/common/connection.d.ts.map +1 -0
  4. package/dist/common/connection.js +119 -0
  5. package/dist/common/connection.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +108 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/tools/aggregate.d.ts +20 -0
  11. package/dist/tools/aggregate.d.ts.map +1 -0
  12. package/dist/tools/aggregate.js +271 -0
  13. package/dist/tools/aggregate.js.map +1 -0
  14. package/dist/tools/app-manager.d.ts +50 -0
  15. package/dist/tools/app-manager.d.ts.map +1 -0
  16. package/dist/tools/app-manager.js +756 -0
  17. package/dist/tools/app-manager.js.map +1 -0
  18. package/dist/tools/atf.d.ts +16 -0
  19. package/dist/tools/atf.d.ts.map +1 -0
  20. package/dist/tools/atf.js +246 -0
  21. package/dist/tools/atf.js.map +1 -0
  22. package/dist/tools/attachment.d.ts +20 -0
  23. package/dist/tools/attachment.d.ts.map +1 -0
  24. package/dist/tools/attachment.js +223 -0
  25. package/dist/tools/attachment.js.map +1 -0
  26. package/dist/tools/batch.d.ts +15 -0
  27. package/dist/tools/batch.d.ts.map +1 -0
  28. package/dist/tools/batch.js +159 -0
  29. package/dist/tools/batch.js.map +1 -0
  30. package/dist/tools/cmdb.d.ts +14 -0
  31. package/dist/tools/cmdb.d.ts.map +1 -0
  32. package/dist/tools/cmdb.js +199 -0
  33. package/dist/tools/cmdb.js.map +1 -0
  34. package/dist/tools/codesearch.d.ts +31 -0
  35. package/dist/tools/codesearch.d.ts.map +1 -0
  36. package/dist/tools/codesearch.js +371 -0
  37. package/dist/tools/codesearch.js.map +1 -0
  38. package/dist/tools/discovery.d.ts +15 -0
  39. package/dist/tools/discovery.d.ts.map +1 -0
  40. package/dist/tools/discovery.js +204 -0
  41. package/dist/tools/discovery.js.map +1 -0
  42. package/dist/tools/execute-script.d.ts +9 -0
  43. package/dist/tools/execute-script.d.ts.map +1 -0
  44. package/dist/tools/execute-script.js +106 -0
  45. package/dist/tools/execute-script.js.map +1 -0
  46. package/dist/tools/find-atf-tests.d.ts +9 -0
  47. package/dist/tools/find-atf-tests.d.ts.map +1 -0
  48. package/dist/tools/find-atf-tests.js +152 -0
  49. package/dist/tools/find-atf-tests.js.map +1 -0
  50. package/dist/tools/health.d.ts +9 -0
  51. package/dist/tools/health.d.ts.map +1 -0
  52. package/dist/tools/health.js +137 -0
  53. package/dist/tools/health.js.map +1 -0
  54. package/dist/tools/lookup-app.d.ts +11 -0
  55. package/dist/tools/lookup-app.d.ts.map +1 -0
  56. package/dist/tools/lookup-app.js +242 -0
  57. package/dist/tools/lookup-app.js.map +1 -0
  58. package/dist/tools/lookup-columns.d.ts +10 -0
  59. package/dist/tools/lookup-columns.d.ts.map +1 -0
  60. package/dist/tools/lookup-columns.js +180 -0
  61. package/dist/tools/lookup-columns.js.map +1 -0
  62. package/dist/tools/lookup-table.d.ts +10 -0
  63. package/dist/tools/lookup-table.d.ts.map +1 -0
  64. package/dist/tools/lookup-table.js +150 -0
  65. package/dist/tools/lookup-table.js.map +1 -0
  66. package/dist/tools/query-batch.d.ts +16 -0
  67. package/dist/tools/query-batch.d.ts.map +1 -0
  68. package/dist/tools/query-batch.js +197 -0
  69. package/dist/tools/query-batch.js.map +1 -0
  70. package/dist/tools/query-syslog.d.ts +9 -0
  71. package/dist/tools/query-syslog.d.ts.map +1 -0
  72. package/dist/tools/query-syslog.js +127 -0
  73. package/dist/tools/query-syslog.js.map +1 -0
  74. package/dist/tools/query-table.d.ts +8 -0
  75. package/dist/tools/query-table.d.ts.map +1 -0
  76. package/dist/tools/query-table.js +137 -0
  77. package/dist/tools/query-table.js.map +1 -0
  78. package/dist/tools/schema.d.ts +24 -0
  79. package/dist/tools/schema.d.ts.map +1 -0
  80. package/dist/tools/schema.js +321 -0
  81. package/dist/tools/schema.js.map +1 -0
  82. package/dist/tools/scope.d.ts +25 -0
  83. package/dist/tools/scope.d.ts.map +1 -0
  84. package/dist/tools/scope.js +226 -0
  85. package/dist/tools/scope.js.map +1 -0
  86. package/dist/tools/scriptsync.d.ts +14 -0
  87. package/dist/tools/scriptsync.d.ts.map +1 -0
  88. package/dist/tools/scriptsync.js +167 -0
  89. package/dist/tools/scriptsync.js.map +1 -0
  90. package/dist/tools/task.d.ts +39 -0
  91. package/dist/tools/task.d.ts.map +1 -0
  92. package/dist/tools/task.js +390 -0
  93. package/dist/tools/task.js.map +1 -0
  94. package/dist/tools/updateset.d.ts +46 -0
  95. package/dist/tools/updateset.d.ts.map +1 -0
  96. package/dist/tools/updateset.js +475 -0
  97. package/dist/tools/updateset.js.map +1 -0
  98. package/dist/tools/workflow.d.ts +9 -0
  99. package/dist/tools/workflow.d.ts.map +1 -0
  100. package/dist/tools/workflow.js +164 -0
  101. package/dist/tools/workflow.js.map +1 -0
  102. package/package.json +68 -0
@@ -0,0 +1,756 @@
1
+ import { z } from "zod";
2
+ import { ApplicationManager, CompanyApplications, AppRepoApplication, BatchDefinition, BatchInstallation, } from "@sonisoft/now-sdk-ext-core";
3
+ import { withConnectionRetry } from "../common/connection.js";
4
+ /**
5
+ * Registers the get_app_details tool on the MCP server.
6
+ *
7
+ * Gets detailed information about a ServiceNow application by its sys_id.
8
+ */
9
+ export function registerGetAppDetailsTool(server) {
10
+ server.registerTool("get_app_details", {
11
+ title: "Get Application Details",
12
+ description: "Get detailed information about a ServiceNow application by its sys_id. " +
13
+ "Returns version, install status, update availability, scope, vendor, " +
14
+ "dependencies, store link, and other metadata.\n\n" +
15
+ "Use `lookup_app` to find an application's sys_id by name, then use " +
16
+ "this tool to get full details.",
17
+ inputSchema: {
18
+ instance: z
19
+ .string()
20
+ .optional()
21
+ .describe("The ServiceNow instance auth alias (e.g., " +
22
+ '"dev224436", "prod"). If not provided, falls back ' +
23
+ "to the SN_AUTH_ALIAS environment variable."),
24
+ app_id: z
25
+ .string()
26
+ .describe("The sys_id of the application to get details for."),
27
+ },
28
+ }, async ({ instance, app_id }) => {
29
+ try {
30
+ const details = await withConnectionRetry(instance, async (snInstance) => {
31
+ const mgr = new ApplicationManager(snInstance);
32
+ return await mgr.getApplicationDetails(app_id);
33
+ });
34
+ if (!details) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Application '${app_id}' not found or not available on this instance.`,
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ const lines = [];
45
+ lines.push("=== Application Details ===");
46
+ lines.push(`Name: ${details.name}`);
47
+ lines.push(`sys_id: ${details.sys_id}`);
48
+ lines.push(`Scope: ${details.scope}`);
49
+ lines.push(`Version: ${details.version}`);
50
+ if (details.latest_version)
51
+ lines.push(`Latest Version: ${details.latest_version}`);
52
+ lines.push(`Installed: ${details.isInstalled}`);
53
+ lines.push(`Update Available: ${details.isInstalledAndUpdateAvailable}`);
54
+ if (details.vendor)
55
+ lines.push(`Vendor: ${details.vendor}`);
56
+ if (details.short_description)
57
+ lines.push(`Description: ${details.short_description}`);
58
+ if (details.install_date)
59
+ lines.push(`Install Date: ${details.install_date}`);
60
+ if (details.update_date)
61
+ lines.push(`Update Date: ${details.update_date}`);
62
+ lines.push(`Active: ${details.active}`);
63
+ lines.push(`Is Store App: ${details.is_store_app}`);
64
+ if (details.store_link)
65
+ lines.push(`Store Link: ${details.store_link}`);
66
+ lines.push(`Can Install/Upgrade: ${details.can_install_or_upgrade}`);
67
+ return {
68
+ content: [{ type: "text", text: lines.join("\n") }],
69
+ };
70
+ }
71
+ catch (error) {
72
+ const message = error instanceof Error ? error.message : String(error);
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `Error getting application details: ${message}`,
78
+ },
79
+ ],
80
+ isError: true,
81
+ };
82
+ }
83
+ });
84
+ }
85
+ /**
86
+ * Registers the validate_app_install tool on the MCP server.
87
+ *
88
+ * Validates whether a set of applications are installed at expected versions.
89
+ */
90
+ export function registerValidateAppInstallTool(server) {
91
+ server.registerTool("validate_app_install", {
92
+ title: "Validate Application Installation",
93
+ description: "Validate whether a set of applications are installed at the expected versions. " +
94
+ "Reports which apps are valid, need installation, need upgrade, or have " +
95
+ "version mismatches.\n\n" +
96
+ "Useful for verifying environment readiness or checking deployment prerequisites.",
97
+ inputSchema: {
98
+ instance: z
99
+ .string()
100
+ .optional()
101
+ .describe("The ServiceNow instance auth alias (e.g., " +
102
+ '"dev224436", "prod"). If not provided, falls back ' +
103
+ "to the SN_AUTH_ALIAS environment variable."),
104
+ packages: z
105
+ .array(z.object({
106
+ id: z.string().describe("Application sys_id"),
107
+ requested_version: z
108
+ .string()
109
+ .describe("Expected version (e.g., '1.2.3')"),
110
+ type: z
111
+ .string()
112
+ .optional()
113
+ .describe("Package type (optional)"),
114
+ load_demo_data: z
115
+ .boolean()
116
+ .optional()
117
+ .describe("Whether to load demo data (optional)"),
118
+ }))
119
+ .describe("List of applications to validate."),
120
+ },
121
+ }, async ({ instance, packages }) => {
122
+ try {
123
+ const result = await withConnectionRetry(instance, async (snInstance) => {
124
+ const mgr = new ApplicationManager(snInstance);
125
+ const defs = packages.map((p) => new BatchDefinition(p.id, p.load_demo_data ?? false, "", "", p.requested_version, p.type ?? ""));
126
+ const batch = new BatchInstallation();
127
+ batch.packages = defs;
128
+ return await mgr.validateBatchInstallation(batch);
129
+ });
130
+ const lines = [];
131
+ lines.push("=== Application Validation Results ===");
132
+ lines.push(`Overall Valid: ${result.isValid}`);
133
+ lines.push(`Total: ${result.totalApplications}`);
134
+ lines.push(`Already Valid: ${result.alreadyValid}`);
135
+ lines.push(`Needs Installation: ${result.needsInstallation}`);
136
+ lines.push(`Needs Upgrade: ${result.needsUpgrade}`);
137
+ lines.push(`Errors: ${result.errors}`);
138
+ lines.push("");
139
+ for (const app of result.applications) {
140
+ lines.push(`--- ${app.name || app.id} ---`);
141
+ lines.push(` Status: ${app.validationStatus}`);
142
+ lines.push(` Requested: ${app.requested_version}`);
143
+ if (app.installed_version)
144
+ lines.push(` Installed: ${app.installed_version}`);
145
+ lines.push(` Installed: ${app.isInstalled}`);
146
+ lines.push(` Version Match: ${app.isVersionMatch}`);
147
+ lines.push(` Needs Action: ${app.needsAction}`);
148
+ if (app.error)
149
+ lines.push(` Error: ${app.error}`);
150
+ }
151
+ return {
152
+ content: [{ type: "text", text: lines.join("\n") }],
153
+ };
154
+ }
155
+ catch (error) {
156
+ const message = error instanceof Error ? error.message : String(error);
157
+ return {
158
+ content: [
159
+ {
160
+ type: "text",
161
+ text: `Error validating applications: ${message}`,
162
+ },
163
+ ],
164
+ isError: true,
165
+ };
166
+ }
167
+ });
168
+ }
169
+ /** Formats elapsed time from a start timestamp. */
170
+ function formatDuration(startMs) {
171
+ const elapsed = Date.now() - startMs;
172
+ const seconds = Math.floor(elapsed / 1000);
173
+ const minutes = Math.floor(seconds / 60);
174
+ const rem = seconds % 60;
175
+ return minutes > 0 ? `${minutes}m ${rem}s` : `${seconds}s`;
176
+ }
177
+ /**
178
+ * Registers the search_store_apps tool on the MCP server.
179
+ *
180
+ * Search/browse ServiceNow store applications by category.
181
+ */
182
+ export function registerSearchStoreAppsTool(server) {
183
+ server.registerTool("search_store_apps", {
184
+ title: "Search Store Applications",
185
+ description: "Search or browse ServiceNow store applications by category.\n\n" +
186
+ "Tab contexts:\n" +
187
+ '- "installed" — list all installed store applications\n' +
188
+ '- "updates" — list installed apps that have updates available\n' +
189
+ '- "available_for_you" — browse apps available for installation\n\n' +
190
+ "Use this to discover what is installed, find available updates, " +
191
+ "or browse for new applications to install.",
192
+ inputSchema: {
193
+ instance: z
194
+ .string()
195
+ .optional()
196
+ .describe("The ServiceNow instance auth alias (e.g., " +
197
+ '"dev224436", "prod"). If not provided, falls back ' +
198
+ "to the SN_AUTH_ALIAS environment variable."),
199
+ tab_context: z
200
+ .enum(["installed", "updates", "available_for_you"])
201
+ .describe('Category to list: "installed" for installed apps, ' +
202
+ '"updates" for apps with available updates, ' +
203
+ '"available_for_you" for apps available to install.'),
204
+ search_key: z
205
+ .string()
206
+ .optional()
207
+ .describe("Optional keyword to filter results by name."),
208
+ limit: z
209
+ .number()
210
+ .min(1)
211
+ .max(200)
212
+ .default(50)
213
+ .describe("Maximum number of results to return. Default 50."),
214
+ },
215
+ }, async ({ instance, tab_context, search_key, limit }) => {
216
+ try {
217
+ const apps = await withConnectionRetry(instance, async (snInstance) => {
218
+ const mgr = new ApplicationManager(snInstance);
219
+ return await mgr.searchApplications({
220
+ tabContext: tab_context,
221
+ searchKey: search_key,
222
+ limit,
223
+ });
224
+ });
225
+ const lines = [];
226
+ lines.push(`=== Store Applications (${tab_context}) ===`);
227
+ if (search_key)
228
+ lines.push(`Search: "${search_key}"`);
229
+ lines.push(`Found: ${apps.length} application(s)`);
230
+ for (let i = 0; i < apps.length; i++) {
231
+ const app = apps[i];
232
+ lines.push("");
233
+ lines.push(`${i + 1}. ${app.name}`);
234
+ lines.push(` sys_id: ${app.sys_id}`);
235
+ if (app.scope)
236
+ lines.push(` Scope: ${app.scope}`);
237
+ lines.push(` Version: ${app.version}`);
238
+ if (app.latest_version)
239
+ lines.push(` Latest Version: ${app.latest_version}`);
240
+ lines.push(` Installed: ${app.isInstalled}`);
241
+ if (app.isInstalledAndUpdateAvailable)
242
+ lines.push(` Update Available: true`);
243
+ if (app.vendor)
244
+ lines.push(` Vendor: ${app.vendor}`);
245
+ if (app.short_description) {
246
+ const desc = app.short_description.length > 120
247
+ ? app.short_description.slice(0, 120) + "..."
248
+ : app.short_description;
249
+ lines.push(` Description: ${desc}`);
250
+ }
251
+ }
252
+ lines.push("");
253
+ lines.push("Tip: Use get_app_details with a sys_id for full details. " +
254
+ "Use install_store_app or update_store_app to take action.");
255
+ return {
256
+ content: [{ type: "text", text: lines.join("\n") }],
257
+ };
258
+ }
259
+ catch (error) {
260
+ const message = error instanceof Error ? error.message : String(error);
261
+ return {
262
+ content: [
263
+ {
264
+ type: "text",
265
+ text: `Error searching store applications: ${message}`,
266
+ },
267
+ ],
268
+ isError: true,
269
+ };
270
+ }
271
+ });
272
+ }
273
+ /**
274
+ * Registers the list_company_apps tool on the MCP server.
275
+ *
276
+ * Lists company-internal (shared internally) applications.
277
+ */
278
+ export function registerListCompanyAppsTool(server) {
279
+ server.registerTool("list_company_apps", {
280
+ title: "List Company Applications",
281
+ description: "List company-internal applications shared within your organization. " +
282
+ "Returns application metadata including name, scope, version, install " +
283
+ "status, and update availability.\n\n" +
284
+ "Optionally filter by scope, sys_id, or installed status.",
285
+ inputSchema: {
286
+ instance: z
287
+ .string()
288
+ .optional()
289
+ .describe("The ServiceNow instance auth alias (e.g., " +
290
+ '"dev224436", "prod"). If not provided, falls back ' +
291
+ "to the SN_AUTH_ALIAS environment variable."),
292
+ scope: z
293
+ .string()
294
+ .optional()
295
+ .describe('Filter by application scope (e.g., "x_acme_my_app"). ' +
296
+ "Returns only the matching application."),
297
+ sys_id: z
298
+ .string()
299
+ .optional()
300
+ .describe("Filter by application sys_id. Returns only the matching application."),
301
+ installed_only: z
302
+ .boolean()
303
+ .default(false)
304
+ .describe("When true, only returns installed applications."),
305
+ },
306
+ }, async ({ instance, scope, sys_id, installed_only }) => {
307
+ try {
308
+ if (scope && sys_id) {
309
+ return {
310
+ content: [
311
+ {
312
+ type: "text",
313
+ text: "Error: Provide either scope or sys_id, not both.",
314
+ },
315
+ ],
316
+ isError: true,
317
+ };
318
+ }
319
+ const result = await withConnectionRetry(instance, async (snInstance) => {
320
+ const companyApps = new CompanyApplications(snInstance);
321
+ if (scope) {
322
+ const app = await companyApps.getCompanyApplicationByScope(scope);
323
+ return app ? [app] : [];
324
+ }
325
+ if (sys_id) {
326
+ const app = await companyApps.getCompanyApplicationBySysId(sys_id);
327
+ return app ? [app] : [];
328
+ }
329
+ if (installed_only) {
330
+ return await companyApps.getInstalledCompanyApplications();
331
+ }
332
+ const response = await companyApps.getCompanyApplications();
333
+ return response.data || [];
334
+ });
335
+ const lines = [];
336
+ lines.push("=== Company Applications ===");
337
+ if (scope)
338
+ lines.push(`Filter: scope = ${scope}`);
339
+ else if (sys_id)
340
+ lines.push(`Filter: sys_id = ${sys_id}`);
341
+ else if (installed_only)
342
+ lines.push("Filter: installed only");
343
+ lines.push(`Found: ${result.length} application(s)`);
344
+ if (result.length === 0 && (scope || sys_id)) {
345
+ lines.push("");
346
+ lines.push("No matching application found.");
347
+ }
348
+ for (let i = 0; i < result.length; i++) {
349
+ const app = result[i];
350
+ lines.push("");
351
+ lines.push(`${i + 1}. ${app.name}`);
352
+ lines.push(` sys_id: ${app.sys_id}`);
353
+ lines.push(` Scope: ${app.scope}`);
354
+ lines.push(` Version: ${app.version}`);
355
+ if (app.latest_version)
356
+ lines.push(` Latest Version: ${app.latest_version}`);
357
+ lines.push(` Installed: ${app.isInstalled}`);
358
+ lines.push(` Can Install/Upgrade: ${app.can_install_or_upgrade}`);
359
+ if (app.vendor)
360
+ lines.push(` Vendor: ${app.vendor}`);
361
+ if (app.short_description) {
362
+ const desc = app.short_description.length > 120
363
+ ? app.short_description.slice(0, 120) + "..."
364
+ : app.short_description;
365
+ lines.push(` Description: ${desc}`);
366
+ }
367
+ }
368
+ return {
369
+ content: [{ type: "text", text: lines.join("\n") }],
370
+ };
371
+ }
372
+ catch (error) {
373
+ const message = error instanceof Error ? error.message : String(error);
374
+ return {
375
+ content: [
376
+ {
377
+ type: "text",
378
+ text: `Error listing company applications: ${message}`,
379
+ },
380
+ ],
381
+ isError: true,
382
+ };
383
+ }
384
+ });
385
+ }
386
+ /**
387
+ * Registers the install_store_app tool on the MCP server.
388
+ *
389
+ * Installs a store application and waits for completion.
390
+ */
391
+ export function registerInstallStoreAppTool(server) {
392
+ server.registerTool("install_store_app", {
393
+ title: "Install Store Application",
394
+ description: "Install a ServiceNow store application on the target instance. " +
395
+ "This is a MUTATIVE, LONG-RUNNING operation that blocks until " +
396
+ "installation completes or times out (default: 30 minutes).\n\n" +
397
+ "IMPORTANT:\n" +
398
+ "- Installation adds new tables, scripts, and configuration to the instance.\n" +
399
+ "- Review the application details (use get_app_details) before installing.\n" +
400
+ "- Ensure the instance has sufficient capacity and the right entitlements.\n" +
401
+ "- Consider testing on a sub-production instance first.\n\n" +
402
+ "Use search_store_apps with tab_context 'available_for_you' to find " +
403
+ "apps available for installation.",
404
+ inputSchema: {
405
+ instance: z
406
+ .string()
407
+ .optional()
408
+ .describe("The ServiceNow instance auth alias (e.g., " +
409
+ '"dev224436", "prod"). If not provided, falls back ' +
410
+ "to the SN_AUTH_ALIAS environment variable."),
411
+ app_id: z
412
+ .string()
413
+ .describe("The source app ID of the application to install. " +
414
+ "Use search_store_apps or get_app_details to find this."),
415
+ version: z
416
+ .string()
417
+ .describe('The version to install (e.g., "1.2.3"). ' +
418
+ "Use get_app_details to see available versions."),
419
+ load_demo_data: z
420
+ .boolean()
421
+ .default(false)
422
+ .describe("Whether to load demo data during installation."),
423
+ timeout_minutes: z
424
+ .number()
425
+ .min(1)
426
+ .max(60)
427
+ .default(30)
428
+ .describe("Maximum time to wait for installation to complete, in minutes. Default 30."),
429
+ },
430
+ }, async ({ instance, app_id, version, load_demo_data, timeout_minutes }) => {
431
+ const startMs = Date.now();
432
+ try {
433
+ const result = await withConnectionRetry(instance, async (snInstance) => {
434
+ const mgr = new ApplicationManager(snInstance);
435
+ return await mgr.installStoreApplicationAndWait({
436
+ appId: app_id,
437
+ version,
438
+ loadDemoData: load_demo_data,
439
+ }, 5000, timeout_minutes * 60 * 1000);
440
+ });
441
+ const lines = [];
442
+ if (result.success) {
443
+ lines.push("=== Store Application Installed ===");
444
+ }
445
+ else {
446
+ lines.push("=== Store Application Installation Failed ===");
447
+ }
448
+ lines.push(`App ID: ${app_id}`);
449
+ lines.push(`Version: ${version}`);
450
+ lines.push(`Status: ${result.status_label}`);
451
+ lines.push(`Message: ${result.status_message}`);
452
+ lines.push(`Completion: ${result.percent_complete}%`);
453
+ if (result.error)
454
+ lines.push(`Error: ${result.error}`);
455
+ lines.push(`Duration: ${formatDuration(startMs)}`);
456
+ return {
457
+ content: [{ type: "text", text: lines.join("\n") }],
458
+ isError: !result.success,
459
+ };
460
+ }
461
+ catch (error) {
462
+ const message = error instanceof Error ? error.message : String(error);
463
+ return {
464
+ content: [
465
+ {
466
+ type: "text",
467
+ text: `Error installing store application: ${message}\n` +
468
+ `Duration: ${formatDuration(startMs)}`,
469
+ },
470
+ ],
471
+ isError: true,
472
+ };
473
+ }
474
+ });
475
+ }
476
+ /**
477
+ * Registers the update_store_app tool on the MCP server.
478
+ *
479
+ * Updates an installed store application and waits for completion.
480
+ */
481
+ export function registerUpdateStoreAppTool(server) {
482
+ server.registerTool("update_store_app", {
483
+ title: "Update Store Application",
484
+ description: "Update an installed ServiceNow store application to a newer version. " +
485
+ "This is a MUTATIVE, LONG-RUNNING operation that blocks until " +
486
+ "the update completes or times out (default: 30 minutes).\n\n" +
487
+ "IMPORTANT:\n" +
488
+ "- Updates may alter existing behavior, modify tables, and affect customizations.\n" +
489
+ "- Customizations to the application may be overwritten during the update.\n" +
490
+ "- Consider testing on a sub-production instance first.\n\n" +
491
+ "Use search_store_apps with tab_context 'updates' to find apps " +
492
+ "with available updates.",
493
+ inputSchema: {
494
+ instance: z
495
+ .string()
496
+ .optional()
497
+ .describe("The ServiceNow instance auth alias (e.g., " +
498
+ '"dev224436", "prod"). If not provided, falls back ' +
499
+ "to the SN_AUTH_ALIAS environment variable."),
500
+ app_id: z
501
+ .string()
502
+ .describe("The source app ID of the application to update. " +
503
+ "Use search_store_apps with tab_context 'updates' to find this."),
504
+ version: z
505
+ .string()
506
+ .describe('The version to update to (e.g., "2.0.0"). ' +
507
+ "Use get_app_details to see the latest available version."),
508
+ load_demo_data: z
509
+ .boolean()
510
+ .default(false)
511
+ .describe("Whether to load demo data during the update."),
512
+ timeout_minutes: z
513
+ .number()
514
+ .min(1)
515
+ .max(60)
516
+ .default(30)
517
+ .describe("Maximum time to wait for the update to complete, in minutes. Default 30."),
518
+ },
519
+ }, async ({ instance, app_id, version, load_demo_data, timeout_minutes }) => {
520
+ const startMs = Date.now();
521
+ try {
522
+ const result = await withConnectionRetry(instance, async (snInstance) => {
523
+ const mgr = new ApplicationManager(snInstance);
524
+ return await mgr.updateStoreApplicationAndWait({
525
+ appId: app_id,
526
+ version,
527
+ loadDemoData: load_demo_data,
528
+ }, 5000, timeout_minutes * 60 * 1000);
529
+ });
530
+ const lines = [];
531
+ if (result.success) {
532
+ lines.push("=== Store Application Updated ===");
533
+ }
534
+ else {
535
+ lines.push("=== Store Application Update Failed ===");
536
+ }
537
+ lines.push(`App ID: ${app_id}`);
538
+ lines.push(`Version: ${version}`);
539
+ lines.push(`Status: ${result.status_label}`);
540
+ lines.push(`Message: ${result.status_message}`);
541
+ lines.push(`Completion: ${result.percent_complete}%`);
542
+ if (result.error)
543
+ lines.push(`Error: ${result.error}`);
544
+ lines.push(`Duration: ${formatDuration(startMs)}`);
545
+ return {
546
+ content: [{ type: "text", text: lines.join("\n") }],
547
+ isError: !result.success,
548
+ };
549
+ }
550
+ catch (error) {
551
+ const message = error instanceof Error ? error.message : String(error);
552
+ return {
553
+ content: [
554
+ {
555
+ type: "text",
556
+ text: `Error updating store application: ${message}\n` +
557
+ `Duration: ${formatDuration(startMs)}`,
558
+ },
559
+ ],
560
+ isError: true,
561
+ };
562
+ }
563
+ });
564
+ }
565
+ /**
566
+ * Registers the install_from_app_repo tool on the MCP server.
567
+ *
568
+ * Installs an application from the company app repository (CI/CD).
569
+ */
570
+ export function registerInstallFromAppRepoTool(server) {
571
+ server.registerTool("install_from_app_repo", {
572
+ title: "Install from App Repository",
573
+ description: "Install an application from the company's ServiceNow application " +
574
+ "repository using the CI/CD API. This is a MUTATIVE, LONG-RUNNING " +
575
+ "operation that blocks until installation completes or times out " +
576
+ "(default: 30 minutes).\n\n" +
577
+ "Typically used for deploying custom applications across instances " +
578
+ "(e.g., dev -> test -> prod). Use list_company_apps to find the " +
579
+ "application scope and sys_id.",
580
+ inputSchema: {
581
+ instance: z
582
+ .string()
583
+ .optional()
584
+ .describe("The ServiceNow instance auth alias (e.g., " +
585
+ '"dev224436", "prod"). If not provided, falls back ' +
586
+ "to the SN_AUTH_ALIAS environment variable."),
587
+ scope: z
588
+ .string()
589
+ .describe('The scope name of the application to install (e.g., "x_acme_my_app").'),
590
+ sys_id: z
591
+ .string()
592
+ .describe("The sys_id of the application in the repository."),
593
+ version: z
594
+ .string()
595
+ .optional()
596
+ .describe("Specific version to install. If omitted, installs the latest."),
597
+ auto_upgrade_base_app: z
598
+ .boolean()
599
+ .default(false)
600
+ .describe("Whether to automatically upgrade the base application if required."),
601
+ base_app_version: z
602
+ .string()
603
+ .optional()
604
+ .describe("Specific version of the base application to upgrade to."),
605
+ timeout_minutes: z
606
+ .number()
607
+ .min(1)
608
+ .max(60)
609
+ .default(30)
610
+ .describe("Maximum time to wait for installation to complete, in minutes. Default 30."),
611
+ },
612
+ }, async ({ instance, scope, sys_id, version, auto_upgrade_base_app, base_app_version, timeout_minutes, }) => {
613
+ const startMs = Date.now();
614
+ try {
615
+ const result = await withConnectionRetry(instance, async (snInstance) => {
616
+ const repo = new AppRepoApplication(snInstance);
617
+ return await repo.installFromAppRepoAndWait({
618
+ scope,
619
+ sys_id,
620
+ version,
621
+ auto_upgrade_base_app,
622
+ base_app_version,
623
+ }, 5000, timeout_minutes * 60 * 1000);
624
+ });
625
+ const lines = [];
626
+ if (result.success) {
627
+ lines.push("=== App Repo Installation Complete ===");
628
+ }
629
+ else {
630
+ lines.push("=== App Repo Installation Failed ===");
631
+ }
632
+ lines.push(`Scope: ${scope}`);
633
+ lines.push(`Sys ID: ${sys_id}`);
634
+ lines.push(`Version: ${version || "latest"}`);
635
+ lines.push(`Status: ${result.status_label}`);
636
+ lines.push(`Message: ${result.status_message}`);
637
+ if (result.status_detail)
638
+ lines.push(`Detail: ${result.status_detail}`);
639
+ lines.push(`Completion: ${result.percent_complete}%`);
640
+ if (result.error)
641
+ lines.push(`Error: ${result.error}`);
642
+ lines.push(`Duration: ${formatDuration(startMs)}`);
643
+ return {
644
+ content: [{ type: "text", text: lines.join("\n") }],
645
+ isError: !result.success,
646
+ };
647
+ }
648
+ catch (error) {
649
+ const message = error instanceof Error ? error.message : String(error);
650
+ return {
651
+ content: [
652
+ {
653
+ type: "text",
654
+ text: `Error installing from app repo: ${message}\n` +
655
+ `Duration: ${formatDuration(startMs)}`,
656
+ },
657
+ ],
658
+ isError: true,
659
+ };
660
+ }
661
+ });
662
+ }
663
+ /**
664
+ * Registers the publish_to_app_repo tool on the MCP server.
665
+ *
666
+ * Publishes an application to the company app repository.
667
+ */
668
+ export function registerPublishToAppRepoTool(server) {
669
+ server.registerTool("publish_to_app_repo", {
670
+ title: "Publish to App Repository",
671
+ description: "Publish an application to the company's ServiceNow application " +
672
+ "repository using the CI/CD API. This is a MUTATIVE, LONG-RUNNING " +
673
+ "operation that blocks until publishing completes or times out " +
674
+ "(default: 30 minutes).\n\n" +
675
+ "This makes the application version available for installation on " +
676
+ "other instances in the company. Use list_company_apps or lookup_app " +
677
+ "to find the scope and sys_id.",
678
+ inputSchema: {
679
+ instance: z
680
+ .string()
681
+ .optional()
682
+ .describe("The ServiceNow instance auth alias (e.g., " +
683
+ '"dev224436", "prod"). If not provided, falls back ' +
684
+ "to the SN_AUTH_ALIAS environment variable."),
685
+ scope: z
686
+ .string()
687
+ .describe('The scope name of the application to publish (e.g., "x_acme_my_app").'),
688
+ sys_id: z
689
+ .string()
690
+ .describe("The sys_id of the application to publish."),
691
+ version: z
692
+ .string()
693
+ .optional()
694
+ .describe("Version number for the published application."),
695
+ dev_notes: z
696
+ .string()
697
+ .optional()
698
+ .describe("Developer notes for this version."),
699
+ timeout_minutes: z
700
+ .number()
701
+ .min(1)
702
+ .max(60)
703
+ .default(30)
704
+ .describe("Maximum time to wait for publishing to complete, in minutes. Default 30."),
705
+ },
706
+ }, async ({ instance, scope, sys_id, version, dev_notes, timeout_minutes }) => {
707
+ const startMs = Date.now();
708
+ try {
709
+ const result = await withConnectionRetry(instance, async (snInstance) => {
710
+ const repo = new AppRepoApplication(snInstance);
711
+ return await repo.publishToAppRepoAndWait({
712
+ scope,
713
+ sys_id,
714
+ version,
715
+ dev_notes,
716
+ }, 5000, timeout_minutes * 60 * 1000);
717
+ });
718
+ const lines = [];
719
+ if (result.success) {
720
+ lines.push("=== App Repo Publish Complete ===");
721
+ }
722
+ else {
723
+ lines.push("=== App Repo Publish Failed ===");
724
+ }
725
+ lines.push(`Scope: ${scope}`);
726
+ lines.push(`Sys ID: ${sys_id}`);
727
+ lines.push(`Version: ${version || "N/A"}`);
728
+ lines.push(`Status: ${result.status_label}`);
729
+ lines.push(`Message: ${result.status_message}`);
730
+ if (result.status_detail)
731
+ lines.push(`Detail: ${result.status_detail}`);
732
+ lines.push(`Completion: ${result.percent_complete}%`);
733
+ if (result.error)
734
+ lines.push(`Error: ${result.error}`);
735
+ lines.push(`Duration: ${formatDuration(startMs)}`);
736
+ return {
737
+ content: [{ type: "text", text: lines.join("\n") }],
738
+ isError: !result.success,
739
+ };
740
+ }
741
+ catch (error) {
742
+ const message = error instanceof Error ? error.message : String(error);
743
+ return {
744
+ content: [
745
+ {
746
+ type: "text",
747
+ text: `Error publishing to app repo: ${message}\n` +
748
+ `Duration: ${formatDuration(startMs)}`,
749
+ },
750
+ ],
751
+ isError: true,
752
+ };
753
+ }
754
+ });
755
+ }
756
+ //# sourceMappingURL=app-manager.js.map