@selfagency/teamdynamix-mcp 0.1.1 → 0.2.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 (4) hide show
  1. package/README.md +11 -1
  2. package/index.js +454 -427
  3. package/index.js.map +1 -1
  4. package/package.json +7 -3
package/index.js CHANGED
@@ -10,12 +10,11 @@ import { z } from "zod";
10
10
  // src/constants.ts
11
11
  var SERVER_NAME = "teamdynamix-mcp";
12
12
  var SERVER_VERSION = "0.2.0";
13
+ var CHARACTER_LIMIT = 25e3;
13
14
  var TEAMDYNAMIX_TOOL_PREFIX = "teamdynamix";
14
15
  var TEAMDYNAMIX_DEFAULT_TIMEOUT_MS = 3e4;
15
16
  var TEAMDYNAMIX_DEFAULT_MAX_RETRIES = 2;
16
17
  var TEAMDYNAMIX_MAX_RETRY_ATTEMPTS = 5;
17
- var TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS = 5e3;
18
- var TEAMDYNAMIX_MAX_RATE_LIMIT_WAIT_MS = 3e4;
19
18
 
20
19
  // src/config.ts
21
20
  var SERVER_NAME_OVERRIDE = process.env["MCP_SERVER_NAME"]?.trim() || void 0;
@@ -92,7 +91,8 @@ function getTeamDynamixConfig() {
92
91
  timeoutMs: normalizeNumberWithDefault(process.env["TEAMDYNAMIX_TIMEOUT_MS"], TEAMDYNAMIX_DEFAULT_TIMEOUT_MS, 1e3),
93
92
  maxRetries,
94
93
  enableWriteTools: normalizeBoolean(process.env["TEAMDYNAMIX_ENABLE_WRITE_TOOLS"], false),
95
- enableAdminTools: normalizeBoolean(process.env["TEAMDYNAMIX_ENABLE_ADMIN_TOOLS"], false)
94
+ enableAdminTools: normalizeBoolean(process.env["TEAMDYNAMIX_ENABLE_ADMIN_TOOLS"], false),
95
+ enableDeleteTools: normalizeBoolean(process.env["TEAMDYNAMIX_ENABLE_DELETE_TOOLS"], false)
96
96
  };
97
97
  }
98
98
  function getTeamDynamixConfigStatus(config = getTeamDynamixConfig()) {
@@ -118,26 +118,6 @@ function getTeamDynamixConfigStatus(config = getTeamDynamixConfig()) {
118
118
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
119
119
 
120
120
  // src/services/teamdynamix/core.service.ts
121
- function parseRateLimit(headers, nowMs = Date.now()) {
122
- const limitRaw = headers.get("X-RateLimit-Limit");
123
- const remainingRaw = headers.get("X-RateLimit-Remaining");
124
- const resetRaw = headers.get("X-RateLimit-Reset");
125
- const resetTime = resetRaw ? Date.parse(resetRaw) : Number.NaN;
126
- const computedWaitMs = Number.isFinite(resetTime) ? resetTime - nowMs : TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS;
127
- const waitMs = Math.min(
128
- Math.max(
129
- Number.isFinite(computedWaitMs) ? computedWaitMs : TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS,
130
- TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS
131
- ),
132
- TEAMDYNAMIX_MAX_RATE_LIMIT_WAIT_MS
133
- );
134
- return {
135
- limit: limitRaw ? Number.parseInt(limitRaw, 10) : null,
136
- remaining: remainingRaw ? Number.parseInt(remainingRaw, 10) : null,
137
- resetAt: Number.isFinite(resetTime) ? new Date(resetTime).toISOString() : null,
138
- waitMs
139
- };
140
- }
141
121
  function redactTeamDynamixConfig(config) {
142
122
  return {
143
123
  baseUrl: config.baseUrl ?? null,
@@ -155,39 +135,6 @@ function redactTeamDynamixConfig(config) {
155
135
  enableAdminTools: config.enableAdminTools
156
136
  };
157
137
  }
158
- function decodeJwtExpiryEpochSeconds(token) {
159
- const segments = token.split(".");
160
- const payload = segments[1];
161
- if (!payload) {
162
- return null;
163
- }
164
- try {
165
- const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
166
- const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
167
- const decoded = Buffer.from(`${normalized}${padding}`, "base64").toString("utf8");
168
- const parsed = JSON.parse(decoded);
169
- return typeof parsed.exp === "number" ? parsed.exp : null;
170
- } catch {
171
- return null;
172
- }
173
- }
174
- function extractAuthToken(payload) {
175
- if (typeof payload === "string" && payload.trim()) {
176
- return payload.trim();
177
- }
178
- if (!payload || typeof payload !== "object") {
179
- throw new Error("Unable to extract bearer token from TeamDynamix authentication response.");
180
- }
181
- const record = payload;
182
- const candidateKeys = ["token", "Token", "accessToken", "AccessToken", "bearerToken", "TokenText", "value"];
183
- for (const key of candidateKeys) {
184
- const candidate = record[key];
185
- if (typeof candidate === "string" && candidate.trim()) {
186
- return candidate.trim();
187
- }
188
- }
189
- throw new Error("Unable to extract bearer token from TeamDynamix authentication response.");
190
- }
191
138
 
192
139
  // src/tools/teamdynamix.domain-gateways.tools.ts
193
140
  import { z as z4 } from "zod";
@@ -397,441 +344,457 @@ var TeamDynamixUserSchema = z3.object({
397
344
  var TeamDynamixListResponseSchema = z3.array(z3.record(z3.string(), z3.unknown())).describe("Array of TeamDynamix entities");
398
345
  var TeamDynamixSingleResponseSchema = z3.record(z3.string(), z3.unknown()).describe("Single TeamDynamix entity response");
399
346
 
400
- // src/services/teamdynamix/client.service.ts
401
- function assertWriteToolsEnabled(config) {
402
- if (!config.enableWriteTools) {
403
- throw new Error(
404
- "Write tools are disabled. Set TEAMDYNAMIX_ENABLE_WRITE_TOOLS=true in your environment to enable ticket creation, updates, comments, and other write operations."
405
- );
347
+ // src/client/sdk-client.factory.ts
348
+ import { createTeamDynamixClient, loginWithPassword, loginWithServiceAccount } from "@selfagency/teamdynamix-ts";
349
+ function createTokenProvider(config, tenant) {
350
+ if (config.authMode === "admin") {
351
+ if (!config.beid || !config.webServicesKey) {
352
+ throw new Error("BEID and WebServicesKey are required for admin authentication");
353
+ }
354
+ return loginWithServiceAccount({
355
+ tenant,
356
+ beid: config.beid,
357
+ webServicesKey: config.webServicesKey
358
+ });
406
359
  }
407
- }
408
- function sleep(ms) {
409
- return new Promise((resolve) => {
410
- setTimeout(resolve, ms);
360
+ if (!config.username || !config.password) {
361
+ throw new Error("Username and password are required for standard authentication");
362
+ }
363
+ return loginWithPassword({
364
+ tenant,
365
+ username: config.username,
366
+ password: config.password
411
367
  });
412
368
  }
413
- function normalizePath(path) {
414
- return path.startsWith("/") ? path : `/${path}`;
369
+ function extractTenant(baseUrl) {
370
+ const normalized = baseUrl.replace(/\/+$/, "").replace(/\/TDWebApi$/, "");
371
+ const withoutProtocol = normalized.replace(/^https?:\/\//, "");
372
+ const parts = withoutProtocol.split("/");
373
+ return parts[0] ?? "";
415
374
  }
416
- var TokenBucket = class {
417
- constructor(capacity, refillRatePerMs) {
418
- this.capacity = capacity;
419
- this.refillRatePerMs = refillRatePerMs;
420
- this.tokens = capacity;
421
- this.lastRefillMs = Date.now();
422
- }
423
- tokens;
424
- lastRefillMs;
425
- refill() {
426
- const now = Date.now();
427
- const elapsed = now - this.lastRefillMs;
428
- this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRatePerMs);
429
- this.lastRefillMs = now;
430
- }
431
- async acquire() {
432
- this.refill();
433
- if (this.tokens >= 1) {
434
- this.tokens -= 1;
435
- return;
375
+ function buildSdkConfig(config, tenant, tokenProvider) {
376
+ return {
377
+ tenant,
378
+ tokenProvider,
379
+ environment: config.baseUrl?.includes("sandbox") ? "sandbox" : "production",
380
+ baseUrl: config.baseUrl,
381
+ timeoutMs: config.timeoutMs,
382
+ runtimeValidationMode: config.enableAdminTools ? "fail-closed" : "fail-open",
383
+ retryPolicy: {
384
+ maxRetries: config.maxRetries
436
385
  }
437
- const waitMs = Math.ceil((1 - this.tokens) / this.refillRatePerMs);
438
- await sleep(waitMs);
439
- return this.acquire();
440
- }
441
- };
442
- var sharedRateLimiter = new TokenBucket(20, 2 / 1e3);
443
- var TeamDynamixClient = class {
444
- constructor(config, rateLimiter = sharedRateLimiter) {
386
+ };
387
+ }
388
+ async function createMcpSdkClient(config) {
389
+ const tenant = extractTenant(config.baseUrl ?? "");
390
+ const tokenProvider = createTokenProvider(config, tenant);
391
+ const sdkConfig = buildSdkConfig(config, tenant, tokenProvider);
392
+ const { client } = await createTeamDynamixClient(sdkConfig);
393
+ return client;
394
+ }
395
+
396
+ // src/services/teamdynamix/client.factory.ts
397
+ var UnifiedTeamDynamixClient = class {
398
+ constructor(config) {
445
399
  this.config = config;
446
- this.rateLimiter = rateLimiter;
447
- }
448
- cachedToken = null;
449
- async getCurrentUser() {
450
- return await this.requestJson("/api/auth/getuser");
451
400
  }
452
- async listApplications() {
453
- return await this.requestJson("/api/applications");
401
+ sdkPromise = null;
402
+ sdk() {
403
+ this.sdkPromise ??= createMcpSdkClient(this.config);
404
+ return this.sdkPromise;
454
405
  }
455
- // -------------------------------------------------------------------------
456
- // Discovery / Enumeration domains
457
- // -------------------------------------------------------------------------
458
- async listAccounts(appId) {
459
- const path = appId ? `/api/${appId}/accounts` : "/api/accounts";
460
- return await this.requestJson(path);
406
+ //
407
+ // Discovery
408
+ //
409
+ getCurrentUser() {
410
+ return this.sdk().then((s) => s.discovery.authGetuser());
461
411
  }
462
- async getAccount(accountId) {
463
- return await this.requestJson(`/api/accounts/${accountId}`);
412
+ listApplications() {
413
+ return this.sdk().then((s) => s.discovery.applications());
464
414
  }
465
- async listLocations() {
466
- return await this.requestJson("/api/locations");
415
+ //
416
+ // Tickets
417
+ //
418
+ getTicket(appId, ticketId) {
419
+ return this.sdk().then((s) => s.tickets.appIdTicketsId({ params: { path: { appId, id: ticketId } } }));
467
420
  }
468
- async listFunctionalRoles() {
469
- return await this.requestJson("/api/functionalroles");
421
+ searchTickets(appId, _body) {
422
+ return this.sdk().then((s) => s.tickets.appIdTicketsFeed({ params: { path: { appId } } }));
470
423
  }
471
- async listCustomAttributes(componentId, appId, associatedTypeId) {
472
- const qs = new URLSearchParams({ componentId: String(componentId) });
473
- if (appId !== void 0) qs.set("appId", String(appId));
474
- if (associatedTypeId !== void 0) qs.set("associatedTypeId", String(associatedTypeId));
475
- return await this.requestJson(`/api/attributes/custom?${qs}`);
424
+ listTicketTypes(appId) {
425
+ return this.sdk().then((s) => s.tickets.appIdTicketsTypes({ params: { path: { appId } } }));
476
426
  }
477
- async listTicketStatuses(appId) {
478
- return await this.requestJson(`/api/${appId}/tickets/statuses`);
427
+ listTicketPriorities(appId) {
428
+ return this.sdk().then((s) => s.tickets.appIdTicketsPriorities({ params: { path: { appId } } }));
479
429
  }
480
- async listTicketTypes(appId) {
481
- return await this.requestJson(`/api/${appId}/tickets/types`);
430
+ listTicketUrgencies(appId) {
431
+ return this.sdk().then((s) => s.tickets.appIdTicketsUrgencies({ params: { path: { appId } } }));
482
432
  }
483
- async listTicketPriorities(appId) {
484
- return await this.requestJson(`/api/${appId}/tickets/priorities`);
433
+ listTicketImpacts(appId) {
434
+ return this.sdk().then((s) => s.tickets.appIdTicketsImpacts({ params: { path: { appId } } }));
485
435
  }
486
- async listTicketUrgencies(appId) {
487
- return await this.requestJson(`/api/${appId}/tickets/urgencies`);
436
+ listTicketSources(appId) {
437
+ return this.sdk().then((s) => s.tickets.appIdTicketsSources({ params: { path: { appId } } }));
488
438
  }
489
- async listTicketImpacts(appId) {
490
- return await this.requestJson(`/api/${appId}/tickets/impacts`);
439
+ getTicketFeed(appId, ticketId) {
440
+ return this.sdk().then((s) => s.tickets.appIdTicketsIdFeed({ params: { path: { appId, id: ticketId } } }));
491
441
  }
492
- async listTicketSources(appId) {
493
- return await this.requestJson(`/api/${appId}/tickets/sources`);
494
- }
495
- async getTicket(appId, ticketId) {
496
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}`);
442
+ //
443
+ // Ticket Relationships
444
+ //
445
+ getTicketTasks(appId, ticketId) {
446
+ return this.sdk().then(
447
+ (s) => s.ticketRelationships.appIdTicketsTicketIdTasks({ params: { path: { appId, ticketId } } })
448
+ );
497
449
  }
498
- async searchTickets(appId, body) {
499
- return await this.requestJson(`/api/${appId}/tickets/search`, {
500
- method: "POST",
501
- body: JSON.stringify(body)
502
- });
450
+ createTicketTask(appId, ticketId, body) {
451
+ return this.sdk().then((s) => s.ticketRelationships.createTicketTask({ appId, ticketId, body }));
503
452
  }
504
- async createTicket(appId, body, notifyRequestor = false, notifyResponsible = false) {
505
- const qs = new URLSearchParams({
506
- NotifyRequestor: String(notifyRequestor),
507
- NotifyResponsible: String(notifyResponsible)
508
- });
509
- return await this.requestJson(`/api/${appId}/tickets?${qs}`, {
510
- method: "POST",
511
- body: JSON.stringify(body)
512
- });
453
+ listTicketAssets(appId, ticketId) {
454
+ return this.sdk().then(
455
+ (s) => s.ticketRelationships.appIdTicketsIdAssets({ params: { path: { appId, id: ticketId } } })
456
+ );
513
457
  }
514
- async updateTicket(appId, ticketId, body, notifyRequestor = false, notifyResponsible = false, comments = "", isPrivate = false) {
515
- const qs = new URLSearchParams({
516
- NotifyRequestor: String(notifyRequestor),
517
- NotifyResponsible: String(notifyResponsible),
518
- ...comments ? { Comments: comments } : {},
519
- IsPrivate: String(isPrivate)
520
- });
521
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}?${qs}`, {
522
- method: "PATCH",
523
- body: JSON.stringify(body)
524
- });
458
+ addTicketAsset(appId, ticketId, assetId) {
459
+ return this.sdk().then((s) => s.ticketRelationships.addTicketAsset({ appId, ticketId, assetId }));
525
460
  }
526
- async addTicketComment(appId, ticketId, body, isPrivate = false, notifyRequestor = false, notifyResponsible = false) {
527
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/feed`, {
528
- method: "POST",
529
- body: JSON.stringify({
530
- Body: body,
531
- IsPrivate: isPrivate,
532
- NotifyRequestor: notifyRequestor,
533
- NotifyResponsible: notifyResponsible
534
- })
461
+ removeTicketAsset(appId, ticketId, assetId) {
462
+ return this.sdk().then((s) => {
463
+ s.ticketRelationships.removeTicketAsset({ appId, ticketId, assetId, confirm: true });
535
464
  });
536
465
  }
537
- async getTicketFeed(appId, ticketId) {
538
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/feed`);
466
+ getTicketContacts(appId, ticketId) {
467
+ return this.sdk().then(
468
+ (s) => s.ticketRelationships.appIdTicketsIdContacts({ params: { path: { appId, id: ticketId } } })
469
+ );
539
470
  }
540
- async getTicketTasks(appId, ticketId) {
541
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/tasks`);
471
+ addTicketContact(appId, ticketId, contactUid) {
472
+ return this.sdk().then((s) => s.ticketRelationships.addTicketContact({ appId, ticketId, contactUid }));
542
473
  }
543
- async createTicketTask(appId, ticketId, body) {
544
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/tasks`, {
545
- method: "POST",
546
- body: JSON.stringify(body)
474
+ removeTicketContact(appId, ticketId, contactUid) {
475
+ return this.sdk().then((s) => {
476
+ s.ticketRelationships.removeTicketContact({ appId, ticketId, contactUid, confirm: true });
547
477
  });
548
478
  }
549
- async listTicketAssets(appId, ticketId) {
550
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets`);
479
+ //
480
+ // People
481
+ //
482
+ getUser(uid) {
483
+ return this.sdk().then((s) => s.people.peopleUid({ params: { path: { uid } } }));
551
484
  }
552
- async addTicketAsset(appId, ticketId, assetId) {
553
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets/${assetId}`, {
554
- method: "POST",
555
- body: JSON.stringify({})
556
- });
485
+ searchUsers(body) {
486
+ const searchText = typeof body["SearchText"] === "string" ? body["SearchText"] : "";
487
+ return this.sdk().then((s) => s.people.peopleLookup({ body: { SearchText: searchText } }));
557
488
  }
558
- async removeTicketAsset(appId, ticketId, assetId) {
559
- await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets/${assetId}`, { method: "DELETE" });
489
+ getGroup(groupId) {
490
+ return this.sdk().then((s) => s.people.groupsId({ params: { path: { id: groupId } } }));
560
491
  }
561
- // -------------------------------------------------------------------------
562
- // People & Groups
563
- // -------------------------------------------------------------------------
564
- async getUser(uid) {
565
- return await this.requestJson(`/api/people/${encodeURIComponent(uid)}`);
492
+ searchGroups(_body) {
493
+ return this.sdk().then((s) => s.people.searchGroups({ body: {} }));
566
494
  }
567
- async searchUsers(body) {
568
- return await this.requestJson("/api/people/search", {
569
- method: "POST",
570
- body: JSON.stringify(body)
571
- });
495
+ getGroupMembers(groupId) {
496
+ return this.sdk().then((s) => s.people.groupsIdMembers({ params: { path: { id: groupId } } }));
572
497
  }
573
- async getGroup(groupId) {
574
- return await this.requestJson(`/api/groups/${groupId}`);
575
- }
576
- async searchGroups(body) {
577
- return await this.requestJson("/api/groups/search", {
578
- method: "POST",
579
- body: JSON.stringify(body)
580
- });
498
+ //
499
+ // Knowledge Base
500
+ //
501
+ getKbArticle(appId, articleId) {
502
+ return this.sdk().then((s) => s.knowledgeBase.appIdKnowledgebaseId({ params: { path: { appId, id: articleId } } }));
503
+ }
504
+ searchKbArticles(appId, body) {
505
+ const keyword = typeof body["SearchText"] === "string" ? body["SearchText"] : "";
506
+ return this.sdk().then((s) => s.knowledgeBase.appIdKnowledgebaseId({ params: { path: { appId, id: 0 } } })).then(
507
+ (r) => Array.isArray(r) ? r.filter((item) => {
508
+ const s = typeof item === "object" && item !== null ? JSON.stringify(item).toLowerCase() : "";
509
+ return keyword ? s.includes(keyword.toLowerCase()) : true;
510
+ }) : []
511
+ );
581
512
  }
582
- async getGroupMembers(groupId) {
583
- return await this.requestJson(`/api/groups/${groupId}/members`);
513
+ listKbCategories(appId) {
514
+ return this.sdk().then(
515
+ (s) => s.knowledgeBase.appIdKnowledgebaseCategories({ params: { path: { appId } } })
516
+ );
584
517
  }
585
- // -------------------------------------------------------------------------
586
- // Knowledge Base
587
- // -------------------------------------------------------------------------
588
- async getKbArticle(appId, articleId) {
589
- return await this.requestJson(`/api/${appId}/knowledgebase/${articleId}`);
590
- }
591
- async searchKbArticles(appId, body) {
592
- return await this.requestJson(`/api/${appId}/knowledgebase/search`, {
593
- method: "POST",
594
- body: JSON.stringify(body)
518
+ //
519
+ // Assets
520
+ //
521
+ getAsset(appId, assetId) {
522
+ return this.sdk().then((s) => s.assets.appIdAssetsId({ params: { path: { appId, id: assetId } } }));
523
+ }
524
+ searchAssets(appId, _body) {
525
+ return this.sdk().then((s) => s.assets.appIdAssetsFeed({ params: { path: { appId } } }));
526
+ }
527
+ listAssetStatuses(appId) {
528
+ return this.sdk().then((s) => s.assets.appIdAssetsStatuses({ params: { path: { appId } } }));
529
+ }
530
+ listProductModels(appId) {
531
+ return this.sdk().then((s) => s.assets.appIdAssetsModels({ params: { path: { appId } } }));
532
+ }
533
+ listVendors(appId) {
534
+ return this.sdk().then((s) => s.cmdb.appIdAssetsVendors({ params: { path: { appId } } }));
535
+ }
536
+ //
537
+ // CMDB
538
+ //
539
+ getConfigurationItem(appId, ciId) {
540
+ return this.sdk().then((s) => s.cmdb.appIdCmdbId({ params: { path: { appId, id: ciId } } }));
541
+ }
542
+ searchConfigurationItems(appId, _body) {
543
+ return this.sdk().then((s) => s.cmdb.appIdCmdbSearches({ params: { path: { appId } } }));
544
+ }
545
+ listCiTypes(appId) {
546
+ return this.sdk().then((s) => s.cmdb.appIdCmdbTypes({ params: { path: { appId } } }));
547
+ }
548
+ listCiRelationshipTypes() {
549
+ return this.sdk().then((s) => s.cmdb.appIdCmdbRelationshiptypes());
550
+ }
551
+ //
552
+ // Services
553
+ //
554
+ listServiceCatalog(appId) {
555
+ return this.sdk().then((s) => s.services.appIdServices({ params: { path: { appId } } }));
556
+ }
557
+ getService(appId, serviceId) {
558
+ return this.sdk().then((s) => s.services.appIdServicesId({ params: { path: { appId, id: serviceId } } }));
559
+ }
560
+ searchServices(appId, body) {
561
+ const keyword = typeof body["Keyword"] === "string" ? body["Keyword"] : "";
562
+ return this.sdk().then((s) => s.services.appIdServices({ params: { path: { appId } } })).then((r) => {
563
+ if (!Array.isArray(r)) return [];
564
+ if (!keyword) return r;
565
+ const lower = keyword.toLowerCase();
566
+ return r.filter((item) => {
567
+ const text = typeof item === "object" && item !== null ? JSON.stringify(item).toLowerCase() : "";
568
+ return text.includes(lower);
569
+ });
595
570
  });
596
571
  }
597
- async listKbCategories(appId) {
598
- return await this.requestJson(`/api/${appId}/knowledgebase/categories`);
572
+ listServiceCategories(appId) {
573
+ return this.sdk().then((s) => s.services.appIdServicesCategories({ params: { path: { appId } } }));
599
574
  }
600
- async createKbArticle(appId, body) {
601
- return await this.requestJson(`/api/${appId}/knowledgebase`, {
602
- method: "POST",
603
- body: JSON.stringify(body)
604
- });
575
+ //
576
+ // Projects
577
+ //
578
+ getProject(projectId) {
579
+ return this.sdk().then((s) => s.projects.projectsId({ params: { path: { id: projectId } } }));
605
580
  }
606
- async updateKbArticle(appId, articleId, body) {
607
- return await this.requestJson(`/api/${appId}/knowledgebase/${articleId}`, {
608
- method: "PUT",
609
- body: JSON.stringify(body)
610
- });
581
+ searchProjects(_body) {
582
+ return this.sdk().then((s) => s.projects.projectsFeed());
611
583
  }
612
- // -------------------------------------------------------------------------
613
- // Assets / CMDB
614
- // -------------------------------------------------------------------------
615
- async getAsset(appId, assetId) {
616
- return await this.requestJson(`/api/${appId}/assets/${assetId}`);
584
+ listProjectTypes() {
585
+ return this.sdk().then((s) => s.projects.projectsTypes());
617
586
  }
618
- async searchAssets(appId, body) {
619
- return await this.requestJson(`/api/${appId}/assets/search`, {
620
- method: "POST",
621
- body: JSON.stringify(body)
622
- });
587
+ getProjectPlans(projectId) {
588
+ return this.sdk().then(
589
+ (s) => s.projects.projectsProjectIDPlansPlanID({ params: { path: { id: projectId } } })
590
+ );
623
591
  }
624
- async listAssetStatuses(appId) {
625
- return await this.requestJson(`/api/${appId}/assets/statuses`);
592
+ getProjectIssues(projectId) {
593
+ return this.sdk().then(
594
+ (s) => s.projects.projectsProjectIdIssuesCategories({ params: { path: { id: projectId } } })
595
+ );
626
596
  }
627
- async listProductModels(appId) {
628
- return await this.requestJson(`/api/${appId}/assets/models`);
597
+ getProjectRisks(projectId) {
598
+ return this.sdk().then(
599
+ (s) => s.projects.projectsProjectIdRisksCategories({ params: { path: { id: projectId } } })
600
+ );
629
601
  }
630
- async listVendors(appId) {
631
- return await this.requestJson(`/api/${appId}/assets/vendors`);
602
+ //
603
+ // Time
604
+ //
605
+ listTimeTypes() {
606
+ return this.sdk().then((s) => s.time.timeTypes());
632
607
  }
633
- async getConfigurationItem(appId, ciId) {
634
- return await this.requestJson(`/api/${appId}/cmdb/${ciId}`);
608
+ getMyTimeEntries(_startDate, _endDate) {
609
+ return this.sdk().then((s) => s.time.timeId({ params: { path: { id: 0 } } }));
635
610
  }
636
- async searchConfigurationItems(appId, body) {
637
- return await this.requestJson(`/api/${appId}/cmdb/search`, {
638
- method: "POST",
639
- body: JSON.stringify(body)
640
- });
611
+ //
612
+ // Reference Data
613
+ //
614
+ listAccounts(_appId) {
615
+ return this.sdk().then((s) => s.referenceData.accounts());
641
616
  }
642
- async listCiTypes(appId) {
643
- return await this.requestJson(`/api/${appId}/cmdb/types`);
617
+ getAccount(accountId) {
618
+ return this.sdk().then((s) => s.referenceData.accountsId({ params: { path: { id: accountId } } }));
644
619
  }
645
- async listCiRelationshipTypes() {
646
- return await this.requestJson("/api/cmdb/relationshiptypes");
620
+ listLocations() {
621
+ return this.sdk().then((s) => s.referenceData.locations());
647
622
  }
648
- // -------------------------------------------------------------------------
649
- // Service Catalog
650
- // -------------------------------------------------------------------------
651
- async listServiceCatalog(appId) {
652
- return await this.requestJson(`/api/${appId}/services`);
623
+ listFunctionalRoles() {
624
+ return this.sdk().then((s) => s.referenceData.securityrolesPermissions());
653
625
  }
654
- async getService(appId, serviceId) {
655
- return await this.requestJson(`/api/${appId}/services/${serviceId}`);
626
+ listCustomAttributes(_componentId, _appId, _associatedTypeId) {
627
+ return this.sdk().then((s) => s.referenceData.attributesCustom());
656
628
  }
657
- async searchServices(appId, body) {
658
- return await this.requestJson(`/api/${appId}/services/search`, {
659
- method: "POST",
660
- body: JSON.stringify(body)
661
- });
629
+ listTicketStatuses(appId) {
630
+ return this.sdk().then((s) => s.referenceData.appIdTicketsStatuses({ params: { path: { appId } } }));
662
631
  }
663
- // -------------------------------------------------------------------------
664
- // Projects
665
- // -------------------------------------------------------------------------
666
- async getProject(projectId) {
667
- return await this.requestJson(`/api/projects/${projectId}`);
668
- }
669
- async searchProjects(body) {
670
- return await this.requestJson("/api/projects/search", {
671
- method: "POST",
672
- body: JSON.stringify(body)
673
- });
632
+ //
633
+ // Mutations
634
+ //
635
+ createTicket(appId, body, _notifyRequestor, _notifyResponsible) {
636
+ return this.sdk().then((s) => s.tickets.createTicket({ appId, body }));
674
637
  }
675
- async listProjectTypes() {
676
- return await this.requestJson("/api/projects/types");
638
+ updateTicket(appId, ticketId, body, _notifyRequestor, _notifyResponsible, _comments, _isPrivate) {
639
+ return this.sdk().then((s) => s.tickets.updateTicket({ appId, ticketId, body }));
677
640
  }
678
- async getProjectPlans(projectId) {
679
- return await this.requestJson(`/api/projects/${projectId}/plans`);
641
+ addTicketComment(appId, ticketId, body, _isPrivate, _notifyRequestor, _notifyResponsible) {
642
+ return this.sdk().then((s) => s.tickets.addTicketComment({ appId, ticketId, body: { Body: body } }));
680
643
  }
681
- async getProjectIssues(projectId) {
682
- return await this.requestJson(`/api/projects/${projectId}/issues`);
644
+ createKbArticle(appId, body) {
645
+ return this.sdk().then((s) => s.knowledgeBase.createArticle({ appId, body }));
683
646
  }
684
- async getProjectRisks(projectId) {
685
- return await this.requestJson(`/api/projects/${projectId}/risks`);
647
+ updateKbArticle(appId, articleId, body) {
648
+ return this.sdk().then((s) => s.knowledgeBase.updateArticle({ appId, articleId, body }));
686
649
  }
687
- async createProjectIssue(projectId, body) {
688
- return await this.requestJson(`/api/projects/${projectId}/issues`, {
689
- method: "POST",
690
- body: JSON.stringify(body)
691
- });
650
+ createProjectIssue(_projectId, body) {
651
+ return this.sdk().then((s) => s.projects.createIssue({ body }));
692
652
  }
693
- async createProjectRisk(projectId, body) {
694
- return await this.requestJson(`/api/projects/${projectId}/risks`, {
695
- method: "POST",
696
- body: JSON.stringify(body)
697
- });
653
+ createProjectRisk(_projectId, body) {
654
+ return this.sdk().then((s) => s.projects.createRisk({ body }));
698
655
  }
699
- async listTimeTypes() {
700
- return await this.requestJson("/api/time/types");
656
+ //
657
+ // Delete mutations (confirm:true required for all)
658
+ //
659
+ deleteAsset(appId, assetId) {
660
+ return this.sdk().then((s) => s.assets.deleteAsset({ appId, assetId, confirm: true }));
701
661
  }
702
- async getMyTimeEntries(startDate, endDate) {
703
- const qs = new URLSearchParams({ startDate, endDate });
704
- return await this.requestJson(`/api/time/entries?${qs}`);
662
+ deleteConfigurationItem(appId, ciId) {
663
+ return this.sdk().then((s) => s.cmdb.deleteConfigurationItem({ appId, configurationItemId: ciId, confirm: true }));
705
664
  }
706
- async getTicketContacts(appId, ticketId) {
707
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/contacts`);
665
+ deleteService(appId, serviceId) {
666
+ return this.sdk().then((s) => s.services.deleteService({ appId, serviceId, confirm: true }));
708
667
  }
709
- async addTicketContact(appId, ticketId, contactUid) {
710
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/contacts/${contactUid}`, {
711
- method: "PUT"
712
- });
668
+ deleteServiceCategory(appId, categoryId) {
669
+ return this.sdk().then((s) => s.services.deleteServiceCategory({ appId, categoryId, confirm: true }));
713
670
  }
714
- async removeTicketContact(appId, ticketId, contactUid) {
715
- await this.requestJson(`/api/${appId}/tickets/${ticketId}/contacts/${contactUid}`, {
716
- method: "DELETE"
717
- });
671
+ deleteTimeEntry(timeEntryId) {
672
+ return this.sdk().then((s) => s.time.deleteTimeEntry({ timeEntryId, confirm: true }));
718
673
  }
719
- async listServiceCategories(appId) {
720
- return await this.requestJson(`/api/${appId}/services/categories`);
674
+ };
675
+ function createConfiguredTeamDynamixClient() {
676
+ return new UnifiedTeamDynamixClient(getTeamDynamixConfig());
677
+ }
678
+ function assertWriteToolsEnabled(config) {
679
+ if (!config.enableWriteTools) {
680
+ throw new Error("Write tools are disabled. Set TEAMDYNAMIX_ENABLE_WRITE_TOOLS=true in your environment.");
721
681
  }
722
- async requestJson(path, options = {}) {
723
- const headers = new Headers(options.headers);
724
- headers.set("Accept", "application/json");
725
- if (options.body && !headers.has("Content-Type") && !(options.body instanceof FormData)) {
726
- headers.set("Content-Type", "application/json");
727
- }
728
- const token = await this.getBearerToken(options.requireAdmin ?? false);
729
- headers.set("Authorization", `Bearer ${token}`);
730
- const requestInit = {
731
- method: options.method ?? "GET",
732
- headers,
733
- body: options.body
734
- };
735
- const endpoint = `${this.config.baseUrl}${normalizePath(path)}`;
736
- const cappedRetries = Math.min(this.config.maxRetries, TEAMDYNAMIX_MAX_RETRY_ATTEMPTS);
737
- await this.rateLimiter.acquire();
738
- for (let attempt = 0; attempt <= cappedRetries; attempt += 1) {
739
- const controller = new AbortController();
740
- const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
741
- let response;
742
- try {
743
- response = await fetch(endpoint, { ...requestInit, signal: controller.signal });
744
- } finally {
745
- clearTimeout(timeoutId);
746
- }
747
- if (response.status === 429 && attempt < cappedRetries) {
748
- const rateLimit = parseRateLimit(response.headers);
749
- if (LOG_LEVEL === "debug") {
750
- console.error(
751
- `[teamdynamix-mcp] rate limited on ${normalizePath(path)}; retry ${attempt + 1}/${cappedRetries} in ${rateLimit.waitMs}ms`
752
- );
753
- }
754
- await sleep(rateLimit.waitMs);
755
- continue;
756
- }
757
- if (!response.ok) {
758
- await response.text();
759
- throw new Error(`TeamDynamix request failed (${response.status}) for ${normalizePath(path)}.`);
760
- }
761
- const contentType = response.headers.get("content-type") ?? "";
762
- if (contentType.includes("application/json")) {
763
- const data = await response.json();
764
- if (Array.isArray(data)) {
765
- TeamDynamixListResponseSchema.parse(data);
766
- } else if (typeof data === "object" && data !== null) {
767
- TeamDynamixSingleResponseSchema.parse(data);
768
- }
769
- return data;
770
- }
771
- const text = await response.text();
772
- return text;
773
- }
774
- throw new Error(`TeamDynamix request exceeded retry budget for ${normalizePath(path)}.`);
775
- }
776
- async getBearerToken(requireAdmin) {
777
- const configStatus = getTeamDynamixConfigStatus(this.config);
778
- if (!configStatus.configured) {
779
- throw new Error(
780
- `TeamDynamix is not configured. Missing: ${configStatus.missing.join(", ")}. Populate the required environment variables in .env before using TeamDynamix tools.`
781
- );
782
- }
783
- if (requireAdmin && this.config.authMode !== "admin") {
784
- throw new Error(
785
- "This TeamDynamix action requires admin authentication, but TEAMDYNAMIX_AUTH_MODE is not set to admin."
786
- );
682
+ }
683
+ function assertDeleteToolsEnabled(config) {
684
+ if (!config.enableDeleteTools) {
685
+ throw new Error("Delete tools are disabled. Set TEAMDYNAMIX_ENABLE_DELETE_TOOLS=true in your environment.");
686
+ }
687
+ }
688
+
689
+ // src/services/teamdynamix/render.service.ts
690
+ import { tablemark } from "tablemark";
691
+ function render(payload, responseFormat, characterLimit = CHARACTER_LIMIT) {
692
+ if (responseFormat === "json") {
693
+ if (typeof payload === "undefined") return "undefined";
694
+ return JSON.stringify(payload, null, 2);
695
+ }
696
+ const markdown = renderMarkdown(payload);
697
+ return truncateMarkdown(markdown, characterLimit);
698
+ }
699
+ function renderMarkdown(payload) {
700
+ if (payload == null) return "";
701
+ if (typeof payload !== "object") {
702
+ return String(payload);
703
+ }
704
+ if (Array.isArray(payload)) {
705
+ if (payload.length === 0) {
706
+ return "";
787
707
  }
788
- const expiresAtMs = this.cachedToken?.expiresAtMs;
789
- if (this.cachedToken && (!expiresAtMs || expiresAtMs > Date.now() + 6e4)) {
790
- return this.cachedToken.token;
708
+ if (payload.every(isPlainObject)) {
709
+ return renderArrayAsMarkdownTable(payload);
791
710
  }
792
- const token = await this.login();
793
- return token;
711
+ return "```json\n" + JSON.stringify(payload, null, 2) + "\n```";
794
712
  }
795
- async login() {
796
- if (!this.config.baseUrl) {
797
- throw new Error("TeamDynamix base URL is not configured. Set TEAMDYNAMIX_BASE_URL.");
713
+ if (isPlainObject(payload)) {
714
+ const hasArrayOfObjects = Object.values(payload).some(isArrayOfPlainObjects);
715
+ if (hasArrayOfObjects) {
716
+ return renderObjectWithTables(payload);
798
717
  }
799
- const isAdmin = this.config.authMode === "admin";
800
- const loginPath = isAdmin ? "/api/auth/loginadmin" : "/api/auth/login";
801
- const body = isAdmin ? JSON.stringify({ BEID: this.config.beid, WebServicesKey: this.config.webServicesKey }) : JSON.stringify({ username: this.config.username, password: this.config.password });
802
- const loginController = new AbortController();
803
- const loginTimeoutId = setTimeout(() => loginController.abort(), this.config.timeoutMs);
804
- let response;
805
- try {
806
- response = await fetch(`${this.config.baseUrl}${loginPath}`, {
807
- method: "POST",
808
- headers: {
809
- Accept: "application/json, text/plain;q=0.9",
810
- "Content-Type": "application/json"
811
- },
812
- body,
813
- signal: loginController.signal
814
- });
815
- } finally {
816
- clearTimeout(loginTimeoutId);
718
+ return renderObjectAsMarkdownList(payload);
719
+ }
720
+ return "";
721
+ }
722
+ function isPlainObject(value) {
723
+ return typeof value === "object" && value !== null && !Array.isArray(value);
724
+ }
725
+ function isArrayOfPlainObjects(value) {
726
+ return Array.isArray(value) && value.length > 0 && value.every(isPlainObject);
727
+ }
728
+ function serializeValue(value) {
729
+ if (typeof value !== "object" || value === null) return String(value);
730
+ if (Array.isArray(value)) return value.map(serializeValue).join(", ");
731
+ return "{ " + Object.entries(value).map(([k, v]) => `${k}: ${typeof v === "object" && v !== null ? "[Object]" : String(v)}`).join(", ") + " }";
732
+ }
733
+ function renderObjectAsMarkdownList(obj) {
734
+ const lines = [];
735
+ for (const [key, val] of Object.entries(obj)) {
736
+ lines.push(`- **${key}**: ${truncateCell(serializeValue(val))}`);
737
+ }
738
+ return lines.join("\n");
739
+ }
740
+ function renderObjectWithTables(obj) {
741
+ const lines = [];
742
+ const scalarEntries = [];
743
+ const arrayEntries = [];
744
+ for (const [key, val] of Object.entries(obj)) {
745
+ if (isArrayOfPlainObjects(val)) {
746
+ arrayEntries.push([key, val]);
747
+ } else {
748
+ scalarEntries.push([key, val]);
817
749
  }
818
- if (!response.ok) {
819
- await response.text();
820
- throw new Error(`TeamDynamix authentication failed (${response.status}).`);
750
+ }
751
+ if (scalarEntries.length > 0) {
752
+ lines.push("**Metadata:**");
753
+ for (const [key, val] of scalarEntries) {
754
+ lines.push(`- **${key}**: ${truncateCell(serializeValue(val))}`);
821
755
  }
822
- const contentType = response.headers.get("content-type") ?? "";
823
- const payload = contentType.includes("application/json") ? await response.json() : await response.text();
824
- const token = extractAuthToken(payload);
825
- const expiryEpochSeconds = decodeJwtExpiryEpochSeconds(token);
826
- this.cachedToken = {
827
- token,
828
- expiresAtMs: expiryEpochSeconds ? expiryEpochSeconds * 1e3 : null
829
- };
830
- return token;
756
+ lines.push("");
831
757
  }
832
- };
833
- function createConfiguredTeamDynamixClient() {
834
- return new TeamDynamixClient(getTeamDynamixConfig());
758
+ for (const [key, arr] of arrayEntries) {
759
+ lines.push(`**${key}:**`);
760
+ const table = renderArrayAsMarkdownTable(arr);
761
+ lines.push(table);
762
+ lines.push("");
763
+ }
764
+ return lines.join("\n");
765
+ }
766
+ function renderArrayAsMarkdownTable(arr) {
767
+ const toCellText = ({ value }) => serializeValue(value);
768
+ let table = tablemark(arr, {
769
+ toCellText,
770
+ headerCase: "preserve"
771
+ });
772
+ table = table.replace(/\| +/g, "| ").replace(/ +\|/g, " |");
773
+ table = table.replace(
774
+ /^(\| +:)(-+)( +\|)/gm,
775
+ (_match, _pre, _dashes, _post, offset, str) => {
776
+ if (str.slice(0, offset).includes(":---")) return _match;
777
+ const headerLine = str.slice(0, offset).split("\n").reverse().find((l) => l.startsWith("|"));
778
+ if (!headerLine) return _match;
779
+ const colCount = headerLine.split("|").length - 2;
780
+ const dashCounts = Array.from({ length: colCount }, (_, i) => 3 + i);
781
+ const newRow = [""].concat(dashCounts.map((n) => ` :${"-".repeat(n)} `)).concat([""]).join("|");
782
+ return newRow;
783
+ }
784
+ );
785
+ return table;
786
+ }
787
+ function truncateCell(str) {
788
+ const MAX = 120;
789
+ if (str.length <= MAX) return str;
790
+ return str.slice(0, MAX) + "\u2026";
791
+ }
792
+ function truncateMarkdown(markdown, limit) {
793
+ if (markdown.length <= limit) return markdown;
794
+ const ellipsis = "...";
795
+ if (limit <= ellipsis.length) return markdown.slice(0, limit);
796
+ const truncated = markdown.slice(0, limit - ellipsis.length).replace(/\n+$/, "");
797
+ return truncated + ellipsis;
835
798
  }
836
799
 
837
800
  // src/tools/teamdynamix.domain-gateways.tools.ts
@@ -882,20 +845,24 @@ var teamdynamixAssetsActionSchema = z4.enum([
882
845
  "get_asset",
883
846
  "search_assets",
884
847
  "list_asset_statuses",
885
- "list_product_models"
848
+ "list_product_models",
849
+ "delete_asset"
886
850
  ]);
887
851
  var teamdynamixCmdbActionSchema = z4.enum([
888
852
  "get_ci",
889
853
  "search_cis",
890
854
  "list_ci_types",
891
855
  "list_ci_relationship_types",
892
- "list_vendors"
856
+ "list_vendors",
857
+ "delete_ci"
893
858
  ]);
894
859
  var teamdynamixServicesActionSchema = z4.enum([
895
860
  "list_services",
896
861
  "get_service",
897
862
  "search_services",
898
- "list_service_categories"
863
+ "list_service_categories",
864
+ "delete_service",
865
+ "delete_service_category"
899
866
  ]);
900
867
  var teamdynamixProjectsActionSchema = z4.enum([
901
868
  "get_project",
@@ -916,11 +883,6 @@ var teamdynamixReferenceDataActionSchema = z4.enum([
916
883
  "list_custom_attributes"
917
884
  ]);
918
885
  var gatewayPayloadSchema = z4.record(z4.string(), z4.unknown()).default({});
919
- function render(data, responseFormat) {
920
- if (responseFormat === "json") return JSON.stringify(data, null, 2);
921
- if (typeof data === "string") return data;
922
- return JSON.stringify(data, null, 2);
923
- }
924
886
  function messageFromError(error) {
925
887
  return error instanceof Error ? error.message : String(error);
926
888
  }
@@ -1527,6 +1489,23 @@ function registerTeamDynamixAssetsGateway(server2) {
1527
1489
  const models = await client.listProductModels(parsed.app_id);
1528
1490
  return toSuccessResponse({ appId: parsed.app_id, count: models.length, models }, response_format);
1529
1491
  }
1492
+ case "delete_asset": {
1493
+ const parsed = parsePayload(
1494
+ z4.object({
1495
+ app_id: TeamDynamixAppIdSchema,
1496
+ asset_id: z4.number().int().positive(),
1497
+ confirm: z4.literal(true)
1498
+ }),
1499
+ payload,
1500
+ action
1501
+ );
1502
+ assertDeleteToolsEnabled(getTeamDynamixConfig());
1503
+ await client.deleteAsset(parsed.app_id, parsed.asset_id);
1504
+ return toSuccessResponse(
1505
+ { appId: parsed.app_id, assetId: parsed.asset_id, deleted: true },
1506
+ response_format
1507
+ );
1508
+ }
1530
1509
  }
1531
1510
  } catch (error) {
1532
1511
  return toErrorResponse(error);
@@ -1586,6 +1565,20 @@ function registerTeamDynamixCmdbGateway(server2) {
1586
1565
  const vendors = await client.listVendors(parsed.app_id);
1587
1566
  return toSuccessResponse({ appId: parsed.app_id, count: vendors.length, vendors }, response_format);
1588
1567
  }
1568
+ case "delete_ci": {
1569
+ const parsed = parsePayload(
1570
+ z4.object({
1571
+ app_id: TeamDynamixAppIdSchema,
1572
+ ci_id: z4.number().int().positive(),
1573
+ confirm: z4.literal(true)
1574
+ }),
1575
+ payload,
1576
+ action
1577
+ );
1578
+ assertDeleteToolsEnabled(getTeamDynamixConfig());
1579
+ await client.deleteConfigurationItem(parsed.app_id, parsed.ci_id);
1580
+ return toSuccessResponse({ appId: parsed.app_id, ciId: parsed.ci_id, deleted: true }, response_format);
1581
+ }
1589
1582
  }
1590
1583
  } catch (error) {
1591
1584
  return toErrorResponse(error);
@@ -1638,6 +1631,40 @@ function registerTeamDynamixServicesGateway(server2) {
1638
1631
  const categories = await client.listServiceCategories(parsed.app_id);
1639
1632
  return toSuccessResponse({ appId: parsed.app_id, count: categories.length, categories }, response_format);
1640
1633
  }
1634
+ case "delete_service": {
1635
+ const parsed = parsePayload(
1636
+ z4.object({
1637
+ app_id: TeamDynamixAppIdSchema,
1638
+ service_id: z4.number().int().positive(),
1639
+ confirm: z4.literal(true)
1640
+ }),
1641
+ payload,
1642
+ action
1643
+ );
1644
+ assertDeleteToolsEnabled(getTeamDynamixConfig());
1645
+ await client.deleteService(parsed.app_id, parsed.service_id);
1646
+ return toSuccessResponse(
1647
+ { appId: parsed.app_id, serviceId: parsed.service_id, deleted: true },
1648
+ response_format
1649
+ );
1650
+ }
1651
+ case "delete_service_category": {
1652
+ const parsed = parsePayload(
1653
+ z4.object({
1654
+ app_id: TeamDynamixAppIdSchema,
1655
+ category_id: z4.number().int().positive(),
1656
+ confirm: z4.literal(true)
1657
+ }),
1658
+ payload,
1659
+ action
1660
+ );
1661
+ assertDeleteToolsEnabled(getTeamDynamixConfig());
1662
+ await client.deleteServiceCategory(parsed.app_id, parsed.category_id);
1663
+ return toSuccessResponse(
1664
+ { appId: parsed.app_id, categoryId: parsed.category_id, deleted: true },
1665
+ response_format
1666
+ );
1667
+ }
1641
1668
  }
1642
1669
  } catch (error) {
1643
1670
  return toErrorResponse(error);