@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.
- package/README.md +18 -3
- package/index.js +500 -421
- package/index.js.map +1 -1
- 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
|
|
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:
|
|
217
|
-
CreatedDateTo:
|
|
218
|
-
ModifiedDateFrom:
|
|
219
|
-
ModifiedDateTo:
|
|
220
|
-
ClosedDateFrom:
|
|
221
|
-
ClosedDateTo:
|
|
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:
|
|
312
|
-
EndDate:
|
|
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:
|
|
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:
|
|
303
|
+
DueDate: IsoDateSchema.optional().describe("Risk resolution due date (ISO 8601).")
|
|
350
304
|
});
|
|
351
305
|
var TimeEntryQuerySchema = z3.object({
|
|
352
|
-
StartDate:
|
|
353
|
-
EndDate:
|
|
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/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
407
|
-
|
|
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
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
428
|
-
|
|
406
|
+
//
|
|
407
|
+
// Discovery
|
|
408
|
+
//
|
|
409
|
+
getCurrentUser() {
|
|
410
|
+
return this.sdk().then((s) => s.discovery.authGetuser());
|
|
429
411
|
}
|
|
430
|
-
|
|
431
|
-
return
|
|
412
|
+
listApplications() {
|
|
413
|
+
return this.sdk().then((s) => s.discovery.applications());
|
|
432
414
|
}
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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
|
-
|
|
443
|
-
return
|
|
424
|
+
listTicketTypes(appId) {
|
|
425
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsTypes({ params: { path: { appId } } }));
|
|
444
426
|
}
|
|
445
|
-
|
|
446
|
-
return
|
|
427
|
+
listTicketPriorities(appId) {
|
|
428
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsPriorities({ params: { path: { appId } } }));
|
|
447
429
|
}
|
|
448
|
-
|
|
449
|
-
return
|
|
430
|
+
listTicketUrgencies(appId) {
|
|
431
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsUrgencies({ params: { path: { appId } } }));
|
|
450
432
|
}
|
|
451
|
-
|
|
452
|
-
return
|
|
433
|
+
listTicketImpacts(appId) {
|
|
434
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsImpacts({ params: { path: { appId } } }));
|
|
453
435
|
}
|
|
454
|
-
|
|
455
|
-
return
|
|
436
|
+
listTicketSources(appId) {
|
|
437
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsSources({ params: { path: { appId } } }));
|
|
456
438
|
}
|
|
457
|
-
|
|
458
|
-
return
|
|
439
|
+
getTicketFeed(appId, ticketId) {
|
|
440
|
+
return this.sdk().then((s) => s.tickets.appIdTicketsIdFeed({ params: { path: { appId, id: ticketId } } }));
|
|
459
441
|
}
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
464
|
-
return
|
|
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
|
-
|
|
470
|
-
return
|
|
471
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
491
|
-
return
|
|
492
|
-
|
|
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
|
-
|
|
502
|
-
return
|
|
466
|
+
getTicketContacts(appId, ticketId) {
|
|
467
|
+
return this.sdk().then(
|
|
468
|
+
(s) => s.ticketRelationships.appIdTicketsIdContacts({ params: { path: { appId, id: ticketId } } })
|
|
469
|
+
);
|
|
503
470
|
}
|
|
504
|
-
|
|
505
|
-
return
|
|
471
|
+
addTicketContact(appId, ticketId, contactUid) {
|
|
472
|
+
return this.sdk().then((s) => s.ticketRelationships.addTicketContact({ appId, ticketId, contactUid }));
|
|
506
473
|
}
|
|
507
|
-
|
|
508
|
-
return
|
|
509
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
return
|
|
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
|
-
|
|
523
|
-
|
|
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
|
-
|
|
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
|
-
|
|
532
|
-
return
|
|
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
|
-
|
|
538
|
-
return
|
|
495
|
+
getGroupMembers(groupId) {
|
|
496
|
+
return this.sdk().then((s) => s.people.groupsIdMembers({ params: { path: { id: groupId } } }));
|
|
539
497
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
547
|
-
return
|
|
513
|
+
listKbCategories(appId) {
|
|
514
|
+
return this.sdk().then(
|
|
515
|
+
(s) => s.knowledgeBase.appIdKnowledgebaseCategories({ params: { path: { appId } } })
|
|
516
|
+
);
|
|
548
517
|
}
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
|
|
553
|
-
return
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
return
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
562
|
-
return
|
|
572
|
+
listServiceCategories(appId) {
|
|
573
|
+
return this.sdk().then((s) => s.services.appIdServicesCategories({ params: { path: { appId } } }));
|
|
563
574
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
});
|
|
575
|
+
//
|
|
576
|
+
// Projects
|
|
577
|
+
//
|
|
578
|
+
getProject(projectId) {
|
|
579
|
+
return this.sdk().then((s) => s.projects.projectsId({ params: { path: { id: projectId } } }));
|
|
569
580
|
}
|
|
570
|
-
|
|
571
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
});
|
|
587
|
+
getProjectPlans(projectId) {
|
|
588
|
+
return this.sdk().then(
|
|
589
|
+
(s) => s.projects.projectsProjectIDPlansPlanID({ params: { path: { id: projectId } } })
|
|
590
|
+
);
|
|
587
591
|
}
|
|
588
|
-
|
|
589
|
-
return
|
|
592
|
+
getProjectIssues(projectId) {
|
|
593
|
+
return this.sdk().then(
|
|
594
|
+
(s) => s.projects.projectsProjectIdIssuesCategories({ params: { path: { id: projectId } } })
|
|
595
|
+
);
|
|
590
596
|
}
|
|
591
|
-
|
|
592
|
-
return
|
|
597
|
+
getProjectRisks(projectId) {
|
|
598
|
+
return this.sdk().then(
|
|
599
|
+
(s) => s.projects.projectsProjectIdRisksCategories({ params: { path: { id: projectId } } })
|
|
600
|
+
);
|
|
593
601
|
}
|
|
594
|
-
|
|
595
|
-
|
|
602
|
+
//
|
|
603
|
+
// Time
|
|
604
|
+
//
|
|
605
|
+
listTimeTypes() {
|
|
606
|
+
return this.sdk().then((s) => s.time.timeTypes());
|
|
596
607
|
}
|
|
597
|
-
|
|
598
|
-
return
|
|
608
|
+
getMyTimeEntries(_startDate, _endDate) {
|
|
609
|
+
return this.sdk().then((s) => s.time.timeId({ params: { path: { id: 0 } } }));
|
|
599
610
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
611
|
+
//
|
|
612
|
+
// Reference Data
|
|
613
|
+
//
|
|
614
|
+
listAccounts(_appId) {
|
|
615
|
+
return this.sdk().then((s) => s.referenceData.accounts());
|
|
605
616
|
}
|
|
606
|
-
|
|
607
|
-
return
|
|
617
|
+
getAccount(accountId) {
|
|
618
|
+
return this.sdk().then((s) => s.referenceData.accountsId({ params: { path: { id: accountId } } }));
|
|
608
619
|
}
|
|
609
|
-
|
|
610
|
-
return
|
|
620
|
+
listLocations() {
|
|
621
|
+
return this.sdk().then((s) => s.referenceData.locations());
|
|
611
622
|
}
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
619
|
-
return
|
|
626
|
+
listCustomAttributes(_componentId, _appId, _associatedTypeId) {
|
|
627
|
+
return this.sdk().then((s) => s.referenceData.attributesCustom());
|
|
620
628
|
}
|
|
621
|
-
|
|
622
|
-
return
|
|
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
|
-
//
|
|
629
|
-
//
|
|
630
|
-
|
|
631
|
-
return
|
|
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
|
-
|
|
640
|
-
return
|
|
638
|
+
updateTicket(appId, ticketId, body, _notifyRequestor, _notifyResponsible, _comments, _isPrivate) {
|
|
639
|
+
return this.sdk().then((s) => s.tickets.updateTicket({ appId, ticketId, body }));
|
|
641
640
|
}
|
|
642
|
-
|
|
643
|
-
return
|
|
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
|
-
|
|
646
|
-
return
|
|
644
|
+
createKbArticle(appId, body) {
|
|
645
|
+
return this.sdk().then((s) => s.knowledgeBase.createArticle({ appId, body }));
|
|
647
646
|
}
|
|
648
|
-
|
|
649
|
-
return
|
|
647
|
+
updateKbArticle(appId, articleId, body) {
|
|
648
|
+
return this.sdk().then((s) => s.knowledgeBase.updateArticle({ appId, articleId, body }));
|
|
650
649
|
}
|
|
651
|
-
|
|
652
|
-
return
|
|
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
|
-
|
|
658
|
-
return
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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
|
-
|
|
671
|
-
return
|
|
665
|
+
deleteService(appId, serviceId) {
|
|
666
|
+
return this.sdk().then((s) => s.services.deleteService({ appId, serviceId, confirm: true }));
|
|
672
667
|
}
|
|
673
|
-
|
|
674
|
-
return
|
|
675
|
-
method: "PUT"
|
|
676
|
-
});
|
|
668
|
+
deleteServiceCategory(appId, categoryId) {
|
|
669
|
+
return this.sdk().then((s) => s.services.deleteServiceCategory({ appId, categoryId, confirm: true }));
|
|
677
670
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
method: "DELETE"
|
|
681
|
-
});
|
|
671
|
+
deleteTimeEntry(timeEntryId) {
|
|
672
|
+
return this.sdk().then((s) => s.time.deleteTimeEntry({ timeEntryId, confirm: true }));
|
|
682
673
|
}
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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 (
|
|
740
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
749
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
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
|
-
|
|
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
|
-
|
|
782
|
-
|
|
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);
|