@jorgeluismlima/teamwork-mcp 1.0.1 → 1.1.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 (3) hide show
  1. package/README.md +18 -3
  2. package/dist/index.js +753 -31
  3. package/package.json +14 -5
package/README.md CHANGED
@@ -7,20 +7,35 @@ A Model Context Protocol (MCP) server that connects to the Teamwork API, allowin
7
7
  This server exposes the following tools:
8
8
 
9
9
  ### Projects & Time Tracking
10
- * **`list_projects`**: Listings projects, filtering by search term.
10
+ * **`list_projects`**: Comprehensive project listing with 50+ filters (date, health, status, tags, etc.) and full API response support.
11
11
  * **`create_time_entry`**: Creates a time entry (timelog) for a specific project.
12
- * **`get_time_entries`**: Retrieves time entries for a project, with optional date filtering.
12
+ * **`get_time_entries`**: Retrieves detailed time entries with 60+ filters (billed status, users, tags, etc.).
13
13
  * **`total_count_of_active_projects`**: Returns the total count of active projects.
14
14
  * **`total_billable_time_per_project`**: Returns total billable minutes per project (optional date range).
15
15
  * **`get_health_stats`**: Returns project health metrics (Good, Bad, OK, etc.), with various filters.
16
16
  * **`get_project_time_totals`**: Returns total hours/minutes for a project (optionally filtered by user).
17
17
 
18
+ ### Project Categories
19
+ * **`list_of_project_categories`**: Lists all project categories with filtering options.
20
+ * **`get_specific_project_category`**: Retrieves details of a specific category.
21
+ * **`list_of_teamwork_project_categories`**: Helper tool to list categories.
22
+
18
23
  ### People & Companies
19
- * **`get_project_people`**: Lists all people associated with a project.
24
+ * **`list_people`**: Lists all people across the site with comprehensive filtering (user type, search term, job role, etc.).
25
+ * **`get_project_people`**: Lists all people associated with a project, with 30+ optional filters.
20
26
  * **`get_person`**: Retrieves detailed information about a specific person (user).
21
27
  * **`list_companies`**: Lists companies (clients).
22
28
  * **`get_company`**: Retrieves details of a specific company.
23
29
 
30
+ ### Projects & Updates
31
+ * **`get_project`**: Retrieves detailed information about a specific project, with comprehensive filtering and include options.
32
+ * **`get_all_project_updates`**: Lists updates across all projects.
33
+ * **`get_project_updates`**: Lists updates for a specific project.
34
+
35
+ ### Tags
36
+ * **`get_all_tags`**: Lists all tags with filtering by name, item type, etc.
37
+ * **`get_tag`**: Retrieves details of a specific tag.
38
+
24
39
  ## Configuration
25
40
 
26
41
  To use this server, you need to configure the following environment variables:
package/dist/index.js CHANGED
@@ -57,30 +57,105 @@ const handleApiError = (error) => {
57
57
  };
58
58
  };
59
59
  server.tool("list_projects", {
60
- searchTerm: z.string().optional().describe("Filter by project name"),
61
- page: z.number().optional().describe("Page number"),
62
- }, async ({ searchTerm, page }) => {
60
+ updatedAfter: z.string().optional().describe("updated after"),
61
+ timeMode: z.enum(["timelogs", "estimated"]).optional().describe("profitability time mode"),
62
+ searchTerm: z.string().optional().describe("filter by project name"),
63
+ reportType: z.enum(["project", "health"]).optional().describe("define the type of the report"),
64
+ reportTimezone: z.string().optional().describe("Optional to configure the report dates displayed in a timezone"),
65
+ reportFormat: z.enum(["csv", "html", "pdf", "xls"]).optional().describe("define the format of the report"),
66
+ projectType: z.string().optional().describe("filter by project type"),
67
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
68
+ orderBy: z.enum(["companyname", "datecreated", "duedate", "lastactivity", "name", "namecaseinsensitive", "ownercompany", "starred", "categoryname"]).optional().describe("order by"),
69
+ notCompletedBefore: z.string().optional().describe("filter by projects that have not been completed before the given date"),
70
+ minLastActivityDate: z.string().optional().describe("filter by min last activity date"),
71
+ maxLastActivityDate: z.string().optional().describe("filter by max last activity date"),
72
+ userId: z.number().optional().describe("filter by user id"),
73
+ pageSize: z.number().optional().describe("number of items in a page"),
74
+ page: z.number().optional().describe("page number"),
75
+ orderByCustomFieldId: z.number().optional().describe("order by custom field id when orderBy is equal to customfield"),
76
+ minBudgetCapacityUsedPercent: z.number().optional().describe("filter by minimum budget capacity used"),
77
+ maxBudgetCapacityUsedPercent: z.number().optional().describe("filter by maximum budget capacity used"),
78
+ useFormulaFields: z.boolean().optional().describe("use formula fields"),
79
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts on a list API endpoint"),
80
+ searchCompanies: z.boolean().optional().describe("include companies in the search"),
81
+ searchByLetter: z.boolean().optional().describe("search projects beginning with the search term character only"),
82
+ onlyStarredProjects: z.boolean().optional().describe("filter by starred projects only"),
83
+ onlyProjectsWithExplicitMembership: z.boolean().optional().describe("only show projects with explicit membership"),
84
+ onlyProjectsThatCanLogTime: z.boolean().optional().describe("can log time on projects"),
85
+ onlyProjectsThatCanAddTasks: z.boolean().optional().describe("can add tasks on projects"),
86
+ onlyArchivedProjects: z.boolean().optional().describe("return only archived projects"),
87
+ matchAllProjectTags: z.boolean().optional().describe("match all project tags"),
88
+ matchAllExcludedTags: z.boolean().optional().describe("match all excluded project tags"),
89
+ isReportDownload: z.boolean().optional().describe("generate a report document"),
90
+ includeTentativeProjects: z.boolean().optional().describe("include alongside normal projects, tentative ones"),
91
+ includeTabSystemStatus: z.boolean().optional().describe("include tab system status in response"),
92
+ includeSubCategories: z.boolean().optional().describe("include sub categories when filtering by ids"),
93
+ includeStats: z.boolean().optional().describe("include project status counts"),
94
+ includeProjectUserInfo: z.boolean().optional().describe("fetch user-specific data such as isStarred"),
95
+ includeProjectProfitability: z.boolean().optional().describe("include project profitability in response"),
96
+ includeProjectDates: z.boolean().optional().describe("include minimum and maximum start/end dates for projects"),
97
+ includeCustomFields: z.boolean().optional().describe("include custom fields"),
98
+ includeCounts: z.boolean().optional().describe("include project related counts"),
99
+ includeCompletedStatus: z.boolean().optional().describe("include completed projects when filtering by project statuses current,late"),
100
+ includeArchivedProjects: z.boolean().optional().describe("include archived projects"),
101
+ hideObservedProjects: z.boolean().optional().describe("hide projects where the logged-in user is just an observer"),
102
+ alwaysIncludeFiltering: z.boolean().optional().describe("includes filters when project ids are provided"),
103
+ usersWithExplicitMembershipIds: z.array(z.number()).optional().describe("only show projects that have an explicit common membership with provided user ids"),
104
+ teamIds: z.array(z.number()).optional().describe("filter by projects that contain users associated with the team ids"),
105
+ selectedColumns: z.array(z.string()).optional().describe("select the columns to use in exports"),
106
+ projectTagIds: z.array(z.number()).optional().describe("filter by project tag ids"),
107
+ projectStatuses: z.array(z.string()).optional().describe("filter by project status (active, current, late, upcoming, completed, deleted)"),
108
+ projectOwnerIds: z.array(z.number()).optional().describe("filter by project owner ids"),
109
+ projectIds: z.array(z.number()).optional().describe("filter by project ids"),
110
+ projectHealths: z.array(z.number()).optional().describe("filter by project healths (0: not set, 1: bad, 2: ok, 3: good)"),
111
+ projectCompanyIds: z.array(z.number()).optional().describe("filter by company ids"),
112
+ projectCategoryIds: z.array(z.number()).optional().describe("filter by project category ids"),
113
+ includeCustomFieldIds: z.array(z.number()).optional().describe("include specific custom fields"),
114
+ include: z.array(z.string()).optional().describe("include related data"),
115
+ featuresEnabled: z.array(z.string()).optional().describe("filter by projects that have features enabled"),
116
+ excludeTagIds: z.array(z.number()).optional().describe("exclude by project tag ids"),
117
+ excludeProjectIds: z.array(z.number()).optional().describe("exclude certain project ids"),
118
+ }, async (args) => {
63
119
  try {
64
- const params = {};
65
- if (searchTerm)
66
- params.searchTerm = searchTerm;
67
- if (page)
68
- params.page = page;
120
+ const params = { ...args };
121
+ // Helper to join arrays
122
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
123
+ if (args.usersWithExplicitMembershipIds)
124
+ params.usersWithExplicitMembershipIds = joinParam(args.usersWithExplicitMembershipIds);
125
+ if (args.teamIds)
126
+ params.teamIds = joinParam(args.teamIds);
127
+ if (args.selectedColumns)
128
+ params.selectedColumns = joinParam(args.selectedColumns);
129
+ if (args.projectTagIds)
130
+ params.projectTagIds = joinParam(args.projectTagIds);
131
+ if (args.projectStatuses)
132
+ params.projectStatuses = joinParam(args.projectStatuses);
133
+ if (args.projectOwnerIds)
134
+ params.projectOwnerIds = joinParam(args.projectOwnerIds);
135
+ if (args.projectIds)
136
+ params.projectIds = joinParam(args.projectIds);
137
+ if (args.projectHealths)
138
+ params.projectHealths = joinParam(args.projectHealths);
139
+ if (args.projectCompanyIds)
140
+ params.projectCompanyIds = joinParam(args.projectCompanyIds);
141
+ if (args.projectCategoryIds)
142
+ params.projectCategoryIds = joinParam(args.projectCategoryIds);
143
+ if (args.includeCustomFieldIds)
144
+ params.includeCustomFieldIds = joinParam(args.includeCustomFieldIds);
145
+ if (args.include)
146
+ params.include = joinParam(args.include);
147
+ if (args.featuresEnabled)
148
+ params.featuresEnabled = joinParam(args.featuresEnabled);
149
+ if (args.excludeTagIds)
150
+ params.excludeTagIds = joinParam(args.excludeTagIds);
151
+ if (args.excludeProjectIds)
152
+ params.excludeProjectIds = joinParam(args.excludeProjectIds);
69
153
  const response = await axiosInstance.get("/projects.json", { params });
70
- const simplifiedProjects = response.data.projects.map((p) => ({
71
- id: p.id,
72
- name: p.name,
73
- description: p.description,
74
- status: p.status,
75
- company: p.company,
76
- createdOn: p.createdOn,
77
- lastUpdated: p.lastUpdated
78
- }));
79
154
  return {
80
155
  content: [
81
156
  {
82
157
  type: "text",
83
- text: JSON.stringify({ projects: simplifiedProjects, meta: response.data.meta }, null, 2),
158
+ text: JSON.stringify(response.data, null, 2),
84
159
  },
85
160
  ],
86
161
  };
@@ -129,18 +204,103 @@ server.tool("create_time_entry", {
129
204
  });
130
205
  server.tool("get_time_entries", {
131
206
  projectId: z.number().describe("The ID of the project"),
132
- page: z.number().optional().describe("Page number"),
133
- fromDate: z.string().optional().describe("Start date (YYYYMMDD)"),
134
- toDate: z.string().optional().describe("End date (YYYYMMDD)"),
135
- }, async ({ projectId, page, fromDate, toDate }) => {
207
+ updatedAfter: z.string().optional().describe("filter by updated after date"),
208
+ startDate: z.string().optional().describe("filter by a starting date"),
209
+ reportFormat: z.string().optional().describe("define the format of the report"),
210
+ projectStatus: z.string().optional().describe("filter by project status (active, current, late, upcoming, completed, deleted)"),
211
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
212
+ orderBy: z.enum(["company", "date", "dateupdated", "project", "task", "tasklist", "user", "description", "billed", "billable", "timespent"]).optional().describe("sort order"),
213
+ invoicedType: z.enum(["all", "invoiced", "noninvoiced"]).optional().describe("filter by invoiced type"),
214
+ endDate: z.string().optional().describe("filter by an ending date"),
215
+ deletedAfter: z.string().optional().describe("filter by deleted after date"),
216
+ billableType: z.enum(["all", "billable", "non-billable"]).optional().describe("filter by billable type"),
217
+ updatedBy: z.number().optional().describe("filter by the user who updated the timelog"),
218
+ ticketId: z.number().optional().describe("filter by ticket id"),
219
+ tasklistId: z.number().optional().describe("filter by tasklist id"),
220
+ taskId: z.number().optional().describe("filter by task id (deprecated, use taskIds)"),
221
+ pageSize: z.number().optional().describe("number of items in a page"),
222
+ page: z.number().optional().describe("page number"),
223
+ invoiceId: z.number().optional().describe("filter by invoice id"),
224
+ budgetId: z.number().optional().describe("filter by budget id"),
225
+ allocationId: z.number().optional().describe("filter by allocation id"),
226
+ useFallbackMethod: z.boolean().optional().describe("use fallback method"),
227
+ unattachedTimelogs: z.boolean().optional().describe("filter by timelogs that are directly logged against projects"),
228
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
229
+ showDeleted: z.boolean().optional().describe("include deleted items"),
230
+ returnCostInfo: z.boolean().optional().describe("used to return the cost rate and total"),
231
+ returnBillableInfo: z.boolean().optional().describe("used to return the billable rate and total"),
232
+ onlyStarredProjects: z.boolean().optional().describe("filter by starred projects only"),
233
+ matchAllTaskTags: z.boolean().optional().describe("match all task tags"),
234
+ matchAllTags: z.boolean().optional().describe("match all tags"),
235
+ matchAllProjectTags: z.boolean().optional().describe("match all project tags"),
236
+ isReportDownload: z.boolean().optional().describe("generate a report document"),
237
+ includeTotals: z.boolean().optional().describe("include totals"),
238
+ includeTentativeProjects: z.boolean().optional().describe("include tentative projects"),
239
+ includePermissions: z.boolean().optional().describe("include permissions"),
240
+ includeDescendants: z.boolean().optional().describe("include descendants"),
241
+ includeArchivedProjects: z.boolean().optional().describe("include archived projects"),
242
+ deletedOnly: z.boolean().optional().describe("only deleted tasks"),
243
+ checkLocking: z.boolean().optional().describe("check timesheets' locking"),
244
+ taskTagIds: z.array(z.number()).optional().describe("filter by task tag ids"),
245
+ taskStatuses: z.array(z.string()).optional().describe("filter by task statuses"),
246
+ taskIds: z.array(z.number()).optional().describe("filter by task ids"),
247
+ tagIds: z.array(z.number()).optional().describe("filter by tag ids"),
248
+ selectedColumns: z.array(z.string()).optional().describe("customise the report by selecting columns"),
249
+ projectTagIds: z.array(z.number()).optional().describe("filter by project tag ids"),
250
+ projectStatuses: z.array(z.string()).optional().describe("filter by project statuses"),
251
+ projectOwnerIds: z.array(z.number()).optional().describe("filter by project owner ids"),
252
+ projectIds: z.array(z.number()).optional().describe("filter by project ids"),
253
+ projectHealths: z.array(z.number()).optional().describe("filter by project healths (0: not set, 1: bad, 2: ok, 3: good)"),
254
+ projectCompanyIds: z.array(z.number()).optional().describe("filter by project company ids"),
255
+ projectCategoryIds: z.array(z.number()).optional().describe("filter by project category ids"),
256
+ include: z.array(z.string()).optional().describe("include related data"),
257
+ ids: z.array(z.number()).optional().describe("filter by ids"),
258
+ assignedToUserIds: z.array(z.number()).optional().describe("filter by assigned user ids"),
259
+ assignedToTeamIds: z.array(z.number()).optional().describe("filter by assigned team ids"),
260
+ assignedToCompanyIds: z.array(z.number()).optional().describe("filter by assigned company ids"),
261
+ assignedTeamIds: z.array(z.number()).optional().describe("filter by assigned team ids"),
262
+ }, async (args) => {
136
263
  try {
137
- const params = {};
138
- if (page)
139
- params.page = page;
140
- if (fromDate)
141
- params.fromDate = fromDate;
142
- if (toDate)
143
- params.toDate = toDate;
264
+ const { projectId, ...restParams } = args;
265
+ const params = { ...restParams };
266
+ // Helper to join arrays
267
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
268
+ if (args.taskTagIds)
269
+ params.taskTagIds = joinParam(args.taskTagIds);
270
+ if (args.taskStatuses)
271
+ params.taskStatuses = joinParam(args.taskStatuses);
272
+ if (args.taskIds)
273
+ params.taskIds = joinParam(args.taskIds);
274
+ if (args.tagIds)
275
+ params.tagIds = joinParam(args.tagIds);
276
+ if (args.selectedColumns)
277
+ params.selectedColumns = joinParam(args.selectedColumns);
278
+ if (args.projectTagIds)
279
+ params.projectTagIds = joinParam(args.projectTagIds);
280
+ if (args.projectStatuses)
281
+ params.projectStatuses = joinParam(args.projectStatuses);
282
+ if (args.projectOwnerIds)
283
+ params.projectOwnerIds = joinParam(args.projectOwnerIds);
284
+ if (args.projectIds)
285
+ params.projectIds = joinParam(args.projectIds);
286
+ if (args.projectHealths)
287
+ params.projectHealths = joinParam(args.projectHealths);
288
+ if (args.projectCompanyIds)
289
+ params.projectCompanyIds = joinParam(args.projectCompanyIds);
290
+ if (args.projectCategoryIds)
291
+ params.projectCategoryIds = joinParam(args.projectCategoryIds);
292
+ if (args.include)
293
+ params.include = joinParam(args.include);
294
+ if (args.ids)
295
+ params.ids = joinParam(args.ids);
296
+ if (args.assignedToUserIds)
297
+ params.assignedToUserIds = joinParam(args.assignedToUserIds);
298
+ if (args.assignedToTeamIds)
299
+ params.assignedToTeamIds = joinParam(args.assignedToTeamIds);
300
+ if (args.assignedToCompanyIds)
301
+ params.assignedToCompanyIds = joinParam(args.assignedToCompanyIds);
302
+ if (args.assignedTeamIds)
303
+ params.assignedTeamIds = joinParam(args.assignedTeamIds);
144
304
  const response = await axiosInstance.get(`/projects/${projectId}/time.json`, { params });
145
305
  return {
146
306
  content: [
@@ -155,11 +315,134 @@ server.tool("get_time_entries", {
155
315
  return handleApiError(error);
156
316
  }
157
317
  });
318
+ server.tool("list_people", {
319
+ userType: z.enum(["account", "collaborator", "contact"]).optional().describe("user type"),
320
+ updatedAfter: z.string().optional().describe("date time"),
321
+ searchTerm: z.string().optional().describe("filter by comment content"),
322
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
323
+ orderBy: z.enum(["name", "namecaseinsensitive", "company"]).optional().describe("order by"),
324
+ lastLoginAfter: z.string().optional().describe("last login after"),
325
+ pageSize: z.number().optional().describe("number of items in a page"),
326
+ page: z.number().optional().describe("page number"),
327
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
328
+ showDeleted: z.boolean().optional().describe("include deleted items"),
329
+ searchUserJobRole: z.boolean().optional().describe("Include user job role in search"),
330
+ orderPrioritiseCurrentUser: z.boolean().optional().describe("Force to have the current/session user in the response"),
331
+ onlySiteOwner: z.boolean().optional().describe("only site owner"),
332
+ onlyOwnerCompany: z.boolean().optional().describe("return people only from the owner company"),
333
+ inclusiveFilter: z.boolean().optional().describe("make the filter inclusive for user ids, teamIds, companyIds"),
334
+ includeServiceAccounts: z.boolean().optional().describe("include service accounts"),
335
+ includePlaceholders: z.boolean().optional().describe("include placeholder users"),
336
+ includeCollaborators: z.boolean().optional().describe("exclude collaborators types"),
337
+ includeClients: z.boolean().optional().describe("include clients"),
338
+ filterByNoCostRate: z.boolean().optional().describe("Returns users who are missing cost rates"),
339
+ excludeContacts: z.boolean().optional().describe("exclude contact types"),
340
+ adminsOnly: z.boolean().optional().describe("only include users with administrator status"),
341
+ teamIds: z.array(z.number()).optional().describe("team ids"),
342
+ projectIds: z.array(z.number()).optional().describe("filter by project ids"),
343
+ jobRoleIds: z.array(z.number()).optional().describe("filter by job role ids"),
344
+ include: z.array(z.string()).optional().describe("include related data"),
345
+ ids: z.array(z.number()).optional().describe("filter by user ids"),
346
+ excludeProjectIds: z.array(z.number()).optional().describe("exclude people assigned to certain project id"),
347
+ excludeIds: z.array(z.number()).optional().describe("exclude certain user ids"),
348
+ emails: z.array(z.string()).optional().describe("filter by user emails"),
349
+ companyIds: z.array(z.number()).optional().describe("company ids"),
350
+ }, async (args) => {
351
+ try {
352
+ const params = { ...args };
353
+ // Helper to join arrays
354
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
355
+ if (args.teamIds)
356
+ params.teamIds = joinParam(args.teamIds);
357
+ if (args.projectIds)
358
+ params.projectIds = joinParam(args.projectIds);
359
+ if (args.jobRoleIds)
360
+ params.jobRoleIds = joinParam(args.jobRoleIds);
361
+ if (args.include)
362
+ params.include = joinParam(args.include);
363
+ if (args.ids)
364
+ params.ids = joinParam(args.ids);
365
+ if (args.excludeProjectIds)
366
+ params.excludeProjectIds = joinParam(args.excludeProjectIds);
367
+ if (args.excludeIds)
368
+ params.excludeIds = joinParam(args.excludeIds);
369
+ if (args.emails)
370
+ params.emails = joinParam(args.emails);
371
+ if (args.companyIds)
372
+ params.companyIds = joinParam(args.companyIds);
373
+ const response = await axiosInstance.get("/people.json", { params });
374
+ return {
375
+ content: [
376
+ {
377
+ type: "text",
378
+ text: JSON.stringify(response.data, null, 2),
379
+ },
380
+ ],
381
+ };
382
+ }
383
+ catch (error) {
384
+ return handleApiError(error);
385
+ }
386
+ });
158
387
  server.tool("get_project_people", {
159
388
  projectId: z.number().describe("The ID of the project"),
160
- }, async ({ projectId }) => {
389
+ userType: z.enum(["account", "collaborator", "contact"]).optional().describe("user type"),
390
+ updatedAfter: z.string().optional().describe("date time"),
391
+ searchTerm: z.string().optional().describe("filter by comment content"),
392
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
393
+ orderBy: z.enum(["name", "namecaseinsensitive", "company"]).optional().describe("order by"),
394
+ lastLoginAfter: z.string().optional().describe("last login after"),
395
+ pageSize: z.number().optional().describe("number of items in a page"),
396
+ page: z.number().optional().describe("page number"),
397
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
398
+ showDeleted: z.boolean().optional().describe("include deleted items"),
399
+ searchUserJobRole: z.boolean().optional().describe("Include user job role in search"),
400
+ orderPrioritiseCurrentUser: z.boolean().optional().describe("Force to have the current/session user in the response"),
401
+ onlySiteOwner: z.boolean().optional().describe("only site owner"),
402
+ onlyOwnerCompany: z.boolean().optional().describe("return people only from the owner company"),
403
+ inclusiveFilter: z.boolean().optional().describe("make the filter inclusive"),
404
+ includeServiceAccounts: z.boolean().optional().describe("include service accounts"),
405
+ includePlaceholders: z.boolean().optional().describe("include placeholder users"),
406
+ includeObservers: z.boolean().optional().describe("include project observers"),
407
+ includeCollaborators: z.boolean().optional().describe("exclude collaborators types"),
408
+ includeClients: z.boolean().optional().describe("include clients"),
409
+ filterByNoCostRate: z.boolean().optional().describe("Returns users who are missing cost rates"),
410
+ excludeContacts: z.boolean().optional().describe("exclude contact types"),
411
+ adminsOnly: z.boolean().optional().describe("only include users with administrator status"),
412
+ teamIds: z.array(z.number()).optional().describe("team ids"),
413
+ projectIds: z.array(z.number()).optional().describe("filter by project ids"),
414
+ jobRoleIds: z.array(z.number()).optional().describe("filter by job role ids"),
415
+ include: z.array(z.string()).optional().describe("include related data"),
416
+ ids: z.array(z.number()).optional().describe("filter by user ids"),
417
+ excludeProjectIds: z.array(z.number()).optional().describe("exclude people assigned to certain project id"),
418
+ excludeIds: z.array(z.number()).optional().describe("exclude certain user ids"),
419
+ emails: z.array(z.string()).optional().describe("filter by user emails"),
420
+ companyIds: z.array(z.number()).optional().describe("company ids"),
421
+ }, async (args) => {
161
422
  try {
162
- const response = await axiosInstance.get(`/projects/${projectId}/people.json`);
423
+ const { projectId, ...restParams } = args;
424
+ const params = { ...restParams };
425
+ // Helper to join arrays
426
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
427
+ if (args.teamIds)
428
+ params.teamIds = joinParam(args.teamIds);
429
+ if (args.projectIds)
430
+ params.projectIds = joinParam(args.projectIds);
431
+ if (args.jobRoleIds)
432
+ params.jobRoleIds = joinParam(args.jobRoleIds);
433
+ if (args.include)
434
+ params.include = joinParam(args.include);
435
+ if (args.ids)
436
+ params.ids = joinParam(args.ids);
437
+ if (args.excludeProjectIds)
438
+ params.excludeProjectIds = joinParam(args.excludeProjectIds);
439
+ if (args.excludeIds)
440
+ params.excludeIds = joinParam(args.excludeIds);
441
+ if (args.emails)
442
+ params.emails = joinParam(args.emails);
443
+ if (args.companyIds)
444
+ params.companyIds = joinParam(args.companyIds);
445
+ const response = await axiosInstance.get(`/projects/${projectId}/people.json`, { params });
163
446
  return {
164
447
  content: [
165
448
  {
@@ -358,6 +641,445 @@ server.tool("get_company", {
358
641
  return handleApiError(error);
359
642
  }
360
643
  });
644
+ server.tool("list_of_project_categories", {
645
+ searchTerm: z.string().optional().describe("Filter by name"),
646
+ onlyStarredProjects: z.boolean().optional().describe("Filter by starred projects only"),
647
+ projectStatuses: z.array(z.string()).optional().describe("Filter by project statuses (Comma separated values)"),
648
+ ids: z.array(z.number()).optional().describe("Filter by specific ids"),
649
+ fields: z.array(z.string()).optional().describe("Fields to return: id, name, color, count, parent, parentId"),
650
+ }, async (args) => {
651
+ try {
652
+ const params = {};
653
+ if (args.searchTerm)
654
+ params.searchTerm = args.searchTerm;
655
+ if (args.onlyStarredProjects !== undefined)
656
+ params.onlyStarredProjects = args.onlyStarredProjects;
657
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
658
+ if (args.projectStatuses)
659
+ params.projectStatuses = joinParam(args.projectStatuses);
660
+ if (args.ids)
661
+ params.ids = joinParam(args.ids);
662
+ if (args.fields)
663
+ params['fields[projectcategories]'] = joinParam(args.fields);
664
+ const response = await axiosInstance.get("/projectcategories.json", { params });
665
+ return {
666
+ content: [
667
+ {
668
+ type: "text",
669
+ text: JSON.stringify(response.data, null, 2),
670
+ },
671
+ ],
672
+ };
673
+ }
674
+ catch (error) {
675
+ return handleApiError(error);
676
+ }
677
+ });
678
+ server.tool("get_specific_project_category", {
679
+ id: z.number().describe("The ID of the category"),
680
+ searchTerm: z.string().optional().describe("Filter by name"),
681
+ onlyStarredProjects: z.boolean().optional().describe("Filter by starred projects only"),
682
+ projectStatuses: z.array(z.string()).optional().describe("Filter by project statuses"),
683
+ ids: z.array(z.number()).optional().describe("Filter by specific ids"),
684
+ fields: z.array(z.string()).optional().describe("Fields to return"),
685
+ }, async (args) => {
686
+ try {
687
+ const params = {};
688
+ if (args.searchTerm)
689
+ params.searchTerm = args.searchTerm;
690
+ if (args.onlyStarredProjects !== undefined)
691
+ params.onlyStarredProjects = args.onlyStarredProjects;
692
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
693
+ if (args.projectStatuses)
694
+ params.projectStatuses = joinParam(args.projectStatuses);
695
+ if (args.ids)
696
+ params.ids = joinParam(args.ids);
697
+ if (args.fields)
698
+ params['fields[projectcategories]'] = joinParam(args.fields);
699
+ const response = await axiosInstance.get(`/projectcategories/${args.id}.json`, { params });
700
+ return {
701
+ content: [
702
+ {
703
+ type: "text",
704
+ text: JSON.stringify(response.data, null, 2),
705
+ },
706
+ ],
707
+ };
708
+ }
709
+ catch (error) {
710
+ return handleApiError(error);
711
+ }
712
+ });
713
+ server.tool("list_of_teamwork_project_categories", {}, async () => {
714
+ try {
715
+ const response = await axiosInstance.get("/projects/teamwork/categories.json");
716
+ return {
717
+ content: [
718
+ {
719
+ type: "text",
720
+ text: JSON.stringify(response.data, null, 2),
721
+ },
722
+ ],
723
+ };
724
+ }
725
+ catch (error) {
726
+ return handleApiError(error);
727
+ }
728
+ });
729
+ server.tool("get_project", {
730
+ projectId: z.number().describe("The ID of the project"),
731
+ updatedAfter: z.string().optional().describe("updated after"),
732
+ timeMode: z.enum(["timelogs", "estimated"]).optional().describe("profitability time mode"),
733
+ searchTerm: z.string().optional().describe("filter by project name"),
734
+ reportType: z.enum(["project", "health"]).optional().describe("define the type of the report"),
735
+ reportTimezone: z.string().optional().describe("Optional to configure the report dates displayed in a timezone"),
736
+ reportFormat: z.enum(["csv", "html", "pdf", "xls"]).optional().describe("define the format of the report"),
737
+ projectType: z.string().optional().describe("filter by project type"),
738
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
739
+ orderBy: z.enum(["companyname", "datecreated", "duedate", "lastactivity", "name", "namecaseinsensitive", "ownercompany", "starred", "categoryname"]).optional().describe("order by"),
740
+ notCompletedBefore: z.string().optional().describe("filter by projects that have not been completed before the given date"),
741
+ minLastActivityDate: z.string().optional().describe("filter by min last activity date"),
742
+ maxLastActivityDate: z.string().optional().describe("filter by max last activity date"),
743
+ userId: z.number().optional().describe("filter by user id"),
744
+ pageSize: z.number().optional().describe("number of items in a page"),
745
+ page: z.number().optional().describe("page number"),
746
+ orderByCustomFieldId: z.number().optional().describe("order by custom field id when orderBy is equal to customfield"),
747
+ minBudgetCapacityUsedPercent: z.number().optional().describe("filter by minimum budget capacity used"),
748
+ maxBudgetCapacityUsedPercent: z.number().optional().describe("filter by maximum budget capacity used"),
749
+ useFormulaFields: z.boolean().optional().describe("use formula fields"),
750
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts on a list API endpoint for performance reasons"),
751
+ searchCompanies: z.boolean().optional().describe("include companies in the search"),
752
+ searchByLetter: z.boolean().optional().describe("search projects beginning with the search term character only"),
753
+ onlyStarredProjects: z.boolean().optional().describe("filter by starred projects only"),
754
+ onlyProjectsWithExplicitMembership: z.boolean().optional().describe("only show projects with explicit membership"),
755
+ onlyProjectsThatCanLogTime: z.boolean().optional().describe("can log time on projects"),
756
+ onlyProjectsThatCanAddTasks: z.boolean().optional().describe("can add tasks on projects"),
757
+ onlyArchivedProjects: z.boolean().optional().describe("return only archived projects"),
758
+ matchAllProjectTags: z.boolean().optional().describe("match all project tags"),
759
+ matchAllExcludedTags: z.boolean().optional().describe("match all excluded project tags"),
760
+ isReportDownload: z.boolean().optional().describe("generate a report document"),
761
+ includeTentativeProjects: z.boolean().optional().describe("include alongside normal projects, tentative ones"),
762
+ includeTabSystemStatus: z.boolean().optional().describe("include tab system status in response"),
763
+ includeSubCategories: z.boolean().optional().describe("include sub categories when filtering by ids"),
764
+ includeStats: z.boolean().optional().describe("include project status counts"),
765
+ includeProjectUserInfo: z.boolean().optional().describe("fetch user-specific data such as isStarred"),
766
+ includeProjectProfitability: z.boolean().optional().describe("include project profitability in response"),
767
+ includeProjectDates: z.boolean().optional().describe("include minimum and maximum start/end dates for projects"),
768
+ includeCustomFields: z.boolean().optional().describe("include custom fields"),
769
+ includeCounts: z.boolean().optional().describe("include project related counts"),
770
+ includeCompletedStatus: z.boolean().optional().describe("include completed projects when filtering by project statuses current,late"),
771
+ includeArchivedProjects: z.boolean().optional().describe("include archived projects"),
772
+ hideObservedProjects: z.boolean().optional().describe("hide projects where the logged-in user is just an observer"),
773
+ alwaysIncludeFiltering: z.boolean().optional().describe("includes filters when project ids are provided"),
774
+ usersWithExplicitMembershipIds: z.array(z.number()).optional().describe("only show projects that have an explicit common membership with provided user ids"),
775
+ teamIds: z.array(z.number()).optional().describe("filter by projects that contain users associated with the team ids"),
776
+ selectedColumns: z.array(z.string()).optional().describe("select the columns to use in exports"),
777
+ projectTagIds: z.array(z.number()).optional().describe("filter by project tag ids"),
778
+ projectStatuses: z.array(z.string()).optional().describe("filter by project status (active, current, late, upcoming, completed, deleted)"),
779
+ projectOwnerIds: z.array(z.number()).optional().describe("filter by project owner ids"),
780
+ projectIds: z.array(z.number()).optional().describe("filter by project ids"),
781
+ projectHealths: z.array(z.number()).optional().describe("filter by project healths (0: not set, 1: bad, 2: ok, 3: good)"),
782
+ projectCompanyIds: z.array(z.number()).optional().describe("filter by company ids"),
783
+ projectCategoryIds: z.array(z.number()).optional().describe("filter by project category ids"),
784
+ includeCustomFieldIds: z.array(z.number()).optional().describe("include specific custom fields"),
785
+ include: z.array(z.string()).optional().describe("include related data"),
786
+ featuresEnabled: z.array(z.string()).optional().describe("filter by projects that have features enabled"),
787
+ excludeTagIds: z.array(z.number()).optional().describe("exclude by project tag ids"),
788
+ excludeProjectIds: z.array(z.number()).optional().describe("exclude certain project ids"),
789
+ // Field arrays for flattening
790
+ fieldsWorkflows: z.array(z.string()).optional().describe("fields[workflows]"),
791
+ fieldsUsers: z.array(z.string()).optional().describe("fields[users]"),
792
+ fieldsTags: z.array(z.string()).optional().describe("fields[tags]"),
793
+ fieldsStages: z.array(z.string()).optional().describe("fields[stages]"),
794
+ fieldsProjects: z.array(z.string()).optional().describe("fields[projects]"),
795
+ fieldsProjectCategories: z.array(z.string()).optional().describe("fields[projectcategories]"),
796
+ fieldsProjectUpdates: z.array(z.string()).optional().describe("fields[projectUpdates]"),
797
+ fieldsProjectBudgets: z.array(z.string()).optional().describe("fields[projectBudgets]"),
798
+ fieldsPortfolioColumns: z.array(z.string()).optional().describe("fields[portfolioColumns]"),
799
+ fieldsPortfolioCards: z.array(z.string()).optional().describe("fields[portfolioCards]"),
800
+ fieldsPortfolioBoards: z.array(z.string()).optional().describe("fields[portfolioBoards]"),
801
+ fieldsIndustries: z.array(z.string()).optional().describe("fields[industries]"),
802
+ fieldsCustomFields: z.array(z.string()).optional().describe("fields[customfields]"),
803
+ fieldsCustomFieldProjects: z.array(z.string()).optional().describe("fields[customfieldProjects]"),
804
+ fieldsCountries: z.array(z.string()).optional().describe("fields[countries]"),
805
+ fieldsCompanies: z.array(z.string()).optional().describe("fields[companies]"),
806
+ }, async (args) => {
807
+ try {
808
+ const { projectId, ...restParams } = args;
809
+ const params = { ...restParams };
810
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
811
+ if (args.usersWithExplicitMembershipIds)
812
+ params.usersWithExplicitMembershipIds = joinParam(args.usersWithExplicitMembershipIds);
813
+ if (args.teamIds)
814
+ params.teamIds = joinParam(args.teamIds);
815
+ if (args.selectedColumns)
816
+ params.selectedColumns = joinParam(args.selectedColumns);
817
+ if (args.projectTagIds)
818
+ params.projectTagIds = joinParam(args.projectTagIds);
819
+ if (args.projectStatuses)
820
+ params.projectStatuses = joinParam(args.projectStatuses);
821
+ if (args.projectOwnerIds)
822
+ params.projectOwnerIds = joinParam(args.projectOwnerIds);
823
+ if (args.projectIds)
824
+ params.projectIds = joinParam(args.projectIds);
825
+ if (args.projectHealths)
826
+ params.projectHealths = joinParam(args.projectHealths);
827
+ if (args.projectCompanyIds)
828
+ params.projectCompanyIds = joinParam(args.projectCompanyIds);
829
+ if (args.projectCategoryIds)
830
+ params.projectCategoryIds = joinParam(args.projectCategoryIds);
831
+ if (args.includeCustomFieldIds)
832
+ params.includeCustomFieldIds = joinParam(args.includeCustomFieldIds);
833
+ if (args.include)
834
+ params.include = joinParam(args.include);
835
+ if (args.featuresEnabled)
836
+ params.featuresEnabled = joinParam(args.featuresEnabled);
837
+ if (args.excludeTagIds)
838
+ params.excludeTagIds = joinParam(args.excludeTagIds);
839
+ if (args.excludeProjectIds)
840
+ params.excludeProjectIds = joinParam(args.excludeProjectIds);
841
+ // Handle fields arrays
842
+ if (args.fieldsWorkflows)
843
+ params['fields[workflows]'] = joinParam(args.fieldsWorkflows);
844
+ if (args.fieldsUsers)
845
+ params['fields[users]'] = joinParam(args.fieldsUsers);
846
+ if (args.fieldsTags)
847
+ params['fields[tags]'] = joinParam(args.fieldsTags);
848
+ if (args.fieldsStages)
849
+ params['fields[stages]'] = joinParam(args.fieldsStages);
850
+ if (args.fieldsProjects)
851
+ params['fields[projects]'] = joinParam(args.fieldsProjects);
852
+ if (args.fieldsProjectCategories)
853
+ params['fields[projectcategories]'] = joinParam(args.fieldsProjectCategories);
854
+ if (args.fieldsProjectUpdates)
855
+ params['fields[projectUpdates]'] = joinParam(args.fieldsProjectUpdates);
856
+ if (args.fieldsProjectBudgets)
857
+ params['fields[projectBudgets]'] = joinParam(args.fieldsProjectBudgets);
858
+ if (args.fieldsPortfolioColumns)
859
+ params['fields[portfolioColumns]'] = joinParam(args.fieldsPortfolioColumns);
860
+ if (args.fieldsPortfolioCards)
861
+ params['fields[portfolioCards]'] = joinParam(args.fieldsPortfolioCards);
862
+ if (args.fieldsPortfolioBoards)
863
+ params['fields[portfolioBoards]'] = joinParam(args.fieldsPortfolioBoards);
864
+ if (args.fieldsIndustries)
865
+ params['fields[industries]'] = joinParam(args.fieldsIndustries);
866
+ if (args.fieldsCustomFields)
867
+ params['fields[customfields]'] = joinParam(args.fieldsCustomFields);
868
+ if (args.fieldsCustomFieldProjects)
869
+ params['fields[customfieldProjects]'] = joinParam(args.fieldsCustomFieldProjects);
870
+ if (args.fieldsCountries)
871
+ params['fields[countries]'] = joinParam(args.fieldsCountries);
872
+ if (args.fieldsCompanies)
873
+ params['fields[companies]'] = joinParam(args.fieldsCompanies);
874
+ const response = await axiosInstance.get(`/projects/${projectId}.json`, { params });
875
+ return {
876
+ content: [
877
+ {
878
+ type: "text",
879
+ text: JSON.stringify(response.data, null, 2),
880
+ },
881
+ ],
882
+ };
883
+ }
884
+ catch (error) {
885
+ return handleApiError(error);
886
+ }
887
+ });
888
+ server.tool("get_all_project_updates", {
889
+ updatedAfter: z.string().optional().describe("filter by updated after"),
890
+ projectStatus: z.string().optional().describe("filter by project status"),
891
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
892
+ orderBy: z.enum(["date", "color", "health", "project", "user"]).optional().describe("order by"),
893
+ createdAfter: z.string().optional().describe("filter by creation date"),
894
+ projectId: z.number().optional().describe("filter by project id"),
895
+ pageSize: z.number().optional().describe("number of items in a page"),
896
+ page: z.number().optional().describe("page number"),
897
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
898
+ showDeleted: z.boolean().optional().describe("include deleted items"),
899
+ reactions: z.boolean().optional().describe("add reactions to the response"),
900
+ onlyStarredProjects: z.boolean().optional().describe("filter by starred projects only"),
901
+ matchAllProjectTags: z.boolean().optional().describe("match all project tags"),
902
+ includeArchivedProjects: z.boolean().optional().describe("include archived projects"),
903
+ emoji: z.boolean().optional().describe("parse emojis to unicode"),
904
+ activeOnly: z.boolean().optional().describe("filter by active"),
905
+ projectTagIds: z.array(z.number()).optional().describe("filter by project tag ids"),
906
+ projectStatuses: z.array(z.string()).optional().describe("list of project status"),
907
+ projectOwnerIds: z.array(z.number()).optional().describe("filter by project owner ids"),
908
+ projectHealths: z.array(z.number()).optional().describe("filter by project health"),
909
+ projectCompanyIds: z.array(z.number()).optional().describe("filter by company ids"),
910
+ projectCategoryIds: z.array(z.number()).optional().describe("filter by project category ids"),
911
+ include: z.array(z.string()).optional().describe("include"),
912
+ fieldsUsers: z.array(z.string()).optional().describe("fields[users]"),
913
+ fieldsProjects: z.array(z.string()).optional().describe("fields[projects]"),
914
+ fieldsProjectUpdates: z.array(z.string()).optional().describe("fields[projectUpdates]"),
915
+ }, async (args) => {
916
+ try {
917
+ const params = { ...args };
918
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
919
+ if (args.projectTagIds)
920
+ params.projectTagIds = joinParam(args.projectTagIds);
921
+ if (args.projectStatuses)
922
+ params.projectStatuses = joinParam(args.projectStatuses);
923
+ if (args.projectOwnerIds)
924
+ params.projectOwnerIds = joinParam(args.projectOwnerIds);
925
+ if (args.projectHealths)
926
+ params.projectHealths = joinParam(args.projectHealths);
927
+ if (args.projectCompanyIds)
928
+ params.projectCompanyIds = joinParam(args.projectCompanyIds);
929
+ if (args.projectCategoryIds)
930
+ params.projectCategoryIds = joinParam(args.projectCategoryIds);
931
+ if (args.include)
932
+ params.include = joinParam(args.include);
933
+ if (args.fieldsUsers)
934
+ params['fields[users]'] = joinParam(args.fieldsUsers);
935
+ if (args.fieldsProjects)
936
+ params['fields[projects]'] = joinParam(args.fieldsProjects);
937
+ if (args.fieldsProjectUpdates)
938
+ params['fields[projectUpdates]'] = joinParam(args.fieldsProjectUpdates);
939
+ const response = await axiosInstance.get("/projects/updates.json", { params });
940
+ return {
941
+ content: [
942
+ {
943
+ type: "text",
944
+ text: JSON.stringify(response.data, null, 2),
945
+ },
946
+ ],
947
+ };
948
+ }
949
+ catch (error) {
950
+ return handleApiError(error);
951
+ }
952
+ });
953
+ server.tool("get_project_updates", {
954
+ projectId: z.number().describe("The ID of the project"),
955
+ updatedAfter: z.string().optional().describe("filter by updated after"),
956
+ projectStatus: z.string().optional().describe("filter by project status"),
957
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
958
+ orderBy: z.enum(["date", "color", "health", "project", "user"]).optional().describe("order by"),
959
+ createdAfter: z.string().optional().describe("filter by creation date"),
960
+ pageSize: z.number().optional().describe("number of items in a page"),
961
+ page: z.number().optional().describe("page number"),
962
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
963
+ showDeleted: z.boolean().optional().describe("include deleted items"),
964
+ reactions: z.boolean().optional().describe("add reactions to the response"),
965
+ onlyStarredProjects: z.boolean().optional().describe("filter by starred projects only"),
966
+ matchAllProjectTags: z.boolean().optional().describe("match all project tags"),
967
+ includeArchivedProjects: z.boolean().optional().describe("include archived projects"),
968
+ emoji: z.boolean().optional().describe("parse emojis to unicode"),
969
+ activeOnly: z.boolean().optional().describe("filter by active"),
970
+ projectTagIds: z.array(z.number()).optional().describe("filter by project tag ids"),
971
+ projectStatuses: z.array(z.string()).optional().describe("list of project status"),
972
+ projectOwnerIds: z.array(z.number()).optional().describe("filter by project owner ids"),
973
+ projectHealths: z.array(z.number()).optional().describe("filter by project health"),
974
+ projectCompanyIds: z.array(z.number()).optional().describe("filter by company ids"),
975
+ projectCategoryIds: z.array(z.number()).optional().describe("filter by project category ids"),
976
+ include: z.array(z.string()).optional().describe("include"),
977
+ fieldsUsers: z.array(z.string()).optional().describe("fields[users]"),
978
+ fieldsProjects: z.array(z.string()).optional().describe("fields[projects]"),
979
+ fieldsProjectUpdates: z.array(z.string()).optional().describe("fields[projectUpdates]"),
980
+ }, async (args) => {
981
+ try {
982
+ const { projectId, ...restParams } = args;
983
+ const params = { ...restParams };
984
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
985
+ if (args.projectTagIds)
986
+ params.projectTagIds = joinParam(args.projectTagIds);
987
+ if (args.projectStatuses)
988
+ params.projectStatuses = joinParam(args.projectStatuses);
989
+ if (args.projectOwnerIds)
990
+ params.projectOwnerIds = joinParam(args.projectOwnerIds);
991
+ if (args.projectHealths)
992
+ params.projectHealths = joinParam(args.projectHealths);
993
+ if (args.projectCompanyIds)
994
+ params.projectCompanyIds = joinParam(args.projectCompanyIds);
995
+ if (args.projectCategoryIds)
996
+ params.projectCategoryIds = joinParam(args.projectCategoryIds);
997
+ if (args.include)
998
+ params.include = joinParam(args.include);
999
+ if (args.fieldsUsers)
1000
+ params['fields[users]'] = joinParam(args.fieldsUsers);
1001
+ if (args.fieldsProjects)
1002
+ params['fields[projects]'] = joinParam(args.fieldsProjects);
1003
+ if (args.fieldsProjectUpdates)
1004
+ params['fields[projectUpdates]'] = joinParam(args.fieldsProjectUpdates);
1005
+ const response = await axiosInstance.get(`/projects/${projectId}/updates.json`, { params });
1006
+ return {
1007
+ content: [
1008
+ {
1009
+ type: "text",
1010
+ text: JSON.stringify(response.data, null, 2),
1011
+ },
1012
+ ],
1013
+ };
1014
+ }
1015
+ catch (error) {
1016
+ return handleApiError(error);
1017
+ }
1018
+ });
1019
+ server.tool("get_all_tags", {
1020
+ updatedAfter: z.string().optional().describe("search for tags updated after the provided date"),
1021
+ searchTerm: z.string().optional().describe("filter by search term"),
1022
+ orderMode: z.enum(["asc", "desc"]).optional().describe("order mode"),
1023
+ orderBy: z.enum(["name", "count", "project", "color", "datelastupdated", "projectdatelastused"]).optional().describe("order by"),
1024
+ itemType: z.enum(["all", "project", "task", "tasklist", "milestone", "message", "comment", "timelog", "file", "user", "company", "invoice", "risk", "notebook", "link", "event", "notebookversion", "fileversion"]).optional().describe("filter by item type"),
1025
+ filter: z.enum(["all", "recent"]).optional().describe("mode used when filtering the tags"),
1026
+ pageSize: z.number().optional().describe("number of items in a page"),
1027
+ page: z.number().optional().describe("page number"),
1028
+ withCounters: z.boolean().optional().describe("include in the response the number of items that use the tag"),
1029
+ skipSpecial: z.boolean().optional().describe("do not include in the response special tags"),
1030
+ skipCounts: z.boolean().optional().describe("SkipCounts allows you to skip doing counts"),
1031
+ searchRightOnly: z.boolean().optional().describe("search term will be placed as a prefix to match the tag names"),
1032
+ skipIds: z.array(z.number()).optional().describe("skip from the result tags with the defined ids"),
1033
+ projectIds: z.array(z.number()).optional().describe("filter by projects"),
1034
+ include: z.array(z.string()).optional().describe("include"),
1035
+ ids: z.array(z.number()).optional().describe("filter by ids"),
1036
+ fieldsTags: z.array(z.string()).optional().describe("fields[tags]"),
1037
+ }, async (args) => {
1038
+ try {
1039
+ const params = { ...args };
1040
+ const joinParam = (arr) => arr ? arr.join(',') : undefined;
1041
+ if (args.skipIds)
1042
+ params.skipIds = joinParam(args.skipIds);
1043
+ if (args.projectIds)
1044
+ params.projectIds = joinParam(args.projectIds);
1045
+ if (args.include)
1046
+ params.include = joinParam(args.include);
1047
+ if (args.ids)
1048
+ params.ids = joinParam(args.ids);
1049
+ if (args.fieldsTags)
1050
+ params['fields[tags]'] = joinParam(args.fieldsTags);
1051
+ const response = await axiosInstance.get("/tags.json", { params });
1052
+ return {
1053
+ content: [
1054
+ {
1055
+ type: "text",
1056
+ text: JSON.stringify(response.data, null, 2),
1057
+ },
1058
+ ],
1059
+ };
1060
+ }
1061
+ catch (error) {
1062
+ return handleApiError(error);
1063
+ }
1064
+ });
1065
+ server.tool("get_tag", {
1066
+ tagId: z.number().describe("The ID of the tag"),
1067
+ }, async ({ tagId }) => {
1068
+ try {
1069
+ const response = await axiosInstance.get(`/tags/${tagId}.json`);
1070
+ return {
1071
+ content: [
1072
+ {
1073
+ type: "text",
1074
+ text: JSON.stringify(response.data, null, 2),
1075
+ },
1076
+ ],
1077
+ };
1078
+ }
1079
+ catch (error) {
1080
+ return handleApiError(error);
1081
+ }
1082
+ });
361
1083
  async function main() {
362
1084
  const transport = new StdioServerTransport();
363
1085
  await server.connect(transport);
package/package.json CHANGED
@@ -3,8 +3,8 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.1",
7
- "description": "MCP Server for Teamwork",
6
+ "version": "1.1.0",
7
+ "description": "MCP Server for Teamwork API V3 - Projects, Time Entries, People, Companies, Tags, and Updates",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
10
10
  "teamwork-mcp": "./dist/index.js"
@@ -16,10 +16,19 @@
16
16
  "scripts": {
17
17
  "build": "tsc",
18
18
  "start": "node dist/index.js",
19
- "dev": "tsc --watch"
19
+ "dev": "tsc --watch",
20
+ "prepublishOnly": "npm run build"
20
21
  },
21
- "keywords": [],
22
- "author": "",
22
+ "keywords": [
23
+ "mcp",
24
+ "teamwork",
25
+ "model-context-protocol",
26
+ "ai",
27
+ "agent",
28
+ "llm",
29
+ "api"
30
+ ],
31
+ "author": "Jorge Luis M. Lima",
23
32
  "license": "ISC",
24
33
  "dependencies": {
25
34
  "@modelcontextprotocol/sdk": "^1.0.1",