@vitalyostanin/youtrack-mcp 0.7.1 → 0.7.4

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 CHANGED
@@ -19,29 +19,33 @@ MCP сервер для полноценной интеграции с YouTrack
19
19
 
20
20
  ## Содержание
21
21
 
22
- - [Требования](#требования)
23
- - [Установка](#установка)
24
- - [Используя npx (Рекомендуется)](#используя-npx-рекомендуется)
25
- - [Используя Claude MCP CLI](#используя-claude-mcp-cli)
26
- - [Ручная установка (Для разработки)](#ручная-установка-для-разработки)
27
- - [Разработка и релиз](#разработка-и-релиз)
28
- - [GitHub Actions Workflows](#github-actions-workflows)
29
- - [Настройка NPM_TOKEN](#настройка-npmtoken)
30
- - [Процесс релиза](#процесс-релиза)
31
- - [Ручная сборка и тестирование](#ручная-сборка-и-тестирование)
32
- - [Запуск сервера (stdio)](#запуск-сервера-stdio)
33
- - [Конфигурация для Code (Рекомендуется)](#конфигурация-для-code-рекомендуется)
34
- - [Конфигурация для Claude Code CLI](#конфигурация-для-claude-code-cli)
35
- - [Конфигурация для VS Code Cline](#конфигурация-для-vs-code-cline)
36
- - [Инструменты MCP](#инструменты-mcp)
37
- - [Сервис](#сервис)
38
- - [Задачи](#задачи)
39
- - [Связи задач](#связи-задач)
40
- - [Звездочки задач](#звездочки-задач)
41
- - [Трудозатраты](#трудозатраты)
42
- - [Пользователи и проекты](#пользователи-и-проекты)
43
- - [Статьи](#статьи)
44
- - [Вложения](#вложения)
22
+ - [YouTrack MCP Server](#youtrack-mcp-server)
23
+ - [Содержание](#содержание)
24
+ - [Требования](#требования)
25
+ - [Установка](#установка)
26
+ - [Используя npx (Рекомендуется)](#используя-npx-рекомендуется)
27
+ - [Используя Claude MCP CLI](#используя-claude-mcp-cli)
28
+ - [Ручная установка (Для разработки)](#ручная-установка-для-разработки)
29
+ - [Разработка и релиз](#разработка-и-релиз)
30
+ - [GitHub Actions Workflows](#github-actions-workflows)
31
+ - [CI Workflow (`.github/workflows/ci.yml`)](#ci-workflow-githubworkflowsciyml)
32
+ - [Publish Workflow (`.github/workflows/publish.yml`)](#publish-workflow-githubworkflowspublishyml)
33
+ - [Настройка NPM\_TOKEN](#настройка-npm_token)
34
+ - [Процесс релиза](#процесс-релиза)
35
+ - [Ручная сборка и тестирование](#ручная-сборка-и-тестирование)
36
+ - [Запуск сервера (stdio)](#запуск-сервера-stdio)
37
+ - [Конфигурация для Code (Рекомендуется)](#конфигурация-для-code-рекомендуется)
38
+ - [Конфигурация для Claude Code CLI](#конфигурация-для-claude-code-cli)
39
+ - [Конфигурация для VS Code Cline](#конфигурация-для-vs-code-cline)
40
+ - [Инструменты MCP](#инструменты-mcp)
41
+ - [Сервис](#сервис)
42
+ - [Задачи](#задачи)
43
+ - [Связи задач](#связи-задач)
44
+ - [Звездочки задач](#звездочки-задач)
45
+ - [Трудозатраты](#трудозатраты)
46
+ - [Пользователи и проекты](#пользователи-и-проекты)
47
+ - [Статьи](#статьи)
48
+ - [Вложения](#вложения)
45
49
 
46
50
  ## Требования
47
51
 
@@ -278,11 +282,11 @@ YOUTRACK_TOKEN = "perm:your-token-here"
278
282
  | Tool | Описание | Основные параметры |
279
283
  | --- | --- | --- |
280
284
  | `issue_lookup` | Краткая информация о задаче. **Использование:** Быстрый просмотр базовой информации о задаче без полной детализации. **Возвращает:** предопределенные поля - id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee. Пользовательские поля не включены. | `issueId` — код задачи (например, `PROJ-123`) |
281
- | `issues_lookup` | Краткая информация о нескольких задачах (пакетный режим, макс 50). **Использование:** Эффективное получение информации о множестве задач одновременно. **Возвращает:** те же поля, что и `issue_lookup`. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач (например, `['PROJ-123', 'PROJ-124']`) |
285
+ | `issues_lookup` | Краткая информация о нескольких задачах (пакетный режим, макс 50). **Использование:** Эффективное получение информации о множестве задач одновременно. **Возвращает:** те же поля, что и `issue_lookup`. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач (например, `['PROJ-123', 'PROJ-124']`), макс 50; `briefOutput` — опционально (по умолчанию `true`) |
282
286
  | `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`) |
283
- | `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач; `briefOutput` — опционально (по умолчанию `true`) |
284
- | `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated | `issueId` — код задачи |
285
- | `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач |
287
+ | `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`) |
288
+ | `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (прямая ссылка на комментарий) | `issueId` — код задачи |
289
+ | `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач, макс 50; `briefOutput` — опционально (по умолчанию `true`) |
286
290
  | `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании, одновременное создание дополнительных связей. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — внутренний ID проекта, `summary`, опционально `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links[]` — массив объектов связи |
287
291
  | `issue_update` | Обновление существующей задачи. **Использование:** Изменение summary, description, родительской задачи. **Примечание:** для изменения исполнителя используйте `issue_assign`. Пустая строка в `parentIssueId` удаляет родителя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, опционально `summary`, `description`, `parentIssueId` (пустая строка очищает родителя), `usesMarkdown` |
288
292
  | `issue_assign` | Назначение исполнителя для задачи. **Использование:** Изменение исполнителя задачи. Поддерживает алиас `me` для текущего пользователя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, `assigneeLogin` — логин исполнителя или `me` |
@@ -296,10 +300,10 @@ YOUTRACK_TOKEN = "perm:your-token-here"
296
300
 
297
301
  | Tool | Описание | Основные параметры |
298
302
  | --- | --- | --- |
299
- | `issue_links` | Список связей (линков) для задачи: «relates to», «duplicate», «parent/child». Возвращает id связи, направление (inward/outward), тип связи и краткую информацию о связанной задаче | `issueId` — код задачи |
303
+ | `issue_links` | Список связей (линков) для задачи: «relates to», «duplicate», «parent/child». Возвращает id связи, направление (inward/outbound), тип связи и краткую информацию о связанной задаче | `issueId` — код задачи |
300
304
  | `issue_link_types` | Список доступных типов связей в YouTrack. Полезно для подбора корректного типа при создании связи | — |
301
- | `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID) |
302
- | `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 связи для удаления |
303
307
 
304
308
  **issue_create — дополнительные параметры:**
305
309
 
@@ -358,6 +362,8 @@ YOUTRACK_TOKEN = "perm:your-token-here"
358
362
  | `article_create` | Создание статьи в базе знаний. **Использование:** Создание документации, инструкций, статей поддержки. Поддерживает иерархическую структуру через `parentArticleId`. **Возвращает:** предопределенные поля созданной статьи - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle, project | `summary` — заголовок статьи, опционально `content` — содержимое, `parentArticleId` — ID родительской статьи для создания подстатьи, `projectId` — ID проекта, `usesMarkdown` — использовать Markdown форматирование, `returnRendered` — вернуть отрендеренный preview |
359
363
  | `article_update` | Обновление существующей статьи. **Использование:** Редактирование содержимого, изменение заголовка статьи. **Возвращает:** предопределенные поля обновленной статьи | `articleId` — ID статьи, опционально `summary` — новый заголовок, `content` — новое содержимое, `usesMarkdown`, `returnRendered` |
360
364
  | `article_search` | Поиск статей в базе знаний по тексту. **Использование:** Полнотекстовый поиск по заголовкам и содержимому статей, поиск документации. Поддерживает фильтрацию по проекту и родительской статье. **Возвращает:** предопределенные поля - id, idReadable, summary, usesMarkdown, contentPreview (если `returnRendered=true`), parentArticle, project. Поле content не включено для производительности. **Ограничение:** минимум 2 символа в запросе, максимум 200 результатов | `query` — текст для поиска (минимум 2 символа), опционально `projectId` — фильтр по проекту, `parentArticleId` — фильтр по родительской статье, `limit` — максимум результатов (макс 200), `returnRendered` — вернуть отрендеренный preview содержимого |
365
+ | `articles_search` | Полнотекстовый поиск по статьям базы знаний YouTrack по заголовку и содержимому. Возвращает `webUrl` для прямого перехода. | `query`, `limit`, `skip`, опционально `projectId`, `parentArticleId` |
366
+ | `issues_search` | Полнотекстовый поиск по задачам YouTrack по summary, description и комментариям. Если `query` не указан или пуст, будут возвращены все задачи. Поддерживает фильтрацию по проектам, исполнителю, автору, статусу и типу. | `query` (необязательный), `limit`, `skip`, `countOnly` (необязательный), `projects` (необязательный), `assignee` (необязательный), `reporter` (необязательный), `state` (необязательный), `type` (необязательный) |
361
367
  | `issues_list` | Список задач по фильтрам с поддержкой сортировки. **Использование:** Дашборды, аудит командной загрузки, подготовка батч-операций. Поддерживает фильтрацию по проектам, датам создания/обновления, статусам, типам, исполнителю. **Возвращает:** краткие или полные данные задачи в зависимости от `briefOutput`, а также информацию о сортировке и пагинации. | Фильтры: `projectIds`, `createdAfter/Before`, `updatedAfter/Before`, `statuses`, `assigneeLogin`, `types`; сортировка: `sortField` (`created`/`updated`), `sortDirection` (`asc`/`desc`); пагинация: `limit`, `skip`; формат: `briefOutput` |
362
368
  | `issues_count` | Подсчёт задач с теми же фильтрами, что у `issues_list`, с разбивкой по проектам. **Использование:** Быстрая оценка объёмов работ перед загрузкой полного списка, подготовка аналитических отчётов. При запросе одного проекта обращается к `/api/issuesGetter/count`, иначе считает постранично. | Те же фильтры, что и `issues_list`; опциональный `top` ограничивает объём ручной агрегации (при необходимости частичной выборки) |
363
369
 
package/README.md CHANGED
@@ -21,31 +21,35 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
21
21
 
22
22
  ## Table of Contents
23
23
 
24
- - [Requirements](#requirements)
25
- - [Installation](#installation)
26
- - [Using npx (Recommended)](#using-npx-recommended)
27
- - [Using Claude MCP CLI](#using-claude-mcp-cli)
28
- - [Manual Installation (Development)](#manual-installation-development)
29
- - [Development & Release](#development--release)
30
- - [GitHub Actions Workflows](#github-actions-workflows)
31
- - [Setting up NPM_TOKEN](#setting-up-npmtoken)
32
- - [Release Process](#release-process)
33
- - [Manual Build & Test](#manual-build--test)
34
- - [Running the server (stdio)](#running-the-server-stdio)
35
- - [Configuration for Code (Recommended)](#configuration-for-code-recommended)
36
- - [Configuration for Claude Code CLI](#configuration-for-claude-code-cli)
37
- - [Configuration for VS Code Cline](#configuration-for-vs-code-cline)
38
- - [MCP Tools](#mcp-tools)
39
- - [Service](#service)
40
- - [Issues](#issues)
41
- - [Issue Links](#issue-links)
42
- - [Issue Stars](#issue-stars)
43
- - [Work Items](#work-items)
44
- - [Users and Projects](#users-and-projects)
45
- - [Articles](#articles)
46
- - [Search](#search)
47
- - [Important Notes](#important-notes)
48
- - [Destructive Operations](#destructive-operations)
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
+ - [Important Notes](#important-notes)
52
+ - [Destructive Operations](#destructive-operations)
49
53
 
50
54
  ## Requirements
51
55
 
@@ -282,8 +286,11 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
282
286
  | Tool | Description | Main Parameters |
283
287
  | --- | --- | --- |
284
288
  | `issue_lookup` | Brief issue information | `issueId` — issue code (e.g., PROJ-123) |
289
+ | `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
290
  | `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` |
291
+ | `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
292
  | `issue_comments` | Issue comments | `issueId` — issue code |
293
+ | `issues_comments` | Comments for multiple issues (batch mode, max 50) | `issueIds[]` — array of issue codes, max 50; `briefOutput` — optional boolean (default `true`) |
287
294
  | `issue_create` | Create issue | `projectId`, `summary`, optional `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links` (array of link objects) |
288
295
  | `issue_update` | Update existing issue | `issueId`, optionally `summary`, `description`, `parentIssueId` (empty string clears parent), `usesMarkdown` |
289
296
  | `issue_assign` | Assign issue to user | `issueId`, `assigneeLogin` (login or `me`) |
@@ -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`, `linkId` |
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
 
@@ -354,12 +361,15 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
354
361
  | `article_create` | Create article in knowledge base | `summary`, optionally `content`, `parentArticleId`, `projectId`, `usesMarkdown`, `returnRendered` |
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` |
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` |
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) |
357
366
 
358
367
  ### Search
359
368
 
360
369
  | Tool | Description | Main Parameters |
361
370
  | --- | --- | --- |
362
- | `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered` |
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) |
363
373
  | `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` |
364
374
  | `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 |
365
375
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitalyostanin/youtrack-mcp",
3
- "version": "0.7.1",
3
+ "version": "0.7.4",
4
4
  "scripts": {
5
5
  "build": "tsc -p tsconfig.json",
6
6
  "postbuild": "chmod +x dist/index.js",
@@ -2,11 +2,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { registerServiceInfoTool } from "./tools/service-info.js";
3
3
  import { registerIssueTools } from "./tools/issue-tools.js";
4
4
  import { registerIssueActivityTools } from "./tools/issue-activity-tools.js";
5
- import { registerIssueSearchTools } from "./tools/issue-search-tools.js";
5
+ import { issuesSearchArgs, issuesSearchHandler } from "./tools/issue-search-tools.js";
6
+ import { articlesSearchArgs, articlesSearchHandler } from "./tools/article-search-tools.js";
6
7
  import { registerWorkitemTools } from "./tools/workitem-tools.js";
7
8
  import { registerWorkitemReportTools } from "./tools/workitem-report-tools.js";
8
9
  import { registerArticleTools } from "./tools/article-tools.js";
9
- import { registerArticleSearchTools } from "./tools/article-search-tools.js";
10
10
  import { registerUserTools } from "./tools/user-tools.js";
11
11
  import { registerProjectTools } from "./tools/project-tools.js";
12
12
  import { registerAttachmentTools } from "./tools/attachment-tools.js";
@@ -39,11 +39,12 @@ export class YoutrackServer {
39
39
  registerServiceInfoTool(this.server, this.client);
40
40
  registerIssueTools(this.server, this.client);
41
41
  registerIssueActivityTools(this.server, this.client);
42
- registerIssueSearchTools(this.server, this.client);
42
+ this.server.tool("issues_search", "Search YouTrack issues by text in summary, description, and comments.", issuesSearchArgs, async (args) => issuesSearchHandler(this.client, args));
43
+ this.server.tool("articles_search", "Search YouTrack knowledge base articles by title and content.", articlesSearchArgs, async (args) => articlesSearchHandler(this.client, args));
43
44
  registerWorkitemTools(this.server, this.client);
44
45
  registerWorkitemReportTools(this.server, this.client);
45
46
  registerArticleTools(this.server, this.client);
46
- registerArticleSearchTools(this.server, this.client);
47
+ // Removed redundant registerArticleSearchTools call (articles_search tool registered manually above)
47
48
  registerUserTools(this.server, this.client);
48
49
  registerProjectTools(this.server, this.client);
49
50
  registerAttachmentTools(this.server, this.client);
@@ -1,4 +1,103 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { z } from "zod";
2
2
  import type { YoutrackClient } from "../youtrack-client.js";
3
- export declare function registerArticleSearchTools(server: McpServer, client: YoutrackClient): void;
3
+ export declare const articlesSearchArgs: {
4
+ query: z.ZodString;
5
+ limit: z.ZodDefault<z.ZodNumber>;
6
+ skip: z.ZodDefault<z.ZodNumber>;
7
+ projectId: z.ZodOptional<z.ZodString>;
8
+ parentArticleId: z.ZodOptional<z.ZodString>;
9
+ };
10
+ export declare const articlesSearchSchema: z.ZodObject<{
11
+ query: z.ZodString;
12
+ limit: z.ZodDefault<z.ZodNumber>;
13
+ skip: z.ZodDefault<z.ZodNumber>;
14
+ projectId: z.ZodOptional<z.ZodString>;
15
+ parentArticleId: z.ZodOptional<z.ZodString>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ query: string;
18
+ skip: number;
19
+ limit: number;
20
+ projectId?: string | undefined;
21
+ parentArticleId?: string | undefined;
22
+ }, {
23
+ query: string;
24
+ skip?: number | undefined;
25
+ limit?: number | undefined;
26
+ projectId?: string | undefined;
27
+ parentArticleId?: string | undefined;
28
+ }>;
29
+ export declare function articlesSearchHandler(client: YoutrackClient, rawInput: unknown): Promise<{
30
+ [x: string]: unknown;
31
+ content: ({
32
+ [x: string]: unknown;
33
+ text: string;
34
+ type: "text";
35
+ _meta?: {
36
+ [x: string]: unknown;
37
+ } | undefined;
38
+ } | {
39
+ [x: string]: unknown;
40
+ type: "image";
41
+ data: string;
42
+ mimeType: string;
43
+ _meta?: {
44
+ [x: string]: unknown;
45
+ } | undefined;
46
+ } | {
47
+ [x: string]: unknown;
48
+ type: "audio";
49
+ data: string;
50
+ mimeType: string;
51
+ _meta?: {
52
+ [x: string]: unknown;
53
+ } | undefined;
54
+ } | {
55
+ [x: string]: unknown;
56
+ name: string;
57
+ type: "resource_link";
58
+ uri: string;
59
+ description?: string | undefined;
60
+ _meta?: {
61
+ [x: string]: unknown;
62
+ } | undefined;
63
+ mimeType?: string | undefined;
64
+ title?: string | undefined;
65
+ icons?: {
66
+ [x: string]: unknown;
67
+ src: string;
68
+ mimeType?: string | undefined;
69
+ sizes?: string[] | undefined;
70
+ }[] | undefined;
71
+ } | {
72
+ [x: string]: unknown;
73
+ type: "resource";
74
+ resource: {
75
+ [x: string]: unknown;
76
+ text: string;
77
+ uri: string;
78
+ _meta?: {
79
+ [x: string]: unknown;
80
+ } | undefined;
81
+ mimeType?: string | undefined;
82
+ } | {
83
+ [x: string]: unknown;
84
+ uri: string;
85
+ blob: string;
86
+ _meta?: {
87
+ [x: string]: unknown;
88
+ } | undefined;
89
+ mimeType?: string | undefined;
90
+ };
91
+ _meta?: {
92
+ [x: string]: unknown;
93
+ } | undefined;
94
+ })[];
95
+ _meta?: {
96
+ [x: string]: unknown;
97
+ } | undefined;
98
+ structuredContent?: {
99
+ [x: string]: unknown;
100
+ } | undefined;
101
+ isError?: boolean | undefined;
102
+ }>;
4
103
  //# sourceMappingURL=article-search-tools.d.ts.map
@@ -1,31 +1,40 @@
1
1
  import { z } from "zod";
2
- import { toolError, toolSuccess } from "../utils/tool-response.js";
3
- const articleSearchArgs = {
4
- query: z.string().min(2).describe("Search string for summary and content"),
2
+ import { toolSuccess, toolError } from "../utils/tool-response.js";
3
+ export const articlesSearchArgs = {
4
+ query: z.string().min(2).describe("Search string for articles (e.g., 'API token')"),
5
+ limit: z.number().int().positive().max(200).default(50).describe("Max results per page"),
6
+ skip: z.number().int().nonnegative().default(0).describe("Offset for pagination"),
5
7
  projectId: z.string().optional().describe("Filter by project ID"),
6
- parentArticleId: z.string().optional().describe("Filter by parent article"),
7
- limit: z.number().int().positive().max(200).optional().describe("Maximum number of results"),
8
- returnRendered: z.boolean().optional().describe("Return rendered content preview"),
8
+ parentArticleId: z.string().optional().describe("Filter by parent article ID"),
9
9
  };
10
- const articleSearchSchema = z.object(articleSearchArgs);
11
- export function registerArticleSearchTools(server, client) {
12
- server.tool("article_search", "Search articles in knowledge base by text. Note: Returns predefined fields only - id, idReadable, summary, usesMarkdown, contentPreview (when returnRendered is true), parentArticle (id, idReadable), project (id, shortName, name). Content field is not included for performance reasons.", articleSearchArgs, async (rawInput) => {
13
- try {
14
- const payload = articleSearchSchema.parse(rawInput);
15
- const articles = await client.searchArticles({
16
- query: payload.query,
17
- projectId: payload.projectId,
18
- parentArticleId: payload.parentArticleId,
19
- limit: payload.limit,
20
- returnRendered: payload.returnRendered,
21
- });
22
- const response = toolSuccess(articles);
23
- return response;
10
+ export const articlesSearchSchema = z.object(articlesSearchArgs);
11
+ export async function articlesSearchHandler(client, rawInput) {
12
+ const input = articlesSearchSchema.parse(rawInput);
13
+ try {
14
+ const params = {
15
+ fields: "id,idReadable,summary,parentArticle(id,idReadable),project(id,shortName,name)",
16
+ query: `{${input.query}}`,
17
+ $top: input.limit,
18
+ $skip: input.skip,
19
+ };
20
+ if (input.projectId) {
21
+ params.query += ` and project: {${input.projectId}}`;
24
22
  }
25
- catch (error) {
26
- const errorResponse = toolError(error);
27
- return errorResponse;
23
+ if (input.parentArticleId) {
24
+ params.query += ` and parent article: {${input.parentArticleId}}`;
28
25
  }
29
- });
26
+ const data = await client["getWithFlexibleTop"]("/api/articles", params);
27
+ const baseUrl = client.config?.baseUrl ?? "";
28
+ const articlesWithLinks = Array.isArray(data)
29
+ ? data.map((article) => ({
30
+ ...article,
31
+ webUrl: `${baseUrl}/articles/${article.idReadable}`,
32
+ }))
33
+ : data;
34
+ return toolSuccess(articlesWithLinks);
35
+ }
36
+ catch (error) {
37
+ return toolError(error);
38
+ }
30
39
  }
31
40
  //# sourceMappingURL=article-search-tools.js.map
@@ -1,4 +1,115 @@
1
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { z } from "zod";
2
2
  import type { YoutrackClient } from "../youtrack-client.js";
3
- export declare function registerIssueSearchTools(server: McpServer, client: YoutrackClient): void;
3
+ export declare const issuesSearchArgs: {
4
+ query: z.ZodOptional<z.ZodString>;
5
+ limit: z.ZodDefault<z.ZodNumber>;
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>;
12
+ };
13
+ export declare const issuesSearchSchema: z.ZodDefault<z.ZodObject<{
14
+ query: z.ZodOptional<z.ZodString>;
15
+ limit: z.ZodDefault<z.ZodNumber>;
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>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ skip: number;
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;
31
+ }, {
32
+ reporter?: string | undefined;
33
+ assignee?: string | undefined;
34
+ type?: string | undefined;
35
+ query?: string | undefined;
36
+ skip?: number | undefined;
37
+ projects?: string[] | undefined;
38
+ limit?: number | undefined;
39
+ state?: string | undefined;
40
+ }>>;
41
+ export declare function issuesSearchHandler(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
+ }>;
4
115
  //# sourceMappingURL=issue-search-tools.d.ts.map
@@ -1,211 +1,91 @@
1
1
  import { z } from "zod";
2
- import { toolError, toolSuccess } from "../utils/tool-response.js";
3
- const issueSearchByUserActivityArgs = {
4
- userLogins: z
5
- .array(z.string().min(1))
6
- .min(1)
7
- .describe("Array of user logins to search for activity (updater, mentions, reporter, assignee, commenter)"),
8
- startDate: z
2
+ import { toolSuccess, toolError } from "../utils/tool-response.js";
3
+ export const issuesSearchArgs = {
4
+ query: z
9
5
  .string()
10
6
  .optional()
11
- .describe("Start date for period filter (YYYY-MM-DD format or timestamp)"),
12
- endDate: z
13
- .string()
14
- .optional()
15
- .describe("End date for period filter (YYYY-MM-DD format or timestamp)"),
16
- dateFilterMode: z
17
- .enum(["issue_updated", "user_activity"])
18
- .optional()
19
- .describe("Date filter mode: 'issue_updated' (default, fast) filters by issue.updated field; 'user_activity' (slow, precise) filters by actual user activity dates including comments, mentions, and field changes history. Use 'user_activity' when you need exact date of user's involvement, e.g., when user was assignee but later changed."),
20
- briefOutput: z
21
- .boolean()
22
- .optional()
23
- .describe("Return brief issue data without description fields (default: true). Set to false to include full description and wikifiedDescription fields."),
24
- limit: z
25
- .number()
26
- .int()
27
- .positive()
28
- .max(200)
29
- .optional()
30
- .describe("Maximum number of results (default: 100, max: 200)"),
31
- skip: z
32
- .number()
33
- .int()
34
- .nonnegative()
35
- .optional()
36
- .describe("Number of results to skip for pagination (default: 0)"),
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')"),
8
+ limit: z.number().int().positive().max(200).default(50).describe("Max results per page"),
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')"),
37
15
  };
38
- const issueSearchByUserActivitySchema = z.object(issueSearchByUserActivityArgs);
39
- const issueListArgs = {
40
- projectIds: z
41
- .array(z.string().min(1))
42
- .optional()
43
- .describe("Filter by project IDs (e.g., ['0-0']). When omitted, returns issues from all projects."),
44
- createdAfter: z
45
- .string()
46
- .optional()
47
- .describe("Only include issues created on or after this date (YYYY-MM-DD or ISO timestamp)."),
48
- createdBefore: z
49
- .string()
50
- .optional()
51
- .describe("Only include issues created on or before this date (YYYY-MM-DD or ISO timestamp)."),
52
- updatedAfter: z
53
- .string()
54
- .optional()
55
- .describe("Only include issues updated on or after this date (YYYY-MM-DD or ISO timestamp)."),
56
- updatedBefore: z
57
- .string()
58
- .optional()
59
- .describe("Only include issues updated on or before this date (YYYY-MM-DD or ISO timestamp)."),
60
- statuses: z
61
- .array(z.string().min(1))
62
- .optional()
63
- .describe("Filter by workflow states (e.g., ['Open', 'In Progress'])."),
64
- assigneeLogin: z
16
+ export const issuesSearchSchema = z
17
+ .object({
18
+ query: z
65
19
  .string()
66
20
  .optional()
67
- .describe("Filter by assignee login (case-sensitive; use 'me' for current user)."),
68
- types: z
69
- .array(z.string().min(1))
70
- .optional()
71
- .describe("Filter by issue types (e.g., ['Bug', 'Feature'])."),
72
- sortField: z
73
- .enum(["created", "updated"])
74
- .optional()
75
- .describe("Sort by created or updated timestamp (default: created)."),
76
- sortDirection: z
77
- .enum(["asc", "desc"])
78
- .optional()
79
- .describe("Sort direction: 'asc' for oldest first, 'desc' for newest first (default)."),
80
- briefOutput: z
81
- .boolean()
82
- .optional()
83
- .describe("Return brief issue data without description fields (default: true). Set to false to include description and wikifiedDescription fields."),
84
- limit: z
85
- .number()
86
- .int()
87
- .positive()
88
- .max(200)
89
- .optional()
90
- .describe("Maximum number of issues to return (default: 200, max: 200)."),
91
- skip: z
92
- .number()
93
- .int()
94
- .nonnegative()
95
- .optional()
96
- .describe("Number of issues to skip for pagination (default: 0)."),
97
- };
98
- const issueListSchema = z.object(issueListArgs);
99
- const issueCountArgs = {
100
- projectIds: z
101
- .array(z.string().min(1))
102
- .optional()
103
- .describe("Filter by project IDs (e.g., ['0-0']). When omitted, counts cover all projects."),
104
- createdAfter: z
105
- .string()
106
- .optional()
107
- .describe("Only include issues created on or after this date (YYYY-MM-DD or ISO timestamp)."),
108
- createdBefore: z
109
- .string()
110
- .optional()
111
- .describe("Only include issues created on or before this date (YYYY-MM-DD or ISO timestamp)."),
112
- updatedAfter: z
113
- .string()
114
- .optional()
115
- .describe("Only include issues updated on or after this date (YYYY-MM-DD or ISO timestamp)."),
116
- updatedBefore: z
117
- .string()
118
- .optional()
119
- .describe("Only include issues updated on or before this date (YYYY-MM-DD or ISO timestamp)."),
120
- statuses: z
121
- .array(z.string().min(1))
122
- .optional()
123
- .describe("Filter by workflow states (e.g., ['Open', 'In Progress'])."),
124
- assigneeLogin: z
125
- .string()
126
- .optional()
127
- .describe("Filter by assignee login (case-sensitive; use 'me' for current user)."),
128
- types: z
129
- .array(z.string().min(1))
130
- .optional()
131
- .describe("Filter by issue types (e.g., ['Bug', 'Feature'])."),
132
- top: z
133
- .number()
134
- .int()
135
- .positive()
136
- .max(1000)
137
- .optional()
138
- .describe("Optional safety limit for manual aggregation when multiple projects are requested. When set, counting stops after processing this many issues, so totals may be partial."),
139
- };
140
- const issueCountSchema = z.object(issueCountArgs);
141
- export function registerIssueSearchTools(server, client) {
142
- server.tool("issue_search_by_user_activity", "Search for issues where specified users had activity (updated, mentioned, reported, assigned, commented) within a given time period. Supports two filter modes: 'issue_updated' (default, fast) uses issue.updated field, 'user_activity' (slow, precise) checks actual user activity dates including comments, mentions, and field changes history. Results are sorted by activity time (most recent first). When 'user_activity' mode is used, each issue includes 'lastActivityDate' field with exact timestamp of user's last activity. Supports pagination via limit and skip parameters. Note: By default (briefOutput=true), each issue includes minimal fields only - id, idReadable, summary, project (id, shortName, name), parent (id, idReadable), assignee (id, login, name). Description fields are excluded to reduce response size. Set briefOutput=false to include full description and wikifiedDescription fields. Custom fields are not included.", issueSearchByUserActivityArgs, async (rawInput) => {
143
- try {
144
- const payload = issueSearchByUserActivitySchema.parse(rawInput);
145
- const results = await client.searchIssuesByUserActivity({
146
- userLogins: payload.userLogins,
147
- startDate: payload.startDate,
148
- endDate: payload.endDate,
149
- dateFilterMode: payload.dateFilterMode,
150
- briefOutput: payload.briefOutput,
151
- limit: payload.limit,
152
- skip: payload.skip,
153
- });
154
- const response = toolSuccess(results);
155
- return response;
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({});
31
+ export async function issuesSearchHandler(client, rawInput) {
32
+ const input = issuesSearchSchema.parse(rawInput);
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}`);
156
47
  }
157
- catch (error) {
158
- const errorResponse = toolError(error);
159
- return errorResponse;
48
+ // Add reporter filter
49
+ if (input.reporter) {
50
+ filters.push(`reporter: ${input.reporter}`);
160
51
  }
161
- });
162
- server.tool("issues_list", "List issues across projects with rich filtering and configurable sorting. Use for: Building dashboards, auditing work across teams, feeding batch workflows. Supports filtering by project IDs, creation/update windows, workflow states, assignee login, and issue types. Sorting accepts created/updated fields with asc/desc direction so you can fetch newest or oldest pages first. Pagination is controlled with limit/skip (max 200 per call). Note: By default (briefOutput=true) returns minimal fields (id, idReadable, summary, project, parent, assignee, watchers.hasStar). Set briefOutput=false to include description and wikifiedDescription fields. After processing a page, re-fetch if you must verify that status or assignee changes did not occur in the meantime.", issueListArgs, async (rawInput) => {
163
- try {
164
- const payload = issueListSchema.parse(rawInput);
165
- const results = await client.listIssues({
166
- projectIds: payload.projectIds,
167
- createdAfter: payload.createdAfter,
168
- createdBefore: payload.createdBefore,
169
- updatedAfter: payload.updatedAfter,
170
- updatedBefore: payload.updatedBefore,
171
- statuses: payload.statuses,
172
- assigneeLogin: payload.assigneeLogin,
173
- types: payload.types,
174
- sortField: payload.sortField,
175
- sortDirection: payload.sortDirection,
176
- briefOutput: payload.briefOutput,
177
- limit: payload.limit,
178
- skip: payload.skip,
179
- });
180
- const response = toolSuccess(results);
181
- return response;
52
+ // Add state filter
53
+ if (input.state) {
54
+ filters.push(`State: {${input.state}}`);
182
55
  }
183
- catch (error) {
184
- const errorResponse = toolError(error);
185
- return errorResponse;
56
+ // Add type filter
57
+ if (input.type) {
58
+ filters.push(`Type: {${input.type}}`);
186
59
  }
187
- });
188
- server.tool("issues_count", "Count issues across projects using the same filters as issues_list. Use for: Gauging workload distribution, preparing dashboards, validating automation scope before fetching full issue payloads. Returns total matches and per-project breakdown when multiple projects are involved. When only one project is filtered, delegates to YouTrack's /api/issuesGetter/count for an exact total; otherwise counts are aggregated client-side with pagination (default batches of 200, controlled by optional top limit for partial sampling). If you use a small top value, totals may be truncated — re-run without top to confirm final numbers.", issueCountArgs, async (rawInput) => {
189
- try {
190
- const payload = issueCountSchema.parse(rawInput);
191
- const results = await client.countIssues({
192
- projectIds: payload.projectIds,
193
- createdAfter: payload.createdAfter,
194
- createdBefore: payload.createdBefore,
195
- updatedAfter: payload.updatedAfter,
196
- updatedBefore: payload.updatedBefore,
197
- statuses: payload.statuses,
198
- assigneeLogin: payload.assigneeLogin,
199
- types: payload.types,
200
- top: payload.top,
201
- });
202
- const response = toolSuccess(results);
203
- return response;
60
+ // Combine query with filters
61
+ if (filters.length > 0) {
62
+ const combinedFilters = filters.join(" and ");
63
+ query = query ? `(${query}) and ${combinedFilters}` : combinedFilters;
204
64
  }
205
- catch (error) {
206
- const errorResponse = toolError(error);
207
- return errorResponse;
65
+ // Fetch issues
66
+ const data = await client["getWithFlexibleTop"]("/api/issues", {
67
+ query,
68
+ $top: limit,
69
+ $skip: skip,
70
+ fields: "id,idReadable,summary,project(shortName,name),assignee(name,login)",
71
+ });
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;
208
79
  }
209
- });
80
+ const result = {
81
+ total,
82
+ byProject: Object.entries(byProject).map(([project, count]) => ({ project, count })),
83
+ items: allIssues,
84
+ };
85
+ return toolSuccess(result);
86
+ }
87
+ catch (error) {
88
+ return toolError(error);
89
+ }
210
90
  }
211
91
  //# sourceMappingURL=issue-search-tools.js.map
@@ -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
@@ -55,7 +55,23 @@ export function mapIssueDetails(issue) {
55
55
  export function mapIssueBrief(issue) {
56
56
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
57
57
  const { description, wikifiedDescription, ...rest } = issue;
58
- return rest;
58
+ // Try to extract assignee from customFields if not present
59
+ let assignee = issue.assignee ?? null;
60
+ if (!assignee && Array.isArray(issue.customFields)) {
61
+ const assigneeField = issue.customFields?.find((f) => f.name === "Assignee" && f.value);
62
+ if (assigneeField?.value) {
63
+ const { id = "", login = "", name = "" } = assigneeField.value;
64
+ assignee = {
65
+ id,
66
+ login,
67
+ name,
68
+ };
69
+ }
70
+ }
71
+ return {
72
+ ...rest,
73
+ assignee,
74
+ };
59
75
  }
60
76
  /**
61
77
  * Map YoutrackWorkItem to MappedYoutrackWorkItem
@@ -38,7 +38,7 @@ const defaultFields = {
38
38
  "summary",
39
39
  "project(id,shortName,name)",
40
40
  "parent(id,idReadable)",
41
- "assignee(id,login,name)",
41
+ "customFields(id,name,value(id,login,name,presentation),$type)",
42
42
  "watchers(hasStar)",
43
43
  ].join(","),
44
44
  issueDetails: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitalyostanin/youtrack-mcp",
3
- "version": "0.7.1",
3
+ "version": "0.7.4",
4
4
  "scripts": {
5
5
  "build": "tsc -p tsconfig.json",
6
6
  "postbuild": "chmod +x dist/index.js",