@vitalyostanin/youtrack-mcp 0.7.0 → 0.7.3

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
 
@@ -53,6 +57,7 @@ MCP сервер для полноценной интеграции с YouTrack
53
57
  - `YOUTRACK_HOLIDAYS` — опциональный список праздничных дат через запятую (формат `YYYY-MM-DD`), исключаемых из отчётов и массовых операций
54
58
  - `YOUTRACK_PRE_HOLIDAYS` — опциональный список предпраздничных дат через запятую, для которых норма времени уменьшается автоматически
55
59
  - `YOUTRACK_USER_ALIASES` — опциональный список соответствий `alias:login` через запятую (например, `me:vyt,petya:p.petrov`), используется при автоматическом выборе исполнителей
60
+ - `YOUTRACK_DEFAULT_PROJECT` — опциональный код проекта (shortName или ID), используемый по умолчанию при создании задач/статей и для преобразования коротких идентификаторов (например, `1234` → `PROJ-1234`)
56
61
  - `YOUTRACK_USE_STRUCTURED_CONTENT` — опциональный, управляет форматом ответа (по умолчанию: `true`). При `true` инструменты возвращают только узел MCP `structuredContent` с полными данными. При `false` инструменты возвращают только узел MCP `content` (один текстовый элемент с JSON-строкой)
57
62
 
58
63
  ## Установка
@@ -64,6 +69,7 @@ MCP сервер для полноценной интеграции с YouTrack
64
69
  ```bash
65
70
  YOUTRACK_URL="https://youtrack.example.com" \
66
71
  YOUTRACK_TOKEN="perm:your-token-here" \
72
+ YOUTRACK_DEFAULT_PROJECT="PROJ" \
67
73
  npx -y @vitalyostanin/youtrack-mcp@latest
68
74
  ```
69
75
 
@@ -281,7 +287,7 @@ YOUTRACK_TOKEN = "perm:your-token-here"
281
287
  | `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач; `briefOutput` — опционально (по умолчанию `true`) |
282
288
  | `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated | `issueId` — код задачи |
283
289
  | `issues_comments` | Комментарии для нескольких задач (пакетный режим, макс 50). **Использование:** Эффективное получение комментариев для множества задач одновременно. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач |
284
- | `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — ID проекта (внутренний YouTrack ID), `summary` — краткое описание задачи, опционально `description`, `parentIssueId`, `assigneeLogin` (логин или `me`), `usesMarkdown` |
290
+ | `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании, одновременное создание дополнительных связей. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — внутренний ID проекта, `summary`, опционально `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links[]` — массив объектов связи |
285
291
  | `issue_update` | Обновление существующей задачи. **Использование:** Изменение summary, description, родительской задачи. **Примечание:** для изменения исполнителя используйте `issue_assign`. Пустая строка в `parentIssueId` удаляет родителя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, опционально `summary`, `description`, `parentIssueId` (пустая строка очищает родителя), `usesMarkdown` |
286
292
  | `issue_assign` | Назначение исполнителя для задачи. **Использование:** Изменение исполнителя задачи. Поддерживает алиас `me` для текущего пользователя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, `assigneeLogin` — логин исполнителя или `me` |
287
293
  | `issue_comment_create` | Добавление комментария к задаче. **Использование:** Оставлять комментарии, заметки, обсуждения в задачах. Поддерживает Markdown. **Возвращает:** поля комментария - id, text, textPreview, usesMarkdown, author, created, updated | `issueId` — ID или код задачи, `text` — текст комментария, опционально `usesMarkdown` |
@@ -299,6 +305,16 @@ YOUTRACK_TOKEN = "perm:your-token-here"
299
305
  | `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID) |
300
306
  | `issue_link_delete` | Удаление существующей связи по её id для указанной задачи | `issueId` — код задачи, `linkId` — id связи |
301
307
 
308
+ **issue_create — дополнительные параметры:**
309
+
310
+ - `parentIssueId` — если указан только числовой идентификатор (`"123"`), автоматически добавляет префикс проекта по умолчанию (например, `BC-123`).
311
+ - `links[]` — массив объектов для создания связей после сохранения задачи. Каждый объект поддерживает поля:
312
+ - `linkType` — имя или id типа связи (например, `Subtask`, `Relates`).
313
+ - `targetId` — idReadable задачи или числовой идентификатор, который также резолвится через проект по умолчанию.
314
+ - `direction` — опционально (`"outbound"` по умолчанию). Используйте `"inbound"`, чтобы инвертировать направление для несимметричных типов.
315
+ - `sourceId` — опционально. Если не указан, исходной считается новая задача; укажите его, чтобы привязать новую задачу как `target` к уже существующей.
316
+ - После создания задачи повторно запросите связи (`issue_links`) для подтверждения, что YouTrack применил все отношения.
317
+
302
318
  ### Звездочки задач
303
319
 
304
320
  | Tool | Описание | Основные параметры |
@@ -346,6 +362,10 @@ YOUTRACK_TOKEN = "perm:your-token-here"
346
362
  | `article_create` | Создание статьи в базе знаний. **Использование:** Создание документации, инструкций, статей поддержки. Поддерживает иерархическую структуру через `parentArticleId`. **Возвращает:** предопределенные поля созданной статьи - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle, project | `summary` — заголовок статьи, опционально `content` — содержимое, `parentArticleId` — ID родительской статьи для создания подстатьи, `projectId` — ID проекта, `usesMarkdown` — использовать Markdown форматирование, `returnRendered` — вернуть отрендеренный preview |
347
363
  | `article_update` | Обновление существующей статьи. **Использование:** Редактирование содержимого, изменение заголовка статьи. **Возвращает:** предопределенные поля обновленной статьи | `articleId` — ID статьи, опционально `summary` — новый заголовок, `content` — новое содержимое, `usesMarkdown`, `returnRendered` |
348
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`, `limit`, `skip` |
367
+ | `issues_list` | Список задач по фильтрам с поддержкой сортировки. **Использование:** Дашборды, аудит командной загрузки, подготовка батч-операций. Поддерживает фильтрацию по проектам, датам создания/обновления, статусам, типам, исполнителю. **Возвращает:** краткие или полные данные задачи в зависимости от `briefOutput`, а также информацию о сортировке и пагинации. | Фильтры: `projectIds`, `createdAfter/Before`, `updatedAfter/Before`, `statuses`, `assigneeLogin`, `types`; сортировка: `sortField` (`created`/`updated`), `sortDirection` (`asc`/`desc`); пагинация: `limit`, `skip`; формат: `briefOutput` |
368
+ | `issues_count` | Подсчёт задач с теми же фильтрами, что у `issues_list`, с разбивкой по проектам. **Использование:** Быстрая оценка объёмов работ перед загрузкой полного списка, подготовка аналитических отчётов. При запросе одного проекта обращается к `/api/issuesGetter/count`, иначе считает постранично. | Те же фильтры, что и `issues_list`; опциональный `top` ограничивает объём ручной агрегации (при необходимости частичной выборки) |
349
369
 
350
370
  ### Вложения
351
371
 
package/README.md CHANGED
@@ -55,9 +55,10 @@ MCP server for comprehensive YouTrack integration with the following capabilitie
55
55
  - `YOUTRACK_TOKEN` — permanent token with read permissions for issues and work items
56
56
  - `YOUTRACK_TIMEZONE` — optional timezone for date operations (default: `Europe/Moscow`), must be a valid IANA timezone identifier (e.g., `Europe/London`, `America/New_York`, `Asia/Tokyo`)
57
57
  - `YOUTRACK_HOLIDAYS` — optional comma-separated list of holiday dates (format `YYYY-MM-DD`), excluded from reports and batch operations
58
- - `YOUTRACK_PRE_HOLIDAYS` — optional comma-separated list of pre-holiday dates with reduced working hours
59
- - `YOUTRACK_USER_ALIASES` — optional comma-separated list of `alias:login` mappings (e.g., `me:vyt,petya:p.petrov`), used for automatic assignee selection
60
- - `YOUTRACK_USE_STRUCTURED_CONTENT` — optional, controls response format (default: `true`). When `true`, tools return only the MCP `structuredContent` node with full data. When `false`, tools return only the MCP `content` node (single text item with JSON string)
58
+ - `YOUTRACK_PRE_HOLIDAYS` — optional comma-separated list of pre-holiday dates with reduced working hours
59
+ - `YOUTRACK_USER_ALIASES` — optional comma-separated list of `alias:login` mappings (e.g., `me:vyt,petya:p.petrov`), used for automatic assignee selection
60
+ - `YOUTRACK_DEFAULT_PROJECT` — optional project code used for manual verification tasks and default parent issues in docs/examples (use `PROJ` in documentation examples)
61
+ - `YOUTRACK_USE_STRUCTURED_CONTENT` — optional, controls response format (default: `true`). When `true`, tools return only the MCP `structuredContent` node with full data. When `false`, tools return only the MCP `content` node (single text item with JSON string)
61
62
 
62
63
  ## Installation
63
64
 
@@ -68,6 +69,7 @@ You can run the server directly with npx without installation:
68
69
  ```bash
69
70
  YOUTRACK_URL="https://youtrack.example.com" \
70
71
  YOUTRACK_TOKEN="perm:your-token-here" \
72
+ YOUTRACK_DEFAULT_PROJECT="PROJ" \
71
73
  npx -y @vitalyostanin/youtrack-mcp@latest
72
74
  ```
73
75
 
@@ -282,7 +284,7 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
282
284
  | `issue_lookup` | Brief issue information | `issueId` — issue code (e.g., PROJ-123) |
283
285
  | `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` |
284
286
  | `issue_comments` | Issue comments | `issueId` — issue code |
285
- | `issue_create` | Create issue | `projectId`, `summary`, optionally `description`, `parentIssueId`, `assigneeLogin`, `usesMarkdown` |
287
+ | `issue_create` | Create issue | `projectId`, `summary`, optional `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links` (array of link objects) |
286
288
  | `issue_update` | Update existing issue | `issueId`, optionally `summary`, `description`, `parentIssueId` (empty string clears parent), `usesMarkdown` |
287
289
  | `issue_assign` | Assign issue to user | `issueId`, `assigneeLogin` (login or `me`) |
288
290
  | `issue_comment_create` | Add comment to issue | `issueId`, `text` — comment text, optionally `usesMarkdown` |
@@ -352,12 +354,16 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
352
354
  | `article_create` | Create article in knowledge base | `summary`, optionally `content`, `parentArticleId`, `projectId`, `usesMarkdown`, `returnRendered` |
353
355
  | `article_update` | Update article | `articleId`, optionally `summary`, `content`, `usesMarkdown`, `returnRendered` |
354
356
  | `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered` |
357
+ | `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`, `limit`, `skip` |
355
359
 
356
360
  ### Search
357
361
 
358
362
  | Tool | Description | Main Parameters |
359
363
  | --- | --- | --- |
360
364
  | `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered` |
365
+ | `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
+ | `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 |
361
367
 
362
368
  ## Important Notes
363
369
 
@@ -366,3 +372,12 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
366
372
  Some operations cannot be undone and require explicit confirmation:
367
373
 
368
374
  - **`issue_attachment_delete`** - Requires `confirmation: true` parameter. Deleted attachments cannot be recovered.
375
+ **issue_create parameters:**
376
+
377
+ - `parentIssueId` — resolves against `YOUTRACK_DEFAULT_PROJECT` when no project prefix is provided (e.g., `123` → `BC-123` if default project is `BC`).
378
+ - `links[]` — optional array describing additional links to create after the issue is saved. Each item supports:
379
+ - `linkType` — name or id of the link type (e.g., `Subtask`, `Relates`).
380
+ - `targetId` — issue id or readable id. Plain numbers are resolved with the default project, matching `resolveIssueId` behaviour.
381
+ - `direction` — optional (`"outbound"` by default). Use `"inbound"` to flip direction for non-symmetric link types.
382
+ - `sourceId` — optional. When omitted, the new issue is treated as the source. Specify to create links from an existing issue to the new one (useful for complex chains).
383
+ - After creation the tool automatically refreshes the issue and attempts to reopen link caches; always re-fetch links with `issue_links` to confirm the final state.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitalyostanin/youtrack-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "scripts": {
5
5
  "build": "tsc -p tsconfig.json",
6
6
  "postbuild": "chmod +x dist/index.js",
@@ -6,6 +6,7 @@ const configSchema = z.object({
6
6
  YOUTRACK_HOLIDAYS: z.string().optional(),
7
7
  YOUTRACK_PRE_HOLIDAYS: z.string().optional(),
8
8
  YOUTRACK_USER_ALIASES: z.string().optional(),
9
+ YOUTRACK_DEFAULT_PROJECT: z.string().optional(),
9
10
  YOUTRACK_USE_STRUCTURED_CONTENT: z
10
11
  .string()
11
12
  .optional()
@@ -37,6 +38,7 @@ export function loadConfig(env = process.env) {
37
38
  userAliases: parsed.data.YOUTRACK_USER_ALIASES
38
39
  ? parseAliasMap(parsed.data.YOUTRACK_USER_ALIASES)
39
40
  : undefined,
41
+ defaultProject: parsed.data.YOUTRACK_DEFAULT_PROJECT,
40
42
  useStructuredContent: parsed.data.YOUTRACK_USE_STRUCTURED_CONTENT,
41
43
  };
42
44
  }
@@ -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
@@ -5,7 +5,10 @@ const articleLookupArgs = {
5
5
  };
6
6
  const articleListArgs = {
7
7
  parentArticleId: z.string().optional().describe("Parent article ID"),
8
- projectId: z.string().optional().describe("Project ID"),
8
+ projectId: z
9
+ .string()
10
+ .optional()
11
+ .describe("Project ID (defaults to YOUTRACK_DEFAULT_PROJECT when omitted)"),
9
12
  };
10
13
  const articleCreateArgs = {
11
14
  summary: z.string().min(1).describe("Article title"),
@@ -14,7 +17,10 @@ const articleCreateArgs = {
14
17
  .optional()
15
18
  .describe("Article content. Supports folded sections: <details> <summary>Title</summary>Content</details>"),
16
19
  parentArticleId: z.string().optional().describe("Parent article ID"),
17
- projectId: z.string().optional().describe("Project ID"),
20
+ projectId: z
21
+ .string()
22
+ .optional()
23
+ .describe("Project ID (defaults to YOUTRACK_DEFAULT_PROJECT when omitted)"),
18
24
  usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
19
25
  returnRendered: z.boolean().optional().describe("Return rendered content preview"),
20
26
  };
@@ -60,7 +66,7 @@ export function registerArticleTools(server, client) {
60
66
  return errorResponse;
61
67
  }
62
68
  });
63
- server.tool("article_create", "Create article in YouTrack knowledge base. Supports markdown with folded sections (<details>/<summary>) in content. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name).", articleCreateArgs, async (rawInput) => {
69
+ server.tool("article_create", "Create article in YouTrack knowledge base. Supports markdown with folded sections (<details>/<summary>) in content. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name). After creation, fetch the article again to confirm rendered content and hierarchy are correct.", articleCreateArgs, async (rawInput) => {
64
70
  try {
65
71
  const payload = articleCreateSchema.parse(rawInput);
66
72
  const article = await client.createArticle({
@@ -79,7 +85,7 @@ export function registerArticleTools(server, client) {
79
85
  return errorResponse;
80
86
  }
81
87
  });
82
- server.tool("article_update", "Update existing article. Supports markdown with folded sections (<details>/<summary>) in content. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name).", articleUpdateArgs, async (rawInput) => {
88
+ server.tool("article_update", "Update existing article. Supports markdown with folded sections (<details>/<summary>) in content. Note: Response includes predefined fields only - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle (id, idReadable), project (id, shortName, name). After updating, re-fetch the article (optionally with rendered content) to verify formatting and links.", articleUpdateArgs, async (rawInput) => {
83
89
  try {
84
90
  const payload = articleUpdateSchema.parse(rawInput);
85
91
  if (payload.summary === undefined && payload.content === undefined) {
@@ -64,7 +64,7 @@ export function registerAttachmentTools(server, client) {
64
64
  return errorResponse;
65
65
  }
66
66
  });
67
- server.tool("issue_attachment_upload", "Upload one or more files to a YouTrack issue. Files must exist on the local filesystem. Note: Can only attach files to existing issues, not during issue creation.", attachmentUploadArgs, async (rawInput) => {
67
+ server.tool("issue_attachment_upload", "Upload one or more files to a YouTrack issue. Files must exist on the local filesystem. Note: Can only attach files to existing issues, not during issue creation. After uploading, fetch the attachments list to verify each file appears with correct metadata.", attachmentUploadArgs, async (rawInput) => {
68
68
  try {
69
69
  const payload = attachmentUploadSchema.parse(rawInput);
70
70
  const result = await client.uploadAttachments({
@@ -80,7 +80,7 @@ export function registerAttachmentTools(server, client) {
80
80
  return errorResponse;
81
81
  }
82
82
  });
83
- server.tool("issue_attachment_delete", "Delete an attachment from a YouTrack issue. IMPORTANT: Requires explicit confirmation via the 'confirmation' parameter to prevent accidental deletion. This is a destructive operation that cannot be undone.", attachmentDeleteArgs, async (rawInput) => {
83
+ server.tool("issue_attachment_delete", "Delete an attachment from a YouTrack issue. IMPORTANT: Requires explicit confirmation via the 'confirmation' parameter to prevent accidental deletion. This is a destructive operation that cannot be undone. After deletion, list attachments again to ensure the file no longer appears.", attachmentDeleteArgs, async (rawInput) => {
84
84
  try {
85
85
  const payload = attachmentDeleteSchema.parse(rawInput);
86
86
  const result = await client.deleteAttachment({
@@ -11,13 +11,12 @@ const linkCreateArgs = {
11
11
  .string()
12
12
  .min(1)
13
13
  .describe("Link type name or id (e.g., 'Relates', 'Duplicate', or a type id)"),
14
+ direction: z
15
+ .enum(["inbound", "outbound"])
16
+ .optional()
17
+ .describe("Direction relative to the source issue (use 'inbound' to flip the relationship)"),
14
18
  };
15
19
  const linkCreateSchema = z.object(linkCreateArgs);
16
- const linkDeleteArgs = {
17
- issueId: z.string().min(1).describe("Issue code that holds the link (e.g., PROJ-123)"),
18
- linkId: z.string().min(1).describe("Issue link id (internal youtrack id)"),
19
- };
20
- const linkDeleteSchema = z.object(linkDeleteArgs);
21
20
  export function registerIssueLinkTools(server, client) {
22
21
  server.tool("issue_links", "List issue links for a given YouTrack issue. Use for: inspecting relationships like 'relates to', 'duplicates', 'parent/child'. Response includes link id, direction, linkType, and counterpart issue brief (idReadable, summary, project, assignee).", issueIdArgs, async (rawInput) => {
23
22
  try {
@@ -38,7 +37,7 @@ export function registerIssueLinkTools(server, client) {
38
37
  return toolError(error);
39
38
  }
40
39
  });
41
- server.tool("issue_link_add", "Create a link between two issues. Provide sourceId, targetId and linkType (name or id). Limitations: link type must exist in project context; API may reject invalid combinations.", linkCreateArgs, async (rawInput) => {
40
+ server.tool("issue_link_add", "Create a link between two issues. Provide sourceId, targetId and linkType (name or id). Limitations: link type must exist in project context; API may reject invalid combinations. After linking, fetch the issue links again to confirm the relationship appears as expected.", linkCreateArgs, async (rawInput) => {
42
41
  try {
43
42
  const input = linkCreateSchema.parse(rawInput);
44
43
  const result = await client.addIssueLink(input);
@@ -48,15 +47,5 @@ export function registerIssueLinkTools(server, client) {
48
47
  return toolError(error);
49
48
  }
50
49
  });
51
- server.tool("issue_link_delete", "Delete an existing issue link by id for a given issue. Use 'issue_links' to discover link ids first.", linkDeleteArgs, async (rawInput) => {
52
- try {
53
- const input = linkDeleteSchema.parse(rawInput);
54
- const result = await client.deleteIssueLink(input);
55
- return toolSuccess(result);
56
- }
57
- catch (error) {
58
- return toolError(error);
59
- }
60
- });
61
50
  }
62
51
  //# sourceMappingURL=issue-link-tools.js.map
@@ -1,4 +1,95 @@
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.ZodString;
5
+ limit: z.ZodDefault<z.ZodNumber>;
6
+ skip: z.ZodDefault<z.ZodNumber>;
7
+ };
8
+ export declare const issuesSearchSchema: z.ZodObject<{
9
+ query: z.ZodString;
10
+ limit: z.ZodDefault<z.ZodNumber>;
11
+ skip: z.ZodDefault<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ query: string;
14
+ skip: number;
15
+ limit: number;
16
+ }, {
17
+ query: string;
18
+ skip?: number | undefined;
19
+ limit?: number | undefined;
20
+ }>;
21
+ export declare function issuesSearchHandler(client: YoutrackClient, rawInput: unknown): Promise<{
22
+ [x: string]: unknown;
23
+ content: ({
24
+ [x: string]: unknown;
25
+ text: string;
26
+ type: "text";
27
+ _meta?: {
28
+ [x: string]: unknown;
29
+ } | undefined;
30
+ } | {
31
+ [x: string]: unknown;
32
+ type: "image";
33
+ data: string;
34
+ mimeType: string;
35
+ _meta?: {
36
+ [x: string]: unknown;
37
+ } | undefined;
38
+ } | {
39
+ [x: string]: unknown;
40
+ type: "audio";
41
+ data: string;
42
+ mimeType: string;
43
+ _meta?: {
44
+ [x: string]: unknown;
45
+ } | undefined;
46
+ } | {
47
+ [x: string]: unknown;
48
+ name: string;
49
+ type: "resource_link";
50
+ uri: string;
51
+ description?: string | undefined;
52
+ _meta?: {
53
+ [x: string]: unknown;
54
+ } | undefined;
55
+ mimeType?: string | undefined;
56
+ title?: string | undefined;
57
+ icons?: {
58
+ [x: string]: unknown;
59
+ src: string;
60
+ mimeType?: string | undefined;
61
+ sizes?: string[] | undefined;
62
+ }[] | undefined;
63
+ } | {
64
+ [x: string]: unknown;
65
+ type: "resource";
66
+ resource: {
67
+ [x: string]: unknown;
68
+ text: string;
69
+ uri: string;
70
+ _meta?: {
71
+ [x: string]: unknown;
72
+ } | undefined;
73
+ mimeType?: string | undefined;
74
+ } | {
75
+ [x: string]: unknown;
76
+ uri: string;
77
+ blob: string;
78
+ _meta?: {
79
+ [x: string]: unknown;
80
+ } | undefined;
81
+ mimeType?: string | undefined;
82
+ };
83
+ _meta?: {
84
+ [x: string]: unknown;
85
+ } | undefined;
86
+ })[];
87
+ _meta?: {
88
+ [x: string]: unknown;
89
+ } | undefined;
90
+ structuredContent?: {
91
+ [x: string]: unknown;
92
+ } | undefined;
93
+ isError?: boolean | undefined;
94
+ }>;
4
95
  //# sourceMappingURL=issue-search-tools.d.ts.map