@vitalyostanin/youtrack-mcp 0.7.3 → 0.8.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-ru.md +15 -9
- package/README.md +44 -30
- package/dist/package.json +1 -1
- package/dist/src/server.js +2 -0
- package/dist/src/tools/issue-search-tools.d.ts +26 -6
- package/dist/src/tools/issue-search-tools.js +73 -6
- package/dist/src/tools/issue-tools.js +24 -0
- package/dist/src/tools/users-activity-tools.d.ts +115 -0
- package/dist/src/tools/users-activity-tools.js +84 -0
- package/dist/src/utils/date.d.ts +2 -0
- package/dist/src/utils/date.js +3 -0
- package/dist/src/utils/mappers.d.ts +0 -1
- package/dist/src/utils/mappers.js +0 -1
- package/dist/src/youtrack-client.d.ts +10 -0
- package/dist/src/youtrack-client.js +59 -23
- package/package.json +1 -1
package/README-ru.md
CHANGED
|
@@ -45,6 +45,7 @@ MCP сервер для полноценной интеграции с YouTrack
|
|
|
45
45
|
- [Трудозатраты](#трудозатраты)
|
|
46
46
|
- [Пользователи и проекты](#пользователи-и-проекты)
|
|
47
47
|
- [Статьи](#статьи)
|
|
48
|
+
- [Лента активности](#лента-активности)
|
|
48
49
|
- [Вложения](#вложения)
|
|
49
50
|
|
|
50
51
|
## Требования
|
|
@@ -282,11 +283,11 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
282
283
|
| Tool | Описание | Основные параметры |
|
|
283
284
|
| --- | --- | --- |
|
|
284
285
|
| `issue_lookup` | Краткая информация о задаче. **Использование:** Быстрый просмотр базовой информации о задаче без полной детализации. **Возвращает:** предопределенные поля - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee. Пользовательские поля не включены. | `issueId` — код задачи (например, `PROJ-123`) |
|
|
285
|
-
| `issues_lookup` | Краткая информация о нескольких задачах (пакетный режим, макс 50). **Использование:** Эффективное получение информации о множестве задач одновременно. **Возвращает:** те же поля, что и `issue_lookup`. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач (например, `['PROJ-123', 'PROJ-124']`) |
|
|
286
|
+
| `issues_lookup` | Краткая информация о нескольких задачах (пакетный режим, макс 50). **Использование:** Эффективное получение информации о множестве задач одновременно. **Возвращает:** те же поля, что и `issue_lookup`. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач (например, `['PROJ-123', 'PROJ-124']`), макс 50; `briefOutput` — опционально (по умолчанию `true`) |
|
|
286
287
|
| `issue_details` | Режимы краткий/полный для деталей задачи. **Использование:** Просмотр деталей задачи. **Краткий (по умолчанию):** предопределённые поля — id, idReadable, summary, description, wikifiedDescription, usesMarkdown, created, updated, resolved, project, parent, assignee, reporter, updater, watchers(hasStar). **Полный (`briefOutput=false`):** добавляет `customFields(id,name,value(id,name,presentation),$type,possibleEvents(id,presentation))`, включая поле `State`. | `issueId` — код задачи; `briefOutput` — опционально (по умолчанию `true`) |
|
|
287
|
-
| `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов
|
|
288
|
-
| `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated | `issueId` — код задачи |
|
|
289
|
-
| `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов
|
|
288
|
+
| `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`) |
|
|
289
|
+
| `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (прямая ссылка на комментарий) | `issueId` — код задачи |
|
|
290
|
+
| `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`) |
|
|
290
291
|
| `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании, одновременное создание дополнительных связей. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — внутренний ID проекта, `summary`, опционально `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links[]` — массив объектов связи |
|
|
291
292
|
| `issue_update` | Обновление существующей задачи. **Использование:** Изменение summary, description, родительской задачи. **Примечание:** для изменения исполнителя используйте `issue_assign`. Пустая строка в `parentIssueId` удаляет родителя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, опционально `summary`, `description`, `parentIssueId` (пустая строка очищает родителя), `usesMarkdown` |
|
|
292
293
|
| `issue_assign` | Назначение исполнителя для задачи. **Использование:** Изменение исполнителя задачи. Поддерживает алиас `me` для текущего пользователя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, `assigneeLogin` — логин исполнителя или `me` |
|
|
@@ -294,16 +295,15 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
294
295
|
| `issue_comment_update` | Обновление существующего комментария. **Использование:** Редактирование текста комментария, изменение режима форматирования, исправление опечаток. Поддерживает Markdown. **Возвращает:** поля комментария - id, text, textPreview, usesMarkdown, author, created, updated, commentUrl | `issueId` — ID или код задачи, `commentId` — ID комментария, опционально `text`, `usesMarkdown`, `muteUpdateNotifications` |
|
|
295
296
|
| `issue_activities` | Получение истории изменений задачи (активности). **Использование:** Просмотр полной истории задачи, отслеживание изменений полей во времени, мониторинг кто и когда что изменил, анализ эволюции задачи, аудит модификаций. **Возвращает:** Элементы активности с временными метками (ISO datetime), авторами, категориями, типами изменений и деталями изменений (добавленные/удалённые значения). Поддерживает фильтрацию по автору, диапазону дат и категориям активности. **Примечание:** Возвращает предопределённые поля - id, timestamp, author (id, login, name), category (id), target (text), добавленные значения, удалённые значения, тип активности. Полезно для понимания жизненного цикла задачи, отслеживания модификаций полей, просмотра истории комментариев и анализа паттернов совместной работы. | `issueId` — код задачи, опционально `author` (логин пользователя), `startDate` (формат YYYY-MM-DD, timestamp или Date), `endDate`, `categories` (через запятую: `CustomFieldCategory` для изменений полей, `CommentsCategory` для комментариев, `AttachmentsCategory` для вложений, `LinksCategory` для связей, `VcsChangeActivityCategory` для VCS изменений, `WorkItemsActivityCategory` для трудозатрат), `limit` (макс 200), `skip` для пагинации |
|
|
296
297
|
| `issue_change_state` | Изменение состояния/статуса задачи через переходы workflow. **Использование:** Перемещение задач через состояния рабочего процесса (например, 'Открыта' → 'В работе'). Автоматически определяет доступные переходы и валидирует запрошенное изменение состояния. Регистронезависимое сопоставление имён состояний. **Возвращает:** информацию о предыдущем состоянии, новом состоянии и использованном переходе | `issueId` — код задачи, `stateName` — имя целевого состояния (например, 'В работе', 'Открыта', 'Исправлена', 'Проверена'). Регистронезависимое |
|
|
297
|
-
| `issue_search_by_user_activity` | Поиск задач по активности пользователей в заданный период. **Использование:** Поиск задач, где пользователи обновляли, были упомянуты, создавали, были назначены или комментировали. Поддерживает два режима фильтрации: `issue_updated` (по умолчанию, быстрый) использует поле issue.updated, `user_activity` (медленный, точный) проверяет фактические даты активности пользователя, включая комментарии, упоминания и историю изменений полей. **Когда использовать точный режим:** когда нужна точная дата участия пользователя (например, когда пользователь был исполнителем, но позже был изменён). **Возвращает:** краткую информацию о задачах (id, idReadable, summary, project, parent, assignee) без description для уменьшения размера ответа. В режиме `user_activity` включает поле `lastActivityDate` с точной датой последней активности пользователя. Результаты отсортированы по времени активности (новые первыми). Поддерживает пагинацию. **Ограничение:** по умолчанию 100 задач, макс 200 | `userLogins[]` — массив логинов пользователей для поиска активности (updater, mentions, reporter, assignee, commenter), опционально `startDate` (формат YYYY-MM-DD или timestamp), `endDate`, `dateFilterMode` (`issue_updated` или `user_activity`), `limit` (по умолчанию 100, макс 200), `skip` для пагинации |
|
|
298
298
|
|
|
299
299
|
### Связи задач
|
|
300
300
|
|
|
301
301
|
| Tool | Описание | Основные параметры |
|
|
302
302
|
| --- | --- | --- |
|
|
303
|
-
| `issue_links` | Список связей (линков) для задачи: «relates to», «duplicate», «parent/child». Возвращает id связи, направление (inward/
|
|
303
|
+
| `issue_links` | Список связей (линков) для задачи: «relates to», «duplicate», «parent/child». Возвращает id связи, направление (inward/outbound), тип связи и краткую информацию о связанной задаче | `issueId` — код задачи |
|
|
304
304
|
| `issue_link_types` | Список доступных типов связей в YouTrack. Полезно для подбора корректного типа при создании связи | — |
|
|
305
|
-
| `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID) |
|
|
306
|
-
| `issue_link_delete` | Удаление существующей связи по её id для указанной задачи | `issueId` — код задачи, `linkId` — id связи |
|
|
305
|
+
| `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID), опционально `direction` (`outbound` или `inbound`) |
|
|
306
|
+
| `issue_link_delete` | Удаление существующей связи по её id для указанной задачи | `issueId` — код задачи, `linkId` — id связи для удаления |
|
|
307
307
|
|
|
308
308
|
**issue_create — дополнительные параметры:**
|
|
309
309
|
|
|
@@ -363,10 +363,16 @@ YOUTRACK_TOKEN = "perm:your-token-here"
|
|
|
363
363
|
| `article_update` | Обновление существующей статьи. **Использование:** Редактирование содержимого, изменение заголовка статьи. **Возвращает:** предопределенные поля обновленной статьи | `articleId` — ID статьи, опционально `summary` — новый заголовок, `content` — новое содержимое, `usesMarkdown`, `returnRendered` |
|
|
364
364
|
| `article_search` | Поиск статей в базе знаний по тексту. **Использование:** Полнотекстовый поиск по заголовкам и содержимому статей, поиск документации. Поддерживает фильтрацию по проекту и родительской статье. **Возвращает:** предопределенные поля - id, idReadable, summary, usesMarkdown, contentPreview (если `returnRendered=true`), parentArticle, project. Поле content не включено для производительности. **Ограничение:** минимум 2 символа в запросе, максимум 200 результатов | `query` — текст для поиска (минимум 2 символа), опционально `projectId` — фильтр по проекту, `parentArticleId` — фильтр по родительской статье, `limit` — максимум результатов (макс 200), `returnRendered` — вернуть отрендеренный preview содержимого |
|
|
365
365
|
| `articles_search` | Полнотекстовый поиск по статьям базы знаний YouTrack по заголовку и содержимому. Возвращает `webUrl` для прямого перехода. | `query`, `limit`, `skip`, опционально `projectId`, `parentArticleId` |
|
|
366
|
-
| `issues_search` | Полнотекстовый поиск по задачам YouTrack по summary, description и комментариям. | `query
|
|
366
|
+
| `issues_search` | Полнотекстовый поиск по задачам YouTrack по summary, description и комментариям. Если `query` не указан или пуст, будут возвращены все задачи. Поддерживает фильтрацию по проектам, исполнителю, автору, статусу и типу. | `query` (необязательный), `limit`, `skip`, `countOnly` (необязательный), `projects` (необязательный), `assignee` (необязательный), `reporter` (необязательный), `state` (необязательный), `type` (необязательный) |
|
|
367
367
|
| `issues_list` | Список задач по фильтрам с поддержкой сортировки. **Использование:** Дашборды, аудит командной загрузки, подготовка батч-операций. Поддерживает фильтрацию по проектам, датам создания/обновления, статусам, типам, исполнителю. **Возвращает:** краткие или полные данные задачи в зависимости от `briefOutput`, а также информацию о сортировке и пагинации. | Фильтры: `projectIds`, `createdAfter/Before`, `updatedAfter/Before`, `statuses`, `assigneeLogin`, `types`; сортировка: `sortField` (`created`/`updated`), `sortDirection` (`asc`/`desc`); пагинация: `limit`, `skip`; формат: `briefOutput` |
|
|
368
368
|
| `issues_count` | Подсчёт задач с теми же фильтрами, что у `issues_list`, с разбивкой по проектам. **Использование:** Быстрая оценка объёмов работ перед загрузкой полного списка, подготовка аналитических отчётов. При запросе одного проекта обращается к `/api/issuesGetter/count`, иначе считает постранично. | Те же фильтры, что и `issues_list`; опциональный `top` ограничивает объём ручной агрегации (при необходимости частичной выборки) |
|
|
369
369
|
|
|
370
|
+
### Лента активности
|
|
371
|
+
|
|
372
|
+
| Tool | Описание | Основные параметры |
|
|
373
|
+
| --- | --- | --- |
|
|
374
|
+
| `users_activity` | Авторо-центричная лента активности на базе `/api/activities`. **Использование:** аудит обновлений коллеги, накопление комментариев/смен состояний по множеству задач, проверка таймлайна выката. Возвращает нормализованные записи с ISO-временем, ссылками на задачи и полями `added`/`removed`. После анализа обязательно повторно запросите затронутые задачи или рабочие элементы, чтобы убедиться в актуальном состоянии. Поддерживаемые категории: `CustomFieldCategory` (изменения полей), `CommentsCategory` (комментарии), `AttachmentsCategory` (файлы), `LinksCategory` (ссылки), `VcsChangeActivityCategory` (VCS изменения), `WorkItemsActivityCategory` (трудозатраты). | `author` *(обязательный)* — логин пользователя (например, `vyt`); `categories` *(обязательный)* — список категорий через запятую из перечня выше; опционально `start` / `end` (ISO-строка, timestamp в мс или `Date`) для ограничения диапазона, `reverse` (boolean) для прямого хронометража, `limit` (по умолчанию 100, макс 200), `skip` (смещение пагинации), `fields` (расширенный перегруз возвращаемых полей). |
|
|
375
|
+
|
|
370
376
|
### Вложения
|
|
371
377
|
|
|
372
378
|
| Tool | Описание | Основные параметры |
|
package/README.md
CHANGED
|
@@ -21,31 +21,36 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
|
|
|
21
21
|
|
|
22
22
|
## Table of Contents
|
|
23
23
|
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
- [
|
|
30
|
-
|
|
31
|
-
- [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- [
|
|
48
|
-
|
|
24
|
+
- [YouTrack MCP Server](#youtrack-mcp-server)
|
|
25
|
+
- [Table of Contents](#table-of-contents)
|
|
26
|
+
- [Requirements](#requirements)
|
|
27
|
+
- [Installation](#installation)
|
|
28
|
+
- [Using npx (Recommended)](#using-npx-recommended)
|
|
29
|
+
- [Using Claude MCP CLI](#using-claude-mcp-cli)
|
|
30
|
+
- [Manual Installation (Development)](#manual-installation-development)
|
|
31
|
+
- [Development \& Release](#development--release)
|
|
32
|
+
- [GitHub Actions Workflows](#github-actions-workflows)
|
|
33
|
+
- [CI Workflow (`.github/workflows/ci.yml`)](#ci-workflow-githubworkflowsciyml)
|
|
34
|
+
- [Publish Workflow (`.github/workflows/publish.yml`)](#publish-workflow-githubworkflowspublishyml)
|
|
35
|
+
- [Setting up NPM\_TOKEN](#setting-up-npm_token)
|
|
36
|
+
- [Release Process](#release-process)
|
|
37
|
+
- [Manual Build \& Test](#manual-build--test)
|
|
38
|
+
- [Running the server (stdio)](#running-the-server-stdio)
|
|
39
|
+
- [Configuration for Code (Recommended)](#configuration-for-code-recommended)
|
|
40
|
+
- [Configuration for Claude Code CLI](#configuration-for-claude-code-cli)
|
|
41
|
+
- [Configuration for VS Code Cline](#configuration-for-vs-code-cline)
|
|
42
|
+
- [MCP Tools](#mcp-tools)
|
|
43
|
+
- [Service](#service)
|
|
44
|
+
- [Issues](#issues)
|
|
45
|
+
- [Issue Links](#issue-links)
|
|
46
|
+
- [Issue Stars](#issue-stars)
|
|
47
|
+
- [Work Items](#work-items)
|
|
48
|
+
- [Users and Projects](#users-and-projects)
|
|
49
|
+
- [Articles](#articles)
|
|
50
|
+
- [Search](#search)
|
|
51
|
+
- [Activity Feed](#activity-feed)
|
|
52
|
+
- [Important Notes](#important-notes)
|
|
53
|
+
- [Destructive Operations](#destructive-operations)
|
|
49
54
|
|
|
50
55
|
## Requirements
|
|
51
56
|
|
|
@@ -282,8 +287,11 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
|
|
|
282
287
|
| Tool | Description | Main Parameters |
|
|
283
288
|
| --- | --- | --- |
|
|
284
289
|
| `issue_lookup` | Brief issue information | `issueId` — issue code (e.g., PROJ-123) |
|
|
290
|
+
| `issues_lookup` | Brief information about multiple issues (batch mode, max 50) | `issueIds[]` — array of issue codes (e.g., ['PROJ-123', 'PROJ-124']), max 50; `briefOutput` — optional boolean (default `true`) |
|
|
285
291
|
| `issue_details` | Issue details with brief/full modes | `issueId` — issue code; `briefOutput` — optional boolean (default `true`). Brief: predefined fields only. Full (`false`): adds `customFields` including `State` |
|
|
292
|
+
| `issues_details` | Detailed information about multiple issues (batch mode, max 50). Brief (default): predefined fields only. Full (`briefOutput=false`): adds `customFields` for each issue | `issueIds[]` — array of issue codes, max 50; `briefOutput` — optional boolean (default `true`) |
|
|
286
293
|
| `issue_comments` | Issue comments | `issueId` — issue code |
|
|
294
|
+
| `issues_comments` | Comments for multiple issues (batch mode, max 50) | `issueIds[]` — array of issue codes, max 50; `briefOutput` — optional boolean (default `true`) |
|
|
287
295
|
| `issue_create` | Create issue | `projectId`, `summary`, optional `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links` (array of link objects) |
|
|
288
296
|
| `issue_update` | Update existing issue | `issueId`, optionally `summary`, `description`, `parentIssueId` (empty string clears parent), `usesMarkdown` |
|
|
289
297
|
| `issue_assign` | Assign issue to user | `issueId`, `assigneeLogin` (login or `me`) |
|
|
@@ -291,7 +299,6 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
|
|
|
291
299
|
| `issue_comment_update` | Update existing comment | `issueId`, `commentId`, optionally `text`, `usesMarkdown`, `muteUpdateNotifications` |
|
|
292
300
|
| `issue_activities` | Get issue change history | `issueId` — issue code, optionally `author` (login), `startDate` (YYYY-MM-DD, timestamp, or Date), `endDate`, `categories` (comma-separated: `CustomFieldCategory` for field changes, `CommentsCategory` for comments, `AttachmentsCategory` for attachments, `LinksCategory` for links, `VcsChangeActivityCategory` for VCS changes, `WorkItemsActivityCategory` for work items), `limit` (max 200), `skip` for pagination. Returns activity items with timestamps (ISO datetime), authors, categories, and change details (added/removed values). Useful for tracking field modifications, reviewing comment history, and analyzing collaboration patterns |
|
|
293
301
|
| `issue_change_state` | Change issue state/status through workflow transitions | `issueId` — issue code, `stateName` — target state name (e.g., 'In Progress', 'Open', 'Fixed', 'Verified'). Case-insensitive. Automatically discovers available transitions and validates requested state change. Returns information about previous state, new state, and transition used. Use for moving issues through workflow states |
|
|
294
|
-
| `issue_search_by_user_activity` | Search issues with user activity | `userLogins[]` — array of user logins, optionally `startDate`, `endDate`, `dateFilterMode` (`issue_updated` fast mode or `user_activity` precise mode), `limit` (default 100, max 200). Finds issues where users updated, mentioned, reported, assigned, or commented. Fast mode filters by issue.updated field; precise mode checks actual user activity dates including comments, mentions, and field changes history (e.g., when user was assignee but later changed). In precise mode, returns `lastActivityDate` field. Sorted by activity time (newest first) |
|
|
295
302
|
| `issue_attachments_list` | Get list of attachments | `issueId` — issue code |
|
|
296
303
|
| `issue_attachment_get` | Get attachment info | `issueId`, `attachmentId` |
|
|
297
304
|
| `issue_attachment_download` | Get download URL for attachment | `issueId`, `attachmentId` — returns signed URL |
|
|
@@ -304,8 +311,8 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
|
|
|
304
311
|
| --- | --- | --- |
|
|
305
312
|
| `issue_links` | List links for an issue (relates to, duplicate, parent/child). Returns link id, direction, linkType, and counterpart issue brief | `issueId` — issue code |
|
|
306
313
|
| `issue_link_types` | List available link types | — |
|
|
307
|
-
| `issue_link_add` | Create a link between two issues | `sourceId`, `targetId`, `linkType` (name or id) |
|
|
308
|
-
| `issue_link_delete` | Delete a link by id for a specific issue | `issueId
|
|
314
|
+
| `issue_link_add` | Create a link between two issues | `sourceId`, `targetId`, `linkType` (name or id), optionally `direction` (`outbound` or `inbound`) |
|
|
315
|
+
| `issue_link_delete` | Delete a link by id for a specific issue | `issueId` — issue code, `linkId` — link id to delete |
|
|
309
316
|
|
|
310
317
|
### Issue Stars
|
|
311
318
|
|
|
@@ -355,13 +362,20 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
|
|
|
355
362
|
| `article_update` | Update article | `articleId`, optionally `summary`, `content`, `usesMarkdown`, `returnRendered` |
|
|
356
363
|
| `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered` |
|
|
357
364
|
| `articles_search` | Full-text search across YouTrack knowledge base articles by title and content. Returns `webUrl` for direct access. | `query`, `limit`, `skip`, optionally `projectId`, `parentArticleId` |
|
|
358
|
-
| `issues_search` | Full-text search across YouTrack issues by summary, description, and comments. | `query
|
|
365
|
+
| `issues_search` | Full-text search across YouTrack issues by summary, description, and comments. If `query` is not provided or empty, all issues will be returned. Supports filtering by projects, assignee, reporter, state, and type. | `query` (optional), `limit`, `skip`, `countOnly` (optional), `projects` (optional), `assignee` (optional), `reporter` (optional), `state` (optional), `type` (optional) |
|
|
359
366
|
|
|
360
367
|
### Search
|
|
361
368
|
|
|
362
369
|
| Tool | Description | Main Parameters |
|
|
363
370
|
| --- | --- | --- |
|
|
364
|
-
| `
|
|
371
|
+
| `articles_search` | Full-text search across YouTrack knowledge base articles by title and content. Returns `webUrl` for direct access | `query` (min 2 chars), `limit` (default 50, max 200), `skip`, optionally `projectId`, `parentArticleId` |
|
|
372
|
+
| `issues_search` | Full-text search across YouTrack issues by summary, description, and comments. If `query` is not provided or empty, all issues will be returned. Supports filtering by projects, assignee, reporter, state, and type | `query` (optional), `limit` (default 50, max 200), `skip`, `countOnly` (optional), `projects` (optional), `assignee` (optional), `reporter` (optional), `state` (optional), `type` (optional) |
|
|
373
|
+
|
|
374
|
+
### Activity Feed
|
|
375
|
+
|
|
376
|
+
| Tool | Description | Main Parameters |
|
|
377
|
+
| --- | --- | --- |
|
|
378
|
+
| `users_activity` | Author-centric activity feed backed by `/api/activities`. **Use for:** auditing a teammate's updates, gathering comment/state changes across many issues, and reviewing deployment timelines. Returns normalized entries with ISO timestamps, optional issue references, and `added`/`removed` payloads. Always follow up by re-fetching the affected issue or work-item to confirm the latest state. Supported categories: `CustomFieldCategory` (field changes), `CommentsCategory` (comments), `AttachmentsCategory` (file events), `LinksCategory` (issue links), `VcsChangeActivityCategory` (VCS changes), `WorkItemsActivityCategory` (work items). | `author` *(required)* — login such as `vyt`; `categories` *(required)* — comma-separated list built from the supported categories above; optional `start` / `end` (ISO string, timestamp in ms, or `Date`) to bound the range, `reverse` (boolean) to switch to chronological order, `limit` (default 100, max 200), `skip` (pagination offset), `fields` (advanced override of response fields). |
|
|
365
379
|
| `issues_list` | List issues across projects with filtering and sorting | Filters: `projectIds`, `createdAfter/Before`, `updatedAfter/Before`, `statuses`, `assigneeLogin`, `types`; Sorting: `sortField`, `sortDirection`; Pagination: `limit`, `skip`; Output mode: `briefOutput` |
|
|
366
380
|
| `issues_count` | Count issues using same filters as `issues_list`, returns per-project breakdown | Same filters as above, optional `top` to cap manual aggregation when many projects are involved |
|
|
367
381
|
|
package/dist/package.json
CHANGED
package/dist/src/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerProjectTools } from "./tools/project-tools.js";
|
|
|
12
12
|
import { registerAttachmentTools } from "./tools/attachment-tools.js";
|
|
13
13
|
import { registerIssueStarTools } from "./tools/issue-star-tools.js";
|
|
14
14
|
import { registerIssueLinkTools } from "./tools/issue-link-tools.js";
|
|
15
|
+
import { usersActivityArgs, usersActivityHandler } from "./tools/users-activity-tools.js";
|
|
15
16
|
import { YoutrackClient } from "./youtrack-client.js";
|
|
16
17
|
import { loadConfig } from "./config.js";
|
|
17
18
|
import { initializeTimezone } from "./utils/date.js";
|
|
@@ -46,6 +47,7 @@ export class YoutrackServer {
|
|
|
46
47
|
registerArticleTools(this.server, this.client);
|
|
47
48
|
// Removed redundant registerArticleSearchTools call (articles_search tool registered manually above)
|
|
48
49
|
registerUserTools(this.server, this.client);
|
|
50
|
+
this.server.tool("users_activity", "Author-centric activity feed backed by /api/activities. Use for: auditing a teammate's updates, gathering comment/state changes across many issues, and reviewing deployment timelines. Always re-fetch affected entities to confirm final state.", usersActivityArgs, async (args) => usersActivityHandler(this.client, args));
|
|
49
51
|
registerProjectTools(this.server, this.client);
|
|
50
52
|
registerAttachmentTools(this.server, this.client);
|
|
51
53
|
registerIssueStarTools(this.server, this.client);
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
3
|
export declare const issuesSearchArgs: {
|
|
4
|
-
query: z.ZodString
|
|
4
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
5
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
6
6
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
7
|
+
projects: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
assignee: z.ZodOptional<z.ZodString>;
|
|
9
|
+
reporter: z.ZodOptional<z.ZodString>;
|
|
10
|
+
state: z.ZodOptional<z.ZodString>;
|
|
11
|
+
type: z.ZodOptional<z.ZodString>;
|
|
7
12
|
};
|
|
8
|
-
export declare const issuesSearchSchema: z.ZodObject<{
|
|
9
|
-
query: z.ZodString
|
|
13
|
+
export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
|
|
14
|
+
query: z.ZodOptional<z.ZodString>;
|
|
10
15
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
11
16
|
skip: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
projects: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
+
assignee: z.ZodOptional<z.ZodString>;
|
|
19
|
+
reporter: z.ZodOptional<z.ZodString>;
|
|
20
|
+
state: z.ZodOptional<z.ZodString>;
|
|
21
|
+
type: z.ZodOptional<z.ZodString>;
|
|
12
22
|
}, "strip", z.ZodTypeAny, {
|
|
13
|
-
query: string;
|
|
14
23
|
skip: number;
|
|
15
24
|
limit: number;
|
|
25
|
+
reporter?: string | undefined;
|
|
26
|
+
assignee?: string | undefined;
|
|
27
|
+
type?: string | undefined;
|
|
28
|
+
query?: string | undefined;
|
|
29
|
+
projects?: string[] | undefined;
|
|
30
|
+
state?: string | undefined;
|
|
16
31
|
}, {
|
|
17
|
-
|
|
32
|
+
reporter?: string | undefined;
|
|
33
|
+
assignee?: string | undefined;
|
|
34
|
+
type?: string | undefined;
|
|
35
|
+
query?: string | undefined;
|
|
18
36
|
skip?: number | undefined;
|
|
37
|
+
projects?: string[] | undefined;
|
|
19
38
|
limit?: number | undefined;
|
|
20
|
-
|
|
39
|
+
state?: string | undefined;
|
|
40
|
+
}>>;
|
|
21
41
|
export declare function issuesSearchHandler(client: YoutrackClient, rawInput: unknown): Promise<{
|
|
22
42
|
[x: string]: unknown;
|
|
23
43
|
content: ({
|
|
@@ -1,21 +1,88 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { toolSuccess, toolError } from "../utils/tool-response.js";
|
|
3
3
|
export const issuesSearchArgs = {
|
|
4
|
-
query: z
|
|
4
|
+
query: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe("Search string for issues (e.g., 'login error'). If not provided or empty, all issues will be returned. You can also use YouTrack Query Language (e.g., 'State: Open', 'Type: Bug')"),
|
|
5
8
|
limit: z.number().int().positive().max(200).default(50).describe("Max results per page"),
|
|
6
9
|
skip: z.number().int().nonnegative().default(0).describe("Offset for pagination"),
|
|
10
|
+
projects: z.array(z.string().min(1)).optional().describe("Filter by project short names (e.g., ['PROJ', 'TEST'])"),
|
|
11
|
+
assignee: z.string().optional().describe("Filter by assignee login (e.g., 'john.doe' or 'me')"),
|
|
12
|
+
reporter: z.string().optional().describe("Filter by reporter/author login (e.g., 'john.doe' or 'me')"),
|
|
13
|
+
state: z.string().optional().describe("Filter by state/status (e.g., 'Open', 'In Progress', 'Fixed')"),
|
|
14
|
+
type: z.string().optional().describe("Filter by issue type (e.g., 'Bug', 'Feature', 'Task')"),
|
|
7
15
|
};
|
|
8
|
-
export const issuesSearchSchema = z
|
|
16
|
+
export const issuesSearchSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
query: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Search string for issues (e.g., 'login error'). If not provided or empty, all issues will be returned. You can also use YouTrack Query Language (e.g., 'State: Open', 'Type: Bug')"),
|
|
22
|
+
limit: z.number().int().positive().max(200).default(50).describe("Max results per page"),
|
|
23
|
+
skip: z.number().int().nonnegative().default(0).describe("Offset for pagination"),
|
|
24
|
+
projects: z.array(z.string().min(1)).optional().describe("Filter by project short names (e.g., ['PROJ', 'TEST'])"),
|
|
25
|
+
assignee: z.string().optional().describe("Filter by assignee login (e.g., 'john.doe' or 'me')"),
|
|
26
|
+
reporter: z.string().optional().describe("Filter by reporter/author login (e.g., 'john.doe' or 'me')"),
|
|
27
|
+
state: z.string().optional().describe("Filter by state/status (e.g., 'Open', 'In Progress', 'Fixed')"),
|
|
28
|
+
type: z.string().optional().describe("Filter by issue type (e.g., 'Bug', 'Feature', 'Task')"),
|
|
29
|
+
})
|
|
30
|
+
.default({});
|
|
9
31
|
export async function issuesSearchHandler(client, rawInput) {
|
|
10
32
|
const input = issuesSearchSchema.parse(rawInput);
|
|
11
33
|
try {
|
|
34
|
+
// Build query parameters
|
|
35
|
+
let { query = "" } = input;
|
|
36
|
+
const { limit = 50, skip = 0 } = input;
|
|
37
|
+
// Collect all filters
|
|
38
|
+
const filters = [];
|
|
39
|
+
// Add project filter
|
|
40
|
+
if (input.projects && input.projects.length > 0) {
|
|
41
|
+
const projectFilter = input.projects.map((p) => `project: ${p}`).join(" or ");
|
|
42
|
+
filters.push(`(${projectFilter})`);
|
|
43
|
+
}
|
|
44
|
+
// Add assignee filter
|
|
45
|
+
if (input.assignee) {
|
|
46
|
+
filters.push(`assignee: ${input.assignee}`);
|
|
47
|
+
}
|
|
48
|
+
// Add reporter filter
|
|
49
|
+
if (input.reporter) {
|
|
50
|
+
filters.push(`reporter: ${input.reporter}`);
|
|
51
|
+
}
|
|
52
|
+
// Add state filter
|
|
53
|
+
if (input.state) {
|
|
54
|
+
filters.push(`State: {${input.state}}`);
|
|
55
|
+
}
|
|
56
|
+
// Add type filter
|
|
57
|
+
if (input.type) {
|
|
58
|
+
filters.push(`Type: {${input.type}}`);
|
|
59
|
+
}
|
|
60
|
+
// Combine query with filters
|
|
61
|
+
if (filters.length > 0) {
|
|
62
|
+
const combinedFilters = filters.join(" and ");
|
|
63
|
+
query = query ? `(${query}) and ${combinedFilters}` : combinedFilters;
|
|
64
|
+
}
|
|
65
|
+
// Fetch issues
|
|
12
66
|
const data = await client["getWithFlexibleTop"]("/api/issues", {
|
|
13
|
-
query
|
|
14
|
-
$top:
|
|
15
|
-
$skip:
|
|
67
|
+
query,
|
|
68
|
+
$top: limit,
|
|
69
|
+
$skip: skip,
|
|
16
70
|
fields: "id,idReadable,summary,project(shortName,name),assignee(name,login)",
|
|
17
71
|
});
|
|
18
|
-
|
|
72
|
+
// Count logic
|
|
73
|
+
const allIssues = Array.isArray(data) ? data : [];
|
|
74
|
+
const total = allIssues.length;
|
|
75
|
+
const byProject = {};
|
|
76
|
+
for (const issue of allIssues) {
|
|
77
|
+
const project = issue.project?.shortName ?? "UNKNOWN";
|
|
78
|
+
byProject[project] = (byProject[project] ?? 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
const result = {
|
|
81
|
+
total,
|
|
82
|
+
byProject: Object.entries(byProject).map(([project, count]) => ({ project, count })),
|
|
83
|
+
items: allIssues,
|
|
84
|
+
};
|
|
85
|
+
return toolSuccess(result);
|
|
19
86
|
}
|
|
20
87
|
catch (error) {
|
|
21
88
|
return toolError(error);
|
|
@@ -92,6 +92,18 @@ const issueChangeStateArgs = {
|
|
|
92
92
|
.describe("Target state name (e.g., 'In Progress', 'Open', 'Fixed', 'Verified'). Case-insensitive."),
|
|
93
93
|
};
|
|
94
94
|
const issueChangeStateSchema = z.object(issueChangeStateArgs);
|
|
95
|
+
const issuesCountArgs = {
|
|
96
|
+
projectIds: z.array(z.string().min(1)).optional().describe("Filter by project IDs or short names"),
|
|
97
|
+
createdAfter: z.string().optional().describe("Filter by creation date (YYYY-MM-DD or timestamp)"),
|
|
98
|
+
createdBefore: z.string().optional().describe("Filter by creation date (YYYY-MM-DD or timestamp)"),
|
|
99
|
+
updatedAfter: z.string().optional().describe("Filter by update date (YYYY-MM-DD or timestamp)"),
|
|
100
|
+
updatedBefore: z.string().optional().describe("Filter by update date (YYYY-MM-DD or timestamp)"),
|
|
101
|
+
statuses: z.array(z.string().min(1)).optional().describe("Filter by state names"),
|
|
102
|
+
types: z.array(z.string().min(1)).optional().describe("Filter by issue types"),
|
|
103
|
+
assigneeLogin: z.string().optional().describe("Filter by assignee login"),
|
|
104
|
+
top: z.number().int().positive().optional().describe("Maximum number of issues to count (optional limit)"),
|
|
105
|
+
};
|
|
106
|
+
const issuesCountSchema = z.object(issuesCountArgs);
|
|
95
107
|
export function registerIssueTools(server, client) {
|
|
96
108
|
server.tool("issue_lookup", "Get brief information about YouTrack issue. Note: Returns predefined fields only - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name). Custom fields are not included.", issueIdArgs, async (rawInput) => {
|
|
97
109
|
try {
|
|
@@ -278,5 +290,17 @@ export function registerIssueTools(server, client) {
|
|
|
278
290
|
return errorResponse;
|
|
279
291
|
}
|
|
280
292
|
});
|
|
293
|
+
server.tool("issues_count", "Count YouTrack issues with optional filters. Returns total count and breakdown by projects. Use for: Getting accurate issue counts without pagination limits, analyzing issue distribution across projects.", issuesCountArgs, async (rawInput) => {
|
|
294
|
+
try {
|
|
295
|
+
const payload = issuesCountSchema.parse(rawInput);
|
|
296
|
+
const result = await client.countIssues(payload);
|
|
297
|
+
const response = toolSuccess(result);
|
|
298
|
+
return response;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
const errorResponse = toolError(error);
|
|
302
|
+
return errorResponse;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
281
305
|
}
|
|
282
306
|
//# sourceMappingURL=issue-tools.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { YoutrackClient } from "../youtrack-client.js";
|
|
3
|
+
export declare const usersActivityArgs: {
|
|
4
|
+
author: z.ZodString;
|
|
5
|
+
start: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodDate]>>;
|
|
6
|
+
end: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodDate]>>;
|
|
7
|
+
categories: z.ZodString;
|
|
8
|
+
reverse: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
skip: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
fields: z.ZodOptional<z.ZodString>;
|
|
12
|
+
};
|
|
13
|
+
export declare const usersActivitySchema: z.ZodObject<{
|
|
14
|
+
author: z.ZodString;
|
|
15
|
+
start: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodDate]>>;
|
|
16
|
+
end: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodDate]>>;
|
|
17
|
+
categories: z.ZodString;
|
|
18
|
+
reverse: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
skip: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
fields: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
author: string;
|
|
24
|
+
skip: number;
|
|
25
|
+
categories: string;
|
|
26
|
+
limit: number;
|
|
27
|
+
reverse?: boolean | undefined;
|
|
28
|
+
fields?: string | undefined;
|
|
29
|
+
start?: string | number | Date | undefined;
|
|
30
|
+
end?: string | number | Date | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
author: string;
|
|
33
|
+
categories: string;
|
|
34
|
+
reverse?: boolean | undefined;
|
|
35
|
+
skip?: number | undefined;
|
|
36
|
+
fields?: string | undefined;
|
|
37
|
+
start?: string | number | Date | undefined;
|
|
38
|
+
end?: string | number | Date | undefined;
|
|
39
|
+
limit?: number | undefined;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function usersActivityHandler(client: YoutrackClient, rawInput: unknown): Promise<{
|
|
42
|
+
[x: string]: unknown;
|
|
43
|
+
content: ({
|
|
44
|
+
[x: string]: unknown;
|
|
45
|
+
text: string;
|
|
46
|
+
type: "text";
|
|
47
|
+
_meta?: {
|
|
48
|
+
[x: string]: unknown;
|
|
49
|
+
} | undefined;
|
|
50
|
+
} | {
|
|
51
|
+
[x: string]: unknown;
|
|
52
|
+
type: "image";
|
|
53
|
+
data: string;
|
|
54
|
+
mimeType: string;
|
|
55
|
+
_meta?: {
|
|
56
|
+
[x: string]: unknown;
|
|
57
|
+
} | undefined;
|
|
58
|
+
} | {
|
|
59
|
+
[x: string]: unknown;
|
|
60
|
+
type: "audio";
|
|
61
|
+
data: string;
|
|
62
|
+
mimeType: string;
|
|
63
|
+
_meta?: {
|
|
64
|
+
[x: string]: unknown;
|
|
65
|
+
} | undefined;
|
|
66
|
+
} | {
|
|
67
|
+
[x: string]: unknown;
|
|
68
|
+
name: string;
|
|
69
|
+
type: "resource_link";
|
|
70
|
+
uri: string;
|
|
71
|
+
description?: string | undefined;
|
|
72
|
+
_meta?: {
|
|
73
|
+
[x: string]: unknown;
|
|
74
|
+
} | undefined;
|
|
75
|
+
mimeType?: string | undefined;
|
|
76
|
+
title?: string | undefined;
|
|
77
|
+
icons?: {
|
|
78
|
+
[x: string]: unknown;
|
|
79
|
+
src: string;
|
|
80
|
+
mimeType?: string | undefined;
|
|
81
|
+
sizes?: string[] | undefined;
|
|
82
|
+
}[] | undefined;
|
|
83
|
+
} | {
|
|
84
|
+
[x: string]: unknown;
|
|
85
|
+
type: "resource";
|
|
86
|
+
resource: {
|
|
87
|
+
[x: string]: unknown;
|
|
88
|
+
text: string;
|
|
89
|
+
uri: string;
|
|
90
|
+
_meta?: {
|
|
91
|
+
[x: string]: unknown;
|
|
92
|
+
} | undefined;
|
|
93
|
+
mimeType?: string | undefined;
|
|
94
|
+
} | {
|
|
95
|
+
[x: string]: unknown;
|
|
96
|
+
uri: string;
|
|
97
|
+
blob: string;
|
|
98
|
+
_meta?: {
|
|
99
|
+
[x: string]: unknown;
|
|
100
|
+
} | undefined;
|
|
101
|
+
mimeType?: string | undefined;
|
|
102
|
+
};
|
|
103
|
+
_meta?: {
|
|
104
|
+
[x: string]: unknown;
|
|
105
|
+
} | undefined;
|
|
106
|
+
})[];
|
|
107
|
+
_meta?: {
|
|
108
|
+
[x: string]: unknown;
|
|
109
|
+
} | undefined;
|
|
110
|
+
structuredContent?: {
|
|
111
|
+
[x: string]: unknown;
|
|
112
|
+
} | undefined;
|
|
113
|
+
isError?: boolean | undefined;
|
|
114
|
+
}>;
|
|
115
|
+
//# sourceMappingURL=users-activity-tools.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { parseDateInput, toIsoDateString, unixMsToDate, getCurrentDate } from "../utils/date.js";
|
|
3
|
+
import { mapActivityItems } from "../utils/mappers.js";
|
|
4
|
+
import { toolSuccess, toolError } from "../utils/tool-response.js";
|
|
5
|
+
export const usersActivityArgs = {
|
|
6
|
+
author: z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.describe("Filter by author login (required, e.g., 'vyt'). Matches activities created by this user."),
|
|
10
|
+
start: z
|
|
11
|
+
.union([z.string(), z.number(), z.date()])
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Inclusive start of the interval. Accepts ISO string, unix timestamp (ms), or Date object. Converted to unix ms for /api/activities (default: no lower bound)."),
|
|
14
|
+
end: z
|
|
15
|
+
.union([z.string(), z.number(), z.date()])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Inclusive end of the interval. Accepts ISO string, unix timestamp (ms), or Date object. Converted to unix ms for /api/activities (default: now)."),
|
|
18
|
+
categories: z
|
|
19
|
+
.string()
|
|
20
|
+
.describe("Comma-separated list of activity categories. Supported values: 'CustomFieldCategory', 'CommentsCategory', 'AttachmentsCategory', 'LinksCategory', 'VcsChangeActivityCategory', 'WorkItemsActivityCategory'."),
|
|
21
|
+
reverse: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("When true, return activities in ascending order (oldest first). Default is false (newest first)."),
|
|
25
|
+
limit: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.positive()
|
|
29
|
+
.max(200)
|
|
30
|
+
.default(100)
|
|
31
|
+
.describe("Maximum number of activities to return (default 100, max 200)."),
|
|
32
|
+
skip: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.nonnegative()
|
|
36
|
+
.default(0)
|
|
37
|
+
.describe("Number of activities to skip for pagination (default 0)."),
|
|
38
|
+
fields: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Override fields parameter passed to /api/activities (advanced). Defaults to issue idReadable, author, added/removed, category, timestamps."),
|
|
42
|
+
};
|
|
43
|
+
export const usersActivitySchema = z.object(usersActivityArgs);
|
|
44
|
+
export async function usersActivityHandler(client, rawInput) {
|
|
45
|
+
try {
|
|
46
|
+
const input = usersActivitySchema.parse(rawInput);
|
|
47
|
+
const startMs = parseDateInput(input.start ?? unixMsToDate(0));
|
|
48
|
+
const endMs = parseDateInput(input.end ?? getCurrentDate());
|
|
49
|
+
if (startMs > endMs) {
|
|
50
|
+
throw new Error("'start' must be earlier than or equal to 'end'.");
|
|
51
|
+
}
|
|
52
|
+
const activities = await client.listActivities({
|
|
53
|
+
author: input.author,
|
|
54
|
+
categories: input.categories,
|
|
55
|
+
start: startMs,
|
|
56
|
+
end: endMs,
|
|
57
|
+
limit: input.limit,
|
|
58
|
+
skip: input.skip,
|
|
59
|
+
fields: input.fields,
|
|
60
|
+
reverse: input.reverse,
|
|
61
|
+
});
|
|
62
|
+
const mappedActivities = mapActivityItems(activities);
|
|
63
|
+
const payload = {
|
|
64
|
+
activities: mappedActivities,
|
|
65
|
+
filters: {
|
|
66
|
+
author: input.author,
|
|
67
|
+
start: toIsoDateString(input.start ?? unixMsToDate(startMs)),
|
|
68
|
+
end: toIsoDateString(input.end ?? unixMsToDate(endMs)),
|
|
69
|
+
categories: input.categories,
|
|
70
|
+
reverse: input.reverse,
|
|
71
|
+
},
|
|
72
|
+
pagination: {
|
|
73
|
+
returned: mappedActivities.length,
|
|
74
|
+
limit: input.limit,
|
|
75
|
+
skip: input.skip,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
return toolSuccess(payload);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return toolError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=users-activity-tools.js.map
|
package/dist/src/utils/date.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
1
2
|
export declare function initializeTimezone(timezone: string): void;
|
|
2
3
|
export declare function getTimezone(): string;
|
|
3
4
|
export declare function parseDateInput(value: string | number | Date): number;
|
|
@@ -31,4 +32,5 @@ export declare function filterWorkingDates(dates: Date[], excludeWeekends?: bool
|
|
|
31
32
|
export declare function isSameDay(left: string | number | Date, right: string | number | Date): boolean;
|
|
32
33
|
export declare function getCurrentTimestamp(): number;
|
|
33
34
|
export declare function getCurrentDate(timezone?: string): Date;
|
|
35
|
+
export declare function getCurrentDateTime(timezone?: string): DateTime;
|
|
34
36
|
//# sourceMappingURL=date.d.ts.map
|
package/dist/src/utils/date.js
CHANGED
|
@@ -144,4 +144,7 @@ export function getCurrentTimestamp() {
|
|
|
144
144
|
export function getCurrentDate(timezone = currentTimezone) {
|
|
145
145
|
return DateTime.now().setZone(timezone).toJSDate();
|
|
146
146
|
}
|
|
147
|
+
export function getCurrentDateTime(timezone = currentTimezone) {
|
|
148
|
+
return DateTime.now().setZone(timezone);
|
|
149
|
+
}
|
|
147
150
|
//# sourceMappingURL=date.js.map
|
|
@@ -48,7 +48,6 @@ export declare function mapIssue(issue: YoutrackIssue): MappedYoutrackIssue;
|
|
|
48
48
|
export declare function mapIssueDetails(issue: YoutrackIssueDetails): MappedYoutrackIssueDetails;
|
|
49
49
|
/**
|
|
50
50
|
* Map YoutrackIssue to brief version (without description fields)
|
|
51
|
-
* Used for reducing payload size in issue_search_by_user_activity tool
|
|
52
51
|
*/
|
|
53
52
|
export declare function mapIssueBrief(issue: YoutrackIssue): MappedYoutrackIssue;
|
|
54
53
|
/**
|
|
@@ -50,7 +50,6 @@ export function mapIssueDetails(issue) {
|
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* Map YoutrackIssue to brief version (without description fields)
|
|
53
|
-
* Used for reducing payload size in issue_search_by_user_activity tool
|
|
54
53
|
*/
|
|
55
54
|
export function mapIssueBrief(issue) {
|
|
56
55
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -53,6 +53,16 @@ export declare class YoutrackClient {
|
|
|
53
53
|
startDate?: number;
|
|
54
54
|
endDate?: number;
|
|
55
55
|
}): Promise<YoutrackActivityItem[]>;
|
|
56
|
+
listActivities({ author, categories, start, end, limit, skip, fields, reverse, }: {
|
|
57
|
+
author: string;
|
|
58
|
+
categories?: string;
|
|
59
|
+
start?: number;
|
|
60
|
+
end?: number;
|
|
61
|
+
limit?: number;
|
|
62
|
+
skip?: number;
|
|
63
|
+
fields?: string;
|
|
64
|
+
reverse?: boolean;
|
|
65
|
+
}): Promise<YoutrackActivityItem[]>;
|
|
56
66
|
createIssue(input: YoutrackIssueCreateInput): Promise<IssueLookupPayload>;
|
|
57
67
|
updateIssue(input: YoutrackIssueUpdateInput): Promise<IssueLookupPayload>;
|
|
58
68
|
assignIssue(input: YoutrackIssueAssignInput): Promise<IssueLookupPayload>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import FormData from "form-data";
|
|
3
3
|
import fs from "fs";
|
|
4
|
+
import { DateTime } from "luxon";
|
|
4
5
|
import { MutexPool } from "@vitalyostanin/mutex-pool";
|
|
5
|
-
import { calculateTotalMinutes, enumerateDateRange, filterWorkingDays, getDayBounds, groupWorkItemsByDate, isWeekend, minutesToHours, parseDateInput, toIsoDateString, validateDateRange, } from "./utils/date.js";
|
|
6
|
+
import { calculateTotalMinutes, enumerateDateRange, filterWorkingDays, getCurrentDateTime, getCurrentTimestamp, getDayBounds, groupWorkItemsByDate, isWeekend, minutesToHours, parseDateInput, toIsoDateString, validateDateRange, } from "./utils/date.js";
|
|
6
7
|
import { mapAttachment, mapAttachments, mapComment, mapComments, mapIssue, mapIssueBrief, mapIssueDetails, mapWorkItem, mapWorkItems, } from "./utils/mappers.js";
|
|
7
8
|
import { buildIssueQuery } from "./utils/issue-query.js";
|
|
8
9
|
const DEFAULT_PAGE_SIZE = 200;
|
|
@@ -120,13 +121,15 @@ export class YoutrackClient {
|
|
|
120
121
|
Object.prototype.hasOwnProperty.call(params, "skip");
|
|
121
122
|
// First attempt: prefer $top/$skip
|
|
122
123
|
const dollarParams = { ...params };
|
|
123
|
-
if (Object.prototype.hasOwnProperty.call(dollarParams, "top") &&
|
|
124
|
-
(dollarParams)
|
|
125
|
-
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(dollarParams, "top") &&
|
|
125
|
+
!Object.prototype.hasOwnProperty.call(dollarParams, "$top")) {
|
|
126
|
+
dollarParams.$top = dollarParams.top;
|
|
127
|
+
delete dollarParams.top;
|
|
126
128
|
}
|
|
127
|
-
if (Object.prototype.hasOwnProperty.call(dollarParams, "skip") &&
|
|
128
|
-
(dollarParams)
|
|
129
|
-
|
|
129
|
+
if (Object.prototype.hasOwnProperty.call(dollarParams, "skip") &&
|
|
130
|
+
!Object.prototype.hasOwnProperty.call(dollarParams, "$skip")) {
|
|
131
|
+
dollarParams.$skip = dollarParams.skip;
|
|
132
|
+
delete dollarParams.skip;
|
|
130
133
|
}
|
|
131
134
|
try {
|
|
132
135
|
const res = await this.http.get(url, { params: dollarParams });
|
|
@@ -137,12 +140,12 @@ export class YoutrackClient {
|
|
|
137
140
|
// Retry with plain top/skip
|
|
138
141
|
const plainParams = { ...params };
|
|
139
142
|
if (Object.prototype.hasOwnProperty.call(plainParams, "$top")) {
|
|
140
|
-
|
|
141
|
-
delete
|
|
143
|
+
plainParams.top = plainParams.$top;
|
|
144
|
+
delete plainParams.$top;
|
|
142
145
|
}
|
|
143
146
|
if (Object.prototype.hasOwnProperty.call(plainParams, "$skip")) {
|
|
144
|
-
|
|
145
|
-
delete
|
|
147
|
+
plainParams.skip = plainParams.$skip;
|
|
148
|
+
delete plainParams.$skip;
|
|
146
149
|
}
|
|
147
150
|
const res2 = await this.http.get(url, { params: plainParams });
|
|
148
151
|
return res2.data;
|
|
@@ -608,6 +611,34 @@ export class YoutrackClient {
|
|
|
608
611
|
throw this.normalizeError(error);
|
|
609
612
|
}
|
|
610
613
|
}
|
|
614
|
+
async listActivities({ author, categories, start, end, limit, skip, fields, reverse, }) {
|
|
615
|
+
const params = {
|
|
616
|
+
author,
|
|
617
|
+
fields: fields ??
|
|
618
|
+
"id,timestamp,author(id,login,name),category(id),target(issue(idReadable,summary),text),added(name,id,login),removed(name,id,login),$type",
|
|
619
|
+
};
|
|
620
|
+
if (categories) {
|
|
621
|
+
params.categories = categories;
|
|
622
|
+
}
|
|
623
|
+
if (typeof start === "number") {
|
|
624
|
+
params.start = start;
|
|
625
|
+
}
|
|
626
|
+
if (typeof end === "number") {
|
|
627
|
+
params.end = end;
|
|
628
|
+
}
|
|
629
|
+
params.$top = Math.min(limit ?? DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE);
|
|
630
|
+
params.$skip = skip ?? 0;
|
|
631
|
+
if (reverse !== undefined) {
|
|
632
|
+
params.reverse = reverse;
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
const response = await this.http.get("/api/activities", { params });
|
|
636
|
+
return response.data;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
throw this.normalizeError(error);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
611
642
|
async createIssue(input) {
|
|
612
643
|
const projectIdentifier = input.projectId ?? this.defaultProject;
|
|
613
644
|
if (!projectIdentifier) {
|
|
@@ -909,7 +940,7 @@ export class YoutrackClient {
|
|
|
909
940
|
// Add date range filter if provided
|
|
910
941
|
if (input.startDate || input.endDate) {
|
|
911
942
|
const startDateStr = input.startDate ? toIsoDateString(input.startDate) : "1970-01-01";
|
|
912
|
-
const endDateStr = input.endDate ? toIsoDateString(input.endDate) :
|
|
943
|
+
const endDateStr = input.endDate ? toIsoDateString(input.endDate) : getCurrentDateTime().toISO();
|
|
913
944
|
filters.push(`updated: ${startDateStr} .. ${endDateStr}`);
|
|
914
945
|
}
|
|
915
946
|
// Add sorting by updated time descending
|
|
@@ -952,7 +983,7 @@ export class YoutrackClient {
|
|
|
952
983
|
// and to reduce candidate set before precise post-filtering.
|
|
953
984
|
if (input.startDate || input.endDate) {
|
|
954
985
|
const startDateStr = input.startDate ? toIsoDateString(input.startDate) : "1970-01-01";
|
|
955
|
-
const endDateStr = input.endDate ? toIsoDateString(input.endDate) :
|
|
986
|
+
const endDateStr = input.endDate ? toIsoDateString(input.endDate) : getCurrentDateTime().toISO();
|
|
956
987
|
filters.push(`updated: ${startDateStr} .. ${endDateStr}`);
|
|
957
988
|
}
|
|
958
989
|
const sortPart = "sort by: updated desc";
|
|
@@ -1057,7 +1088,10 @@ export class YoutrackClient {
|
|
|
1057
1088
|
const limit = input.limit ?? 100;
|
|
1058
1089
|
const paginatedIssues = issuesWithActivity.slice(skip, skip + limit);
|
|
1059
1090
|
const mapperFn = briefOutput ? mapIssueBrief : mapIssue;
|
|
1060
|
-
const resultIssues = paginatedIssues.map(({ issue, lastActivityDate }) => ({
|
|
1091
|
+
const resultIssues = paginatedIssues.map(({ issue, lastActivityDate }) => ({
|
|
1092
|
+
...mapperFn(issue),
|
|
1093
|
+
lastActivityDate,
|
|
1094
|
+
}));
|
|
1061
1095
|
return {
|
|
1062
1096
|
issues: resultIssues,
|
|
1063
1097
|
userLogins: input.userLogins,
|
|
@@ -1085,8 +1119,8 @@ export class YoutrackClient {
|
|
|
1085
1119
|
* Used in user_activity mode to determine which issues had activity from specified users
|
|
1086
1120
|
*/
|
|
1087
1121
|
filterIssuesByUserActivity(candidateIssues, detailsLight, commentsLight, activitiesResults, input) {
|
|
1088
|
-
const startTimestamp = input.startDate ?
|
|
1089
|
-
const endTimestamp = input.endDate ?
|
|
1122
|
+
const startTimestamp = input.startDate ? parseDateInput(input.startDate) : 0;
|
|
1123
|
+
const endTimestamp = input.endDate ? parseDateInput(input.endDate) : getCurrentTimestamp();
|
|
1090
1124
|
const issuesWithActivity = [];
|
|
1091
1125
|
for (let i = 0; i < candidateIssues.length; i++) {
|
|
1092
1126
|
const issue = candidateIssues[i];
|
|
@@ -1137,7 +1171,7 @@ export class YoutrackClient {
|
|
|
1137
1171
|
}
|
|
1138
1172
|
if (dates.length > 0) {
|
|
1139
1173
|
const lastActivityTimestamp = Math.max(...dates);
|
|
1140
|
-
const lastActivityDate =
|
|
1174
|
+
const lastActivityDate = DateTime.fromMillis(lastActivityTimestamp).toISO() ?? "";
|
|
1141
1175
|
issuesWithActivity.push({
|
|
1142
1176
|
issue,
|
|
1143
1177
|
lastActivityDate,
|
|
@@ -1145,7 +1179,7 @@ export class YoutrackClient {
|
|
|
1145
1179
|
}
|
|
1146
1180
|
}
|
|
1147
1181
|
// Sort by lastActivityDate descending
|
|
1148
|
-
issuesWithActivity.sort((a, b) =>
|
|
1182
|
+
issuesWithActivity.sort((a, b) => (a.lastActivityDate < b.lastActivityDate ? 1 : -1));
|
|
1149
1183
|
return issuesWithActivity;
|
|
1150
1184
|
}
|
|
1151
1185
|
// Helper to perform fallback request without braces in user filters
|
|
@@ -1157,7 +1191,7 @@ export class YoutrackClient {
|
|
|
1157
1191
|
}
|
|
1158
1192
|
if (input.startDate || input.endDate) {
|
|
1159
1193
|
const startDateStr = input.startDate ? toIsoDateString(input.startDate) : "1970-01-01";
|
|
1160
|
-
const endDateStr = input.endDate ? toIsoDateString(input.endDate) :
|
|
1194
|
+
const endDateStr = input.endDate ? toIsoDateString(input.endDate) : getCurrentDateTime().toISO();
|
|
1161
1195
|
fallbackFilters.push(`updated: ${startDateStr} .. ${endDateStr}`);
|
|
1162
1196
|
}
|
|
1163
1197
|
const fallbackQuery = (fallbackFilters.length ? `${fallbackFilters.join(" and ")} ` : "") + sortPart;
|
|
@@ -1788,8 +1822,8 @@ export class YoutrackClient {
|
|
|
1788
1822
|
}
|
|
1789
1823
|
resolveReportBoundary(items, mode) {
|
|
1790
1824
|
if (!items.length) {
|
|
1791
|
-
const todayIso =
|
|
1792
|
-
return todayIso;
|
|
1825
|
+
const todayIso = getCurrentDateTime().toISO();
|
|
1826
|
+
return todayIso ?? "";
|
|
1793
1827
|
}
|
|
1794
1828
|
const timestamps = items.map((item) => item.date);
|
|
1795
1829
|
const target = mode === "min" ? Math.min(...timestamps) : Math.max(...timestamps);
|
|
@@ -2095,7 +2129,7 @@ export class YoutrackClient {
|
|
|
2095
2129
|
const { briefOutput = true, limit = DEFAULT_PAGE_SIZE, skip = 0, sortField, sortDirection, ...filtersInput } = input;
|
|
2096
2130
|
const { query } = await buildIssueQuery(filtersInput, (projectId) => this.getProjectById(projectId));
|
|
2097
2131
|
const fields = briefOutput ? defaultFields.issueSearchBrief : defaultFields.issueSearch;
|
|
2098
|
-
const { field: resolvedField, direction: resolvedDirection, sortParam } = this.resolveSort(sortField, sortDirection);
|
|
2132
|
+
const { field: resolvedField, direction: resolvedDirection, sortParam, } = this.resolveSort(sortField, sortDirection);
|
|
2099
2133
|
const params = {
|
|
2100
2134
|
$top: Math.min(limit, DEFAULT_PAGE_SIZE),
|
|
2101
2135
|
$skip: skip,
|
|
@@ -2104,7 +2138,9 @@ export class YoutrackClient {
|
|
|
2104
2138
|
...(query ? { query } : {}),
|
|
2105
2139
|
};
|
|
2106
2140
|
const data = await this.getWithFlexibleTop("/api/issues", params);
|
|
2107
|
-
const issues = Array.isArray(data)
|
|
2141
|
+
const issues = Array.isArray(data)
|
|
2142
|
+
? data.map((issue) => (briefOutput ? mapIssueBrief(issue) : mapIssue(issue)))
|
|
2143
|
+
: [];
|
|
2108
2144
|
return {
|
|
2109
2145
|
issues,
|
|
2110
2146
|
filters: {
|