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 +107 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +43 -0
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +10 -0
- package/dist/tools/ad-groups.d.ts +3 -0
- package/dist/tools/ad-groups.js +113 -0
- package/dist/tools/ad-plans.d.ts +3 -0
- package/dist/tools/ad-plans.js +95 -0
- package/dist/tools/agency.d.ts +3 -0
- package/dist/tools/agency.js +53 -0
- package/dist/tools/banners.d.ts +3 -0
- package/dist/tools/banners.js +84 -0
- package/dist/tools/blacklists.d.ts +3 -0
- package/dist/tools/blacklists.js +21 -0
- package/dist/tools/media.d.ts +3 -0
- package/dist/tools/media.js +18 -0
- package/dist/tools/remarketing.d.ts +3 -0
- package/dist/tools/remarketing.js +90 -0
- package/dist/tools/statistics.d.ts +3 -0
- package/dist/tools/statistics.js +29 -0
- package/dist/tools/targeting.d.ts +3 -0
- package/dist/tools/targeting.js +37 -0
- package/dist/vk-api.d.ts +13 -0
- package/dist/vk-api.js +51 -0
- package/package.json +26 -0
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
|
package/dist/index.d.ts
ADDED
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,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,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,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,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,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,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,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,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,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,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
|
+
}
|
package/dist/vk-api.d.ts
ADDED
|
@@ -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
|
+
}
|