yandex-direct-mcp-plus 1.5.0 → 1.6.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Node](https://img.shields.io/badge/node-%3E%3D22-green.svg)](https://nodejs.org)
8
8
 
9
- - **58 инструментов**, из них 24 только читают. Кампании и стратегии, группы, объявления и модерация, ключевые фразы и ставки, минус-фразы и общие наборы, быстрые ссылки, уточнения, изображения, визитки, корректировки ставок, ретаргетинг, аудиторные и динамические цели, фиды, расписание показов, статистика, поисковые запросы, баланс и справочники.
9
+ - **60 инструментов**, из них 25 только читают. Кампании и стратегии, группы, объявления и модерация, ключевые фразы и ставки, минус-фразы и общие наборы, быстрые ссылки, уточнения, изображения, визитки, корректировки ставок, ретаргетинг, аудиторные и динамические цели, фиды, расписание показов, статистика, поисковые запросы, баланс и справочники.
10
10
  - **Деньги — в рублях**, на вводе и на выводе; в микроединицы API сервер переводит сам. Поддержан агентский режим (`Client-Login`).
11
11
  - **ID — строками** (`"1915016273214320641"`): 64-битные идентификаторы Директа не помещаются в число JavaScript и молча теряют точность. Здесь это стережёт правило линтера, а не внимательность.
12
12
  - **Реклама боевая.** Тестовой среды у Директа больше нет — какие инструменты тратят деньги и что удаляют необратимо, перечислено в разделе [Что меняет данные](#что-меняет-данные).
@@ -18,7 +18,7 @@
18
18
  - [Установка](#установка) — Claude Code, Claude Desktop, Cursor, из исходников
19
19
  - [Токен](#токен) — как получить и какие переменные окружения нужны
20
20
  - [Что меняет данные](#что-меняет-данные) — что тратит бюджет и что необратимо
21
- - [Инструменты](#инструменты-58) — полный список с описаниями
21
+ - [Инструменты](#инструменты-60) — полный список с описаниями
22
22
  - [Разработка](#разработка) — сборка, тесты, архитектура
23
23
 
24
24
  ## Что можно делать
@@ -35,7 +35,7 @@
35
35
  Найди код региона для Новосибирска
36
36
  ```
37
37
 
38
- Полный список — [58 инструментов](#инструменты-58) ниже.
38
+ Полный список — [60 инструментов](#инструменты-60) ниже.
39
39
 
40
40
  ## Установка
41
41
 
@@ -89,7 +89,7 @@ OAuth-токен выпускается для приложения, зарег
89
89
 
90
90
  Граница проходит не по «чтение или запись», а по скорости, с которой действие превращается в деньги.
91
91
 
92
- **24 инструмента только читают** — все `list_*`, `get_*` и справочники. Вызвать их безопасно всегда.
92
+ **25 инструментов только читают** — все `list_*`, `get_*` и справочники. Вызвать их безопасно всегда.
93
93
 
94
94
  **Тратят бюджет или запускают показы** — восемь:
95
95
 
@@ -108,11 +108,11 @@ OAuth-токен выпускается для приложения, зарег
108
108
 
109
109
  `manage_campaigns` (`delete`), `manage_ads` (`delete`), `manage_keywords` (`delete`), `delete_ad_groups`, `delete_ad_extensions`, `delete_sitelinks`, `delete_vcards`, `delete_bid_adjustments`, `delete_retargeting_lists`, `manage_ad_images` (`delete`), `manage_dynamic_targets` (`delete`), `set_audience_targets` (`delete`), `manage_negative_keyword_shared_sets` (`delete`).
110
110
 
111
- Сюда же — `set_campaign_negative_keywords` и `set_ad_group_negative_keywords` в режиме `replace`: он затирает прежний список минус-фраз целиком. Именно поэтому у них нет режима по умолчанию — `mode` приходится назвать явно.
111
+ Сюда же — `set_campaign_negative_keywords` и `set_ad_group_negative_keywords` в режиме `replace`: он затирает прежний список минус-фраз целиком. Именно поэтому у них нет режима по умолчанию — `mode` приходится назвать явно. Так же устроен `set_priority_goals`: `replace` и `remove` убирают цели стратегии, а любая смена целей перезапускает её обучение.
112
112
 
113
113
  Остальные инструменты создают и правят объекты. Пока кампания не прошла модерацию и не включена, показов по ней нет и бюджет не расходуется.
114
114
 
115
- ## Инструменты (58)
115
+ ## Инструменты (60)
116
116
 
117
117
  **Кампании**
118
118
 
@@ -125,6 +125,7 @@ OAuth-токен выпускается для приложения, зарег
125
125
  | `manage_campaigns` | suspend/resume/archive/unarchive/delete для списка кампаний |
126
126
  | `get_strategy` | Получить стратегию текстово-графической кампании |
127
127
  | `set_strategy` | Сменить стратегию: ручная, максимум кликов, средняя цена клика/конверсии, оплата за конверсию |
128
+ | `set_priority_goals` | Цели стратегии и их ценность в рублях: добавить, убрать или заменить список |
128
129
  | `get_time_targeting` | Расписание показов: часовой пояс, часы по дням недели, праздники |
129
130
  | `set_time_targeting` | Задать расписание показов и почасовые коэффициенты (заменяет целиком) |
130
131
 
@@ -155,6 +156,7 @@ OAuth-токен выпускается для приложения, зарег
155
156
  | `add_keywords` | Добавить ключевые фразы |
156
157
  | `update_keywords` | Изменить текст фразы и подстановочные переменные `{param1}`/`{param2}` |
157
158
  | `set_keyword_bids` | Установить ставки (поиск/сети, рубли) на фразах/группах/кампаниях |
159
+ | `get_keyword_auction` | Аукцион по фразам: ставки и списываемые цены по позициям, ставки конкурентов, цена входа (рубли) |
158
160
  | `manage_keywords` | suspend/resume/delete |
159
161
  | `set_campaign_negative_keywords` | Минус-фразы кампании: `mode` обязателен — `replace`, `add` или `remove` |
160
162
  | `get_campaign_negative_keywords` | Получить минус-фразы кампаний |
@@ -6,7 +6,7 @@ import { createTextAdTool, listAdsTool, manageAdsTool, moderateAdsTool, updateTe
6
6
  import { listAudienceTargetsTool, setAudienceTargetsTool } from "#tools/audience-targets/tool";
7
7
  import { addBidAdjustmentsTool, deleteBidAdjustmentsTool, getBidAdjustmentsTool, setBidAdjustmentsTool } from "#tools/bid-adjustments/tool";
8
8
  import { listBusinessesTool } from "#tools/businesses/tool";
9
- import { createCampaignTool, getCampaignTool, getStrategyTool, listCampaignsTool, manageCampaignsTool, setStrategyTool, updateCampaignTool } from "#tools/campaigns/tool";
9
+ import { createCampaignTool, getCampaignTool, getStrategyTool, listCampaignsTool, manageCampaignsTool, setPriorityGoalsTool, setStrategyTool, updateCampaignTool } from "#tools/campaigns/tool";
10
10
  import { getChangesTool } from "#tools/changes/tool";
11
11
  import { getRegionsTool, listTimeZonesTool } from "#tools/dictionaries/tool";
12
12
  import { listDynamicTargetsTool, manageDynamicTargetsTool } from "#tools/dynamic-targets/tool";
@@ -28,6 +28,7 @@ export const tools = [
28
28
  manageCampaignsTool,
29
29
  getStrategyTool,
30
30
  setStrategyTool,
31
+ setPriorityGoalsTool,
31
32
  getTimeTargetingTool,
32
33
  setTimeTargetingTool,
33
34
  // Группы объявлений
@@ -11,6 +11,7 @@ export declare const MAX_ADS_PER_MODERATION = 10000;
11
11
  export declare const MAX_SHARED_SETS_PER_CALL = 30;
12
12
  export declare const MAX_SHARED_SETS_PER_AD_GROUP = 3;
13
13
  export declare const SHARED_SET_NAME_MAX = 255;
14
+ export declare const MAX_PRIORITY_GOALS = 30;
14
15
  export declare const MAX_IDS_PER_CALL = 10000;
15
16
  export declare const AD_TEXT_LIMITS: {
16
17
  readonly title: 56;
@@ -16,6 +16,8 @@ export const MAX_ADS_PER_MODERATION = 10_000;
16
16
  export const MAX_SHARED_SETS_PER_CALL = 30;
17
17
  export const MAX_SHARED_SETS_PER_AD_GROUP = 3;
18
18
  export const SHARED_SET_NAME_MAX = 255;
19
+ // Ключевых целей стратегии у кампании — не больше тридцати (справочник campaigns/update).
20
+ export const MAX_PRIORITY_GOALS = 30;
19
21
  // Универсальный потолок на список ID в SelectionCriteria большинства сервисов.
20
22
  export const MAX_IDS_PER_CALL = 10_000;
21
23
  // Длины текстов объявления, справочник TextAd.
@@ -1,5 +1,5 @@
1
1
  import type { z } from "zod";
2
- import type { createCampaignSchema, getCampaignSchema, getStrategySchema, listCampaignsSchema, manageCampaignsSchema, setStrategySchema, updateCampaignSchema } from "./schema.js";
2
+ import type { createCampaignSchema, getCampaignSchema, getStrategySchema, listCampaignsSchema, manageCampaignsSchema, setPriorityGoalsSchema, setStrategySchema, updateCampaignSchema } from "./schema.js";
3
3
  export declare function handleListCampaigns(params: z.infer<typeof listCampaignsSchema>): Promise<string>;
4
4
  export declare function handleGetCampaign(params: z.infer<typeof getCampaignSchema>): Promise<string>;
5
5
  export declare function handleCreateCampaign(params: z.infer<typeof createCampaignSchema>): Promise<string>;
@@ -8,4 +8,6 @@ export declare function handleManageCampaigns(params: z.infer<typeof manageCampa
8
8
  export declare function handleGetStrategy(params: z.infer<typeof getStrategySchema>): Promise<string>;
9
9
  type StrategyParams = z.infer<typeof setStrategySchema>;
10
10
  export declare function handleSetStrategy(params: StrategyParams): Promise<string>;
11
+ type PriorityGoalsParams = z.infer<typeof setPriorityGoalsSchema>;
12
+ export declare function handleSetPriorityGoals(params: PriorityGoalsParams): Promise<string>;
11
13
  export {};
@@ -1,8 +1,10 @@
1
1
  import { apiPost } from "#shared/api/client";
2
+ import { MAX_PRIORITY_GOALS } from "#shared/config/limits";
2
3
  import { getCampaignSettingsKey } from "#shared/lib/campaign-type";
3
4
  import { formatResult } from "#shared/lib/format";
4
5
  import { apiId, apiIds } from "#shared/lib/id";
5
6
  import { buildPage } from "#shared/lib/pagination";
7
+ import { mergePriorityGoals } from "./priority-goals.js";
6
8
  import { CAMPAIGN_LIST_FIELDS } from "./schema.js";
7
9
  // Набор по умолчанию объявлен в контракте: его перечисляет описание параметра fields.
8
10
  const DETAIL_FIELDS = [...CAMPAIGN_LIST_FIELDS, "EndDate"];
@@ -236,3 +238,68 @@ export async function handleSetStrategy(params) {
236
238
  });
237
239
  return formatResult(data);
238
240
  }
241
+ // Типы, у которых PriorityGoals есть в CampaignUpdateItem: сверено с WSDL campaigns
242
+ // 13.09.2026. У MobileApp и CpmBanner поля нет — список свой, как у TrackingParams.
243
+ const PRIORITY_GOALS_TYPES = ["TEXT_CAMPAIGN", "DYNAMIC_TEXT_CAMPAIGN", "SMART_CAMPAIGN", "UNIFIED_CAMPAIGN"];
244
+ // Имя поля совпадает у всех четырёх типов, включая смарт-кампании.
245
+ const PRIORITY_GOALS_FIELDS = ["PriorityGoals"];
246
+ // Тип и текущие цели читаются одним вызовом: имя объекта настроек нужно и для replace, где
247
+ // сливать не с чем. Отсутствие кампании в ответе — ошибка до update, а не пустой список.
248
+ async function readPriorityGoals(campaignId) {
249
+ const data = await apiPost("campaigns", "get", {
250
+ SelectionCriteria: { Ids: [apiId(campaignId)] },
251
+ FieldNames: ["Id", "Type"],
252
+ TextCampaignFieldNames: PRIORITY_GOALS_FIELDS,
253
+ DynamicTextCampaignFieldNames: PRIORITY_GOALS_FIELDS,
254
+ SmartCampaignFieldNames: PRIORITY_GOALS_FIELDS,
255
+ UnifiedCampaignFieldNames: PRIORITY_GOALS_FIELDS
256
+ });
257
+ const campaign = data.result?.Campaigns?.[0];
258
+ if (!campaign)
259
+ throw new Error(`Кампания ${campaignId} не найдена или недоступна — объединять цели не с чем.`);
260
+ const { Type: type } = campaign;
261
+ const settingsKey = type && PRIORITY_GOALS_TYPES.includes(type) ? getCampaignSettingsKey(type) : undefined;
262
+ if (!settingsKey) {
263
+ throw new Error(`Кампания ${campaignId} имеет тип ${type ?? "неизвестный"}, а цели стратегии поддерживают только ${PRIORITY_GOALS_TYPES.join(", ")}.`);
264
+ }
265
+ const items = campaign[settingsKey]?.PriorityGoals?.Items ?? [];
266
+ const goals = items.map((item) => ({
267
+ goalId: String(item.GoalId),
268
+ value: item.Value,
269
+ isMetrikaSourceOfValue: item.IsMetrikaSourceOfValue
270
+ }));
271
+ return { settingsKey, goals };
272
+ }
273
+ // Проверка до чтения: без ценности add и replace бессмысленны, и узнать это надо, не
274
+ // потратив вызов API. Для remove ценность не участвует в слиянии.
275
+ function toIncomingGoals({ goals, mode }) {
276
+ return goals.map((goal) => {
277
+ if (mode !== "remove" && goal.value === undefined) {
278
+ throw new Error(`value обязателен для цели ${goal.goal_id} при mode=${mode}.`);
279
+ }
280
+ return { goalId: goal.goal_id, value: goal.value ?? 0 };
281
+ });
282
+ }
283
+ // Operation обязателен и принимает только SET (справочник campaigns/update).
284
+ // IsMetrikaSourceOfValue нужен лишь стратегиям на ДРР, поэтому уходит, только если был.
285
+ function toApiGoal(goal) {
286
+ const item = { GoalId: apiId(goal.goalId), Value: goal.value, Operation: "SET" };
287
+ if (goal.isMetrikaSourceOfValue !== undefined)
288
+ item.IsMetrikaSourceOfValue = goal.isMetrikaSourceOfValue;
289
+ return item;
290
+ }
291
+ // Форма тела подтверждена записью 13.09.2026 на боевой кампании по просьбе пользователя.
292
+ // Пустой список уходит null: в WSDL PriorityGoals объявлен nillable, а у Items minOccurs="1".
293
+ export async function handleSetPriorityGoals(params) {
294
+ const incoming = toIncomingGoals(params);
295
+ const { settingsKey, goals } = await readPriorityGoals(params.campaign_id);
296
+ const merged = mergePriorityGoals(goals, incoming, params.mode);
297
+ if (merged.length > MAX_PRIORITY_GOALS) {
298
+ throw new Error(`У кампании не больше ${MAX_PRIORITY_GOALS} целей стратегии, после слияния их ${merged.length}.`);
299
+ }
300
+ const priorityGoals = merged.length > 0 ? { Items: merged.map(toApiGoal) } : null;
301
+ const data = await apiPost("campaigns", "update", {
302
+ Campaigns: [{ Id: apiId(params.campaign_id), [settingsKey]: { PriorityGoals: priorityGoals } }]
303
+ });
304
+ return formatResult(data);
305
+ }
@@ -0,0 +1,7 @@
1
+ export type PriorityGoalsMode = "replace" | "add" | "remove";
2
+ export type PriorityGoal = {
3
+ goalId: string;
4
+ value: number;
5
+ isMetrikaSourceOfValue?: string;
6
+ };
7
+ export declare function mergePriorityGoals(existing: PriorityGoal[], incoming: PriorityGoal[], mode: PriorityGoalsMode): PriorityGoal[];
@@ -0,0 +1,24 @@
1
+ // Слияние целей стратегии для режимов add и remove. Директ перезаписывает
2
+ // PriorityGoals.Items целиком, поэтому объединять обязан сценарий — а здесь это чистая
3
+ // функция, чтобы правило слияния проверялось обычным тестом, а не двумя подменами fetch.
4
+ export function mergePriorityGoals(existing, incoming, mode) {
5
+ // Повтор одной цели во входящем списке схлопывается, выигрывает последняя ценность:
6
+ // два элемента с одним GoalId Директ принял бы за противоречие, а не за уточнение.
7
+ const incomingById = new Map(incoming.map((goal) => [goal.goalId, goal]));
8
+ if (mode === "replace")
9
+ return [...incomingById.values()];
10
+ if (mode === "remove")
11
+ return existing.filter((goal) => !incomingById.has(goal.goalId));
12
+ // add — это upsert: у уже заданной цели меняется только ценность, признак источника
13
+ // ценности остаётся прежним. Новые цели дописываются в конец.
14
+ const merged = existing.map((goal) => {
15
+ const update = incomingById.get(goal.goalId);
16
+ return update ? { ...goal, value: update.value } : goal;
17
+ });
18
+ const existingIds = new Set(existing.map((goal) => goal.goalId));
19
+ for (const goal of incomingById.values()) {
20
+ if (!existingIds.has(goal.goalId))
21
+ merged.push(goal);
22
+ }
23
+ return merged;
24
+ }
@@ -47,3 +47,11 @@ export declare const setStrategySchema: z.ZodObject<{
47
47
  goal_id: z.ZodOptional<z.ZodString>;
48
48
  network_limit_percent: z.ZodOptional<z.ZodInt>;
49
49
  }, z.core.$strip>;
50
+ export declare const setPriorityGoalsSchema: z.ZodObject<{
51
+ campaign_id: z.ZodString;
52
+ goals: z.ZodArray<z.ZodObject<{
53
+ goal_id: z.ZodString;
54
+ value: z.ZodOptional<z.ZodPipe<z.ZodNumber, z.ZodTransform<number, number>>>;
55
+ }, z.core.$strip>>;
56
+ mode: z.ZodLiteral<"add" | "remove" | "replace">;
57
+ }, z.core.$strip>;
@@ -3,7 +3,7 @@
3
3
  import { z } from "zod";
4
4
  import { API_FIELDS } from "#shared/config/api-fields";
5
5
  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";
6
- import { MAX_CAMPAIGNS_PER_CALL } from "#shared/config/limits";
6
+ import { MAX_CAMPAIGNS_PER_CALL, MAX_PRIORITY_GOALS } from "#shared/config/limits";
7
7
  import { dateField } from "#shared/lib/date";
8
8
  import { fieldsField } from "#shared/lib/fields";
9
9
  import { idField } from "#shared/lib/id";
@@ -125,10 +125,30 @@ export const setStrategySchema = z.object({
125
125
  conversion_price: rublesField("Цена конверсии в рублях для PAY_FOR_CONVERSION: списывается за конверсию, а не за клик").optional(),
126
126
  goal_id: idField("ID цели Метрики для AVERAGE_CPA, PAY_FOR_CONVERSION и WB_MAXIMUM_CONVERSION_RATE; для оплаты за конверсию обязателен. " +
127
127
  "Для WB_MAXIMUM_CONVERSION_RATE допустимо служебное 13 — оптимизация по ключевым целям кампании (PriorityGoals); " +
128
- "Директ принимает его, только если в PriorityGoals есть цель, кроме 12 «Вовлечённые сессии»").optional(),
128
+ "Директ принимает его, только если в PriorityGoals есть цель, кроме 12 «Вовлечённые сессии»; сами цели задаёт set_priority_goals").optional(),
129
129
  network_limit_percent: z
130
130
  .int()
131
131
  .check(z.gte(1, { error: "Доля расходов не меньше 1%" }), z.lte(100, { error: "Доля расходов не больше 100%" }))
132
132
  .optional()
133
133
  .meta({ description: "Доля расходов в сетях для NETWORK_DEFAULT, проценты" })
134
134
  });
135
+ // Цели стратегии перезаписываются целиком, как минус-фразы, и режим обязателен по той же
136
+ // причине: replace с одной целью на просьбе «добавь цель» стёр бы остальные, а Директ
137
+ // ответил бы успехом. Ценность необязательна в схеме только ради remove — там цель лишь
138
+ // называется; для add и replace её требует хендлер.
139
+ const priorityGoal = z.object({
140
+ goal_id: idField("ID цели Метрики; служебное 12 — «Вовлечённые сессии». Названия целей есть только в Метрике"),
141
+ value: rublesField("Ценность конверсии по цели в рублях; обязательна при mode=add и replace, при remove не нужна").optional()
142
+ });
143
+ export const setPriorityGoalsSchema = z.object({
144
+ campaign_id: idField("ID кампании: текстово-графической, динамической, смарт или единой перфоманс-кампании"),
145
+ goals: z
146
+ .array(priorityGoal)
147
+ .check(z.maxLength(MAX_PRIORITY_GOALS, { error: `У кампании не больше ${MAX_PRIORITY_GOALS} целей стратегии` }))
148
+ .meta({
149
+ description: "Цели: при mode=replace — полный новый список взамен прежнего (пустой массив очищает), при add — что добавить или чью ценность поменять, при remove — что убрать"
150
+ }),
151
+ mode: z.literal(["replace", "add", "remove"]).meta({
152
+ description: "Обязателен. replace — заменить список целиком (прежние цели теряются), add — добавить цели или поменять ценность уже заданных, remove — убрать перечисленные. Текущий список сервер читает сам, это дополнительный вызов API"
153
+ })
154
+ });
@@ -5,3 +5,4 @@ export declare const updateCampaignTool: import("#shared/lib/tool").ToolDescript
5
5
  export declare const manageCampaignsTool: import("#shared/lib/tool").ToolDescriptor;
6
6
  export declare const getStrategyTool: import("#shared/lib/tool").ToolDescriptor;
7
7
  export declare const setStrategyTool: import("#shared/lib/tool").ToolDescriptor;
8
+ export declare const setPriorityGoalsTool: import("#shared/lib/tool").ToolDescriptor;
@@ -1,11 +1,13 @@
1
1
  // Дескрипторы инструментов домена: имя, описание и аннотация живут рядом с хендлером.
2
2
  import { DESTRUCTIVE, 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";
3
+ import { handleCreateCampaign, handleGetCampaign, handleGetStrategy, handleListCampaigns, handleManageCampaigns, handleSetPriorityGoals, handleSetStrategy, handleUpdateCampaign } from "./handler.js";
4
+ import { createCampaignSchema, getCampaignSchema, getStrategySchema, listCampaignsSchema, manageCampaignsSchema, setPriorityGoalsSchema, setStrategySchema, updateCampaignSchema } from "./schema.js";
5
5
  export const listCampaignsTool = defineTool({
6
6
  name: "list_campaigns",
7
7
  title: "Список кампаний",
8
- description: "Список рекламных кампаний Яндекс.Директ с фильтрацией по статусу и типу. Бюджеты — в рублях. По умолчанию возвращается узкий набор полей; нужны другие (Funds, TimeZone, NegativeKeywords и прочие из CampaignFieldEnum) — перечислите их в fields.",
8
+ description: "Список рекламных кампаний Яндекс.Директ с фильтрацией по статусу и типу. Бюджеты — в рублях. По умолчанию возвращается узкий набор полей; нужны другие (Funds, TimeZone, NegativeKeywords и прочие из CampaignFieldEnum) — перечислите их в fields. " +
9
+ "Кампаний «Баннер на поиске» (MCBANNER) API не отдаёт вовсе: их нет в списке, и это граница API, " +
10
+ "а не ошибка логина или фильтра — такие кампании видны только в веб-интерфейсе.",
9
11
  annotations: READ,
10
12
  schema: listCampaignsSchema,
11
13
  handler: handleListCampaigns
@@ -16,7 +18,8 @@ export const getCampaignTool = defineTool({
16
18
  description: "Детальная информация о кампании по ID: бюджет (руб), статус и пояснение к нему, даты, статистика, " +
17
19
  "UTM-разметка, цели и их ценность (PriorityGoals), счётчики Метрики, модель атрибуции и прочие настройки. " +
18
20
  "Реальные ID целей — PriorityGoals.Items[].GoalId; GoalId 13 в стратегии — служебное «ключевые цели», " +
19
- "то есть оптимизация по этим PriorityGoals.",
21
+ "то есть оптимизация по этим PriorityGoals. Пустой ответ по ID из веб-интерфейса не значит, что номер " +
22
+ "неверный: кампании «Баннер на поиске» (MCBANNER) API не отдаёт, по ID они приходят пустыми.",
20
23
  annotations: READ,
21
24
  schema: getCampaignSchema,
22
25
  handler: handleGetCampaign
@@ -66,3 +69,16 @@ export const setStrategyTool = defineTool({
66
69
  schema: setStrategySchema,
67
70
  handler: handleSetStrategy
68
71
  });
72
+ export const setPriorityGoalsTool = defineTool({
73
+ name: "set_priority_goals",
74
+ title: "Цели стратегии",
75
+ description: "Задать цели стратегии кампании (PriorityGoals) и их ценность в рублях — по ним автостратегия оптимизирует " +
76
+ "ставки, в том числе «максимум конверсий» со служебным GoalId 13. Режим mode обязателен: add добавляет цели или " +
77
+ "меняет ценность уже заданных, remove убирает названные, replace заменяет список целиком (пустой массив очищает). " +
78
+ "Текущий список сервер читает сам. Чтобы добавить цель к существующим, нужен add: replace с одной целью сотрёт " +
79
+ "остальные. ⚠️ Смена целей перезапускает обучение стратегии. Поддерживаются текстово-графические, динамические, " +
80
+ "смарт и единые перфоманс-кампании; ID целей — из Метрики.",
81
+ annotations: DESTRUCTIVE,
82
+ schema: setPriorityGoalsSchema,
83
+ handler: handleSetPriorityGoals
84
+ });
@@ -4,8 +4,9 @@ import { formatResult } from "#shared/lib/format";
4
4
  import { apiIds } from "#shared/lib/id";
5
5
  import { buildPage } from "#shared/lib/pagination";
6
6
  const NO_MONEY = { money: false };
7
- // Sitelinks из SitelinksSetFieldEnum не запрашивается: сами ссылки приезжают
8
- // отдельным параметром SitelinkFieldNames, набором ниже.
7
+ // Sitelinks из SitelinksSetFieldEnum не запрашивается: он взаимоисключающий с
8
+ // SitelinkFieldNames (пробой 13.09.2026, ошибка 4004), а сами ссылки приезжают этим
9
+ // параметром, набором ниже.
9
10
  const SET_FIELDS = ["Id"];
10
11
  export async function handleListSitelinks(params) {
11
12
  const request = {
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "yandex-direct-mcp-plus",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "MCP-сервер для API Яндекс.Директа v5: кампании, объявления, ключевые фразы, ставки, минус-фразы, статистика. Деньги в рублях, ID без потери точности.",
5
+ "mcpName": "io.github.Pavelsiba/yandex-direct-mcp-plus",
5
6
  "type": "module",
6
7
  "main": "dist/app/index.js",
7
8
  "imports": {
@@ -64,6 +65,7 @@
64
65
  "@commitlint/config-conventional": "^21.2.2",
65
66
  "@commitlint/types": "^21.2.0",
66
67
  "@semantic-release/changelog": "^7.0.0",
68
+ "@semantic-release/exec": "^7.1.0",
67
69
  "@semantic-release/git": "^11.0.1",
68
70
  "@types/json-bigint": "^1.0.4",
69
71
  "@types/node": "^26.4.1",