@vitalyostanin/youtrack-mcp 0.7.0 → 0.7.1

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
@@ -53,6 +53,7 @@ MCP сервер для полноценной интеграции с YouTrack
53
53
  - `YOUTRACK_HOLIDAYS` — опциональный список праздничных дат через запятую (формат `YYYY-MM-DD`), исключаемых из отчётов и массовых операций
54
54
  - `YOUTRACK_PRE_HOLIDAYS` — опциональный список предпраздничных дат через запятую, для которых норма времени уменьшается автоматически
55
55
  - `YOUTRACK_USER_ALIASES` — опциональный список соответствий `alias:login` через запятую (например, `me:vyt,petya:p.petrov`), используется при автоматическом выборе исполнителей
56
+ - `YOUTRACK_DEFAULT_PROJECT` — опциональный код проекта (shortName или ID), используемый по умолчанию при создании задач/статей и для преобразования коротких идентификаторов (например, `1234` → `PROJ-1234`)
56
57
  - `YOUTRACK_USE_STRUCTURED_CONTENT` — опциональный, управляет форматом ответа (по умолчанию: `true`). При `true` инструменты возвращают только узел MCP `structuredContent` с полными данными. При `false` инструменты возвращают только узел MCP `content` (один текстовый элемент с JSON-строкой)
57
58
 
58
59
  ## Установка
@@ -64,6 +65,7 @@ MCP сервер для полноценной интеграции с YouTrack
64
65
  ```bash
65
66
  YOUTRACK_URL="https://youtrack.example.com" \
66
67
  YOUTRACK_TOKEN="perm:your-token-here" \
68
+ YOUTRACK_DEFAULT_PROJECT="PROJ" \
67
69
  npx -y @vitalyostanin/youtrack-mcp@latest
68
70
  ```
69
71
 
@@ -281,7 +283,7 @@ YOUTRACK_TOKEN = "perm:your-token-here"
281
283
  | `issues_details` | Режимы краткий/полный для множества задач (макс 50). **Краткий:** предопределённые поля. **Полный (`briefOutput=false`):** добавляет `customFields` для каждой задачи. **Замечание:** объём ответа может быть большим, поэтому по умолчанию используется краткий режим. **Ограничение:** максимум 50 задач за запрос | `issueIds[]` — массив кодов задач; `briefOutput` — опционально (по умолчанию `true`) |
282
284
  | `issue_comments` | Комментарии задачи. **Использование:** Просмотр всех комментариев к задаче. **Возвращает:** предопределенные поля - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated | `issueId` — код задачи |
283
285
  | `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` |
286
+ | `issue_create` | Создание новой задачи в YouTrack. **Использование:** Создание задач, подзадач (через `parentIssueId`), назначение исполнителя при создании, одновременное создание дополнительных связей. **Возвращает:** стандартные поля созданной задачи (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Пользовательские поля не включены. | `projectId` — внутренний ID проекта, `summary`, опционально `description`, `parentIssueId`, `assigneeLogin`, `stateName`, `usesMarkdown`, `links[]` — массив объектов связи |
285
287
  | `issue_update` | Обновление существующей задачи. **Использование:** Изменение summary, description, родительской задачи. **Примечание:** для изменения исполнителя используйте `issue_assign`. Пустая строка в `parentIssueId` удаляет родителя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, опционально `summary`, `description`, `parentIssueId` (пустая строка очищает родителя), `usesMarkdown` |
286
288
  | `issue_assign` | Назначение исполнителя для задачи. **Использование:** Изменение исполнителя задачи. Поддерживает алиас `me` для текущего пользователя. **Возвращает:** стандартные поля обновленной задачи | `issueId` — ID или код задачи, `assigneeLogin` — логин исполнителя или `me` |
287
289
  | `issue_comment_create` | Добавление комментария к задаче. **Использование:** Оставлять комментарии, заметки, обсуждения в задачах. Поддерживает Markdown. **Возвращает:** поля комментария - id, text, textPreview, usesMarkdown, author, created, updated | `issueId` — ID или код задачи, `text` — текст комментария, опционально `usesMarkdown` |
@@ -299,6 +301,16 @@ YOUTRACK_TOKEN = "perm:your-token-here"
299
301
  | `issue_link_add` | Создание связи между двумя задачами | `sourceId` — исходная задача, `targetId` — целевая задача, `linkType` — имя или id типа связи (например, `Relates` или UUID) |
300
302
  | `issue_link_delete` | Удаление существующей связи по её id для указанной задачи | `issueId` — код задачи, `linkId` — id связи |
301
303
 
304
+ **issue_create — дополнительные параметры:**
305
+
306
+ - `parentIssueId` — если указан только числовой идентификатор (`"123"`), автоматически добавляет префикс проекта по умолчанию (например, `BC-123`).
307
+ - `links[]` — массив объектов для создания связей после сохранения задачи. Каждый объект поддерживает поля:
308
+ - `linkType` — имя или id типа связи (например, `Subtask`, `Relates`).
309
+ - `targetId` — idReadable задачи или числовой идентификатор, который также резолвится через проект по умолчанию.
310
+ - `direction` — опционально (`"outbound"` по умолчанию). Используйте `"inbound"`, чтобы инвертировать направление для несимметричных типов.
311
+ - `sourceId` — опционально. Если не указан, исходной считается новая задача; укажите его, чтобы привязать новую задачу как `target` к уже существующей.
312
+ - После создания задачи повторно запросите связи (`issue_links`) для подтверждения, что YouTrack применил все отношения.
313
+
302
314
  ### Звездочки задач
303
315
 
304
316
  | Tool | Описание | Основные параметры |
@@ -346,6 +358,8 @@ YOUTRACK_TOKEN = "perm:your-token-here"
346
358
  | `article_create` | Создание статьи в базе знаний. **Использование:** Создание документации, инструкций, статей поддержки. Поддерживает иерархическую структуру через `parentArticleId`. **Возвращает:** предопределенные поля созданной статьи - id, idReadable, summary, content, contentPreview, usesMarkdown, parentArticle, project | `summary` — заголовок статьи, опционально `content` — содержимое, `parentArticleId` — ID родительской статьи для создания подстатьи, `projectId` — ID проекта, `usesMarkdown` — использовать Markdown форматирование, `returnRendered` — вернуть отрендеренный preview |
347
359
  | `article_update` | Обновление существующей статьи. **Использование:** Редактирование содержимого, изменение заголовка статьи. **Возвращает:** предопределенные поля обновленной статьи | `articleId` — ID статьи, опционально `summary` — новый заголовок, `content` — новое содержимое, `usesMarkdown`, `returnRendered` |
348
360
  | `article_search` | Поиск статей в базе знаний по тексту. **Использование:** Полнотекстовый поиск по заголовкам и содержимому статей, поиск документации. Поддерживает фильтрацию по проекту и родительской статье. **Возвращает:** предопределенные поля - id, idReadable, summary, usesMarkdown, contentPreview (если `returnRendered=true`), parentArticle, project. Поле content не включено для производительности. **Ограничение:** минимум 2 символа в запросе, максимум 200 результатов | `query` — текст для поиска (минимум 2 символа), опционально `projectId` — фильтр по проекту, `parentArticleId` — фильтр по родительской статье, `limit` — максимум результатов (макс 200), `returnRendered` — вернуть отрендеренный preview содержимого |
361
+ | `issues_list` | Список задач по фильтрам с поддержкой сортировки. **Использование:** Дашборды, аудит командной загрузки, подготовка батч-операций. Поддерживает фильтрацию по проектам, датам создания/обновления, статусам, типам, исполнителю. **Возвращает:** краткие или полные данные задачи в зависимости от `briefOutput`, а также информацию о сортировке и пагинации. | Фильтры: `projectIds`, `createdAfter/Before`, `updatedAfter/Before`, `statuses`, `assigneeLogin`, `types`; сортировка: `sortField` (`created`/`updated`), `sortDirection` (`asc`/`desc`); пагинация: `limit`, `skip`; формат: `briefOutput` |
362
+ | `issues_count` | Подсчёт задач с теми же фильтрами, что у `issues_list`, с разбивкой по проектам. **Использование:** Быстрая оценка объёмов работ перед загрузкой полного списка, подготовка аналитических отчётов. При запросе одного проекта обращается к `/api/issuesGetter/count`, иначе считает постранично. | Те же фильтры, что и `issues_list`; опциональный `top` ограничивает объём ручной агрегации (при необходимости частичной выборки) |
349
363
 
350
364
  ### Вложения
351
365
 
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` |
@@ -358,6 +360,8 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
358
360
  | Tool | Description | Main Parameters |
359
361
  | --- | --- | --- |
360
362
  | `article_search` | Search articles in knowledge base | `query`, optionally `projectId`, `parentArticleId`, `limit`, `returnRendered` |
363
+ | `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
+ | `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
365
 
362
366
  ## Important Notes
363
367
 
@@ -366,3 +370,12 @@ Tools return either `structuredContent` (default) or a text `content` item, depe
366
370
  Some operations cannot be undone and require explicit confirmation:
367
371
 
368
372
  - **`issue_attachment_delete`** - Requires `confirmation: true` parameter. Deleted attachments cannot be recovered.
373
+ **issue_create parameters:**
374
+
375
+ - `parentIssueId` — resolves against `YOUTRACK_DEFAULT_PROJECT` when no project prefix is provided (e.g., `123` → `BC-123` if default project is `BC`).
376
+ - `links[]` — optional array describing additional links to create after the issue is saved. Each item supports:
377
+ - `linkType` — name or id of the link type (e.g., `Subtask`, `Relates`).
378
+ - `targetId` — issue id or readable id. Plain numbers are resolved with the default project, matching `resolveIssueId` behaviour.
379
+ - `direction` — optional (`"outbound"` by default). Use `"inbound"` to flip direction for non-symmetric link types.
380
+ - `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).
381
+ - 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.1",
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
  }
@@ -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
@@ -36,6 +36,108 @@ const issueSearchByUserActivityArgs = {
36
36
  .describe("Number of results to skip for pagination (default: 0)"),
37
37
  };
38
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
65
+ .string()
66
+ .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);
39
141
  export function registerIssueSearchTools(server, client) {
40
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) => {
41
143
  try {
@@ -57,5 +159,53 @@ export function registerIssueSearchTools(server, client) {
57
159
  return errorResponse;
58
160
  }
59
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;
182
+ }
183
+ catch (error) {
184
+ const errorResponse = toolError(error);
185
+ return errorResponse;
186
+ }
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;
204
+ }
205
+ catch (error) {
206
+ const errorResponse = toolError(error);
207
+ return errorResponse;
208
+ }
209
+ });
60
210
  }
61
211
  //# sourceMappingURL=issue-search-tools.js.map
@@ -21,15 +21,15 @@ function createToolHandler(handler) {
21
21
  */
22
22
  export function registerIssueStarTools(server, client) {
23
23
  // Tool: issue_star
24
- server.tool("issue_star", "Add star to YouTrack issue for current user. Use for: Marking important issues, Adding issues to watchlist, Quick access to frequently used issues. Returns: Confirmation of star status with issueId and starred flag. Note: If issue is already starred, returns success without making changes (idempotent operation).", {
24
+ server.tool("issue_star", "Add star to YouTrack issue for current user. Use for: Marking important issues, Adding issues to watchlist, Quick access to frequently used issues. Returns: Confirmation of star status with issueId and starred flag. Note: If issue is already starred, returns success without making changes (idempotent operation). After starring, fetch the issue or starred list again to confirm the flag is set.", {
25
25
  issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
26
26
  }, createToolHandler(async (args) => client.starIssue(args.issueId)));
27
27
  // Tool: issue_unstar
28
- server.tool("issue_unstar", "Remove star from YouTrack issue for current user. Use for: Removing issues from watchlist, Cleaning up unneeded stars, Managing starred issues list. Returns: Confirmation of unstar operation with issueId and starred flag. Note: If issue is not currently starred, returns success without making changes (idempotent operation).", {
28
+ server.tool("issue_unstar", "Remove star from YouTrack issue for current user. Use for: Removing issues from watchlist, Cleaning up unneeded stars, Managing starred issues list. Returns: Confirmation of unstar operation with issueId and starred flag. Note: If issue is not currently starred, returns success without making changes (idempotent operation). After unstarring, refresh the issue or starred list to ensure the star was cleared.", {
29
29
  issueId: z.string().min(1).describe("Issue code (e.g., PROJ-123)"),
30
30
  }, createToolHandler(async (args) => client.unstarIssue(args.issueId)));
31
31
  // Tool: issues_star_batch
32
- server.tool("issues_star_batch", "Add stars to multiple YouTrack issues (batch mode, max 50 issues). Use for: Bulk marking important issues, Batch adding to watchlist, Processing multiple issues at once, Quick setup of starred issues list. Returns: Object with 'successful' array (starred issues) and 'failed' array (errors with issue IDs). Note: Operations are processed with concurrency limit (10 concurrent requests) to prevent API overload. Partial success is possible - some issues may succeed while others fail.", {
32
+ server.tool("issues_star_batch", "Add stars to multiple YouTrack issues (batch mode, max 50 issues). Use for: Bulk marking important issues, Batch adding to watchlist, Processing multiple issues at once, Quick setup of starred issues list. Returns: Object with 'successful' array (starred issues) and 'failed' array (errors with issue IDs). Note: Operations are processed with concurrency limit (10 concurrent requests) to prevent API overload. Partial success is possible - some issues may succeed while others fail. After completion, reload the starred list to confirm which issues were starred.", {
33
33
  issueIds: z
34
34
  .array(z.string().min(1))
35
35
  .min(1)
@@ -37,7 +37,7 @@ export function registerIssueStarTools(server, client) {
37
37
  .describe("Array of issue codes (e.g., ['PROJ-123', 'PROJ-124']), max 50"),
38
38
  }, createToolHandler(async (args) => client.starIssues(args.issueIds)));
39
39
  // Tool: issues_unstar_batch
40
- server.tool("issues_unstar_batch", "Remove stars from multiple YouTrack issues (batch mode, max 50 issues). Use for: Bulk cleanup of watchlist, Batch removal of unneeded stars, Processing multiple issues at once, Managing starred issues list. Returns: Object with 'successful' array (unstarred issues) and 'failed' array (errors with issue IDs). Note: Operations are processed with concurrency limit (10 concurrent requests) to prevent API overload. Partial success is possible - some issues may succeed while others fail.", {
40
+ server.tool("issues_unstar_batch", "Remove stars from multiple YouTrack issues (batch mode, max 50 issues). Use for: Bulk cleanup of watchlist, Batch removal of unneeded stars, Processing multiple issues at once, Managing starred issues list. Returns: Object with 'successful' array (unstarred issues) and 'failed' array (errors with issue IDs). Note: Operations are processed with concurrency limit (10 concurrent requests) to prevent API overload. Partial success is possible - some issues may succeed while others fail. After completion, fetch the starred list again to verify removals.", {
41
41
  issueIds: z
42
42
  .array(z.string().min(1))
43
43
  .min(1)
@@ -21,7 +21,7 @@ const issueIdsArgs = {
21
21
  };
22
22
  const issueIdsSchema = z.object(issueIdsArgs);
23
23
  const issueCreateArgs = {
24
- projectId: z.string().min(1).describe("Project ID (YouTrack internal id)"),
24
+ projectId: z.string().optional().describe("Project ID (defaults to YOUTRACK_DEFAULT_PROJECT when omitted)"),
25
25
  summary: z.string().min(1).describe("Brief issue description"),
26
26
  description: z
27
27
  .string()
@@ -29,6 +29,22 @@ const issueCreateArgs = {
29
29
  .describe("Full description. Supports folded sections: <details> <summary>Title</summary>Content</details>"),
30
30
  parentIssueId: z.string().optional().describe("Parent issue ID"),
31
31
  assigneeLogin: z.string().optional().describe("Assignee login or me"),
32
+ stateName: z.string().optional().describe("Initial state name (case-insensitive)"),
33
+ links: z
34
+ .array(z.object({
35
+ linkType: z.string().min(1).describe("Link type name or id (e.g., 'Subtask')"),
36
+ targetId: z.string().min(1).describe("Target issue code or readable id"),
37
+ sourceId: z
38
+ .string()
39
+ .optional()
40
+ .describe("Source issue code override (defaults to the new issue)"),
41
+ direction: z
42
+ .enum(["inbound", "outbound"])
43
+ .optional()
44
+ .describe("Direction relative to the new issue (default: outbound)"),
45
+ }))
46
+ .optional()
47
+ .describe("Optional array of links to create after issue creation"),
32
48
  usesMarkdown: z.boolean().optional().describe("Use Markdown formatting"),
33
49
  };
34
50
  const issueCreateSchema = z.object(issueCreateArgs);
@@ -114,15 +130,17 @@ export function registerIssueTools(server, client) {
114
130
  return errorResponse;
115
131
  }
116
132
  });
117
- server.tool("issue_create", "Create new issue in YouTrack. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueCreateArgs, async (rawInput) => {
133
+ server.tool("issue_create", "Create new issue in YouTrack. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included. After the call, fetch the created issue again to confirm that every requested property (assignee, state, links, etc.) was applied by YouTrack.", issueCreateArgs, async (rawInput) => {
118
134
  try {
119
135
  const payload = issueCreateSchema.parse(rawInput);
120
136
  const issue = await client.createIssue({
121
- project: payload.projectId,
137
+ projectId: payload.projectId,
122
138
  summary: payload.summary,
123
139
  description: payload.description,
124
140
  parentIssueId: payload.parentIssueId,
125
141
  assigneeLogin: payload.assigneeLogin,
142
+ stateName: payload.stateName,
143
+ links: payload.links,
126
144
  usesMarkdown: payload.usesMarkdown,
127
145
  });
128
146
  const response = toolSuccess(issue);
@@ -133,7 +151,7 @@ export function registerIssueTools(server, client) {
133
151
  return errorResponse;
134
152
  }
135
153
  });
136
- server.tool("issue_update", "Update existing issue. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueUpdateArgs, async (rawInput) => {
154
+ server.tool("issue_update", "Update existing issue. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included. Always re-fetch the issue after the update to verify that each requested change was applied.", issueUpdateArgs, async (rawInput) => {
137
155
  try {
138
156
  const payload = issueUpdateSchema.parse(rawInput);
139
157
  if (payload.summary === undefined &&
@@ -156,7 +174,7 @@ export function registerIssueTools(server, client) {
156
174
  return errorResponse;
157
175
  }
158
176
  });
159
- server.tool("issue_assign", "Assign assignee to issue. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included.", issueAssignArgs, async (rawInput) => {
177
+ server.tool("issue_assign", "Assign assignee to issue. Note: Response includes standard fields only (id, idReadable, summary, description, wikifiedDescription, usesMarkdown, project, parent, assignee). Custom fields are not included. After assignment, fetch the issue again to ensure the new assignee is set.", issueAssignArgs, async (rawInput) => {
160
178
  try {
161
179
  const payload = issueAssignSchema.parse(rawInput);
162
180
  const issue = await client.assignIssue({
@@ -171,7 +189,7 @@ export function registerIssueTools(server, client) {
171
189
  return errorResponse;
172
190
  }
173
191
  });
174
- server.tool("issue_comment_create", "Add comment to issue. Supports markdown with folded sections (<details>/<summary>) for hiding logs, code examples, etc. Note: Response includes comment fields - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (direct link to comment).", issueCommentCreateArgs, async (rawInput) => {
192
+ server.tool("issue_comment_create", "Add comment to issue. Supports markdown with folded sections (<details>/<summary>) for hiding logs, code examples, etc. Note: Response includes comment fields - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (direct link to comment). Reload the comments list if you need to confirm formatting or visibility.", issueCommentCreateArgs, async (rawInput) => {
175
193
  try {
176
194
  const payload = issueCommentCreateSchema.parse(rawInput);
177
195
  const comment = await client.createIssueComment({
@@ -187,7 +205,7 @@ export function registerIssueTools(server, client) {
187
205
  return errorResponse;
188
206
  }
189
207
  });
190
- server.tool("issue_comment_update", "Update existing issue comment. Supports markdown with folded sections (<details>/<summary>). Note: Response includes comment fields - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (direct link to comment). Use for: Editing comment text, changing formatting mode, correcting typos in comments.", issueCommentUpdateArgs, async (rawInput) => {
208
+ server.tool("issue_comment_update", "Update existing issue comment. Supports markdown with folded sections (<details>/<summary>). Note: Response includes comment fields - id, text, textPreview, usesMarkdown, author (id, login, name), created, updated, commentUrl (direct link to comment). Use for: Editing comment text, changing formatting mode, correcting typos in comments. After updating, fetch the comment again if you need to confirm rendered content.", issueCommentUpdateArgs, async (rawInput) => {
191
209
  try {
192
210
  const payload = issueCommentUpdateSchema.parse(rawInput);
193
211
  if (payload.text === undefined && payload.usesMarkdown === undefined) {
@@ -245,7 +263,7 @@ export function registerIssueTools(server, client) {
245
263
  return errorResponse;
246
264
  }
247
265
  });
248
- server.tool("issue_change_state", "Change issue state/status using state machine transitions. Use for: Moving issues through workflow states (e.g., from 'Open' to 'In Progress'), updating issue status, triggering state transitions. Note: Only valid transitions are allowed based on current state and workflow rules. The tool automatically discovers available transitions and validates the requested state change. Returns information about the previous state, new state, and the transition used.", issueChangeStateArgs, async (rawInput) => {
266
+ server.tool("issue_change_state", "Change issue state/status using state machine transitions. Use for: Moving issues through workflow states (e.g., from 'Open' to 'In Progress'), updating issue status, triggering state transitions. Note: Only valid transitions are allowed based on current state and workflow rules. The tool automatically discovers available transitions and validates the requested state change. Returns information about the previous state, new state, and the transition used. After the transition, fetch issue details again to ensure the state is what you expect.", issueChangeStateArgs, async (rawInput) => {
249
267
  try {
250
268
  const payload = issueChangeStateSchema.parse(rawInput);
251
269
  const result = await client.changeIssueState({
@@ -132,7 +132,7 @@ export function registerWorkitemTools(server, client) {
132
132
  return errorResponse;
133
133
  }
134
134
  });
135
- server.tool("workitem_create", "Create work item record. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email).", workItemCreateArgs, async (rawInput) => {
135
+ server.tool("workitem_create", "Create work item record. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email). After the call, re-fetch work items to confirm the new record appears with expected duration and description.", workItemCreateArgs, async (rawInput) => {
136
136
  try {
137
137
  const payload = workItemCreateSchema.parse(rawInput);
138
138
  const item = await client.createWorkItemMapped({
@@ -151,7 +151,7 @@ export function registerWorkitemTools(server, client) {
151
151
  return errorResponse;
152
152
  }
153
153
  });
154
- server.tool("workitem_create_idempotent", "Create work item record if similar one does not exist. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email).", workItemIdempotentArgs, async (rawInput) => {
154
+ server.tool("workitem_create_idempotent", "Create work item record if similar one does not exist. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email). After creation, reload work items to verify whether the entry was newly added or an existing one was reused.", workItemIdempotentArgs, async (rawInput) => {
155
155
  try {
156
156
  const payload = workItemIdempotentSchema.parse(rawInput);
157
157
  const item = await client.createWorkItemIdempotent({
@@ -169,7 +169,7 @@ export function registerWorkitemTools(server, client) {
169
169
  return errorResponse;
170
170
  }
171
171
  });
172
- server.tool("workitem_update", "Update work item record. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email).", workItemUpdateArgs, async (rawInput) => {
172
+ server.tool("workitem_update", "Update work item record. Supports markdown with folded sections (<details>/<summary>) in description. Note: Response includes predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email). After updating, fetch the work item again to confirm the new values were applied.", workItemUpdateArgs, async (rawInput) => {
173
173
  try {
174
174
  const payload = workItemUpdateSchema.parse(rawInput);
175
175
  if (payload.date === undefined &&
@@ -195,7 +195,7 @@ export function registerWorkitemTools(server, client) {
195
195
  return errorResponse;
196
196
  }
197
197
  });
198
- server.tool("workitem_delete", "Delete work item record", workItemDeleteArgs, async (rawInput) => {
198
+ server.tool("workitem_delete", "Delete work item record. After deletion, list work items again to ensure the record was removed.", workItemDeleteArgs, async (rawInput) => {
199
199
  try {
200
200
  const payload = workItemDeleteSchema.parse(rawInput);
201
201
  const result = await client.deleteWorkItem(payload.issueId, payload.workItemId);
@@ -207,7 +207,7 @@ export function registerWorkitemTools(server, client) {
207
207
  return errorResponse;
208
208
  }
209
209
  });
210
- server.tool("workitems_create_period", "Create work items for period. Supports markdown with folded sections (<details>/<summary>) in description. Note: Created work items include predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email).", workItemsPeriodArgs, async (rawInput) => {
210
+ server.tool("workitems_create_period", "Create work items for period. Supports markdown with folded sections (<details>/<summary>) in description. Note: Created work items include predefined fields only - id, date, duration (minutes, presentation), text, textPreview, usesMarkdown, description, issue (id, idReadable), author (id, login, name, email). After bulk creation, reload work items to validate that each day received the expected entry.", workItemsPeriodArgs, async (rawInput) => {
211
211
  try {
212
212
  const payload = workItemsPeriodSchema.parse(rawInput);
213
213
  const result = await client.createWorkItemsForPeriod({