@selfagency/teamdynamix-mcp 0.1.0 → 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 +18 -3
  2. package/index.js +500 -421
  3. package/index.js.map +1 -1
  4. package/package.json +7 -3
package/index.js CHANGED
@@ -10,17 +10,19 @@ 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;
22
21
  var SERVER_VERSION_OVERRIDE = process.env["MCP_SERVER_VERSION"]?.trim() || void 0;
23
- var LOG_LEVEL = process.env["MCP_LOG_LEVEL"] === "debug" || process.env["MCP_LOG_LEVEL"] === "warn" || process.env["MCP_LOG_LEVEL"] === "error" ? process.env["MCP_LOG_LEVEL"] : "info";
22
+ var validLogLevels = ["debug", "info", "warn", "error"];
23
+ var logLevelSchema = z.enum(validLogLevels);
24
+ var parsedLogLevel = logLevelSchema.safeParse(process.env["MCP_LOG_LEVEL"]?.trim());
25
+ var LOG_LEVEL = parsedLogLevel.success ? parsedLogLevel.data : "info";
24
26
  function normalizeOptionalString(value) {
25
27
  const normalized = value?.trim();
26
28
  return normalized ? normalized : void 0;
@@ -89,7 +91,8 @@ function getTeamDynamixConfig() {
89
91
  timeoutMs: normalizeNumberWithDefault(process.env["TEAMDYNAMIX_TIMEOUT_MS"], TEAMDYNAMIX_DEFAULT_TIMEOUT_MS, 1e3),
90
92
  maxRetries,
91
93
  enableWriteTools: normalizeBoolean(process.env["TEAMDYNAMIX_ENABLE_WRITE_TOOLS"], false),
92
- 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)
93
96
  };
94
97
  }
95
98
  function getTeamDynamixConfigStatus(config = getTeamDynamixConfig()) {
@@ -115,26 +118,6 @@ function getTeamDynamixConfigStatus(config = getTeamDynamixConfig()) {
115
118
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
116
119
 
117
120
  // src/services/teamdynamix/core.service.ts
118
- function parseRateLimit(headers, nowMs = Date.now()) {
119
- const limitRaw = headers.get("X-RateLimit-Limit");
120
- const remainingRaw = headers.get("X-RateLimit-Remaining");
121
- const resetRaw = headers.get("X-RateLimit-Reset");
122
- const resetTime = resetRaw ? Date.parse(resetRaw) : Number.NaN;
123
- const computedWaitMs = Number.isFinite(resetTime) ? resetTime - nowMs : TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS;
124
- const waitMs = Math.min(
125
- Math.max(
126
- Number.isFinite(computedWaitMs) ? computedWaitMs : TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS,
127
- TEAMDYNAMIX_MIN_RATE_LIMIT_WAIT_MS
128
- ),
129
- TEAMDYNAMIX_MAX_RATE_LIMIT_WAIT_MS
130
- );
131
- return {
132
- limit: limitRaw ? Number.parseInt(limitRaw, 10) : null,
133
- remaining: remainingRaw ? Number.parseInt(remainingRaw, 10) : null,
134
- resetAt: Number.isFinite(resetTime) ? new Date(resetTime).toISOString() : null,
135
- waitMs
136
- };
137
- }
138
121
  function redactTeamDynamixConfig(config) {
139
122
  return {
140
123
  baseUrl: config.baseUrl ?? null,
@@ -152,39 +135,6 @@ function redactTeamDynamixConfig(config) {
152
135
  enableAdminTools: config.enableAdminTools
153
136
  };
154
137
  }
155
- function decodeJwtExpiryEpochSeconds(token) {
156
- const segments = token.split(".");
157
- const payload = segments[1];
158
- if (!payload) {
159
- return null;
160
- }
161
- try {
162
- const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
163
- const padding = normalized.length % 4 === 0 ? "" : "=".repeat(4 - normalized.length % 4);
164
- const decoded = Buffer.from(`${normalized}${padding}`, "base64").toString("utf8");
165
- const parsed = JSON.parse(decoded);
166
- return typeof parsed.exp === "number" ? parsed.exp : null;
167
- } catch {
168
- return null;
169
- }
170
- }
171
- function extractAuthToken(payload) {
172
- if (typeof payload === "string" && payload.trim()) {
173
- return payload.trim();
174
- }
175
- if (!payload || typeof payload !== "object") {
176
- throw new Error("Unable to extract bearer token from TeamDynamix authentication response.");
177
- }
178
- const record = payload;
179
- const candidateKeys = ["token", "Token", "accessToken", "AccessToken", "bearerToken", "TokenText", "value"];
180
- for (const key of candidateKeys) {
181
- const candidate = record[key];
182
- if (typeof candidate === "string" && candidate.trim()) {
183
- return candidate.trim();
184
- }
185
- }
186
- throw new Error("Unable to extract bearer token from TeamDynamix authentication response.");
187
- }
188
138
 
189
139
  // src/tools/teamdynamix.domain-gateways.tools.ts
190
140
  import { z as z4 } from "zod";
@@ -199,10 +149,14 @@ var TextTransformModeSchema = z2.enum(["uppercase", "lowercase", "trim", "slug"]
199
149
 
200
150
  // src/schemas/teamdynamix/index.ts
201
151
  var TeamDynamixAppIdSchema = z3.number().int().positive().describe("TeamDynamix application ID.");
152
+ var IsoDateSchema = z3.string().regex(
153
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/,
154
+ "Must be a valid ISO 8601 date string."
155
+ );
202
156
  var TeamDynamixGuidSchema = z3.string().uuid().describe("TeamDynamix GUID identifier.");
203
157
  var TeamDynamixResponseFormatSchema = ResponseFormatSchema;
204
158
  var TicketSearchSchema = z3.object({
205
- Keywords: z3.string().optional().describe("Full-text search term."),
159
+ Keywords: z3.string().max(500).optional().describe("Full-text search term."),
206
160
  MaxResults: z3.number().int().min(1).max(1e3).optional().default(50).describe("Maximum results to return (1\u20131000)."),
207
161
  StatusIDs: z3.array(z3.number().int()).optional().describe("Filter by ticket status IDs."),
208
162
  TypeIDs: z3.array(z3.number().int()).optional().describe("Filter by ticket type IDs."),
@@ -213,25 +167,25 @@ var TicketSearchSchema = z3.object({
213
167
  ResponsibleGroupIDs: z3.array(z3.number().int()).optional().describe("Filter by responsible group IDs."),
214
168
  ResponsibleUids: z3.array(z3.string().uuid()).optional().describe("Filter by responsible user GUIDs."),
215
169
  RequestorUids: z3.array(z3.string().uuid()).optional().describe("Filter by requestor user GUIDs."),
216
- CreatedDateFrom: z3.string().optional().describe("ISO 8601 start date for creation date filter."),
217
- CreatedDateTo: z3.string().optional().describe("ISO 8601 end date for creation date filter."),
218
- ModifiedDateFrom: z3.string().optional().describe("ISO 8601 start date for last-modified filter."),
219
- ModifiedDateTo: z3.string().optional().describe("ISO 8601 end date for last-modified filter."),
220
- ClosedDateFrom: z3.string().optional().describe("ISO 8601 start date for closed date filter."),
221
- ClosedDateTo: z3.string().optional().describe("ISO 8601 end date for closed date filter."),
222
- SortBy: z3.string().optional().describe("Field name to sort results by."),
170
+ CreatedDateFrom: IsoDateSchema.optional().describe("ISO 8601 start date for creation date filter."),
171
+ CreatedDateTo: IsoDateSchema.optional().describe("ISO 8601 end date for creation date filter."),
172
+ ModifiedDateFrom: IsoDateSchema.optional().describe("ISO 8601 start date for last-modified filter."),
173
+ ModifiedDateTo: IsoDateSchema.optional().describe("ISO 8601 end date for last-modified filter."),
174
+ ClosedDateFrom: IsoDateSchema.optional().describe("ISO 8601 start date for closed date filter."),
175
+ ClosedDateTo: IsoDateSchema.optional().describe("ISO 8601 end date for closed date filter."),
176
+ SortBy: z3.string().max(100).optional().describe("Field name to sort results by."),
223
177
  SortOrder: z3.enum(["A", "D"]).optional().describe("Sort order: A = ascending, D = descending.")
224
178
  });
225
179
  var TicketCreateSchema = z3.object({
226
180
  TypeID: z3.number().int().positive().describe("Ticket type ID."),
227
- Title: z3.string().min(1).describe("Ticket title/subject."),
181
+ Title: z3.string().min(1).max(500).describe("Ticket title/subject."),
228
182
  AccountID: z3.number().int().positive().optional().describe("Account/department ID."),
229
183
  StatusID: z3.number().int().nonnegative().optional().describe("Initial ticket status ID."),
230
184
  PriorityID: z3.number().int().nonnegative().optional().describe("Ticket priority ID."),
231
185
  UrgencyID: z3.number().int().nonnegative().optional().describe("Ticket urgency ID."),
232
186
  ImpactID: z3.number().int().nonnegative().optional().describe("Ticket impact ID."),
233
187
  SourceID: z3.number().int().nonnegative().optional().describe("Ticket source ID."),
234
- Description: z3.string().optional().describe("Full description/body of the ticket (HTML supported)."),
188
+ Description: z3.string().max(65535).optional().describe("Full description/body of the ticket (HTML supported)."),
235
189
  RequestorUID: z3.string().uuid().optional().describe("GUID of the requestor."),
236
190
  ResponsibleUID: z3.string().uuid().optional().describe("GUID of the responsible technician."),
237
191
  ResponsibleGroupID: z3.number().int().positive().optional().describe("Responsible group ID."),
@@ -245,56 +199,56 @@ var TicketCreateSchema = z3.object({
245
199
  });
246
200
  var TicketPatchSchema = z3.object({
247
201
  TicketID: z3.number().int().positive().describe("Ticket ID to update."),
248
- Attributes: z3.record(z3.string(), z3.string()).describe(
249
- 'Fields to update as key/value pairs. Keys must be valid ticket field names (e.g. "StatusID", "Title", "ResponsibleUID").'
202
+ Attributes: z3.record(z3.string().max(100), z3.string().max(65535)).refine((obj) => Object.keys(obj).length <= 50, { message: "Attributes may contain at most 50 fields per update." }).describe(
203
+ 'Fields to update as key/value pairs. Keys must be valid ticket field names (e.g. "StatusID", "Title", "ResponsibleUID"). Maximum 50 fields per update.'
250
204
  ),
251
205
  NotifyRequestor: z3.boolean().optional().default(false).describe("Notify the requestor of the change."),
252
206
  NotifyResponsible: z3.boolean().optional().default(false).describe("Notify the responsible technician of the change."),
253
- Comments: z3.string().optional().describe("Comment to attach to this update."),
207
+ Comments: z3.string().max(65535).optional().describe("Comment to attach to this update."),
254
208
  IsPrivate: z3.boolean().optional().default(false).describe("Whether the comment is private.")
255
209
  });
256
210
  var TicketCommentSchema = z3.object({
257
211
  TicketID: z3.number().int().positive().describe("Ticket ID to comment on."),
258
- Body: z3.string().min(1).describe("Comment body (HTML supported)."),
212
+ Body: z3.string().min(1).max(65535).describe("Comment body (HTML supported)."),
259
213
  IsPrivate: z3.boolean().optional().default(false).describe("Whether this comment is private."),
260
214
  NotifyRequestor: z3.boolean().optional().default(false).describe("Notify the requestor."),
261
215
  NotifyResponsible: z3.boolean().optional().default(false).describe("Notify the responsible technician.")
262
216
  });
263
217
  var UserSearchSchema = z3.object({
264
- SearchText: z3.string().optional().describe("Name, username, or email to search for."),
218
+ SearchText: z3.string().max(500).optional().describe("Name, username, or email to search for."),
265
219
  IsActive: z3.boolean().optional().describe("Filter by active (true) or inactive (false) users."),
266
220
  IsEmployee: z3.boolean().optional().describe("Filter to employees only."),
267
221
  AppID: z3.number().int().positive().optional().describe("Scope search to a specific application."),
268
222
  MaxResults: z3.number().int().min(1).max(1e3).optional().default(25).describe("Maximum results (1\u20131000).")
269
223
  });
270
224
  var GroupSearchSchema = z3.object({
271
- NameLike: z3.string().optional().describe("Partial group name to search."),
225
+ NameLike: z3.string().max(500).optional().describe("Partial group name to search."),
272
226
  IsActive: z3.boolean().optional().describe("Filter by active (true) or inactive (false) groups."),
273
227
  AppID: z3.number().int().positive().optional().describe("Scope search to a specific application.")
274
228
  });
275
229
  var KbArticleSearchSchema = z3.object({
276
- SearchText: z3.string().optional().describe("Full-text search within articles."),
230
+ SearchText: z3.string().max(500).optional().describe("Full-text search within articles."),
277
231
  CategoryID: z3.number().int().positive().optional().describe("Filter by KB category ID."),
278
232
  IsPublished: z3.boolean().optional().describe("Filter to published articles only."),
279
233
  MaxResults: z3.number().int().min(1).max(500).optional().default(25).describe("Maximum results (1\u2013500).")
280
234
  });
281
235
  var AssetSearchSchema = z3.object({
282
- SerialLike: z3.string().optional().describe("Partial serial number to match."),
283
- TagLike: z3.string().optional().describe("Partial asset tag to match."),
284
- SearchText: z3.string().optional().describe("General text search across asset fields."),
236
+ SerialLike: z3.string().max(200).optional().describe("Partial serial number to match."),
237
+ TagLike: z3.string().max(200).optional().describe("Partial asset tag to match."),
238
+ SearchText: z3.string().max(500).optional().describe("General text search across asset fields."),
285
239
  StatusIDs: z3.array(z3.number().int()).optional().describe("Filter by asset status IDs."),
286
240
  OwnerUID: z3.string().uuid().optional().describe("Filter by asset owner GUID."),
287
241
  UsingDepartmentID: z3.number().int().positive().optional().describe("Filter by using department ID."),
288
242
  MaxResults: z3.number().int().min(1).max(1e3).optional().default(25).describe("Maximum results (1\u20131000).")
289
243
  });
290
244
  var ServiceSearchSchema = z3.object({
291
- SearchText: z3.string().optional().describe("Full-text search across service fields."),
245
+ SearchText: z3.string().max(500).optional().describe("Full-text search across service fields."),
292
246
  IsActive: z3.boolean().optional().describe("Filter to active services only."),
293
247
  CategoryID: z3.number().int().positive().optional().describe("Filter by service category ID."),
294
248
  MaxResults: z3.number().int().min(1).max(500).optional().default(25).describe("Maximum results (1\u2013500).")
295
249
  });
296
250
  var ProjectSearchSchema = z3.object({
297
- NameLike: z3.string().optional().describe("Partial project name to search."),
251
+ NameLike: z3.string().max(500).optional().describe("Partial project name to search."),
298
252
  TypeIDs: z3.array(z3.number().int()).optional().describe("Filter by project type IDs."),
299
253
  IsActive: z3.boolean().optional().describe("Filter to active projects."),
300
254
  ManagerUID: z3.string().uuid().optional().describe("Filter by project manager GUID."),
@@ -302,28 +256,28 @@ var ProjectSearchSchema = z3.object({
302
256
  });
303
257
  var TicketTaskCreateSchema = z3.object({
304
258
  TicketID: z3.number().int().positive().describe("Parent ticket ID."),
305
- Title: z3.string().min(1).describe("Task title."),
306
- Description: z3.string().optional().describe("Task description."),
259
+ Title: z3.string().min(1).max(500).describe("Task title."),
260
+ Description: z3.string().max(65535).optional().describe("Task description."),
307
261
  IsActive: z3.boolean().optional().default(true).describe("Whether the task is active."),
308
262
  AssignedUID: z3.string().uuid().optional().describe("GUID of the assigned user."),
309
263
  AssignedGroupID: z3.number().int().positive().optional().describe("Assigned group ID."),
310
264
  EstimatedMinutes: z3.number().int().nonnegative().optional().describe("Estimated minutes to complete."),
311
- StartDate: z3.string().optional().describe("Task start date (ISO 8601)."),
312
- EndDate: z3.string().optional().describe("Task due date (ISO 8601).")
265
+ StartDate: IsoDateSchema.optional().describe("Task start date (ISO 8601)."),
266
+ EndDate: IsoDateSchema.optional().describe("Task due date (ISO 8601).")
313
267
  });
314
268
  var KbArticleCreateSchema = z3.object({
315
- Subject: z3.string().min(1).describe("Article title/subject."),
316
- Body: z3.string().min(1).describe("Article body content (HTML supported)."),
317
- Summary: z3.string().optional().describe("Short article summary."),
269
+ Subject: z3.string().min(1).max(500).describe("Article title/subject."),
270
+ Body: z3.string().min(1).max(5e5).describe("Article body content (HTML supported)."),
271
+ Summary: z3.string().max(2e3).optional().describe("Short article summary."),
318
272
  CategoryID: z3.number().int().positive().optional().describe("KB category ID."),
319
273
  IsPublished: z3.boolean().optional().default(false).describe("Whether to publish the article immediately."),
320
- Tags: z3.string().optional().describe("Comma-separated tags for the article.")
274
+ Tags: z3.string().max(1e3).optional().describe("Comma-separated tags for the article.")
321
275
  });
322
276
  var KbArticleUpdateSchema = KbArticleCreateSchema.partial().extend({
323
277
  ArticleID: z3.number().int().positive().describe("KB article ID to update.")
324
278
  });
325
279
  var CiSearchSchema = z3.object({
326
- SearchText: z3.string().optional().describe("Full-text search across CI fields."),
280
+ SearchText: z3.string().max(500).optional().describe("Full-text search across CI fields."),
327
281
  TypeIDs: z3.array(z3.number().int()).optional().describe("Filter by CI type IDs."),
328
282
  OwnerUID: z3.string().uuid().optional().describe("Filter by owner GUID."),
329
283
  MaxResults: z3.number().int().min(1).max(1e3).optional().default(25).describe("Maximum results (1\u20131000).")
@@ -332,25 +286,25 @@ var CustomAttributeComponentIdSchema = z3.number().int().positive().describe(
332
286
  "TeamDynamix component ID for the attribute context. Common values: 9 = Ticket, 27 = Asset, 63 = KB Article, 31 = Person."
333
287
  );
334
288
  var ProjectIssueCreateSchema = z3.object({
335
- Title: z3.string().min(1).describe("Issue title."),
336
- Description: z3.string().optional().describe("Issue description."),
289
+ Title: z3.string().min(1).max(500).describe("Issue title."),
290
+ Description: z3.string().max(65535).optional().describe("Issue description."),
337
291
  AssignedUID: z3.string().uuid().optional().describe("GUID of the assigned user."),
338
292
  StatusID: z3.number().int().nonnegative().optional().describe("Issue status ID."),
339
293
  PriorityID: z3.number().int().nonnegative().optional().describe("Issue priority ID."),
340
- DueDate: z3.string().optional().describe("Issue due date (ISO 8601).")
294
+ DueDate: IsoDateSchema.optional().describe("Issue due date (ISO 8601).")
341
295
  });
342
296
  var ProjectRiskCreateSchema = z3.object({
343
- Title: z3.string().min(1).describe("Risk title."),
344
- Description: z3.string().optional().describe("Risk description."),
297
+ Title: z3.string().min(1).max(500).describe("Risk title."),
298
+ Description: z3.string().max(65535).optional().describe("Risk description."),
345
299
  AssignedUID: z3.string().uuid().optional().describe("GUID of the risk owner."),
346
300
  StatusID: z3.number().int().nonnegative().optional().describe("Risk status ID."),
347
301
  Probability: z3.number().int().min(1).max(100).optional().describe("Probability percentage (1\u2013100)."),
348
302
  Impact: z3.number().int().min(1).max(5).optional().describe("Impact level (1 = low, 5 = critical)."),
349
- DueDate: z3.string().optional().describe("Risk resolution due date (ISO 8601).")
303
+ DueDate: IsoDateSchema.optional().describe("Risk resolution due date (ISO 8601).")
350
304
  });
351
305
  var TimeEntryQuerySchema = z3.object({
352
- StartDate: z3.string().describe("Start date for time entries query (ISO 8601 date, e.g. 2026-01-01)."),
353
- EndDate: z3.string().describe("End date for time entries query (ISO 8601 date, e.g. 2026-01-31).")
306
+ StartDate: IsoDateSchema.describe("Start date for time entries query (ISO 8601 date, e.g. 2026-01-01)."),
307
+ EndDate: IsoDateSchema.describe("End date for time entries query (ISO 8601 date, e.g. 2026-01-31).")
354
308
  });
355
309
  var TeamDynamixEntitySchema = z3.record(z3.string(), z3.unknown()).describe("Base TeamDynamix entity with flexible additional fields");
356
310
  var TeamDynamixNamedEntitySchema = z3.object({
@@ -390,396 +344,457 @@ var TeamDynamixUserSchema = z3.object({
390
344
  var TeamDynamixListResponseSchema = z3.array(z3.record(z3.string(), z3.unknown())).describe("Array of TeamDynamix entities");
391
345
  var TeamDynamixSingleResponseSchema = z3.record(z3.string(), z3.unknown()).describe("Single TeamDynamix entity response");
392
346
 
393
- // src/services/teamdynamix/client.service.ts
394
- function assertWriteToolsEnabled(config) {
395
- if (!config.enableWriteTools) {
396
- throw new Error(
397
- "Write tools are disabled. Set TEAMDYNAMIX_ENABLE_WRITE_TOOLS=true in your environment to enable ticket creation, updates, comments, and other write operations."
398
- );
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
+ });
399
359
  }
400
- }
401
- function sleep(ms) {
402
- return new Promise((resolve) => {
403
- 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
404
367
  });
405
368
  }
406
- function normalizePath(path) {
407
- 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] ?? "";
408
374
  }
409
- var TeamDynamixClient = class {
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
385
+ }
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 {
410
398
  constructor(config) {
411
399
  this.config = config;
412
400
  }
413
- cachedToken = null;
414
- async getCurrentUser() {
415
- return await this.requestJson("/api/auth/getuser");
416
- }
417
- async listApplications() {
418
- return await this.requestJson("/api/applications");
419
- }
420
- // -------------------------------------------------------------------------
421
- // Discovery / Enumeration domains
422
- // -------------------------------------------------------------------------
423
- async listAccounts(appId) {
424
- const path = appId ? `/api/${appId}/accounts` : "/api/accounts";
425
- return await this.requestJson(path);
401
+ sdkPromise = null;
402
+ sdk() {
403
+ this.sdkPromise ??= createMcpSdkClient(this.config);
404
+ return this.sdkPromise;
426
405
  }
427
- async getAccount(accountId) {
428
- return await this.requestJson(`/api/accounts/${accountId}`);
406
+ //
407
+ // Discovery
408
+ //
409
+ getCurrentUser() {
410
+ return this.sdk().then((s) => s.discovery.authGetuser());
429
411
  }
430
- async listLocations() {
431
- return await this.requestJson("/api/locations");
412
+ listApplications() {
413
+ return this.sdk().then((s) => s.discovery.applications());
432
414
  }
433
- async listFunctionalRoles() {
434
- return await this.requestJson("/api/functionalroles");
415
+ //
416
+ // Tickets
417
+ //
418
+ getTicket(appId, ticketId) {
419
+ return this.sdk().then((s) => s.tickets.appIdTicketsId({ params: { path: { appId, id: ticketId } } }));
435
420
  }
436
- async listCustomAttributes(componentId, appId, associatedTypeId) {
437
- const qs = new URLSearchParams({ componentId: String(componentId) });
438
- if (appId !== void 0) qs.set("appId", String(appId));
439
- if (associatedTypeId !== void 0) qs.set("associatedTypeId", String(associatedTypeId));
440
- return await this.requestJson(`/api/attributes/custom?${qs}`);
421
+ searchTickets(appId, _body) {
422
+ return this.sdk().then((s) => s.tickets.appIdTicketsFeed({ params: { path: { appId } } }));
441
423
  }
442
- async listTicketStatuses(appId) {
443
- return await this.requestJson(`/api/${appId}/tickets/statuses`);
424
+ listTicketTypes(appId) {
425
+ return this.sdk().then((s) => s.tickets.appIdTicketsTypes({ params: { path: { appId } } }));
444
426
  }
445
- async listTicketTypes(appId) {
446
- return await this.requestJson(`/api/${appId}/tickets/types`);
427
+ listTicketPriorities(appId) {
428
+ return this.sdk().then((s) => s.tickets.appIdTicketsPriorities({ params: { path: { appId } } }));
447
429
  }
448
- async listTicketPriorities(appId) {
449
- return await this.requestJson(`/api/${appId}/tickets/priorities`);
430
+ listTicketUrgencies(appId) {
431
+ return this.sdk().then((s) => s.tickets.appIdTicketsUrgencies({ params: { path: { appId } } }));
450
432
  }
451
- async listTicketUrgencies(appId) {
452
- return await this.requestJson(`/api/${appId}/tickets/urgencies`);
433
+ listTicketImpacts(appId) {
434
+ return this.sdk().then((s) => s.tickets.appIdTicketsImpacts({ params: { path: { appId } } }));
453
435
  }
454
- async listTicketImpacts(appId) {
455
- return await this.requestJson(`/api/${appId}/tickets/impacts`);
436
+ listTicketSources(appId) {
437
+ return this.sdk().then((s) => s.tickets.appIdTicketsSources({ params: { path: { appId } } }));
456
438
  }
457
- async listTicketSources(appId) {
458
- return await this.requestJson(`/api/${appId}/tickets/sources`);
439
+ getTicketFeed(appId, ticketId) {
440
+ return this.sdk().then((s) => s.tickets.appIdTicketsIdFeed({ params: { path: { appId, id: ticketId } } }));
459
441
  }
460
- async getTicket(appId, ticketId) {
461
- 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
+ );
462
449
  }
463
- async searchTickets(appId, body) {
464
- return await this.requestJson(`/api/${appId}/tickets/search`, {
465
- method: "POST",
466
- body: JSON.stringify(body)
467
- });
450
+ createTicketTask(appId, ticketId, body) {
451
+ return this.sdk().then((s) => s.ticketRelationships.createTicketTask({ appId, ticketId, body }));
468
452
  }
469
- async createTicket(appId, body, notifyRequestor = false, notifyResponsible = false) {
470
- return await this.requestJson(
471
- `/api/${appId}/tickets?NotifyRequestor=${notifyRequestor}&NotifyResponsible=${notifyResponsible}`,
472
- {
473
- method: "POST",
474
- body: JSON.stringify(body)
475
- }
453
+ listTicketAssets(appId, ticketId) {
454
+ return this.sdk().then(
455
+ (s) => s.ticketRelationships.appIdTicketsIdAssets({ params: { path: { appId, id: ticketId } } })
476
456
  );
477
457
  }
478
- async updateTicket(appId, ticketId, body, notifyRequestor = false, notifyResponsible = false, comments = "", isPrivate = false) {
479
- const qs = new URLSearchParams({
480
- NotifyRequestor: String(notifyRequestor),
481
- NotifyResponsible: String(notifyResponsible),
482
- ...comments ? { Comments: comments } : {},
483
- IsPrivate: String(isPrivate)
484
- });
485
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}?${qs}`, {
486
- method: "PATCH",
487
- body: JSON.stringify(body)
488
- });
458
+ addTicketAsset(appId, ticketId, assetId) {
459
+ return this.sdk().then((s) => s.ticketRelationships.addTicketAsset({ appId, ticketId, assetId }));
489
460
  }
490
- async addTicketComment(appId, ticketId, body, isPrivate = false, notifyRequestor = false, notifyResponsible = false) {
491
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/feed`, {
492
- method: "POST",
493
- body: JSON.stringify({
494
- Body: body,
495
- IsPrivate: isPrivate,
496
- NotifyRequestor: notifyRequestor,
497
- NotifyResponsible: notifyResponsible
498
- })
461
+ removeTicketAsset(appId, ticketId, assetId) {
462
+ return this.sdk().then((s) => {
463
+ s.ticketRelationships.removeTicketAsset({ appId, ticketId, assetId, confirm: true });
499
464
  });
500
465
  }
501
- async getTicketFeed(appId, ticketId) {
502
- 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
+ );
503
470
  }
504
- async getTicketTasks(appId, ticketId) {
505
- 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 }));
506
473
  }
507
- async createTicketTask(appId, ticketId, body) {
508
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/tasks`, {
509
- method: "POST",
510
- body: JSON.stringify(body)
474
+ removeTicketContact(appId, ticketId, contactUid) {
475
+ return this.sdk().then((s) => {
476
+ s.ticketRelationships.removeTicketContact({ appId, ticketId, contactUid, confirm: true });
511
477
  });
512
478
  }
513
- async listTicketAssets(appId, ticketId) {
514
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets`);
515
- }
516
- async addTicketAsset(appId, ticketId, assetId) {
517
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets/${assetId}`, {
518
- method: "POST",
519
- body: JSON.stringify({})
520
- });
479
+ //
480
+ // People
481
+ //
482
+ getUser(uid) {
483
+ return this.sdk().then((s) => s.people.peopleUid({ params: { path: { uid } } }));
521
484
  }
522
- async removeTicketAsset(appId, ticketId, assetId) {
523
- await this.requestJson(`/api/${appId}/tickets/${ticketId}/assets/${assetId}`, { method: "DELETE" });
485
+ searchUsers(body) {
486
+ const searchText = typeof body["SearchText"] === "string" ? body["SearchText"] : "";
487
+ return this.sdk().then((s) => s.people.peopleLookup({ body: { SearchText: searchText } }));
524
488
  }
525
- // -------------------------------------------------------------------------
526
- // People & Groups
527
- // -------------------------------------------------------------------------
528
- async getUser(uid) {
529
- return await this.requestJson(`/api/people/${encodeURIComponent(uid)}`);
489
+ getGroup(groupId) {
490
+ return this.sdk().then((s) => s.people.groupsId({ params: { path: { id: groupId } } }));
530
491
  }
531
- async searchUsers(body) {
532
- return await this.requestJson("/api/people/search", {
533
- method: "POST",
534
- body: JSON.stringify(body)
535
- });
492
+ searchGroups(_body) {
493
+ return this.sdk().then((s) => s.people.searchGroups({ body: {} }));
536
494
  }
537
- async getGroup(groupId) {
538
- return await this.requestJson(`/api/groups/${groupId}`);
495
+ getGroupMembers(groupId) {
496
+ return this.sdk().then((s) => s.people.groupsIdMembers({ params: { path: { id: groupId } } }));
539
497
  }
540
- async searchGroups(body) {
541
- return await this.requestJson("/api/groups/search", {
542
- method: "POST",
543
- body: JSON.stringify(body)
544
- });
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
+ );
545
512
  }
546
- async getGroupMembers(groupId) {
547
- 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
+ );
548
517
  }
549
- // -------------------------------------------------------------------------
550
- // Knowledge Base
551
- // -------------------------------------------------------------------------
552
- async getKbArticle(appId, articleId) {
553
- return await this.requestJson(`/api/${appId}/knowledgebase/${articleId}`);
554
- }
555
- async searchKbArticles(appId, body) {
556
- return await this.requestJson(`/api/${appId}/knowledgebase/search`, {
557
- method: "POST",
558
- 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
+ });
559
570
  });
560
571
  }
561
- async listKbCategories(appId) {
562
- return await this.requestJson(`/api/${appId}/knowledgebase/categories`);
572
+ listServiceCategories(appId) {
573
+ return this.sdk().then((s) => s.services.appIdServicesCategories({ params: { path: { appId } } }));
563
574
  }
564
- async createKbArticle(appId, body) {
565
- return await this.requestJson(`/api/${appId}/knowledgebase`, {
566
- method: "POST",
567
- body: JSON.stringify(body)
568
- });
575
+ //
576
+ // Projects
577
+ //
578
+ getProject(projectId) {
579
+ return this.sdk().then((s) => s.projects.projectsId({ params: { path: { id: projectId } } }));
569
580
  }
570
- async updateKbArticle(appId, articleId, body) {
571
- return await this.requestJson(`/api/${appId}/knowledgebase/${articleId}`, {
572
- method: "PUT",
573
- body: JSON.stringify(body)
574
- });
581
+ searchProjects(_body) {
582
+ return this.sdk().then((s) => s.projects.projectsFeed());
575
583
  }
576
- // -------------------------------------------------------------------------
577
- // Assets / CMDB
578
- // -------------------------------------------------------------------------
579
- async getAsset(appId, assetId) {
580
- return await this.requestJson(`/api/${appId}/assets/${assetId}`);
584
+ listProjectTypes() {
585
+ return this.sdk().then((s) => s.projects.projectsTypes());
581
586
  }
582
- async searchAssets(appId, body) {
583
- return await this.requestJson(`/api/${appId}/assets/search`, {
584
- method: "POST",
585
- body: JSON.stringify(body)
586
- });
587
+ getProjectPlans(projectId) {
588
+ return this.sdk().then(
589
+ (s) => s.projects.projectsProjectIDPlansPlanID({ params: { path: { id: projectId } } })
590
+ );
587
591
  }
588
- async listAssetStatuses(appId) {
589
- 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
+ );
590
596
  }
591
- async listProductModels(appId) {
592
- 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
+ );
593
601
  }
594
- async listVendors(appId) {
595
- return await this.requestJson(`/api/${appId}/assets/vendors`);
602
+ //
603
+ // Time
604
+ //
605
+ listTimeTypes() {
606
+ return this.sdk().then((s) => s.time.timeTypes());
596
607
  }
597
- async getConfigurationItem(appId, ciId) {
598
- 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 } } }));
599
610
  }
600
- async searchConfigurationItems(appId, body) {
601
- return await this.requestJson(`/api/${appId}/cmdb/search`, {
602
- method: "POST",
603
- body: JSON.stringify(body)
604
- });
611
+ //
612
+ // Reference Data
613
+ //
614
+ listAccounts(_appId) {
615
+ return this.sdk().then((s) => s.referenceData.accounts());
605
616
  }
606
- async listCiTypes(appId) {
607
- return await this.requestJson(`/api/${appId}/cmdb/types`);
617
+ getAccount(accountId) {
618
+ return this.sdk().then((s) => s.referenceData.accountsId({ params: { path: { id: accountId } } }));
608
619
  }
609
- async listCiRelationshipTypes() {
610
- return await this.requestJson("/api/cmdb/relationshiptypes");
620
+ listLocations() {
621
+ return this.sdk().then((s) => s.referenceData.locations());
611
622
  }
612
- // -------------------------------------------------------------------------
613
- // Service Catalog
614
- // -------------------------------------------------------------------------
615
- async listServiceCatalog(appId) {
616
- return await this.requestJson(`/api/${appId}/services`);
623
+ listFunctionalRoles() {
624
+ return this.sdk().then((s) => s.referenceData.securityrolesPermissions());
617
625
  }
618
- async getService(appId, serviceId) {
619
- return await this.requestJson(`/api/${appId}/services/${serviceId}`);
626
+ listCustomAttributes(_componentId, _appId, _associatedTypeId) {
627
+ return this.sdk().then((s) => s.referenceData.attributesCustom());
620
628
  }
621
- async searchServices(appId, body) {
622
- return await this.requestJson(`/api/${appId}/services/search`, {
623
- method: "POST",
624
- body: JSON.stringify(body)
625
- });
629
+ listTicketStatuses(appId) {
630
+ return this.sdk().then((s) => s.referenceData.appIdTicketsStatuses({ params: { path: { appId } } }));
626
631
  }
627
- // -------------------------------------------------------------------------
628
- // Projects
629
- // -------------------------------------------------------------------------
630
- async getProject(projectId) {
631
- return await this.requestJson(`/api/projects/${projectId}`);
632
- }
633
- async searchProjects(body) {
634
- return await this.requestJson("/api/projects/search", {
635
- method: "POST",
636
- body: JSON.stringify(body)
637
- });
632
+ //
633
+ // Mutations
634
+ //
635
+ createTicket(appId, body, _notifyRequestor, _notifyResponsible) {
636
+ return this.sdk().then((s) => s.tickets.createTicket({ appId, body }));
638
637
  }
639
- async listProjectTypes() {
640
- 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 }));
641
640
  }
642
- async getProjectPlans(projectId) {
643
- 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 } }));
644
643
  }
645
- async getProjectIssues(projectId) {
646
- return await this.requestJson(`/api/projects/${projectId}/issues`);
644
+ createKbArticle(appId, body) {
645
+ return this.sdk().then((s) => s.knowledgeBase.createArticle({ appId, body }));
647
646
  }
648
- async getProjectRisks(projectId) {
649
- 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 }));
650
649
  }
651
- async createProjectIssue(projectId, body) {
652
- return await this.requestJson(`/api/projects/${projectId}/issues`, {
653
- method: "POST",
654
- body: JSON.stringify(body)
655
- });
650
+ createProjectIssue(_projectId, body) {
651
+ return this.sdk().then((s) => s.projects.createIssue({ body }));
656
652
  }
657
- async createProjectRisk(projectId, body) {
658
- return await this.requestJson(`/api/projects/${projectId}/risks`, {
659
- method: "POST",
660
- body: JSON.stringify(body)
661
- });
653
+ createProjectRisk(_projectId, body) {
654
+ return this.sdk().then((s) => s.projects.createRisk({ body }));
662
655
  }
663
- async listTimeTypes() {
664
- 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 }));
665
661
  }
666
- async getMyTimeEntries(startDate, endDate) {
667
- const qs = new URLSearchParams({ startDate, endDate });
668
- 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 }));
669
664
  }
670
- async getTicketContacts(appId, ticketId) {
671
- 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 }));
672
667
  }
673
- async addTicketContact(appId, ticketId, contactUid) {
674
- return await this.requestJson(`/api/${appId}/tickets/${ticketId}/contacts/${contactUid}`, {
675
- method: "PUT"
676
- });
668
+ deleteServiceCategory(appId, categoryId) {
669
+ return this.sdk().then((s) => s.services.deleteServiceCategory({ appId, categoryId, confirm: true }));
677
670
  }
678
- async removeTicketContact(appId, ticketId, contactUid) {
679
- await this.requestJson(`/api/${appId}/tickets/${ticketId}/contacts/${contactUid}`, {
680
- method: "DELETE"
681
- });
671
+ deleteTimeEntry(timeEntryId) {
672
+ return this.sdk().then((s) => s.time.deleteTimeEntry({ timeEntryId, confirm: true }));
682
673
  }
683
- async listServiceCategories(appId) {
684
- 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.");
685
681
  }
686
- async requestJson(path, options = {}) {
687
- const headers = new Headers(options.headers);
688
- headers.set("Accept", "application/json");
689
- if (options.body && !headers.has("Content-Type") && !(options.body instanceof FormData)) {
690
- headers.set("Content-Type", "application/json");
691
- }
692
- const token = await this.getBearerToken(options.requireAdmin ?? false);
693
- headers.set("Authorization", `Bearer ${token}`);
694
- const requestInit = {
695
- method: options.method ?? "GET",
696
- headers,
697
- body: options.body
698
- };
699
- const endpoint = `${this.config.baseUrl}${normalizePath(path)}`;
700
- const cappedRetries = Math.min(this.config.maxRetries, TEAMDYNAMIX_MAX_RETRY_ATTEMPTS);
701
- for (let attempt = 0; attempt <= cappedRetries; attempt += 1) {
702
- const response = await fetch(endpoint, requestInit);
703
- if (response.status === 429 && attempt < cappedRetries) {
704
- const rateLimit = parseRateLimit(response.headers);
705
- if (LOG_LEVEL === "debug") {
706
- console.error(
707
- `[teamdynamix-mcp] rate limited on ${normalizePath(path)}; retry ${attempt + 1}/${cappedRetries} in ${rateLimit.waitMs}ms`
708
- );
709
- }
710
- await sleep(rateLimit.waitMs);
711
- continue;
712
- }
713
- if (!response.ok) {
714
- await response.text();
715
- throw new Error(`TeamDynamix request failed (${response.status}) for ${normalizePath(path)}.`);
716
- }
717
- const contentType = response.headers.get("content-type") ?? "";
718
- if (contentType.includes("application/json")) {
719
- const data = await response.json();
720
- if (Array.isArray(data)) {
721
- TeamDynamixListResponseSchema.parse(data);
722
- } else if (typeof data === "object" && data !== null) {
723
- TeamDynamixSingleResponseSchema.parse(data);
724
- }
725
- return data;
726
- }
727
- const text = await response.text();
728
- return text;
729
- }
730
- throw new Error(`TeamDynamix request exceeded retry budget for ${normalizePath(path)}.`);
731
- }
732
- async getBearerToken(requireAdmin) {
733
- const configStatus = getTeamDynamixConfigStatus(this.config);
734
- if (!configStatus.configured) {
735
- throw new Error(
736
- `TeamDynamix is not configured. Missing: ${configStatus.missing.join(", ")}. Populate the required environment variables in .env before using TeamDynamix tools.`
737
- );
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 "";
738
707
  }
739
- if (requireAdmin && this.config.authMode !== "admin") {
740
- throw new Error(
741
- "This TeamDynamix action requires admin authentication, but TEAMDYNAMIX_AUTH_MODE is not set to admin."
742
- );
708
+ if (payload.every(isPlainObject)) {
709
+ return renderArrayAsMarkdownTable(payload);
743
710
  }
744
- const expiresAtMs = this.cachedToken?.expiresAtMs;
745
- if (this.cachedToken && (!expiresAtMs || expiresAtMs > Date.now() + 6e4)) {
746
- return this.cachedToken.token;
711
+ return "```json\n" + JSON.stringify(payload, null, 2) + "\n```";
712
+ }
713
+ if (isPlainObject(payload)) {
714
+ const hasArrayOfObjects = Object.values(payload).some(isArrayOfPlainObjects);
715
+ if (hasArrayOfObjects) {
716
+ return renderObjectWithTables(payload);
747
717
  }
748
- const token = await this.login();
749
- return token;
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))}`);
750
737
  }
751
- async login() {
752
- if (!this.config.baseUrl) {
753
- throw new Error("TeamDynamix base URL is not configured. Set TEAMDYNAMIX_BASE_URL.");
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]);
754
749
  }
755
- const isAdmin = this.config.authMode === "admin";
756
- const loginPath = isAdmin ? "/api/auth/loginadmin" : "/api/auth/login";
757
- const body = isAdmin ? JSON.stringify({ BEID: this.config.beid, WebServicesKey: this.config.webServicesKey }) : JSON.stringify({ username: this.config.username, password: this.config.password });
758
- const response = await fetch(`${this.config.baseUrl}${loginPath}`, {
759
- method: "POST",
760
- headers: {
761
- Accept: "application/json, text/plain;q=0.9",
762
- "Content-Type": "application/json"
763
- },
764
- body
765
- });
766
- if (!response.ok) {
767
- await response.text();
768
- 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))}`);
769
755
  }
770
- const contentType = response.headers.get("content-type") ?? "";
771
- const payload = contentType.includes("application/json") ? await response.json() : await response.text();
772
- const token = extractAuthToken(payload);
773
- const expiryEpochSeconds = decodeJwtExpiryEpochSeconds(token);
774
- this.cachedToken = {
775
- token,
776
- expiresAtMs: expiryEpochSeconds ? expiryEpochSeconds * 1e3 : null
777
- };
778
- return token;
756
+ lines.push("");
779
757
  }
780
- };
781
- function createConfiguredTeamDynamixClient() {
782
- 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;
783
798
  }
784
799
 
785
800
  // src/tools/teamdynamix.domain-gateways.tools.ts
@@ -830,20 +845,24 @@ var teamdynamixAssetsActionSchema = z4.enum([
830
845
  "get_asset",
831
846
  "search_assets",
832
847
  "list_asset_statuses",
833
- "list_product_models"
848
+ "list_product_models",
849
+ "delete_asset"
834
850
  ]);
835
851
  var teamdynamixCmdbActionSchema = z4.enum([
836
852
  "get_ci",
837
853
  "search_cis",
838
854
  "list_ci_types",
839
855
  "list_ci_relationship_types",
840
- "list_vendors"
856
+ "list_vendors",
857
+ "delete_ci"
841
858
  ]);
842
859
  var teamdynamixServicesActionSchema = z4.enum([
843
860
  "list_services",
844
861
  "get_service",
845
862
  "search_services",
846
- "list_service_categories"
863
+ "list_service_categories",
864
+ "delete_service",
865
+ "delete_service_category"
847
866
  ]);
848
867
  var teamdynamixProjectsActionSchema = z4.enum([
849
868
  "get_project",
@@ -864,11 +883,6 @@ var teamdynamixReferenceDataActionSchema = z4.enum([
864
883
  "list_custom_attributes"
865
884
  ]);
866
885
  var gatewayPayloadSchema = z4.record(z4.string(), z4.unknown()).default({});
867
- function render(data, responseFormat) {
868
- if (responseFormat === "json") return JSON.stringify(data, null, 2);
869
- if (typeof data === "string") return data;
870
- return JSON.stringify(data, null, 2);
871
- }
872
886
  function messageFromError(error) {
873
887
  return error instanceof Error ? error.message : String(error);
874
888
  }
@@ -1475,6 +1489,23 @@ function registerTeamDynamixAssetsGateway(server2) {
1475
1489
  const models = await client.listProductModels(parsed.app_id);
1476
1490
  return toSuccessResponse({ appId: parsed.app_id, count: models.length, models }, response_format);
1477
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
+ }
1478
1509
  }
1479
1510
  } catch (error) {
1480
1511
  return toErrorResponse(error);
@@ -1534,6 +1565,20 @@ function registerTeamDynamixCmdbGateway(server2) {
1534
1565
  const vendors = await client.listVendors(parsed.app_id);
1535
1566
  return toSuccessResponse({ appId: parsed.app_id, count: vendors.length, vendors }, response_format);
1536
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
+ }
1537
1582
  }
1538
1583
  } catch (error) {
1539
1584
  return toErrorResponse(error);
@@ -1586,6 +1631,40 @@ function registerTeamDynamixServicesGateway(server2) {
1586
1631
  const categories = await client.listServiceCategories(parsed.app_id);
1587
1632
  return toSuccessResponse({ appId: parsed.app_id, count: categories.length, categories }, response_format);
1588
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
+ }
1589
1668
  }
1590
1669
  } catch (error) {
1591
1670
  return toErrorResponse(error);