vk-ads-mcp 1.0.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 ADDED
@@ -0,0 +1,107 @@
1
+ # vk-ads-mcp
2
+
3
+ MCP (Model Context Protocol) server for the VK Ads API. Allows AI assistants to manage VK advertising campaigns, ad groups, banners, audiences, and more through a standardized tool interface.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx -y vk-ads-mcp
9
+ ```
10
+
11
+ Or install globally:
12
+
13
+ ```bash
14
+ npm install -g vk-ads-mcp
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Set the following environment variables:
20
+
21
+ | Variable | Required | Description |
22
+ |---|---|---|
23
+ | `VK_ACCESS_TOKEN` | Yes | VK Ads API access token |
24
+ | `VK_PROXY_URL` | No | API proxy URL (defaults to `https://rk.targethunter.ru/vkr.request`) |
25
+
26
+ ### Claude Desktop
27
+
28
+ Add to your Claude Desktop `claude_desktop_config.json`:
29
+
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "vk-ads": {
34
+ "command": "npx",
35
+ "args": ["-y", "vk-ads-mcp"],
36
+ "env": {
37
+ "VK_ACCESS_TOKEN": "your-token-here"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Tools
45
+
46
+ ### Account
47
+ - **vk_get_user** -- Get current user/ad account info
48
+ - **vk_get_throttling** -- Get API rate limit info
49
+
50
+ ### Ad Plans (Campaigns)
51
+ - **vk_get_ad_plans** -- List campaigns with filtering and pagination
52
+ - **vk_get_ad_plan** -- Get single campaign details by ID
53
+ - **vk_create_ad_plan** -- Create a new campaign
54
+ - **vk_update_ad_plan** -- Update a campaign
55
+ - **vk_delete_ad_plan** -- Delete a campaign
56
+ - **vk_mass_action_ad_plans** -- Bulk start/stop/delete campaigns
57
+
58
+ ### Ad Groups
59
+ - **vk_get_ad_groups** -- List ad groups with filtering
60
+ - **vk_get_ad_group** -- Get single ad group details
61
+ - **vk_create_ad_group** -- Create an ad group
62
+ - **vk_update_ad_group** -- Update an ad group
63
+ - **vk_delete_ad_group** -- Delete an ad group
64
+ - **vk_mass_action_ad_groups** -- Bulk start/stop/delete ad groups
65
+
66
+ ### Banners (Ads)
67
+ - **vk_get_banners** -- List banners with filtering
68
+ - **vk_get_banner** -- Get single banner details
69
+ - **vk_create_banner** -- Create a banner
70
+ - **vk_update_banner** -- Update a banner
71
+ - **vk_delete_banner** -- Delete a banner
72
+ - **vk_mass_action_banners** -- Bulk start/stop/delete banners
73
+
74
+ ### Statistics
75
+ - **vk_get_statistics** -- Get performance stats (impressions, clicks, spend, CTR) for campaigns, ad groups, or banners by day/hour/summary
76
+
77
+ ### Remarketing
78
+ - **vk_get_remarketing_users_lists** -- List remarketing audiences
79
+ - **vk_get_remarketing_users_list** -- Get single audience details
80
+ - **vk_create_remarketing_users_list** -- Create an audience (upload emails, phones, VK IDs)
81
+ - **vk_delete_remarketing_users_list** -- Delete an audience
82
+ - **vk_get_remarketing_counters** -- List remarketing pixels
83
+ - **vk_get_remarketing_counter** -- Get single pixel details
84
+ - **vk_create_remarketing_counter** -- Create a remarketing pixel
85
+ - **vk_delete_remarketing_counter** -- Delete a remarketing pixel
86
+
87
+ ### Targeting
88
+ - **vk_get_regions** -- List regions for targeting
89
+ - **vk_get_interests** -- List interest categories for targeting
90
+ - **vk_get_projection** -- Estimate audience size for targeting parameters
91
+
92
+ ### Agency
93
+ - **vk_get_agency_clients** -- List agency clients
94
+ - **vk_get_agency_client** -- Get single client details
95
+ - **vk_get_agency_managers** -- List agency managers
96
+ - **vk_get_agency_manager** -- Get single manager details
97
+
98
+ ### Media
99
+ - **vk_upload_image** -- Upload an image for use in banners
100
+
101
+ ### Blacklists
102
+ - **vk_get_blacklists** -- List placement blacklists
103
+ - **vk_get_blacklist** -- Get single blacklist details
104
+
105
+ ## License
106
+
107
+ ISC
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { VkAdsApi } from "./vk-api.js";
5
+ import { registerAdPlanTools } from "./tools/ad-plans.js";
6
+ import { registerAdGroupTools } from "./tools/ad-groups.js";
7
+ import { registerBannerTools } from "./tools/banners.js";
8
+ import { registerStatisticsTools } from "./tools/statistics.js";
9
+ import { registerRemarketingTools } from "./tools/remarketing.js";
10
+ import { registerTargetingTools } from "./tools/targeting.js";
11
+ import { registerAgencyTools } from "./tools/agency.js";
12
+ import { registerAccountTools } from "./tools/account.js";
13
+ import { registerMediaTools } from "./tools/media.js";
14
+ import { registerBlacklistTools } from "./tools/blacklists.js";
15
+ const accessToken = process.env.VK_ACCESS_TOKEN;
16
+ const proxyUrl = process.env.VK_PROXY_URL || "https://rk.targethunter.ru/vkr.request";
17
+ if (!accessToken) {
18
+ console.error("VK_ACCESS_TOKEN is required. Set it as environment variable.");
19
+ process.exit(1);
20
+ }
21
+ const api = new VkAdsApi({ accessToken, proxyUrl });
22
+ const server = new McpServer({
23
+ name: "vk-ads-mcp",
24
+ version: "2.0.0",
25
+ });
26
+ registerAccountTools(server, api);
27
+ registerAdPlanTools(server, api);
28
+ registerAdGroupTools(server, api);
29
+ registerBannerTools(server, api);
30
+ registerStatisticsTools(server, api);
31
+ registerRemarketingTools(server, api);
32
+ registerTargetingTools(server, api);
33
+ registerAgencyTools(server, api);
34
+ registerMediaTools(server, api);
35
+ registerBlacklistTools(server, api);
36
+ async function main() {
37
+ const transport = new StdioServerTransport();
38
+ await server.connect(transport);
39
+ }
40
+ main().catch((error) => {
41
+ console.error("Failed to start VK Ads MCP server:", error);
42
+ process.exit(1);
43
+ });
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerAccountTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,10 @@
1
+ export function registerAccountTools(server, api) {
2
+ server.tool("vk_get_user", "Получить информацию о текущем пользователе/рекламном аккаунте.", {}, async () => {
3
+ const result = await api.get("user.json");
4
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5
+ });
6
+ server.tool("vk_get_throttling", "Получить информацию о лимитах запросов к API (rate limits).", {}, async () => {
7
+ const result = await api.get("throttling.json");
8
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
9
+ });
10
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerAdGroupTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ export function registerAdGroupTools(server, api) {
3
+ server.tool("vk_get_ad_groups", "Получить список групп объявлений (ad groups). Поддерживает фильтрацию по кампании и пагинацию.", {
4
+ fields: z.string().optional().describe("Поля через запятую, например: id,name,status,ad_plan_id"),
5
+ limit: z.number().optional().describe("Макс. кол-во результатов (по умолчанию 20, макс 50)"),
6
+ offset: z.number().optional().describe("Смещение для пагинации"),
7
+ _id__in: z.string().optional().describe("Фильтр по ID через запятую: 123,456"),
8
+ _ad_plan_id__in: z.string().optional().describe("Фильтр по ID кампании: 123,456"),
9
+ _status__in: z.string().optional().describe("Фильтр по статусу"),
10
+ }, async (params) => {
11
+ const query = {};
12
+ if (params.fields)
13
+ query.fields = params.fields;
14
+ if (params.limit)
15
+ query.limit = String(params.limit);
16
+ if (params.offset)
17
+ query.offset = String(params.offset);
18
+ if (params._id__in)
19
+ query._id__in = params._id__in;
20
+ if (params._ad_plan_id__in)
21
+ query._ad_plan_id__in = params._ad_plan_id__in;
22
+ if (params._status__in)
23
+ query._status__in = params._status__in;
24
+ const result = await api.get("ad_groups.json", query);
25
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
26
+ });
27
+ server.tool("vk_get_ad_group", "Получить детали одной группы объявлений по ID.", {
28
+ id: z.number().describe("ID группы объявлений"),
29
+ fields: z.string().optional().describe("Поля через запятую"),
30
+ }, async (params) => {
31
+ const query = {};
32
+ if (params.fields)
33
+ query.fields = params.fields;
34
+ const result = await api.get(`ad_groups/${params.id}.json`, query);
35
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
36
+ });
37
+ server.tool("vk_create_ad_group", "Создать группу объявлений (ad group) в кампании.", {
38
+ ad_plan_id: z.number().describe("ID кампании (ad plan), к которой привязать группу"),
39
+ name: z.string().optional().describe("Название группы"),
40
+ targeting: z.string().optional().describe("JSON с параметрами таргетинга"),
41
+ age_restrictions: z.string().optional().describe("Возрастные ограничения"),
42
+ utm: z.string().optional().describe("UTM-метки"),
43
+ budget_limit: z.string().optional().describe("Лимит бюджета"),
44
+ budget_limit_day: z.string().optional().describe("Дневной лимит"),
45
+ bid_type: z.string().optional().describe("Тип ставки"),
46
+ max_price: z.string().optional().describe("Максимальная цена"),
47
+ }, async (params) => {
48
+ const body = { ad_plan_id: params.ad_plan_id };
49
+ if (params.name)
50
+ body.name = params.name;
51
+ if (params.targeting)
52
+ body.targeting = JSON.parse(params.targeting);
53
+ if (params.age_restrictions)
54
+ body.age_restrictions = params.age_restrictions;
55
+ if (params.utm)
56
+ body.utm = params.utm;
57
+ if (params.budget_limit)
58
+ body.budget_limit = params.budget_limit;
59
+ if (params.budget_limit_day)
60
+ body.budget_limit_day = params.budget_limit_day;
61
+ if (params.bid_type)
62
+ body.bid_type = params.bid_type;
63
+ if (params.max_price)
64
+ body.max_price = params.max_price;
65
+ const result = await api.post("ad_groups.json", body);
66
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
67
+ });
68
+ server.tool("vk_update_ad_group", "Обновить группу объявлений.", {
69
+ id: z.number().describe("ID группы объявлений"),
70
+ name: z.string().optional().describe("Новое название"),
71
+ status: z.string().optional().describe("Новый статус"),
72
+ targeting: z.string().optional().describe("JSON с параметрами таргетинга"),
73
+ utm: z.string().optional().describe("UTM-метки"),
74
+ budget_limit: z.string().optional().describe("Лимит бюджета"),
75
+ budget_limit_day: z.string().optional().describe("Дневной лимит"),
76
+ max_price: z.string().optional().describe("Максимальная цена"),
77
+ }, async (params) => {
78
+ const body = {};
79
+ if (params.name)
80
+ body.name = params.name;
81
+ if (params.status)
82
+ body.status = params.status;
83
+ if (params.targeting)
84
+ body.targeting = JSON.parse(params.targeting);
85
+ if (params.utm)
86
+ body.utm = params.utm;
87
+ if (params.budget_limit)
88
+ body.budget_limit = params.budget_limit;
89
+ if (params.budget_limit_day)
90
+ body.budget_limit_day = params.budget_limit_day;
91
+ if (params.max_price)
92
+ body.max_price = params.max_price;
93
+ const result = await api.post(`ad_groups/${params.id}.json`, body);
94
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
95
+ });
96
+ server.tool("vk_delete_ad_group", "Удалить группу объявлений.", {
97
+ id: z.number().describe("ID группы для удаления"),
98
+ }, async (params) => {
99
+ const result = await api.delete(`ad_groups/${params.id}.json`);
100
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
101
+ });
102
+ server.tool("vk_mass_action_ad_groups", "Массовые действия над группами объявлений (пауза, запуск, удаление).", {
103
+ action: z.enum(["start", "stop", "delete"]).describe("Действие: start, stop, delete"),
104
+ ids: z.string().describe("ID групп через запятую: 123,456,789"),
105
+ }, async (params) => {
106
+ const body = {
107
+ action: params.action,
108
+ ids: params.ids.split(",").map((id) => Number(id.trim())),
109
+ };
110
+ const result = await api.post("ad_groups/mass_action.json", body);
111
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
112
+ });
113
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerAdPlanTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ export function registerAdPlanTools(server, api) {
3
+ server.tool("vk_get_ad_plans", "Получить список рекламных кампаний (ad plans). Поддерживает фильтрацию и пагинацию.", {
4
+ fields: z.string().optional().describe("Поля через запятую, например: id,name,status"),
5
+ limit: z.number().optional().describe("Макс. кол-во результатов (по умолчанию 20, макс 50)"),
6
+ offset: z.number().optional().describe("Смещение для пагинации"),
7
+ _id__in: z.string().optional().describe("Фильтр по ID через запятую: 123,456"),
8
+ _status__in: z.string().optional().describe("Фильтр по статусу: active,blocked,deleted"),
9
+ }, async (params) => {
10
+ const query = {};
11
+ if (params.fields)
12
+ query.fields = params.fields;
13
+ if (params.limit)
14
+ query.limit = String(params.limit);
15
+ if (params.offset)
16
+ query.offset = String(params.offset);
17
+ if (params._id__in)
18
+ query._id__in = params._id__in;
19
+ if (params._status__in)
20
+ query._status__in = params._status__in;
21
+ const result = await api.get("ad_plans.json", query);
22
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23
+ });
24
+ server.tool("vk_get_ad_plan", "Получить детали одной рекламной кампании по ID.", {
25
+ id: z.number().describe("ID кампании (ad plan)"),
26
+ fields: z.string().optional().describe("Поля через запятую"),
27
+ }, async (params) => {
28
+ const query = {};
29
+ if (params.fields)
30
+ query.fields = params.fields;
31
+ const result = await api.get(`ad_plans/${params.id}.json`, query);
32
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
33
+ });
34
+ server.tool("vk_create_ad_plan", "Создать новую рекламную кампанию (ad plan).", {
35
+ name: z.string().describe("Название кампании"),
36
+ objective: z.string().optional().describe("Цель кампании"),
37
+ budget_limit: z.string().optional().describe("Лимит бюджета"),
38
+ budget_limit_day: z.string().optional().describe("Дневной лимит бюджета"),
39
+ autobidding_mode: z.string().optional().describe("Режим автоматических ставок"),
40
+ mixing: z.string().optional().describe("Настройки микширования"),
41
+ }, async (params) => {
42
+ const body = { name: params.name };
43
+ if (params.objective)
44
+ body.objective = params.objective;
45
+ if (params.budget_limit)
46
+ body.budget_limit = params.budget_limit;
47
+ if (params.budget_limit_day)
48
+ body.budget_limit_day = params.budget_limit_day;
49
+ if (params.autobidding_mode)
50
+ body.autobidding_mode = params.autobidding_mode;
51
+ if (params.mixing)
52
+ body.mixing = params.mixing;
53
+ const result = await api.post("ad_plans.json", body);
54
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
55
+ });
56
+ server.tool("vk_update_ad_plan", "Обновить рекламную кампанию (ad plan).", {
57
+ id: z.number().describe("ID кампании"),
58
+ name: z.string().optional().describe("Новое название"),
59
+ status: z.string().optional().describe("Новый статус: active, blocked, deleted"),
60
+ budget_limit: z.string().optional().describe("Лимит бюджета"),
61
+ budget_limit_day: z.string().optional().describe("Дневной лимит"),
62
+ autobidding_mode: z.string().optional().describe("Режим автоставок"),
63
+ }, async (params) => {
64
+ const body = {};
65
+ if (params.name)
66
+ body.name = params.name;
67
+ if (params.status)
68
+ body.status = params.status;
69
+ if (params.budget_limit)
70
+ body.budget_limit = params.budget_limit;
71
+ if (params.budget_limit_day)
72
+ body.budget_limit_day = params.budget_limit_day;
73
+ if (params.autobidding_mode)
74
+ body.autobidding_mode = params.autobidding_mode;
75
+ const result = await api.post(`ad_plans/${params.id}.json`, body);
76
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
77
+ });
78
+ server.tool("vk_delete_ad_plan", "Удалить рекламную кампанию (ad plan).", {
79
+ id: z.number().describe("ID кампании для удаления"),
80
+ }, async (params) => {
81
+ const result = await api.delete(`ad_plans/${params.id}.json`);
82
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
83
+ });
84
+ server.tool("vk_mass_action_ad_plans", "Массовые действия над кампаниями (пауза, запуск, удаление). Принимает JSON с массивом ID и действием.", {
85
+ action: z.enum(["start", "stop", "delete"]).describe("Действие: start, stop, delete"),
86
+ ids: z.string().describe("ID кампаний через запятую: 123,456,789"),
87
+ }, async (params) => {
88
+ const body = {
89
+ action: params.action,
90
+ ids: params.ids.split(",").map((id) => Number(id.trim())),
91
+ };
92
+ const result = await api.post("ad_plans/mass_action.json", body);
93
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
94
+ });
95
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerAgencyTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ export function registerAgencyTools(server, api) {
3
+ server.tool("vk_get_agency_clients", "Получить список клиентов агентского аккаунта.", {
4
+ fields: z.string().optional().describe("Поля через запятую"),
5
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
6
+ offset: z.number().optional().describe("Смещение"),
7
+ }, async (params) => {
8
+ const query = {};
9
+ if (params.fields)
10
+ query.fields = params.fields;
11
+ if (params.limit)
12
+ query.limit = String(params.limit);
13
+ if (params.offset)
14
+ query.offset = String(params.offset);
15
+ const result = await api.get("agency/clients.json", query);
16
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
17
+ });
18
+ server.tool("vk_get_agency_client", "Получить информацию об одном клиенте агентства по ID.", {
19
+ id: z.number().describe("ID клиента"),
20
+ fields: z.string().optional().describe("Поля через запятую"),
21
+ }, async (params) => {
22
+ const query = {};
23
+ if (params.fields)
24
+ query.fields = params.fields;
25
+ const result = await api.get(`agency/clients/${params.id}.json`, query);
26
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27
+ });
28
+ server.tool("vk_get_agency_managers", "Получить список менеджеров агентства.", {
29
+ fields: z.string().optional().describe("Поля через запятую"),
30
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
31
+ offset: z.number().optional().describe("Смещение"),
32
+ }, async (params) => {
33
+ const query = {};
34
+ if (params.fields)
35
+ query.fields = params.fields;
36
+ if (params.limit)
37
+ query.limit = String(params.limit);
38
+ if (params.offset)
39
+ query.offset = String(params.offset);
40
+ const result = await api.get("agency/managers.json", query);
41
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
42
+ });
43
+ server.tool("vk_get_agency_manager", "Получить информацию об одном менеджере агентства по ID.", {
44
+ id: z.number().describe("ID менеджера"),
45
+ fields: z.string().optional().describe("Поля через запятую"),
46
+ }, async (params) => {
47
+ const query = {};
48
+ if (params.fields)
49
+ query.fields = params.fields;
50
+ const result = await api.get(`agency/managers/${params.id}.json`, query);
51
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
52
+ });
53
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerBannerTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ export function registerBannerTools(server, api) {
3
+ server.tool("vk_get_banners", "Получить список баннеров (объявлений). Поддерживает фильтрацию по кампании, группе и пагинацию.", {
4
+ fields: z.string().optional().describe("Поля через запятую, например: id,status,ad_group_id,moderation_status"),
5
+ limit: z.number().optional().describe("Макс. кол-во результатов (по умолчанию 20, макс 50)"),
6
+ offset: z.number().optional().describe("Смещение для пагинации"),
7
+ _id__in: z.string().optional().describe("Фильтр по ID через запятую: 123,456"),
8
+ _ad_group_id__in: z.string().optional().describe("Фильтр по ID группы объявлений: 123,456"),
9
+ _ad_plan_id__in: z.string().optional().describe("Фильтр по ID кампании: 123,456"),
10
+ _status__in: z.string().optional().describe("Фильтр по статусу"),
11
+ }, async (params) => {
12
+ const query = {};
13
+ if (params.fields)
14
+ query.fields = params.fields;
15
+ if (params.limit)
16
+ query.limit = String(params.limit);
17
+ if (params.offset)
18
+ query.offset = String(params.offset);
19
+ if (params._id__in)
20
+ query._id__in = params._id__in;
21
+ if (params._ad_group_id__in)
22
+ query._ad_group_id__in = params._ad_group_id__in;
23
+ if (params._ad_plan_id__in)
24
+ query._ad_plan_id__in = params._ad_plan_id__in;
25
+ if (params._status__in)
26
+ query._status__in = params._status__in;
27
+ const result = await api.get("banners.json", query);
28
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
29
+ });
30
+ server.tool("vk_get_banner", "Получить детали одного баннера (объявления) по ID.", {
31
+ id: z.number().describe("ID баннера"),
32
+ fields: z.string().optional().describe("Поля через запятую"),
33
+ }, async (params) => {
34
+ const query = {};
35
+ if (params.fields)
36
+ query.fields = params.fields;
37
+ const result = await api.get(`banners/${params.id}.json`, query);
38
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
39
+ });
40
+ server.tool("vk_create_banner", "Создать баннер (объявление) в группе объявлений.", {
41
+ ad_group_id: z.number().describe("ID группы объявлений"),
42
+ content: z.string().describe("JSON с контентом баннера (заголовок, текст, изображение, ссылка и т.д.)"),
43
+ moderation_status: z.string().optional().describe("Статус модерации"),
44
+ }, async (params) => {
45
+ const body = {
46
+ ad_group_id: params.ad_group_id,
47
+ content: JSON.parse(params.content),
48
+ };
49
+ if (params.moderation_status)
50
+ body.moderation_status = params.moderation_status;
51
+ const result = await api.post("banners.json", body);
52
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
53
+ });
54
+ server.tool("vk_update_banner", "Обновить баннер (объявление).", {
55
+ id: z.number().describe("ID баннера"),
56
+ content: z.string().optional().describe("JSON с новым контентом баннера"),
57
+ status: z.string().optional().describe("Новый статус"),
58
+ }, async (params) => {
59
+ const body = {};
60
+ if (params.content)
61
+ body.content = JSON.parse(params.content);
62
+ if (params.status)
63
+ body.status = params.status;
64
+ const result = await api.post(`banners/${params.id}.json`, body);
65
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
66
+ });
67
+ server.tool("vk_delete_banner", "Удалить баннер (объявление).", {
68
+ id: z.number().describe("ID баннера для удаления"),
69
+ }, async (params) => {
70
+ const result = await api.delete(`banners/${params.id}.json`);
71
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
72
+ });
73
+ server.tool("vk_mass_action_banners", "Массовые действия над баннерами (пауза, запуск, удаление).", {
74
+ action: z.enum(["start", "stop", "delete"]).describe("Действие: start, stop, delete"),
75
+ ids: z.string().describe("ID баннеров через запятую: 123,456,789"),
76
+ }, async (params) => {
77
+ const body = {
78
+ action: params.action,
79
+ ids: params.ids.split(",").map((id) => Number(id.trim())),
80
+ };
81
+ const result = await api.post("banners/mass_action.json", body);
82
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
83
+ });
84
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerBlacklistTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ export function registerBlacklistTools(server, api) {
3
+ server.tool("vk_get_blacklists", "Получить список чёрных списков площадок (place black lists).", {
4
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
5
+ offset: z.number().optional().describe("Смещение"),
6
+ }, async (params) => {
7
+ const query = {};
8
+ if (params.limit)
9
+ query.limit = String(params.limit);
10
+ if (params.offset)
11
+ query.offset = String(params.offset);
12
+ const result = await api.get("place_black_lists.json", query);
13
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
14
+ });
15
+ server.tool("vk_get_blacklist", "Получить один чёрный список площадок по ID.", {
16
+ id: z.number().describe("ID чёрного списка"),
17
+ }, async (params) => {
18
+ const result = await api.get(`place_black_lists/${params.id}.json`);
19
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
20
+ });
21
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerMediaTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ export function registerMediaTools(server, api) {
3
+ server.tool("vk_upload_image", "Загрузить изображение для использования в баннерах. Принимает base64-encoded данные или URL изображения.", {
4
+ image_url: z.string().optional().describe("URL изображения для загрузки"),
5
+ width: z.number().optional().describe("Ширина изображения"),
6
+ height: z.number().optional().describe("Высота изображения"),
7
+ }, async (params) => {
8
+ const body = {};
9
+ if (params.image_url)
10
+ body.image_url = params.image_url;
11
+ if (params.width)
12
+ body.width = params.width;
13
+ if (params.height)
14
+ body.height = params.height;
15
+ const result = await api.post("images.json", body);
16
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
17
+ });
18
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerRemarketingTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ export function registerRemarketingTools(server, api) {
3
+ // --- Списки пользователей (аудитории ремаркетинга) ---
4
+ server.tool("vk_get_remarketing_users_lists", "Получить списки аудиторий ремаркетинга (users lists).", {
5
+ fields: z.string().optional().describe("Поля через запятую"),
6
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
7
+ offset: z.number().optional().describe("Смещение"),
8
+ }, async (params) => {
9
+ const query = {};
10
+ if (params.fields)
11
+ query.fields = params.fields;
12
+ if (params.limit)
13
+ query.limit = String(params.limit);
14
+ if (params.offset)
15
+ query.offset = String(params.offset);
16
+ const result = await api.get("remarketing/users_lists.json", query);
17
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
18
+ });
19
+ server.tool("vk_get_remarketing_users_list", "Получить одну аудиторию ремаркетинга по ID.", {
20
+ id: z.number().describe("ID аудитории"),
21
+ fields: z.string().optional().describe("Поля через запятую"),
22
+ }, async (params) => {
23
+ const query = {};
24
+ if (params.fields)
25
+ query.fields = params.fields;
26
+ const result = await api.get(`remarketing/users_lists/${params.id}.json`, query);
27
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
28
+ });
29
+ server.tool("vk_create_remarketing_users_list", "Создать аудиторию ремаркетинга. Можно загрузить контакты (emails, телефоны, VK ID).", {
30
+ name: z.string().describe("Название аудитории"),
31
+ type: z.string().optional().describe("Тип списка: dmp_id, email, phone, idfa_gaid, vk_id, ok_id, mmr_id"),
32
+ contacts: z.string().optional().describe("Контакты через перенос строки (emails, телефоны или ID)"),
33
+ }, async (params) => {
34
+ const body = { name: params.name };
35
+ if (params.type)
36
+ body.type = params.type;
37
+ if (params.contacts)
38
+ body.contacts = params.contacts;
39
+ const result = await api.post("remarketing/users_lists.json", body);
40
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
41
+ });
42
+ server.tool("vk_delete_remarketing_users_list", "Удалить аудиторию ремаркетинга.", {
43
+ id: z.number().describe("ID аудитории для удаления"),
44
+ }, async (params) => {
45
+ const result = await api.delete(`remarketing/users_lists/${params.id}.json`);
46
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
47
+ });
48
+ // --- Счётчики (пиксели) ---
49
+ server.tool("vk_get_remarketing_counters", "Получить список счётчиков ремаркетинга (пикселей).", {
50
+ fields: z.string().optional().describe("Поля через запятую"),
51
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
52
+ offset: z.number().optional().describe("Смещение"),
53
+ }, async (params) => {
54
+ const query = {};
55
+ if (params.fields)
56
+ query.fields = params.fields;
57
+ if (params.limit)
58
+ query.limit = String(params.limit);
59
+ if (params.offset)
60
+ query.offset = String(params.offset);
61
+ const result = await api.get("remarketing/counters.json", query);
62
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
63
+ });
64
+ server.tool("vk_get_remarketing_counter", "Получить один счётчик ремаркетинга по ID.", {
65
+ id: z.number().describe("ID счётчика"),
66
+ fields: z.string().optional().describe("Поля через запятую"),
67
+ }, async (params) => {
68
+ const query = {};
69
+ if (params.fields)
70
+ query.fields = params.fields;
71
+ const result = await api.get(`remarketing/counters/${params.id}.json`, query);
72
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
73
+ });
74
+ server.tool("vk_create_remarketing_counter", "Создать счётчик ремаркетинга (пиксель).", {
75
+ name: z.string().describe("Название счётчика"),
76
+ domain: z.string().optional().describe("Домен сайта"),
77
+ }, async (params) => {
78
+ const body = { name: params.name };
79
+ if (params.domain)
80
+ body.domain = params.domain;
81
+ const result = await api.post("remarketing/counters.json", body);
82
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
83
+ });
84
+ server.tool("vk_delete_remarketing_counter", "Удалить счётчик ремаркетинга.", {
85
+ id: z.number().describe("ID счётчика для удаления"),
86
+ }, async (params) => {
87
+ const result = await api.delete(`remarketing/counters/${params.id}.json`);
88
+ return { content: [{ type: "text", text: JSON.stringify(result ?? { success: true }, null, 2) }] };
89
+ });
90
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerStatisticsTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ export function registerStatisticsTools(server, api) {
3
+ server.tool("vk_get_statistics", "Получить статистику по кампаниям, группам объявлений или баннерам. Возвращает показы, клики, расходы, CTR и другие метрики.", {
4
+ object_type: z
5
+ .enum(["ad_plans", "ad_groups", "banners"])
6
+ .describe("Тип объекта: ad_plans (кампании), ad_groups (группы), banners (баннеры)"),
7
+ time_mode: z
8
+ .enum(["day", "hour", "summary"])
9
+ .describe("Режим времени: day (по дням), hour (по часам), summary (итого)"),
10
+ id: z.string().describe("ID объектов через запятую: 123,456"),
11
+ date_from: z.string().describe("Дата начала в формате YYYY-MM-DD"),
12
+ date_to: z.string().describe("Дата окончания в формате YYYY-MM-DD"),
13
+ metrics: z
14
+ .string()
15
+ .optional()
16
+ .describe("Метрики через запятую или 'all' для всех. Примеры: impressions,clicks,spent,ctr"),
17
+ }, async (params) => {
18
+ const endpoint = `statistics/${params.object_type}/${params.time_mode}.json`;
19
+ const query = {
20
+ id: params.id,
21
+ date_from: params.date_from,
22
+ date_to: params.date_to,
23
+ };
24
+ if (params.metrics)
25
+ query.metrics = params.metrics;
26
+ const result = await api.get(endpoint, query);
27
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
28
+ });
29
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { VkAdsApi } from "../vk-api.js";
3
+ export declare function registerTargetingTools(server: McpServer, api: VkAdsApi): void;
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ export function registerTargetingTools(server, api) {
3
+ server.tool("vk_get_regions", "Получить список регионов для таргетинга.", {
4
+ q: z.string().optional().describe("Поисковый запрос для фильтрации регионов"),
5
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
6
+ offset: z.number().optional().describe("Смещение"),
7
+ }, async (params) => {
8
+ const query = {};
9
+ if (params.q)
10
+ query.q = params.q;
11
+ if (params.limit)
12
+ query.limit = String(params.limit);
13
+ if (params.offset)
14
+ query.offset = String(params.offset);
15
+ const result = await api.get("regions.json", query);
16
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
17
+ });
18
+ server.tool("vk_get_interests", "Получить список категорий интересов для таргетинга.", {
19
+ limit: z.number().optional().describe("Макс. кол-во результатов"),
20
+ offset: z.number().optional().describe("Смещение"),
21
+ }, async (params) => {
22
+ const query = {};
23
+ if (params.limit)
24
+ query.limit = String(params.limit);
25
+ if (params.offset)
26
+ query.offset = String(params.offset);
27
+ const result = await api.get("interests.json", query);
28
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
29
+ });
30
+ server.tool("vk_get_projection", "Оценить размер аудитории по параметрам таргетинга. Возвращает прогнозируемый охват.", {
31
+ targeting: z.string().describe("JSON с параметрами таргетинга (регионы, пол, возраст, интересы и т.д.)"),
32
+ }, async (params) => {
33
+ const body = JSON.parse(params.targeting);
34
+ const result = await api.post("projection.json", body);
35
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
36
+ });
37
+ }
@@ -0,0 +1,13 @@
1
+ export interface VkApiConfig {
2
+ accessToken: string;
3
+ proxyUrl: string;
4
+ }
5
+ export declare class VkAdsApi {
6
+ private config;
7
+ constructor(config: VkApiConfig);
8
+ private buildMethod;
9
+ private request;
10
+ get(endpoint: string, queryParams?: Record<string, string>): Promise<unknown>;
11
+ post(endpoint: string, body: Record<string, unknown>, queryParams?: Record<string, string>): Promise<unknown>;
12
+ delete(endpoint: string, queryParams?: Record<string, string>): Promise<unknown>;
13
+ }
package/dist/vk-api.js ADDED
@@ -0,0 +1,51 @@
1
+ export class VkAdsApi {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ buildMethod(endpoint, queryParams) {
7
+ let method = `/api/v2/${endpoint}`;
8
+ if (queryParams) {
9
+ const entries = Object.entries(queryParams).filter(([, v]) => v !== undefined && v !== null && v !== "");
10
+ if (entries.length > 0) {
11
+ const qs = entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
12
+ method += `?${qs}`;
13
+ }
14
+ }
15
+ return method;
16
+ }
17
+ async request(method, params) {
18
+ const body = new FormData();
19
+ body.append("method", method);
20
+ body.append("access_token", this.config.accessToken);
21
+ if (params && Object.keys(params).length > 0) {
22
+ body.append("params", JSON.stringify(params));
23
+ }
24
+ const response = await fetch(this.config.proxyUrl, {
25
+ method: "POST",
26
+ body,
27
+ });
28
+ if (!response.ok) {
29
+ const text = await response.text();
30
+ throw new Error(`VK Ads API HTTP ${response.status}: ${text}`);
31
+ }
32
+ const data = await response.json();
33
+ if (data && typeof data === "object" && "error" in data) {
34
+ const err = data;
35
+ throw new Error(`VK Ads API error ${err.error.code ?? "unknown"}: ${err.error.message ?? JSON.stringify(err.error)}`);
36
+ }
37
+ return data;
38
+ }
39
+ async get(endpoint, queryParams) {
40
+ const method = this.buildMethod(endpoint, queryParams);
41
+ return this.request(method);
42
+ }
43
+ async post(endpoint, body, queryParams) {
44
+ const method = this.buildMethod(endpoint, queryParams);
45
+ return this.request(method, body);
46
+ }
47
+ async delete(endpoint, queryParams) {
48
+ const method = this.buildMethod(endpoint, queryParams);
49
+ return this.request(method, { _method: "DELETE" });
50
+ }
51
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "vk-ads-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for managing VK Ads campaigns through Claude",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "vk-ads-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "npx tsx src/index.ts"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.12.1"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "typescript": "^5.7.0",
24
+ "tsx": "^4.19.0"
25
+ }
26
+ }