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,139 @@
1
+ // В домене две разные сущности, отсюда и два набора инструментов. Собственные минус-фразы
2
+ // кампании или группы — список, живущий внутри неё. Общий набор — именованный список
3
+ // уровня аккаунта: он заводится один раз и переиспользуется, поэтому у него свой сервис,
4
+ // своя форма поля (голый массив, не { Items: [...] }) и отдельная привязка к объекту.
5
+ //
6
+ // Минус-фразы Директа перезаписываются целиком: и NegativeKeywords.Items, и привязки
7
+ // наборов. Инструменты этого домена названы set_*/link_* именно поэтому — они задают
8
+ // новое значение, а не дополняют старое. Режимы add и remove у set_*_negative_keywords
9
+ // прячут чтение и слияние внутрь сервера, чтобы «добавить одну фразу» не оборачивалось
10
+ // стёртым списком; link_* и наборы по-прежнему только заменяют.
11
+ import { z } from "zod";
12
+ import { NEGATIVE_KEYWORD_SET_ACTIONS } from "#shared/config/enums";
13
+ import { MAX_AD_GROUPS_PER_CALL, MAX_CAMPAIGNS_PER_CALL, MAX_SHARED_SETS_PER_AD_GROUP, MAX_SHARED_SETS_PER_CALL, SHARED_SET_NAME_MAX } from "#shared/config/limits";
14
+ import { idField } from "#shared/lib/id";
15
+ import { pageFields } from "#shared/lib/pagination";
16
+ const negativeKeyword = z
17
+ .string()
18
+ .check(z.minLength(1, { error: "Минус-фраза не может быть пустой" }), z.regex(/\S/, { error: "Минус-фраза не может состоять из одних пробелов" }));
19
+ // Режим общий для кампании и группы: снаружи это один и тот же вопрос «заменить,
20
+ // дописать или убрать». Значения по умолчанию нет намеренно: replace затирает список
21
+ // целиком, и пропуск поля на просьбе «добавь одну фразу» стёр бы всё остальное молча —
22
+ // Директ ответил бы успехом. Обязательное поле превращает пропуск в ошибку валидации
23
+ // до записи, то есть требует назвать намерение вслух.
24
+ const negativeKeywordsMode = () => z.literal(["replace", "add", "remove"]).meta({
25
+ description: "Обязателен. replace — заменить список целиком (пустой массив очищает, прежние фразы теряются), add — дописать к текущим, remove — убрать перечисленные. add и remove сначала читают текущий список, это дополнительный вызов API. Фразы сравниваются так же, как их сравнивает Директ: без учёта регистра, буквы ё и е равны, краевые и повторные пробелы не учитываются, операторы закрепления ! и + игнорируются"
26
+ });
27
+ export const getCampaignNegativeKeywordsSchema = z.object({
28
+ campaign_ids: z
29
+ .array(idField("ID кампании"))
30
+ .check(z.minLength(1, { error: "Список кампаний пуст" }), z.maxLength(MAX_CAMPAIGNS_PER_CALL, {
31
+ error: `За один вызов допустимо не больше ${MAX_CAMPAIGNS_PER_CALL} кампаний`
32
+ }))
33
+ .meta({ description: "Кампании, минус-фразы которых нужно прочитать" })
34
+ });
35
+ export const setCampaignNegativeKeywordsSchema = z.object({
36
+ campaign_id: idField("ID кампании"),
37
+ negative_keywords: z.array(negativeKeyword).meta({
38
+ description: "Минус-фразы кампании: при mode=replace — полный новый список взамен прежнего, при add — что дописать, при remove — что убрать"
39
+ }),
40
+ mode: negativeKeywordsMode()
41
+ });
42
+ export const setAdGroupNegativeKeywordsSchema = z.object({
43
+ ad_group_id: idField("ID группы объявлений"),
44
+ negative_keywords: z.array(negativeKeyword).meta({
45
+ description: "Минус-фразы группы: при mode=replace — полный новый список взамен прежнего, при add — что дописать, при remove — что убрать"
46
+ }),
47
+ mode: negativeKeywordsMode()
48
+ });
49
+ export const listNegativeKeywordSharedSetsSchema = z.object({
50
+ set_ids: z
51
+ .array(idField("ID общего набора минус-фраз"))
52
+ .check(z.maxLength(MAX_SHARED_SETS_PER_CALL, {
53
+ error: `За один вызов допустимо не больше ${MAX_SHARED_SETS_PER_CALL} наборов`
54
+ }))
55
+ .optional()
56
+ .meta({ description: "Конкретные наборы; без них возвращаются все наборы аккаунта" }),
57
+ ...pageFields
58
+ });
59
+ const sharedSetName = z
60
+ .string()
61
+ .check(z.minLength(1, { error: "Название не может быть пустым" }), z.maxLength(SHARED_SET_NAME_MAX, { error: `Название длиннее ${SHARED_SET_NAME_MAX} символов` }));
62
+ const addSharedSet = z.object({
63
+ name: sharedSetName.meta({ description: "Название набора" }),
64
+ negative_keywords: z
65
+ .array(negativeKeyword)
66
+ .check(z.minLength(1, { error: "Набор не может быть пустым" }))
67
+ .meta({ description: "Минус-фразы набора" })
68
+ });
69
+ const updateSharedSet = z.object({
70
+ set_id: idField("ID изменяемого набора"),
71
+ name: sharedSetName.optional().meta({ description: "Новое название набора" }),
72
+ negative_keywords: z
73
+ .array(negativeKeyword)
74
+ .optional()
75
+ .meta({ description: "Полный новый список минус-фраз набора — прежний затирается целиком" })
76
+ });
77
+ // Функция, а не готовая проверка: один и тот же объект проверки не переиспользуется
78
+ // тремя схемами — каждая получает свой.
79
+ const sharedSetsPerCall = () => z.maxLength(MAX_SHARED_SETS_PER_CALL, { error: `Не больше ${MAX_SHARED_SETS_PER_CALL} наборов за вызов` });
80
+ export const manageNegativeKeywordSharedSetsSchema = z.object({
81
+ action: z.literal(NEGATIVE_KEYWORD_SET_ACTIONS).meta({
82
+ description: "Что сделать с наборами: add, update или delete (необратимо)"
83
+ }),
84
+ add_sets: z
85
+ .array(addSharedSet)
86
+ .check(z.minLength(1, { error: "Список наборов пуст" }), sharedSetsPerCall())
87
+ .optional()
88
+ .meta({ description: "Наборы для создания; обязателен при action=add" }),
89
+ update_sets: z
90
+ .array(updateSharedSet)
91
+ .check(z.minLength(1, { error: "Список наборов пуст" }), sharedSetsPerCall())
92
+ .optional()
93
+ .meta({ description: "Наборы для изменения; обязателен при action=update" }),
94
+ set_ids: z
95
+ .array(idField("ID общего набора минус-фраз"))
96
+ .check(z.minLength(1, { error: "Список наборов пуст" }), sharedSetsPerCall())
97
+ .optional()
98
+ .meta({ description: "Наборы для удаления; обязателен при action=delete" })
99
+ });
100
+ // Набор привязывается и к группе, и к кампании — это одно и то же действие над разными
101
+ // объектами, поэтому инструмент один. Оба списка необязательны по отдельности, но пустым
102
+ // вызов быть не может: проверку несёт сам объект, иначе «ничего не сделал» вернулось бы
103
+ // успехом.
104
+ export const linkNegativeKeywordSetsSchema = z
105
+ .object({
106
+ ad_group_ids: z
107
+ .array(idField("ID группы объявлений"))
108
+ .check(z.maxLength(MAX_AD_GROUPS_PER_CALL, {
109
+ error: `За один вызов допустимо не больше ${MAX_AD_GROUPS_PER_CALL} групп`
110
+ }))
111
+ .optional()
112
+ .meta({ description: "Группы, которым назначаются наборы" }),
113
+ campaign_ids: z
114
+ .array(idField("ID кампании"))
115
+ .check(z.maxLength(MAX_CAMPAIGNS_PER_CALL, {
116
+ error: `За один вызов допустимо не больше ${MAX_CAMPAIGNS_PER_CALL} кампаний`
117
+ }))
118
+ .optional()
119
+ .meta({
120
+ description: "Кампании, которым назначаются наборы. Привязка на уровне кампании действует на все её группы. Тип кампании сервер читает сам — это дополнительный вызов API; общие наборы поддерживают TEXT_CAMPAIGN, DYNAMIC_TEXT_CAMPAIGN, MOBILE_APP_CAMPAIGN и UNIFIED_CAMPAIGN"
121
+ }),
122
+ set_ids: z
123
+ .array(idField("ID общего набора минус-фраз"))
124
+ .check(z.maxLength(MAX_SHARED_SETS_PER_AD_GROUP, {
125
+ error: `К объекту привязывается не больше ${MAX_SHARED_SETS_PER_AD_GROUP} наборов`
126
+ }))
127
+ .meta({
128
+ description: "Полный новый список наборов объекта — прежние привязки затираются. Пустой массив снимает все"
129
+ })
130
+ })
131
+ .check((ctx) => {
132
+ if (ctx.value.ad_group_ids?.length || ctx.value.campaign_ids?.length)
133
+ return;
134
+ ctx.issues.push({
135
+ code: "custom",
136
+ input: ctx.value,
137
+ message: "Укажите ad_group_ids и/или campaign_ids — кому назначаются наборы"
138
+ });
139
+ });
@@ -0,0 +1,3 @@
1
+ export declare function buildCampaignLink(campaignId: string, campaignType: string | undefined, sharedSetIds: {
2
+ Items: bigint[];
3
+ } | null): Record<string, unknown>;
@@ -0,0 +1,21 @@
1
+ // Привязка общих наборов к кампании устроена не так, как к группе. У группы
2
+ // NegativeKeywordSharedSetIds — поле верхнего уровня; у кампании оно спрятано внутрь
3
+ // объекта настроек, а имя этого объекта зависит от типа кампании и берётся из
4
+ // #shared/lib/campaign-type. Тип в запросе на привязку не передаётся, поэтому сценарий
5
+ // читает его перед записью, а здесь лежит чистая часть — проверка типа и сборка элемента.
6
+ import { getCampaignSettingsKey } from "#shared/lib/campaign-type";
7
+ import { apiId } from "#shared/lib/id";
8
+ // Свой список, а не общий: набор поддерживающих типов у каждого поля свой. Сверено с WSDL
9
+ // campaigns 05.09.2026 — NegativeKeywordSharedSetIds объявлен в TextCampaignBase,
10
+ // DynamicTextCampaignBase, UnifiedCampaignBase и MobileAppCampaignUpdateItem, но не в
11
+ // SmartCampaign и не в CpmBannerCampaign.
12
+ const SUPPORTED_TYPES = ["TEXT_CAMPAIGN", "DYNAMIC_TEXT_CAMPAIGN", "MOBILE_APP_CAMPAIGN", "UNIFIED_CAMPAIGN"];
13
+ // Тип, которого нет в списке, молча пропустить нельзя: вызов вернул бы успех, а набор
14
+ // остался бы непривязанным.
15
+ export function buildCampaignLink(campaignId, campaignType, sharedSetIds) {
16
+ const settingsKey = campaignType && SUPPORTED_TYPES.includes(campaignType) ? getCampaignSettingsKey(campaignType) : undefined;
17
+ if (!settingsKey) {
18
+ throw new Error(`Кампания ${campaignId} имеет тип ${campaignType ?? "неизвестный"}, а общие наборы минус-фраз поддерживают только ${SUPPORTED_TYPES.join(", ")}.`);
19
+ }
20
+ return { Id: apiId(campaignId), [settingsKey]: { NegativeKeywordSharedSetIds: sharedSetIds } };
21
+ }
@@ -0,0 +1,6 @@
1
+ export declare const getCampaignNegativeKeywordsTool: import("#shared/lib/tool").ToolDescriptor;
2
+ export declare const setCampaignNegativeKeywordsTool: import("#shared/lib/tool").ToolDescriptor;
3
+ export declare const setAdGroupNegativeKeywordsTool: import("#shared/lib/tool").ToolDescriptor;
4
+ export declare const listNegativeKeywordSharedSetsTool: import("#shared/lib/tool").ToolDescriptor;
5
+ export declare const manageNegativeKeywordSharedSetsTool: import("#shared/lib/tool").ToolDescriptor;
6
+ export declare const linkNegativeKeywordSetsTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,51 @@
1
+ import { DESTRUCTIVE, defineTool, IDEMPOTENT, READ } from "#shared/lib/tool";
2
+ import { handleGetCampaignNegativeKeywords, handleLinkNegativeKeywordSets, handleListNegativeKeywordSharedSets, handleManageNegativeKeywordSharedSets, handleSetAdGroupNegativeKeywords, handleSetCampaignNegativeKeywords } from "./handler.js";
3
+ import { getCampaignNegativeKeywordsSchema, linkNegativeKeywordSetsSchema, listNegativeKeywordSharedSetsSchema, manageNegativeKeywordSharedSetsSchema, setAdGroupNegativeKeywordsSchema, setCampaignNegativeKeywordsSchema } from "./schema.js";
4
+ export const getCampaignNegativeKeywordsTool = defineTool({
5
+ name: "get_campaign_negative_keywords",
6
+ title: "Получить минус-фразы кампаний",
7
+ description: "Получить текущие минус-фразы кампаний по их ID.",
8
+ annotations: READ,
9
+ schema: getCampaignNegativeKeywordsSchema,
10
+ handler: handleGetCampaignNegativeKeywords
11
+ });
12
+ export const setCampaignNegativeKeywordsTool = defineTool({
13
+ name: "set_campaign_negative_keywords",
14
+ title: "Минус-фразы кампании",
15
+ description: "Минус-фразы на уровне кампании. Режим mode обязателен: replace заменяет список целиком (прежние фразы теряются, пустой массив очищает), add дописывает к текущим, remove убирает названные — читать список перед этим не нужно, add и remove делают это сами. Чтобы добавить фразу к существующим, нужен add: replace с одной фразой сотрёт остальные.",
16
+ annotations: DESTRUCTIVE,
17
+ schema: setCampaignNegativeKeywordsSchema,
18
+ handler: handleSetCampaignNegativeKeywords
19
+ });
20
+ export const setAdGroupNegativeKeywordsTool = defineTool({
21
+ name: "set_ad_group_negative_keywords",
22
+ title: "Минус-фразы группы",
23
+ description: "Минус-фразы на уровне группы объявлений. Режим mode обязателен: replace заменяет список целиком (прежние фразы теряются, пустой массив очищает), add дописывает к текущим, remove убирает названные — читать список перед этим не нужно, add и remove делают это сами. Чтобы добавить фразу к существующим, нужен add: replace с одной фразой сотрёт остальные.",
24
+ annotations: DESTRUCTIVE,
25
+ schema: setAdGroupNegativeKeywordsSchema,
26
+ handler: handleSetAdGroupNegativeKeywords
27
+ });
28
+ export const listNegativeKeywordSharedSetsTool = defineTool({
29
+ name: "list_negative_keyword_shared_sets",
30
+ title: "Общие наборы минус-фраз",
31
+ description: "Получить общие наборы минус-фраз аккаунта.",
32
+ annotations: READ,
33
+ schema: listNegativeKeywordSharedSetsSchema,
34
+ handler: handleListNegativeKeywordSharedSets
35
+ });
36
+ export const manageNegativeKeywordSharedSetsTool = defineTool({
37
+ name: "manage_negative_keyword_shared_sets",
38
+ title: "Управление общими минус-фразами",
39
+ description: "Создать, изменить или удалить общие наборы минус-фраз аккаунта.",
40
+ annotations: DESTRUCTIVE,
41
+ schema: manageNegativeKeywordSharedSetsSchema,
42
+ handler: handleManageNegativeKeywordSharedSets
43
+ });
44
+ export const linkNegativeKeywordSetsTool = defineTool({
45
+ name: "link_negative_keyword_sets",
46
+ title: "Привязать общие минус-фразы",
47
+ description: "Заменить привязки общих наборов минус-фраз у кампаний и/или групп объявлений. Привязка к кампании действует на все её группы. Пустой set_ids очищает привязки.",
48
+ annotations: IDEMPOTENT,
49
+ schema: linkNegativeKeywordSetsSchema,
50
+ handler: handleLinkNegativeKeywordSets
51
+ });
@@ -0,0 +1,4 @@
1
+ import type { z } from "zod";
2
+ import type { addRetargetingListSchema, listRetargetingListsSchema } from "./schema.js";
3
+ export declare function handleListRetargetingLists(params: z.infer<typeof listRetargetingListsSchema>): Promise<string>;
4
+ export declare function handleAddRetargetingList(params: z.infer<typeof addRetargetingListSchema>): Promise<string>;
@@ -0,0 +1,47 @@
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 NO_MONEY = { money: false };
6
+ const LIST_FIELDS = [
7
+ "Type",
8
+ "Id",
9
+ "Name",
10
+ "Description",
11
+ "Rules",
12
+ "IsAvailable",
13
+ "Scope",
14
+ "AvailableForTargetsInAdGroupTypes"
15
+ ];
16
+ export async function handleListRetargetingLists(params) {
17
+ const selection = {};
18
+ if (params.retargeting_list_ids?.length)
19
+ selection.Ids = apiIds(params.retargeting_list_ids);
20
+ if (params.types?.length)
21
+ selection.Types = params.types;
22
+ const request = { FieldNames: LIST_FIELDS };
23
+ if (Object.keys(selection).length > 0)
24
+ request.SelectionCriteria = selection;
25
+ const page = buildPage(params);
26
+ if (page)
27
+ request.Page = page;
28
+ return formatResult(await apiPost("retargetinglists", "get", request), NO_MONEY);
29
+ }
30
+ export async function handleAddRetargetingList(params) {
31
+ const retargetingList = {
32
+ Name: params.name,
33
+ Type: params.type,
34
+ Rules: params.rules.map((rule) => ({
35
+ Operator: rule.operator,
36
+ Arguments: rule.arguments.map((argument) => {
37
+ const item = { ExternalId: apiId(argument.external_id) };
38
+ if (argument.membership_life_span !== undefined)
39
+ item.MembershipLifeSpan = argument.membership_life_span;
40
+ return item;
41
+ })
42
+ }))
43
+ };
44
+ if (params.description !== undefined)
45
+ retargetingList.Description = params.description;
46
+ return formatResult(await apiPost("retargetinglists", "add", { RetargetingLists: [retargetingList] }), NO_MONEY);
47
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ export declare const listRetargetingListsSchema: z.ZodObject<{
3
+ limit: z.ZodOptional<z.ZodInt>;
4
+ offset: z.ZodOptional<z.ZodInt>;
5
+ retargeting_list_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ types: z.ZodOptional<z.ZodArray<z.ZodLiteral<"AUDIENCE" | "RETARGETING">>>;
7
+ }, z.core.$strip>;
8
+ export declare const addRetargetingListSchema: z.ZodObject<{
9
+ name: z.ZodString;
10
+ type: z.ZodDefault<z.ZodLiteral<"AUDIENCE" | "RETARGETING">>;
11
+ description: z.ZodOptional<z.ZodString>;
12
+ rules: z.ZodArray<z.ZodObject<{
13
+ operator: z.ZodLiteral<"ALL" | "ANY" | "NONE">;
14
+ arguments: z.ZodArray<z.ZodObject<{
15
+ external_id: z.ZodString;
16
+ membership_life_span: z.ZodOptional<z.ZodInt>;
17
+ }, z.core.$strip>>;
18
+ }, z.core.$strip>>;
19
+ }, z.core.$strip>;
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import { RETARGETING_RULE_OPERATORS, RETARGETING_TYPES } from "#shared/config/enums";
3
+ import { MAX_IDS_PER_CALL, RETARGETING_LIMITS } from "#shared/config/limits";
4
+ import { idField } from "#shared/lib/id";
5
+ import { pageFields } from "#shared/lib/pagination";
6
+ export const listRetargetingListsSchema = z.object({
7
+ retargeting_list_ids: z
8
+ .array(idField("ID условия ретаргетинга"))
9
+ .check(z.maxLength(MAX_IDS_PER_CALL, { error: `За один вызов допустимо не больше ${MAX_IDS_PER_CALL} условий` }))
10
+ .optional()
11
+ .meta({ description: "Конкретные условия; без них возвращаются все" }),
12
+ types: z
13
+ .array(z.literal(RETARGETING_TYPES))
14
+ .optional()
15
+ .meta({ description: "Фильтр по типу: RETARGETING — цели Метрики, AUDIENCE — сегменты Аудиторий" }),
16
+ ...pageFields
17
+ });
18
+ const ruleArgument = z.object({
19
+ external_id: idField("ID цели Метрики или сегмента Аудиторий"),
20
+ membership_life_span: z
21
+ .int()
22
+ .check(z.gte(1, { error: "Срок не меньше одного дня" }), z.lte(RETARGETING_LIMITS.membershipDays, { error: `Срок не больше ${RETARGETING_LIMITS.membershipDays} дней` }))
23
+ .optional()
24
+ .meta({
25
+ description: `За сколько последних дней учитывать выполнение цели, 1–${RETARGETING_LIMITS.membershipDays}`
26
+ })
27
+ });
28
+ const rule = z.object({
29
+ operator: z.literal(RETARGETING_RULE_OPERATORS).meta({
30
+ description: "Как соединять условия: ALL — выполнены все, ANY — хотя бы одно, NONE — ни одного"
31
+ }),
32
+ arguments: z
33
+ .array(ruleArgument)
34
+ .check(z.minLength(1, { error: "Правило без условий" }))
35
+ .meta({ description: "Цели и сегменты, на которых строится правило" })
36
+ });
37
+ export const addRetargetingListSchema = z.object({
38
+ name: z
39
+ .string()
40
+ .check(z.minLength(1, { error: "Название не может быть пустым" }), z.maxLength(RETARGETING_LIMITS.name, { error: `Название длиннее ${RETARGETING_LIMITS.name} символов` }))
41
+ .meta({ description: "Название условия ретаргетинга" }),
42
+ type: z
43
+ .literal(RETARGETING_TYPES)
44
+ .default("RETARGETING")
45
+ .meta({ description: "RETARGETING — по целям Метрики, AUDIENCE — по сегментам Яндекс.Аудиторий" }),
46
+ description: z
47
+ .string()
48
+ .check(z.maxLength(RETARGETING_LIMITS.description, {
49
+ error: `Описание длиннее ${RETARGETING_LIMITS.description} символов`
50
+ }))
51
+ .optional()
52
+ .meta({ description: "Описание условия — видно только в интерфейсе, на показы не влияет" }),
53
+ rules: z
54
+ .array(rule)
55
+ .check(z.minLength(1, { error: "Условие без правил" }))
56
+ .meta({ description: "Правила условия; между собой они соединяются логическим И" })
57
+ });
@@ -0,0 +1,2 @@
1
+ export declare const listRetargetingListsTool: import("#shared/lib/tool").ToolDescriptor;
2
+ export declare const addRetargetingListTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,19 @@
1
+ import { defineTool, READ, WRITE } from "#shared/lib/tool";
2
+ import { handleAddRetargetingList, handleListRetargetingLists } from "./handler.js";
3
+ import { addRetargetingListSchema, listRetargetingListsSchema } from "./schema.js";
4
+ export const listRetargetingListsTool = defineTool({
5
+ name: "list_retargeting_lists",
6
+ title: "Списки ретаргетинга",
7
+ description: "Получить условия ретаргетинга и подбора аудитории с правилами и областью применения.",
8
+ annotations: READ,
9
+ schema: listRetargetingListsSchema,
10
+ handler: handleListRetargetingLists
11
+ });
12
+ export const addRetargetingListTool = defineTool({
13
+ name: "add_retargeting_list",
14
+ title: "Создать список ретаргетинга",
15
+ description: "Создать условие ретаргетинга из целей Метрики, сегментов или интересов.",
16
+ annotations: WRITE,
17
+ schema: addRetargetingListSchema,
18
+ handler: handleAddRetargetingList
19
+ });
@@ -0,0 +1,3 @@
1
+ import type { z } from "zod";
2
+ import type { getSearchQueriesSchema } from "./schema.js";
3
+ export declare function handleGetSearchQueries(params: z.infer<typeof getSearchQueriesSchema>): Promise<string>;
@@ -0,0 +1,28 @@
1
+ import { apiReport } from "#shared/api/reports";
2
+ import { reportName } from "#shared/lib/report";
3
+ const DEFAULT_FIELDS = [
4
+ "Query",
5
+ "CampaignId",
6
+ "CampaignName",
7
+ "AdGroupId",
8
+ "AdGroupName",
9
+ "Criterion",
10
+ "Impressions",
11
+ "Clicks",
12
+ "Cost"
13
+ ];
14
+ export async function handleGetSearchQueries(params) {
15
+ return apiReport({
16
+ SelectionCriteria: {
17
+ DateFrom: params.date_from,
18
+ DateTo: params.date_to,
19
+ Filter: [{ Field: "CampaignId", Operator: "IN", Values: params.campaign_ids }]
20
+ },
21
+ FieldNames: params.fields ?? DEFAULT_FIELDS,
22
+ ReportName: reportName("search_queries"),
23
+ ReportType: "SEARCH_QUERY_PERFORMANCE_REPORT",
24
+ DateRangeType: "CUSTOM_DATE",
25
+ Format: "TSV",
26
+ IncludeVAT: "YES"
27
+ });
28
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export declare const getSearchQueriesSchema: z.ZodObject<{
3
+ campaign_ids: z.ZodArray<z.ZodString>;
4
+ date_from: z.ZodString;
5
+ date_to: z.ZodString;
6
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ }, z.core.$strip>;
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { dateField } from "#shared/lib/date";
3
+ import { idField } from "#shared/lib/id";
4
+ export const getSearchQueriesSchema = z.object({
5
+ campaign_ids: z
6
+ .array(idField("ID кампании"))
7
+ .check(z.minLength(1, { error: "Укажите хотя бы одну кампанию" }))
8
+ .meta({ description: "Кампании, по которым нужен отчёт о поисковых запросах" }),
9
+ date_from: dateField("Первый день периода, YYYY-MM-DD"),
10
+ date_to: dateField("Последний день периода включительно, YYYY-MM-DD"),
11
+ fields: z
12
+ .array(z.string().check(z.minLength(1, { error: "Имя поля не может быть пустым" })))
13
+ .optional()
14
+ .meta({
15
+ description: "Поля отчёта; по умолчанию Query, CampaignId, CampaignName, AdGroupId, AdGroupName, Criterion, Impressions, Clicks, Cost"
16
+ })
17
+ });
@@ -0,0 +1 @@
1
+ export declare const getSearchQueriesTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,11 @@
1
+ import { defineTool, READ } from "#shared/lib/tool";
2
+ import { handleGetSearchQueries } from "./handler.js";
3
+ import { getSearchQueriesSchema } from "./schema.js";
4
+ export const getSearchQueriesTool = defineTool({
5
+ name: "get_search_queries",
6
+ title: "Поисковые запросы",
7
+ description: "Отчёт по фактическим поисковым запросам для анализа и добавления минус-фраз.",
8
+ annotations: READ,
9
+ schema: getSearchQueriesSchema,
10
+ handler: handleGetSearchQueries
11
+ });
@@ -0,0 +1,4 @@
1
+ import type { z } from "zod";
2
+ import type { listSitelinksSchema, setSitelinksSchema } from "./schema.js";
3
+ export declare function handleListSitelinks(params: z.infer<typeof listSitelinksSchema>): Promise<string>;
4
+ export declare function handleSetSitelinks(params: z.infer<typeof setSitelinksSchema>): Promise<string>;
@@ -0,0 +1,28 @@
1
+ import { apiPost } from "#shared/api/client";
2
+ import { formatResult } from "#shared/lib/format";
3
+ import { apiIds } from "#shared/lib/id";
4
+ import { buildPage } from "#shared/lib/pagination";
5
+ const NO_MONEY = { money: false };
6
+ export async function handleListSitelinks(params) {
7
+ const request = {
8
+ FieldNames: ["Id"],
9
+ SitelinkFieldNames: ["Title", "Href", "Description", "TurboPageId"]
10
+ };
11
+ if (params.sitelink_set_ids?.length)
12
+ request.SelectionCriteria = { Ids: apiIds(params.sitelink_set_ids) };
13
+ const page = buildPage(params);
14
+ if (page)
15
+ request.Page = page;
16
+ return formatResult(await apiPost("sitelinks", "get", request), NO_MONEY);
17
+ }
18
+ export async function handleSetSitelinks(params) {
19
+ const sitelinks = params.sitelinks.map((link) => {
20
+ const item = { Title: link.title };
21
+ if (link.href !== undefined)
22
+ item.Href = link.href;
23
+ if (link.description !== undefined)
24
+ item.Description = link.description;
25
+ return item;
26
+ });
27
+ return formatResult(await apiPost("sitelinks", "add", { SitelinksSets: [{ Sitelinks: sitelinks }] }), NO_MONEY);
28
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ export declare const listSitelinksSchema: z.ZodObject<{
3
+ limit: z.ZodOptional<z.ZodInt>;
4
+ offset: z.ZodOptional<z.ZodInt>;
5
+ sitelink_set_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ }, z.core.$strip>;
7
+ export declare const setSitelinksSchema: z.ZodObject<{
8
+ sitelinks: z.ZodArray<z.ZodObject<{
9
+ title: z.ZodString;
10
+ href: z.ZodOptional<z.ZodString>;
11
+ description: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$strip>>;
13
+ }, z.core.$strip>;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { MAX_IDS_PER_CALL, SITELINK_LIMITS } from "#shared/config/limits";
3
+ import { idField } from "#shared/lib/id";
4
+ import { pageFields } from "#shared/lib/pagination";
5
+ export const listSitelinksSchema = z.object({
6
+ sitelink_set_ids: z
7
+ .array(idField("ID набора быстрых ссылок"))
8
+ .check(z.maxLength(MAX_IDS_PER_CALL, { error: `За один вызов допустимо не больше ${MAX_IDS_PER_CALL} наборов` }))
9
+ .optional()
10
+ .meta({ description: "Конкретные наборы; без них возвращаются все наборы аккаунта" }),
11
+ ...pageFields
12
+ });
13
+ const sitelink = z.object({
14
+ title: z
15
+ .string()
16
+ .check(z.minLength(1, { error: "Текст ссылки не может быть пустым" }), z.maxLength(SITELINK_LIMITS.title, { error: `Текст ссылки длиннее ${SITELINK_LIMITS.title} символов` }))
17
+ .meta({ description: `Текст быстрой ссылки, до ${SITELINK_LIMITS.title} символов` }),
18
+ href: z
19
+ .string()
20
+ .check(z.maxLength(SITELINK_LIMITS.href, { error: `Ссылка длиннее ${SITELINK_LIMITS.href} символов` }))
21
+ .optional()
22
+ .meta({ description: "URL с протоколом и доменом" }),
23
+ description: z
24
+ .string()
25
+ .check(z.maxLength(SITELINK_LIMITS.description, { error: `Описание длиннее ${SITELINK_LIMITS.description} символов` }))
26
+ .optional()
27
+ .meta({ description: `Описание ссылки, до ${SITELINK_LIMITS.description} символов` })
28
+ });
29
+ // Набор создаётся целиком: Директ не умеет дописать ссылку в существующий набор.
30
+ export const setSitelinksSchema = z.object({
31
+ sitelinks: z
32
+ .array(sitelink)
33
+ .check(z.minLength(1, { error: "Набор не может быть пустым" }), z.maxLength(SITELINK_LIMITS.perSet, { error: `В наборе не больше ${SITELINK_LIMITS.perSet} ссылок` }))
34
+ .meta({ description: `Новый набор из 1–${SITELINK_LIMITS.perSet} быстрых ссылок` })
35
+ });
@@ -0,0 +1,2 @@
1
+ export declare const listSitelinksTool: import("#shared/lib/tool").ToolDescriptor;
2
+ export declare const setSitelinksTool: import("#shared/lib/tool").ToolDescriptor;
@@ -0,0 +1,19 @@
1
+ import { defineTool, READ, WRITE } from "#shared/lib/tool";
2
+ import { handleListSitelinks, handleSetSitelinks } from "./handler.js";
3
+ import { listSitelinksSchema, setSitelinksSchema } from "./schema.js";
4
+ export const listSitelinksTool = defineTool({
5
+ name: "list_sitelinks",
6
+ title: "Список быстрых ссылок",
7
+ description: "Получить все или выбранные наборы быстрых ссылок.",
8
+ annotations: READ,
9
+ schema: listSitelinksSchema,
10
+ handler: handleListSitelinks
11
+ });
12
+ export const setSitelinksTool = defineTool({
13
+ name: "set_sitelinks",
14
+ title: "Создать быстрые ссылки",
15
+ description: "Создать новый набор из 1–8 быстрых ссылок. Возвращает ID набора для привязки к объявлению.",
16
+ annotations: WRITE,
17
+ schema: setSitelinksSchema,
18
+ handler: handleSetSitelinks
19
+ });
@@ -0,0 +1,3 @@
1
+ import type { z } from "zod";
2
+ import type { getStatisticsSchema } from "./schema.js";
3
+ export declare function handleGetStatistics(params: z.infer<typeof getStatisticsSchema>): Promise<string>;
@@ -0,0 +1,20 @@
1
+ import { apiReport } from "#shared/api/reports";
2
+ import { reportName } from "#shared/lib/report";
3
+ const DEFAULT_FIELDS = ["Date", "CampaignName", "Impressions", "Clicks", "Cost", "Ctr", "AvgCpc"];
4
+ // Отчёт приходит готовым TSV: ReportService форматирует его сам, деньги отдаёт
5
+ // в рублях (returnMoneyInMicros=false), поэтому formatResult здесь не участвует.
6
+ export async function handleGetStatistics(params) {
7
+ return apiReport({
8
+ SelectionCriteria: {
9
+ DateFrom: params.date_from,
10
+ DateTo: params.date_to,
11
+ Filter: [{ Field: "CampaignId", Operator: "IN", Values: params.campaign_ids }]
12
+ },
13
+ FieldNames: params.fields ?? DEFAULT_FIELDS,
14
+ ReportName: reportName("report"),
15
+ ReportType: "CAMPAIGN_PERFORMANCE_REPORT",
16
+ DateRangeType: "CUSTOM_DATE",
17
+ Format: "TSV",
18
+ IncludeVAT: "YES"
19
+ });
20
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export declare const getStatisticsSchema: z.ZodObject<{
3
+ campaign_ids: z.ZodArray<z.ZodString>;
4
+ date_from: z.ZodString;
5
+ date_to: z.ZodString;
6
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ }, z.core.$strip>;
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import { dateField } from "#shared/lib/date";
3
+ import { idField } from "#shared/lib/id";
4
+ export const getStatisticsSchema = z.object({
5
+ campaign_ids: z
6
+ .array(idField("ID кампании"))
7
+ .check(z.minLength(1, { error: "Укажите хотя бы одну кампанию" }))
8
+ .meta({ description: "Кампании, по которым строится отчёт" }),
9
+ date_from: dateField("Первый день периода, YYYY-MM-DD"),
10
+ date_to: dateField("Последний день периода включительно, YYYY-MM-DD"),
11
+ fields: z
12
+ .array(z.string().check(z.minLength(1, { error: "Имя поля не может быть пустым" })))
13
+ .optional()
14
+ .meta({
15
+ description: "Поля отчёта; по умолчанию Date, CampaignName, Impressions, Clicks, Cost, Ctr, AvgCpc. Деньги приходят в рублях"
16
+ })
17
+ });
@@ -0,0 +1 @@
1
+ export declare const getStatisticsTool: import("#shared/lib/tool").ToolDescriptor;