yandex-direct-mcp-plus 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +186 -0
  3. package/dist/app/index.d.ts +2 -0
  4. package/dist/app/index.js +15 -0
  5. package/dist/app/registry.d.ts +2 -0
  6. package/dist/app/registry.js +83 -0
  7. package/dist/app/server.d.ts +4 -0
  8. package/dist/app/server.js +30 -0
  9. package/dist/shared/api/client.d.ts +1 -0
  10. package/dist/shared/api/client.js +16 -0
  11. package/dist/shared/api/errors.d.ts +2 -0
  12. package/dist/shared/api/errors.js +21 -0
  13. package/dist/shared/api/fetch.d.ts +5 -0
  14. package/dist/shared/api/fetch.js +62 -0
  15. package/dist/shared/api/json.d.ts +2 -0
  16. package/dist/shared/api/json.js +13 -0
  17. package/dist/shared/api/parse.d.ts +2 -0
  18. package/dist/shared/api/parse.js +10 -0
  19. package/dist/shared/api/reports.d.ts +6 -0
  20. package/dist/shared/api/reports.js +45 -0
  21. package/dist/shared/api/v4.d.ts +1 -0
  22. package/dist/shared/api/v4.js +18 -0
  23. package/dist/shared/config/endpoints.d.ts +3 -0
  24. package/dist/shared/config/endpoints.js +8 -0
  25. package/dist/shared/config/enums.d.ts +30 -0
  26. package/dist/shared/config/enums.js +137 -0
  27. package/dist/shared/config/env.d.ts +2 -0
  28. package/dist/shared/config/env.js +14 -0
  29. package/dist/shared/config/limits.d.ts +58 -0
  30. package/dist/shared/config/limits.js +54 -0
  31. package/dist/shared/lib/campaign-type.d.ts +12 -0
  32. package/dist/shared/lib/campaign-type.js +20 -0
  33. package/dist/shared/lib/date.d.ts +3 -0
  34. package/dist/shared/lib/date.js +17 -0
  35. package/dist/shared/lib/format.d.ts +3 -0
  36. package/dist/shared/lib/format.js +51 -0
  37. package/dist/shared/lib/id.d.ts +4 -0
  38. package/dist/shared/lib/id.js +21 -0
  39. package/dist/shared/lib/money.d.ts +6 -0
  40. package/dist/shared/lib/money.js +21 -0
  41. package/dist/shared/lib/pagination.d.ts +9 -0
  42. package/dist/shared/lib/pagination.js +26 -0
  43. package/dist/shared/lib/report.d.ts +1 -0
  44. package/dist/shared/lib/report.js +5 -0
  45. package/dist/shared/lib/tool.d.ts +24 -0
  46. package/dist/shared/lib/tool.js +20 -0
  47. package/dist/tools/account/handler.d.ts +3 -0
  48. package/dist/tools/account/handler.js +12 -0
  49. package/dist/tools/account/schema.d.ts +4 -0
  50. package/dist/tools/account/schema.js +7 -0
  51. package/dist/tools/account/tool.d.ts +1 -0
  52. package/dist/tools/account/tool.js +11 -0
  53. package/dist/tools/ad-extensions/handler.d.ts +5 -0
  54. package/dist/tools/ad-extensions/handler.js +35 -0
  55. package/dist/tools/ad-extensions/schema.d.ts +14 -0
  56. package/dist/tools/ad-extensions/schema.js +37 -0
  57. package/dist/tools/ad-extensions/tool.d.ts +3 -0
  58. package/dist/tools/ad-extensions/tool.js +27 -0
  59. package/dist/tools/ad-groups/handler.d.ts +5 -0
  60. package/dist/tools/ad-groups/handler.js +33 -0
  61. package/dist/tools/ad-groups/schema.d.ts +14 -0
  62. package/dist/tools/ad-groups/schema.js +27 -0
  63. package/dist/tools/ad-groups/tool.d.ts +3 -0
  64. package/dist/tools/ad-groups/tool.js +27 -0
  65. package/dist/tools/ad-images/handler.d.ts +5 -0
  66. package/dist/tools/ad-images/handler.js +47 -0
  67. package/dist/tools/ad-images/schema.d.ts +13 -0
  68. package/dist/tools/ad-images/schema.js +37 -0
  69. package/dist/tools/ad-images/tool.d.ts +1 -0
  70. package/dist/tools/ad-images/tool.js +11 -0
  71. package/dist/tools/ads/handler.d.ts +7 -0
  72. package/dist/tools/ads/handler.js +54 -0
  73. package/dist/tools/ads/schema.d.ts +27 -0
  74. package/dist/tools/ads/schema.js +68 -0
  75. package/dist/tools/ads/tool.d.ts +5 -0
  76. package/dist/tools/ads/tool.js +43 -0
  77. package/dist/tools/audience-targets/handler.d.ts +6 -0
  78. package/dist/tools/audience-targets/handler.js +96 -0
  79. package/dist/tools/audience-targets/schema.d.ts +29 -0
  80. package/dist/tools/audience-targets/schema.js +84 -0
  81. package/dist/tools/audience-targets/tool.d.ts +2 -0
  82. package/dist/tools/audience-targets/tool.js +19 -0
  83. package/dist/tools/bid-adjustments/handler.d.ts +4 -0
  84. package/dist/tools/bid-adjustments/handler.js +43 -0
  85. package/dist/tools/bid-adjustments/schema.d.ts +16 -0
  86. package/dist/tools/bid-adjustments/schema.js +50 -0
  87. package/dist/tools/bid-adjustments/tool.d.ts +2 -0
  88. package/dist/tools/bid-adjustments/tool.js +19 -0
  89. package/dist/tools/businesses/handler.d.ts +3 -0
  90. package/dist/tools/businesses/handler.js +26 -0
  91. package/dist/tools/businesses/schema.d.ts +6 -0
  92. package/dist/tools/businesses/schema.js +12 -0
  93. package/dist/tools/businesses/tool.d.ts +1 -0
  94. package/dist/tools/businesses/tool.js +11 -0
  95. package/dist/tools/campaigns/handler.d.ts +11 -0
  96. package/dist/tools/campaigns/handler.js +200 -0
  97. package/dist/tools/campaigns/schema.d.ts +46 -0
  98. package/dist/tools/campaigns/schema.js +109 -0
  99. package/dist/tools/campaigns/tool.d.ts +7 -0
  100. package/dist/tools/campaigns/tool.js +61 -0
  101. package/dist/tools/changes/handler.d.ts +5 -0
  102. package/dist/tools/changes/handler.js +33 -0
  103. package/dist/tools/changes/schema.d.ts +9 -0
  104. package/dist/tools/changes/schema.js +35 -0
  105. package/dist/tools/changes/tool.d.ts +1 -0
  106. package/dist/tools/changes/tool.js +11 -0
  107. package/dist/tools/dictionaries/handler.d.ts +5 -0
  108. package/dist/tools/dictionaries/handler.js +66 -0
  109. package/dist/tools/dictionaries/schema.d.ts +9 -0
  110. package/dist/tools/dictionaries/schema.js +23 -0
  111. package/dist/tools/dictionaries/tool.d.ts +2 -0
  112. package/dist/tools/dictionaries/tool.js +19 -0
  113. package/dist/tools/dynamic-targets/handler.d.ts +6 -0
  114. package/dist/tools/dynamic-targets/handler.js +96 -0
  115. package/dist/tools/dynamic-targets/schema.d.ts +33 -0
  116. package/dist/tools/dynamic-targets/schema.js +91 -0
  117. package/dist/tools/dynamic-targets/tool.d.ts +2 -0
  118. package/dist/tools/dynamic-targets/tool.js +19 -0
  119. package/dist/tools/feeds/handler.d.ts +3 -0
  120. package/dist/tools/feeds/handler.js +29 -0
  121. package/dist/tools/feeds/schema.d.ts +6 -0
  122. package/dist/tools/feeds/schema.js +12 -0
  123. package/dist/tools/feeds/tool.d.ts +1 -0
  124. package/dist/tools/feeds/tool.js +11 -0
  125. package/dist/tools/keywords/handler.d.ts +6 -0
  126. package/dist/tools/keywords/handler.js +53 -0
  127. package/dist/tools/keywords/schema.d.ts +21 -0
  128. package/dist/tools/keywords/schema.js +41 -0
  129. package/dist/tools/keywords/tool.d.ts +4 -0
  130. package/dist/tools/keywords/tool.js +35 -0
  131. package/dist/tools/negative-keywords/handler.d.ts +8 -0
  132. package/dist/tools/negative-keywords/handler.js +135 -0
  133. package/dist/tools/negative-keywords/merge.d.ts +2 -0
  134. package/dist/tools/negative-keywords/merge.js +36 -0
  135. package/dist/tools/negative-keywords/schema.d.ts +37 -0
  136. package/dist/tools/negative-keywords/schema.js +139 -0
  137. package/dist/tools/negative-keywords/shared-set-link.d.ts +3 -0
  138. package/dist/tools/negative-keywords/shared-set-link.js +21 -0
  139. package/dist/tools/negative-keywords/tool.d.ts +6 -0
  140. package/dist/tools/negative-keywords/tool.js +51 -0
  141. package/dist/tools/retargeting/handler.d.ts +4 -0
  142. package/dist/tools/retargeting/handler.js +47 -0
  143. package/dist/tools/retargeting/schema.d.ts +19 -0
  144. package/dist/tools/retargeting/schema.js +57 -0
  145. package/dist/tools/retargeting/tool.d.ts +2 -0
  146. package/dist/tools/retargeting/tool.js +19 -0
  147. package/dist/tools/search-queries/handler.d.ts +3 -0
  148. package/dist/tools/search-queries/handler.js +28 -0
  149. package/dist/tools/search-queries/schema.d.ts +7 -0
  150. package/dist/tools/search-queries/schema.js +17 -0
  151. package/dist/tools/search-queries/tool.d.ts +1 -0
  152. package/dist/tools/search-queries/tool.js +11 -0
  153. package/dist/tools/sitelinks/handler.d.ts +4 -0
  154. package/dist/tools/sitelinks/handler.js +28 -0
  155. package/dist/tools/sitelinks/schema.d.ts +13 -0
  156. package/dist/tools/sitelinks/schema.js +35 -0
  157. package/dist/tools/sitelinks/tool.d.ts +2 -0
  158. package/dist/tools/sitelinks/tool.js +19 -0
  159. package/dist/tools/statistics/handler.d.ts +3 -0
  160. package/dist/tools/statistics/handler.js +20 -0
  161. package/dist/tools/statistics/schema.d.ts +7 -0
  162. package/dist/tools/statistics/schema.js +17 -0
  163. package/dist/tools/statistics/tool.d.ts +1 -0
  164. package/dist/tools/statistics/tool.js +11 -0
  165. package/dist/tools/time-targeting/handler.d.ts +6 -0
  166. package/dist/tools/time-targeting/handler.js +124 -0
  167. package/dist/tools/time-targeting/schema.d.ts +19 -0
  168. package/dist/tools/time-targeting/schema.js +73 -0
  169. package/dist/tools/time-targeting/tool.d.ts +2 -0
  170. package/dist/tools/time-targeting/tool.js +20 -0
  171. package/dist/tools/vcards/handler.d.ts +4 -0
  172. package/dist/tools/vcards/handler.js +105 -0
  173. package/dist/tools/vcards/schema.d.ts +27 -0
  174. package/dist/tools/vcards/schema.js +43 -0
  175. package/dist/tools/vcards/tool.d.ts +2 -0
  176. package/dist/tools/vcards/tool.js +19 -0
  177. package/package.json +121 -0
@@ -0,0 +1,109 @@
1
+ // Контракт инструментов кампаний. Здесь же граница snake_case → camelCase
2
+ // и конвертация рублей в микроединицы: хендлер получает готовые значения.
3
+ import { z } from "zod";
4
+ import { CAMPAIGN_ACTIONS, CAMPAIGN_STATUS_ACTIONS, CAMPAIGN_STATUSES, CAMPAIGN_TYPES, CAMPAIGN_TYPES_CREATABLE, NETWORK_STRATEGIES, SEARCH_STRATEGIES, SETTABLE_NETWORK_STRATEGIES, SETTABLE_SEARCH_STRATEGIES } from "#shared/config/enums";
5
+ import { dateField } from "#shared/lib/date";
6
+ import { idField } from "#shared/lib/id";
7
+ import { rublesField } from "#shared/lib/money";
8
+ import { pageFields } from "#shared/lib/pagination";
9
+ export const listCampaignsSchema = z.object({
10
+ status: z.literal(CAMPAIGN_STATUSES).optional().meta({ description: "Фильтр по статусу модерации кампании" }),
11
+ types: z.array(z.literal(CAMPAIGN_TYPES)).optional().meta({ description: "Фильтр по типам кампаний" }),
12
+ ...pageFields
13
+ });
14
+ export const getCampaignSchema = z.object({
15
+ campaign_id: idField("ID рекламной кампании, десятичная строка")
16
+ });
17
+ // UTM-разметка кампании. Директ дописывает эту строку к ссылке каждого объявления, поэтому
18
+ // метки задаются один раз на кампанию, а не вписываются в href руками. Знак «?» не нужен:
19
+ // его подставляет Директ, а присланный превратился бы в «??».
20
+ //
21
+ // Не литерал и без разбора на пары: набор меток открытый, а в значениях живут подстановки
22
+ // Директа ({campaign_id}, {keyword} и прочие) — проверка вида «ключ=значение» отвергала бы
23
+ // их. В WSDL это xsd:string без ограничения длины, поэтому проверяем только непустоту.
24
+ const trackingParamsField = () => z
25
+ .string()
26
+ .check(z.minLength(1, { error: "Разметка не может быть пустой строкой — чтобы снять её, передайте null" }))
27
+ .nullable()
28
+ .optional()
29
+ .meta({
30
+ description: "UTM-разметка, дописывается к ссылкам всех объявлений кампании. Без ведущего «?»: utm_source=yandex&utm_campaign={campaign_id}. Допустимы подстановки Директа в фигурных скобках. null снимает разметку"
31
+ });
32
+ export const createCampaignSchema = z.object({
33
+ name: z
34
+ .string()
35
+ .check(z.minLength(1, { error: "Название не может быть пустым" }))
36
+ .meta({ description: "Название кампании" }),
37
+ type: z
38
+ .literal(CAMPAIGN_TYPES_CREATABLE)
39
+ .default("TEXT_CAMPAIGN")
40
+ .meta({ description: "Тип кампании: текстово-графическая или динамические объявления" }),
41
+ start_date: dateField("Дата начала показов, YYYY-MM-DD"),
42
+ daily_budget: rublesField("Дневной бюджет в рублях, например 1000 — это 1000 ₽").optional(),
43
+ search_strategy: z
44
+ .literal(SEARCH_STRATEGIES)
45
+ .default("HIGHEST_POSITION")
46
+ .meta({ description: "Стратегия показов на поиске; SERVING_OFF отключает показы на поиске" }),
47
+ network_strategy: z
48
+ .literal(NETWORK_STRATEGIES)
49
+ .default("SERVING_OFF")
50
+ .meta({ description: "Стратегия показов в сетях (РСЯ); SERVING_OFF отключает показы в сетях" }),
51
+ // Не литерал: в WSDL это xsd:string, закрытого списка нет — значения живут
52
+ // в справочнике TimeZones, его отдаёт list_time_zones.
53
+ time_zone: z
54
+ .string()
55
+ .check(z.minLength(1, { error: "Часовой пояс не может быть пустым" }))
56
+ .optional()
57
+ .meta({
58
+ description: "Часовой пояс показов, например Europe/Moscow (по умолчанию). Список — в справочнике list_time_zones; на даты отчётов не влияет, они всегда по Москве"
59
+ }),
60
+ tracking_params: trackingParamsField()
61
+ });
62
+ export const updateCampaignSchema = z.object({
63
+ campaign_id: idField("ID кампании, десятичная строка"),
64
+ name: z
65
+ .string()
66
+ .check(z.minLength(1, { error: "Название не может быть пустым" }))
67
+ .optional()
68
+ .meta({ description: "Новое название" }),
69
+ daily_budget: rublesField("Новый дневной бюджет в рублях").optional(),
70
+ status: z
71
+ .literal(CAMPAIGN_STATUS_ACTIONS)
72
+ .optional()
73
+ .meta({ description: "Действие со статусом показов: SUSPEND (остановить), RESUME, ARCHIVE, UNARCHIVE" }),
74
+ tracking_params: trackingParamsField()
75
+ });
76
+ export const manageCampaignsSchema = z.object({
77
+ campaign_ids: z
78
+ .array(idField("ID кампании, десятичная строка"))
79
+ .check(z.minLength(1, { error: "Список кампаний пуст" }), z.maxLength(1000, { error: "За один вызов допустимо не больше 1000 кампаний" }))
80
+ .meta({ description: "ID кампаний, над которыми выполняется действие" }),
81
+ action: z.literal(CAMPAIGN_ACTIONS).meta({ description: "Действие: suspend, resume, archive, unarchive" })
82
+ });
83
+ export const getStrategySchema = z.object({
84
+ campaign_id: idField("ID текстово-графической кампании")
85
+ });
86
+ // Набор уже, чем SEARCH_STRATEGIES: хендлер умеет собрать настройки только для
87
+ // перечисленных типов. Настройки общие на обе стороны — автоматическая стратегия
88
+ // по правилам Директа стоит на одной из них, вторая идёт NETWORK_DEFAULT или
89
+ // SERVING_OFF, так что двусмысленности не возникает.
90
+ export const setStrategySchema = z.object({
91
+ campaign_id: idField("ID текстово-графической кампании"),
92
+ search_type: z.literal(SETTABLE_SEARCH_STRATEGIES).meta({
93
+ description: "Стратегия на поиске: HIGHEST_POSITION (ручная), WB_MAXIMUM_CLICKS, AVERAGE_CPC, AVERAGE_CPA, PAY_FOR_CONVERSION или SERVING_OFF"
94
+ }),
95
+ network_type: z.literal(SETTABLE_NETWORK_STRATEGIES).meta({
96
+ description: "Стратегия в сетях: NETWORK_DEFAULT (по настройкам поиска), MAXIMUM_COVERAGE, WB_MAXIMUM_CLICKS, AVERAGE_CPC, AVERAGE_CPA, PAY_FOR_CONVERSION или SERVING_OFF"
97
+ }),
98
+ weekly_spend_limit: rublesField("Недельный бюджет в рублях; обязателен для WB_MAXIMUM_CLICKS, для остальных автостратегий необязателен").optional(),
99
+ bid_ceiling: rublesField("Максимальная ставка в рублях для WB_MAXIMUM_CLICKS и AVERAGE_CPA").optional(),
100
+ average_cpc: rublesField("Средняя цена клика в рублях; обязательна для AVERAGE_CPC").optional(),
101
+ average_cpa: rublesField("Средняя цена конверсии в рублях; обязательна для AVERAGE_CPA").optional(),
102
+ conversion_price: rublesField("Цена конверсии в рублях для PAY_FOR_CONVERSION: списывается за конверсию, а не за клик").optional(),
103
+ goal_id: idField("ID цели Метрики для AVERAGE_CPA и PAY_FOR_CONVERSION; для оплаты за конверсию обязателен").optional(),
104
+ network_limit_percent: z
105
+ .int()
106
+ .check(z.gte(1, { error: "Доля расходов не меньше 1%" }), z.lte(100, { error: "Доля расходов не больше 100%" }))
107
+ .optional()
108
+ .meta({ description: "Доля расходов в сетях для NETWORK_DEFAULT, проценты" })
109
+ });
@@ -0,0 +1,7 @@
1
+ export declare const listCampaignsTool: import("#shared/lib/tool").ToolDescriptor;
2
+ export declare const getCampaignTool: import("#shared/lib/tool").ToolDescriptor;
3
+ export declare const createCampaignTool: import("#shared/lib/tool").ToolDescriptor;
4
+ export declare const updateCampaignTool: import("#shared/lib/tool").ToolDescriptor;
5
+ export declare const manageCampaignsTool: import("#shared/lib/tool").ToolDescriptor;
6
+ export declare const getStrategyTool: import("#shared/lib/tool").ToolDescriptor;
7
+ export declare const setStrategyTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,61 @@
1
+ // Дескрипторы инструментов домена: имя, описание и аннотация живут рядом с хендлером.
2
+ import { defineTool, IDEMPOTENT, READ, WRITE } from "#shared/lib/tool";
3
+ import { handleCreateCampaign, handleGetCampaign, handleGetStrategy, handleListCampaigns, handleManageCampaigns, handleSetStrategy, handleUpdateCampaign } from "./handler.js";
4
+ import { createCampaignSchema, getCampaignSchema, getStrategySchema, listCampaignsSchema, manageCampaignsSchema, setStrategySchema, updateCampaignSchema } from "./schema.js";
5
+ export const listCampaignsTool = defineTool({
6
+ name: "list_campaigns",
7
+ title: "Список кампаний",
8
+ description: "Список рекламных кампаний Яндекс.Директ с фильтрацией по статусу и типу. Бюджеты — в рублях.",
9
+ annotations: READ,
10
+ schema: listCampaignsSchema,
11
+ handler: handleListCampaigns
12
+ });
13
+ export const getCampaignTool = defineTool({
14
+ name: "get_campaign",
15
+ title: "Кампания по ID",
16
+ description: "Детальная информация о кампании по ID: бюджет (руб), статус, даты, статистика.",
17
+ annotations: READ,
18
+ schema: getCampaignSchema,
19
+ handler: handleGetCampaign
20
+ });
21
+ export const createCampaignTool = defineTool({
22
+ name: "create_campaign",
23
+ title: "Создать кампанию",
24
+ description: "Создать новую рекламную кампанию. Бюджет в рублях. ⚠️ Тестовой среды у Директа нет: кампания создаётся в боевом аккаунте. Деньги она начнёт тратить после модерации и включения, поэтому созданную для проверки оставляйте черновиком.",
25
+ annotations: WRITE,
26
+ schema: createCampaignSchema,
27
+ handler: handleCreateCampaign
28
+ });
29
+ export const updateCampaignTool = defineTool({
30
+ name: "update_campaign",
31
+ title: "Обновить кампанию",
32
+ description: "Обновить кампанию: название, бюджет (руб), UTM-разметку и/или статус (SUSPEND/RESUME/ARCHIVE/UNARCHIVE). Разметка действует на ссылки всех объявлений кампании.",
33
+ annotations: IDEMPOTENT,
34
+ schema: updateCampaignSchema,
35
+ handler: handleUpdateCampaign
36
+ });
37
+ export const manageCampaignsTool = defineTool({
38
+ name: "manage_campaigns",
39
+ title: "Управление кампаниями",
40
+ description: "Запустить, остановить, архивировать или разархивировать несколько кампаний.",
41
+ annotations: IDEMPOTENT,
42
+ schema: manageCampaignsSchema,
43
+ handler: handleManageCampaigns
44
+ });
45
+ export const getStrategyTool = defineTool({
46
+ name: "get_strategy",
47
+ title: "Стратегия кампании",
48
+ description: "Получить текущую стратегию показов текстово-графической кампании.",
49
+ annotations: READ,
50
+ schema: getStrategySchema,
51
+ handler: handleGetStrategy
52
+ });
53
+ export const setStrategyTool = defineTool({
54
+ name: "set_strategy",
55
+ title: "Изменить стратегию кампании",
56
+ description: "Изменить стратегию текстово-графической кампании: ручная, максимум кликов, средняя цена клика или конверсии, " +
57
+ "оплата за конверсию. Цены — в рублях, цель Метрики — goal_id.",
58
+ annotations: IDEMPOTENT,
59
+ schema: setStrategySchema,
60
+ handler: handleSetStrategy
61
+ });
@@ -0,0 +1,5 @@
1
+ import type { z } from "zod";
2
+ import type { getChangesSchema } from "./schema.js";
3
+ type Params = z.infer<typeof getChangesSchema>;
4
+ export declare function handleGetChanges(params: Params): Promise<string>;
5
+ export {};
@@ -0,0 +1,33 @@
1
+ import { apiPost } from "#shared/api/client";
2
+ import { formatResult } from "#shared/lib/format";
3
+ import { apiIds } from "#shared/lib/id";
4
+ const NO_MONEY = { money: false };
5
+ // Директ требует ровно один тип селектора: смешивать кампании, группы и объявления нельзя.
6
+ function selectedScope(params) {
7
+ const scopes = [
8
+ params.campaign_ids?.length ? "CampaignIds" : undefined,
9
+ params.ad_group_ids?.length ? "AdGroupIds" : undefined,
10
+ params.ad_ids?.length ? "AdIds" : undefined
11
+ ].filter((scope) => scope !== undefined);
12
+ if (scopes.length !== 1) {
13
+ throw new Error("Для mode=objects передайте ровно один из campaign_ids/ad_group_ids/ad_ids.");
14
+ }
15
+ return scopes[0];
16
+ }
17
+ export async function handleGetChanges(params) {
18
+ if (params.mode === "campaigns") {
19
+ return formatResult(await apiPost("changes", "checkCampaigns", { Timestamp: params.timestamp }), NO_MONEY);
20
+ }
21
+ const scope = selectedScope(params);
22
+ const request = {
23
+ Timestamp: params.timestamp,
24
+ FieldNames: params.field_names ?? [scope]
25
+ };
26
+ if (params.campaign_ids?.length)
27
+ request.CampaignIds = apiIds(params.campaign_ids);
28
+ if (params.ad_group_ids?.length)
29
+ request.AdGroupIds = apiIds(params.ad_group_ids);
30
+ if (params.ad_ids?.length)
31
+ request.AdIds = apiIds(params.ad_ids);
32
+ return formatResult(await apiPost("changes", "check", request), NO_MONEY);
33
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const getChangesSchema: z.ZodObject<{
3
+ mode: z.ZodLiteral<"campaigns" | "objects">;
4
+ timestamp: z.ZodString;
5
+ campaign_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ ad_group_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ ad_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
+ field_names: z.ZodOptional<z.ZodArray<z.ZodLiteral<"AdGroupIds" | "AdIds" | "CampaignIds" | "CampaignsStat">>>;
9
+ }, z.core.$strip>;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { CHANGES_FIELD_NAMES, CHANGES_MODES } from "#shared/config/enums";
3
+ import { MAX_AD_GROUPS_PER_CHANGES_CALL, MAX_ADS_PER_CHANGES_CALL, MAX_CAMPAIGNS_PER_CHANGES_CALL } from "#shared/config/limits";
4
+ import { timestampField } from "#shared/lib/date";
5
+ import { idField } from "#shared/lib/id";
6
+ export const getChangesSchema = z.object({
7
+ mode: z.literal(CHANGES_MODES).meta({
8
+ description: "campaigns — какие кампании менялись целиком; objects — что изменилось внутри выбранных объектов"
9
+ }),
10
+ timestamp: timestampField("Момент, начиная с которого искать изменения: YYYY-MM-DDThh:mm:ssZ"),
11
+ campaign_ids: z
12
+ .array(idField("ID кампании"))
13
+ .check(z.minLength(1, { error: "Список кампаний пуст" }), z.maxLength(MAX_CAMPAIGNS_PER_CHANGES_CALL, {
14
+ error: `Не больше ${MAX_CAMPAIGNS_PER_CHANGES_CALL} кампаний за вызов`
15
+ }))
16
+ .optional()
17
+ .meta({ description: "Кампании для mode=objects" }),
18
+ ad_group_ids: z
19
+ .array(idField("ID группы объявлений"))
20
+ .check(z.minLength(1, { error: "Список групп пуст" }), z.maxLength(MAX_AD_GROUPS_PER_CHANGES_CALL, {
21
+ error: `Не больше ${MAX_AD_GROUPS_PER_CHANGES_CALL} групп за вызов`
22
+ }))
23
+ .optional()
24
+ .meta({ description: "Группы для mode=objects" }),
25
+ ad_ids: z
26
+ .array(idField("ID объявления"))
27
+ .check(z.minLength(1, { error: "Список объявлений пуст" }), z.maxLength(MAX_ADS_PER_CHANGES_CALL, { error: `Не больше ${MAX_ADS_PER_CHANGES_CALL} объявлений за вызов` }))
28
+ .optional()
29
+ .meta({ description: "Объявления для mode=objects" }),
30
+ field_names: z
31
+ .array(z.literal(CHANGES_FIELD_NAMES))
32
+ .check(z.minLength(1, { error: "Список полей пуст" }))
33
+ .optional()
34
+ .meta({ description: "Какие изменения интересуют; по умолчанию — соответствующие переданным ID" })
35
+ });
@@ -0,0 +1 @@
1
+ export declare const getChangesTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,11 @@
1
+ import { defineTool, READ } from "#shared/lib/tool";
2
+ import { handleGetChanges } from "./handler.js";
3
+ import { getChangesSchema } from "./schema.js";
4
+ export const getChangesTool = defineTool({
5
+ name: "get_changes",
6
+ title: "Изменения объектов",
7
+ description: "Проверить изменения кампаний, групп и объявлений начиная с указанного времени.",
8
+ annotations: READ,
9
+ schema: getChangesSchema,
10
+ handler: handleGetChanges
11
+ });
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ import type { getRegionsSchema, listTimeZonesSchema } from "./schema.js";
3
+ export declare function clearDictionaryCache(): void;
4
+ export declare function handleGetRegions(params: z.infer<typeof getRegionsSchema>): Promise<string>;
5
+ export declare function handleListTimeZones(params: z.infer<typeof listTimeZonesSchema>): Promise<string>;
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import { apiPost } from "#shared/api/client";
3
+ import { parseApiResult } from "#shared/api/parse";
4
+ import { formatResult } from "#shared/lib/format";
5
+ // Схемы ответа нестрогие (`looseObject`): обязательны только поля, на которые опирается
6
+ // инструмент, остальное проходит насквозь и попадает в вывод. Директ добавляет поля без
7
+ // предупреждения — строгая схема превратила бы такое добавление в отказ инструмента.
8
+ const geoRegionSchema = z.looseObject({
9
+ GeoRegionId: z.number(),
10
+ GeoRegionName: z.string()
11
+ });
12
+ const timeZoneSchema = z.looseObject({
13
+ TimeZone: z.string(),
14
+ TimeZoneName: z.string()
15
+ });
16
+ // Имя справочника связано с типом его записей здесь и только здесь: перепутать пару
17
+ // или ошибиться в имени теперь нельзя — это ловит компилятор.
18
+ const DICTIONARIES = {
19
+ GeoRegions: geoRegionSchema,
20
+ TimeZones: timeZoneSchema
21
+ };
22
+ // Справочники Директа — тысячи записей и почти неизменны, поэтому держатся
23
+ // в памяти процесса: иначе каждый вызов тратил бы баллы API на одно и то же.
24
+ let cache = {};
25
+ // Кэш переживает импорты, поэтому тесту нужен явный сброс: без него проверка
26
+ // «сходил в сеть один раз» зависела бы от того, загрузил ли справочник кто-то раньше.
27
+ export function clearDictionaryCache() {
28
+ cache = {};
29
+ }
30
+ async function loadDictionary(name) {
31
+ const cached = cache[name];
32
+ if (cached)
33
+ return cached;
34
+ const data = (await apiPost("dictionaries", "get", { DictionaryNames: [name] }));
35
+ const items = parseApiResult(z.array(DICTIONARIES[name]), data?.result?.[name] ?? [], `справочник ${name}`);
36
+ // Запись по generic-ключу компилятор не выводит: для него `cache[name]` — пересечение
37
+ // всех вариантов. Чтение (`cache[name]` выше) типизировано точно, каст только здесь.
38
+ cache[name] = items;
39
+ return items;
40
+ }
41
+ function filterByName(items, names, search) {
42
+ if (!search)
43
+ return items;
44
+ const needle = search.toLowerCase();
45
+ return items.filter((item) => names(item).some((name) => String(name ?? "")
46
+ .toLowerCase()
47
+ .includes(needle)));
48
+ }
49
+ function limitedOutput(matched, limit) {
50
+ const limited = matched.slice(0, limit);
51
+ const note = matched.length > limited.length
52
+ ? `ℹ️ Показано ${limited.length} из ${matched.length}. Уточните search или увеличьте limit.\n\n`
53
+ : "";
54
+ // Записи справочника короткие, но вывод всё равно идёт через общий форматтер:
55
+ // один формат ответа на все инструменты, никаких исключений.
56
+ return note + formatResult(limited, { money: false });
57
+ }
58
+ export async function handleGetRegions(params) {
59
+ const regions = await loadDictionary("GeoRegions");
60
+ return limitedOutput(filterByName(regions, (region) => [region.GeoRegionName], params.search), params.limit);
61
+ }
62
+ export async function handleListTimeZones(params) {
63
+ const zones = await loadDictionary("TimeZones");
64
+ // Ищем и по коду (Europe/Moscow), и по названию: модель приходит то с одним, то с другим.
65
+ return limitedOutput(filterByName(zones, (zone) => [zone.TimeZone, zone.TimeZoneName], params.search), params.limit);
66
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const getRegionsSchema: z.ZodObject<{
3
+ search: z.ZodOptional<z.ZodString>;
4
+ limit: z.ZodDefault<z.ZodInt>;
5
+ }, z.core.$strip>;
6
+ export declare const listTimeZonesSchema: z.ZodObject<{
7
+ search: z.ZodOptional<z.ZodString>;
8
+ limit: z.ZodDefault<z.ZodInt>;
9
+ }, z.core.$strip>;
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { REGIONS_DEFAULT_LIMIT, REGIONS_MAX_LIMIT, TIME_ZONES_DEFAULT_LIMIT, TIME_ZONES_MAX_LIMIT } from "#shared/config/limits";
3
+ export const getRegionsSchema = z.object({
4
+ search: z
5
+ .string()
6
+ .optional()
7
+ .meta({ description: "Фильтр по названию региона: подстрока без учёта регистра, например «москва»" }),
8
+ limit: z
9
+ .int()
10
+ .check(z.positive({ error: "Лимит должен быть больше нуля" }), z.lte(REGIONS_MAX_LIMIT, { error: `Лимит не больше ${REGIONS_MAX_LIMIT}` }))
11
+ .default(REGIONS_DEFAULT_LIMIT)
12
+ .meta({ description: `Сколько регионов вернуть, максимум ${REGIONS_MAX_LIMIT}` })
13
+ });
14
+ export const listTimeZonesSchema = z.object({
15
+ search: z.string().optional().meta({
16
+ description: "Фильтр по коду или названию пояса: подстрока без учёта регистра, например «moscow» или «Екатеринбург»"
17
+ }),
18
+ limit: z
19
+ .int()
20
+ .check(z.positive({ error: "Лимит должен быть больше нуля" }), z.lte(TIME_ZONES_MAX_LIMIT, { error: `Лимит не больше ${TIME_ZONES_MAX_LIMIT}` }))
21
+ .default(TIME_ZONES_DEFAULT_LIMIT)
22
+ .meta({ description: `Сколько часовых поясов вернуть, максимум ${TIME_ZONES_MAX_LIMIT}` })
23
+ });
@@ -0,0 +1,2 @@
1
+ export declare const getRegionsTool: import("#shared/lib/tool").ToolDescriptor;
2
+ export declare const listTimeZonesTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,19 @@
1
+ import { defineTool, READ } from "#shared/lib/tool";
2
+ import { handleGetRegions, handleListTimeZones } from "./handler.js";
3
+ import { getRegionsSchema, listTimeZonesSchema } from "./schema.js";
4
+ export const getRegionsTool = defineTool({
5
+ name: "get_regions",
6
+ title: "Справочник регионов",
7
+ description: "Справочник кодов регионов (GeoRegions) для таргетинга. Фильтр по названию. 225 = Россия.",
8
+ annotations: READ,
9
+ schema: getRegionsSchema,
10
+ handler: handleGetRegions
11
+ });
12
+ export const listTimeZonesTool = defineTool({
13
+ name: "list_time_zones",
14
+ title: "Справочник часовых поясов",
15
+ description: "Справочник часовых поясов (TimeZones) для set_time_targeting и create_campaign. Фильтр по коду или названию.",
16
+ annotations: READ,
17
+ schema: listTimeZonesSchema,
18
+ handler: handleListTimeZones
19
+ });
@@ -0,0 +1,6 @@
1
+ import type { z } from "zod";
2
+ import type { listDynamicTargetsSchema, manageDynamicTargetsSchema } from "./schema.js";
3
+ type Params = z.infer<typeof manageDynamicTargetsSchema>;
4
+ export declare function handleListDynamicTargets(params: z.infer<typeof listDynamicTargetsSchema>): Promise<string>;
5
+ export declare function handleManageDynamicTargets(params: Params): Promise<string>;
6
+ export {};
@@ -0,0 +1,96 @@
1
+ import { apiPost } from "#shared/api/client";
2
+ import { formatResult } from "#shared/lib/format";
3
+ import { apiId, apiIds } from "#shared/lib/id";
4
+ import { buildPage } from "#shared/lib/pagination";
5
+ const LIST_FIELDS = [
6
+ "Id",
7
+ "AdGroupId",
8
+ "CampaignId",
9
+ "Name",
10
+ "Bid",
11
+ "ContextBid",
12
+ "StrategyPriority",
13
+ "State",
14
+ "StatusClarification",
15
+ "Conditions",
16
+ "ConditionType"
17
+ ];
18
+ export async function handleListDynamicTargets(params) {
19
+ const selection = {};
20
+ if (params.dynamic_target_ids?.length)
21
+ selection.Ids = apiIds(params.dynamic_target_ids);
22
+ if (params.ad_group_ids?.length)
23
+ selection.AdGroupIds = apiIds(params.ad_group_ids);
24
+ if (params.campaign_ids?.length)
25
+ selection.CampaignIds = apiIds(params.campaign_ids);
26
+ if (params.states?.length)
27
+ selection.States = params.states;
28
+ const request = { SelectionCriteria: selection, FieldNames: LIST_FIELDS };
29
+ const page = buildPage(params);
30
+ if (page)
31
+ request.Page = page;
32
+ return formatResult(await apiPost("dynamictextadtargets", "get", request));
33
+ }
34
+ function buildTarget(target) {
35
+ const item = {
36
+ AdGroupId: apiId(target.ad_group_id),
37
+ Name: target.name,
38
+ Conditions: target.conditions.map((condition) => ({
39
+ Operand: condition.operand,
40
+ Operator: condition.operator,
41
+ Arguments: condition.arguments
42
+ }))
43
+ };
44
+ if (target.bid !== undefined)
45
+ item.Bid = target.bid;
46
+ if (target.context_bid !== undefined)
47
+ item.ContextBid = target.context_bid;
48
+ if (target.strategy_priority)
49
+ item.StrategyPriority = target.strategy_priority;
50
+ return item;
51
+ }
52
+ function buildBid(bid) {
53
+ const selectors = [bid.dynamic_target_id, bid.ad_group_id, bid.campaign_id].filter(Boolean);
54
+ if (selectors.length !== 1) {
55
+ throw new Error("Каждая ставка должна содержать ровно один ID цели, группы или кампании.");
56
+ }
57
+ if (bid.bid === undefined && bid.context_bid === undefined && bid.strategy_priority === undefined) {
58
+ throw new Error("Для каждой ставки укажите bid, context_bid и/или strategy_priority.");
59
+ }
60
+ const item = {};
61
+ if (bid.dynamic_target_id)
62
+ item.Id = apiId(bid.dynamic_target_id);
63
+ if (bid.ad_group_id)
64
+ item.AdGroupId = apiId(bid.ad_group_id);
65
+ if (bid.campaign_id)
66
+ item.CampaignId = apiId(bid.campaign_id);
67
+ if (bid.bid !== undefined)
68
+ item.Bid = bid.bid;
69
+ if (bid.context_bid !== undefined)
70
+ item.ContextBid = bid.context_bid;
71
+ if (bid.strategy_priority)
72
+ item.StrategyPriority = bid.strategy_priority;
73
+ return item;
74
+ }
75
+ export async function handleManageDynamicTargets(params) {
76
+ if (params.action === "add") {
77
+ if (!params.targets?.length)
78
+ throw new Error("Для action=add передайте targets.");
79
+ // Динамические цели в API называются Webpages — имя поля тела повторяет справочник.
80
+ const webpages = params.targets.map(buildTarget);
81
+ return formatResult(await apiPost("dynamictextadtargets", "add", { Webpages: webpages }));
82
+ }
83
+ if (params.action === "set_bids") {
84
+ if (!params.bids?.length)
85
+ throw new Error("Для action=set_bids передайте bids.");
86
+ const bids = params.bids.map(buildBid);
87
+ return formatResult(await apiPost("dynamictextadtargets", "setBids", { Bids: bids }));
88
+ }
89
+ if (!params.dynamic_target_ids?.length) {
90
+ throw new Error(`Для action=${params.action} передайте dynamic_target_ids.`);
91
+ }
92
+ const data = await apiPost("dynamictextadtargets", params.action, {
93
+ SelectionCriteria: { Ids: apiIds(params.dynamic_target_ids) }
94
+ });
95
+ return formatResult(data);
96
+ }
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ export declare const listDynamicTargetsSchema: z.ZodObject<{
3
+ limit: z.ZodOptional<z.ZodInt>;
4
+ offset: z.ZodOptional<z.ZodInt>;
5
+ dynamic_target_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ ad_group_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ campaign_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
8
+ states: z.ZodOptional<z.ZodArray<z.ZodLiteral<"ON" | "SUSPENDED">>>;
9
+ }, z.core.$strip>;
10
+ export declare const manageDynamicTargetsSchema: z.ZodObject<{
11
+ action: z.ZodLiteral<"add" | "delete" | "resume" | "set_bids" | "suspend">;
12
+ targets: z.ZodOptional<z.ZodArray<z.ZodObject<{
13
+ ad_group_id: z.ZodString;
14
+ name: z.ZodString;
15
+ conditions: z.ZodArray<z.ZodObject<{
16
+ operand: z.ZodLiteral<"DOMAIN" | "OFFERS_LIST_URL" | "PAGE_CONTENT" | "PAGE_TITLE" | "URL">;
17
+ operator: z.ZodLiteral<"CONTAINS_ANY" | "EQUALS_ANY" | "NOT_CONTAINS_ALL" | "NOT_EQUALS_ALL">;
18
+ arguments: z.ZodArray<z.ZodString>;
19
+ }, z.core.$strip>>;
20
+ bid: z.ZodOptional<z.ZodPipe<z.ZodNumber, z.ZodTransform<number, number>>>;
21
+ context_bid: z.ZodOptional<z.ZodPipe<z.ZodNumber, z.ZodTransform<number, number>>>;
22
+ strategy_priority: z.ZodOptional<z.ZodLiteral<"HIGH" | "LOW" | "NORMAL">>;
23
+ }, z.core.$strip>>>;
24
+ bids: z.ZodOptional<z.ZodArray<z.ZodObject<{
25
+ dynamic_target_id: z.ZodOptional<z.ZodString>;
26
+ ad_group_id: z.ZodOptional<z.ZodString>;
27
+ campaign_id: z.ZodOptional<z.ZodString>;
28
+ bid: z.ZodOptional<z.ZodPipe<z.ZodNumber, z.ZodTransform<number, number>>>;
29
+ context_bid: z.ZodOptional<z.ZodPipe<z.ZodNumber, z.ZodTransform<number, number>>>;
30
+ strategy_priority: z.ZodOptional<z.ZodLiteral<"HIGH" | "LOW" | "NORMAL">>;
31
+ }, z.core.$strip>>>;
32
+ dynamic_target_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ }, z.core.$strip>;