ppspy-mcp-server 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.
@@ -0,0 +1,10 @@
1
+ interface ApiResponse {
2
+ code: number;
3
+ message: string;
4
+ data: any;
5
+ }
6
+ export declare function apiGet(path: string, params?: Record<string, any>): Promise<ApiResponse>;
7
+ export declare function apiPost(path: string, body?: Record<string, any>): Promise<ApiResponse>;
8
+ export declare function apiPut(path: string, body?: Record<string, any>): Promise<ApiResponse>;
9
+ export declare function apiDelete(path: string, body?: Record<string, any>): Promise<ApiResponse>;
10
+ export {};
package/dist/client.js ADDED
@@ -0,0 +1,67 @@
1
+ const BASE_URL = "https://api.ppspy.com";
2
+ function getApiKey() {
3
+ const key = process.env.PPSPY_API_KEY;
4
+ if (!key) {
5
+ throw new Error("PPSPY_API_KEY environment variable is required");
6
+ }
7
+ return key;
8
+ }
9
+ function buildHeaders() {
10
+ return {
11
+ Authorization: getApiKey(),
12
+ "Content-Type": "application/json",
13
+ Accept: "application/json",
14
+ };
15
+ }
16
+ export async function apiGet(path, params) {
17
+ const url = new URL(path, BASE_URL);
18
+ if (params) {
19
+ for (const [key, value] of Object.entries(params)) {
20
+ if (value !== undefined && value !== null && value !== "") {
21
+ url.searchParams.set(key, String(value));
22
+ }
23
+ }
24
+ }
25
+ const res = await fetch(url.toString(), { headers: buildHeaders() });
26
+ const json = (await res.json());
27
+ if (json.code !== 200) {
28
+ throw new Error(`PPSPY API error ${json.code}: ${json.message}`);
29
+ }
30
+ return json;
31
+ }
32
+ export async function apiPost(path, body) {
33
+ const res = await fetch(`${BASE_URL}${path}`, {
34
+ method: "POST",
35
+ headers: buildHeaders(),
36
+ body: JSON.stringify(body ?? {}),
37
+ });
38
+ const json = (await res.json());
39
+ if (json.code !== 200) {
40
+ throw new Error(`PPSPY API error ${json.code}: ${json.message}`);
41
+ }
42
+ return json;
43
+ }
44
+ export async function apiPut(path, body) {
45
+ const res = await fetch(`${BASE_URL}${path}`, {
46
+ method: "PUT",
47
+ headers: buildHeaders(),
48
+ body: JSON.stringify(body ?? {}),
49
+ });
50
+ const json = (await res.json());
51
+ if (json.code !== 200) {
52
+ throw new Error(`PPSPY API error ${json.code}: ${json.message}`);
53
+ }
54
+ return json;
55
+ }
56
+ export async function apiDelete(path, body) {
57
+ const res = await fetch(`${BASE_URL}${path}`, {
58
+ method: "DELETE",
59
+ headers: buildHeaders(),
60
+ ...(body ? { body: JSON.stringify(body) } : {}),
61
+ });
62
+ const json = (await res.json());
63
+ if (json.code !== 200) {
64
+ throw new Error(`PPSPY API error ${json.code}: ${json.message}`);
65
+ }
66
+ return json;
67
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
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 { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import express from "express";
6
+ import { randomUUID } from "node:crypto";
7
+ import { registerSupplementTools } from "./tools/supplement.js";
8
+ import { registerShopifyStoreTools } from "./tools/shopify-store.js";
9
+ import { registerShopifyProductTools } from "./tools/shopify-product.js";
10
+ import { registerAdsLibraryTools } from "./tools/ads-library.js";
11
+ import { registerAdStoreTools } from "./tools/ad-store.js";
12
+ import { registerAdProductTools } from "./tools/ad-product.js";
13
+ import { registerAdMonitorTools } from "./tools/ad-monitor.js";
14
+ import { registerSalesMonitorTools } from "./tools/sales-monitor.js";
15
+ function createServer() {
16
+ const server = new McpServer({
17
+ name: "ppspy",
18
+ version: "1.0.0",
19
+ });
20
+ registerSupplementTools(server);
21
+ registerShopifyStoreTools(server);
22
+ registerShopifyProductTools(server);
23
+ registerAdsLibraryTools(server);
24
+ registerAdStoreTools(server);
25
+ registerAdProductTools(server);
26
+ registerAdMonitorTools(server);
27
+ registerSalesMonitorTools(server);
28
+ return server;
29
+ }
30
+ const transportMode = process.argv.includes("--http") ? "http" : "stdio";
31
+ if (transportMode === "stdio") {
32
+ // stdio mode — for Claude Desktop / Claude Code
33
+ const server = createServer();
34
+ const transport = new StdioServerTransport();
35
+ await server.connect(transport);
36
+ }
37
+ else {
38
+ // Streamable HTTP mode — for web clients
39
+ const app = express();
40
+ app.use(express.json());
41
+ const transports = new Map();
42
+ app.post("/mcp", async (req, res) => {
43
+ const sessionId = req.headers["mcp-session-id"];
44
+ if (sessionId && transports.has(sessionId)) {
45
+ const transport = transports.get(sessionId);
46
+ await transport.handleRequest(req, res, req.body);
47
+ return;
48
+ }
49
+ if (sessionId && !transports.has(sessionId)) {
50
+ res.status(400).json({ error: "Invalid session ID" });
51
+ return;
52
+ }
53
+ const transport = new StreamableHTTPServerTransport({
54
+ sessionIdGenerator: () => randomUUID(),
55
+ onsessioninitialized: (id) => {
56
+ transports.set(id, transport);
57
+ },
58
+ });
59
+ transport.onclose = () => {
60
+ const id = [...transports.entries()].find(([, t]) => t === transport)?.[0];
61
+ if (id)
62
+ transports.delete(id);
63
+ };
64
+ const server = createServer();
65
+ await server.connect(transport);
66
+ await transport.handleRequest(req, res, req.body);
67
+ });
68
+ app.get("/mcp", async (req, res) => {
69
+ const sessionId = req.headers["mcp-session-id"];
70
+ if (!sessionId || !transports.has(sessionId)) {
71
+ res.status(400).json({ error: "Invalid or missing session ID" });
72
+ return;
73
+ }
74
+ const transport = transports.get(sessionId);
75
+ await transport.handleRequest(req, res);
76
+ });
77
+ app.delete("/mcp", async (req, res) => {
78
+ const sessionId = req.headers["mcp-session-id"];
79
+ if (!sessionId || !transports.has(sessionId)) {
80
+ res.status(400).json({ error: "Invalid or missing session ID" });
81
+ return;
82
+ }
83
+ const transport = transports.get(sessionId);
84
+ await transport.handleRequest(req, res);
85
+ });
86
+ const PORT = parseInt(process.env.PORT || "3000", 10);
87
+ app.listen(PORT, () => {
88
+ console.log(`PPSPY MCP Server running on http://localhost:${PORT}/mcp`);
89
+ });
90
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAdMonitorTools(server: McpServer): void;
@@ -0,0 +1,234 @@
1
+ import { z } from "zod";
2
+ import { apiPost, apiGet, apiPut, apiDelete } from "../client.js";
3
+ export function registerAdMonitorTools(server) {
4
+ // ===== Group CRUD =====
5
+ server.tool("ppspy_ad_monitor_create_group", "Create an ad monitoring group. Free.", {
6
+ name: z.string().describe("Group name (required)"),
7
+ }, async (params) => {
8
+ const res = await apiPost("/open-api/v1/ad-monitor-groups", params);
9
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
10
+ });
11
+ server.tool("ppspy_ad_monitor_group_list", "List all ad monitoring groups. Free.", {}, async () => {
12
+ const res = await apiGet("/open-api/v1/ad-monitor-groups");
13
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
14
+ });
15
+ server.tool("ppspy_ad_monitor_edit_group", "Edit an ad monitoring group name. Free.", {
16
+ id: z.string().describe("Group ID (required)"),
17
+ name: z.string().describe("New group name (required)"),
18
+ }, async ({ id, name }) => {
19
+ const res = await apiPut(`/open-api/v1/ad-monitor-groups/${id}`, { name });
20
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
21
+ });
22
+ server.tool("ppspy_ad_monitor_delete_group", "Delete an ad monitoring group. WARNING: All tasks under this group will be stopped. Free.", {
23
+ id: z.string().describe("Group ID (required)"),
24
+ }, async ({ id }) => {
25
+ const res = await apiDelete(`/open-api/v1/ad-monitor-groups/${id}`);
26
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
27
+ });
28
+ // ===== Advertiser Search =====
29
+ server.tool("ppspy_ad_monitor_advertiser_search", "Search Facebook advertisers by name. Free.", {
30
+ name: z.string().describe("Advertiser name to search (required)"),
31
+ }, async (params) => {
32
+ const res = await apiPost("/open-api/v1/ad-monitor-task/search-fb-ads-advertiser", params);
33
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
34
+ });
35
+ // ===== Task CRUD =====
36
+ server.tool("ppspy_ad_monitor_create_task", "Create ad monitoring task(s). Cost: 1 monitoring quota per task. Provide one of: facebook_page_id_list, facebook_page_url_list, facebook_public_page_number_list, or facebook_shop_url_list. Priority: page_id > page_url > page_number > shop_url.", {
37
+ facebook_page_id_list: z.array(z.string()).optional().describe("Facebook Advertiser ID list"),
38
+ facebook_page_url_list: z.array(z.string()).optional().describe("Facebook Advertiser URL list"),
39
+ facebook_public_page_number_list: z.array(z.string()).optional().describe("Facebook Public Page Number list"),
40
+ facebook_shop_url_list: z.array(z.string()).optional().describe("Store/Website URL list"),
41
+ group_id: z.string().optional().describe("Ad Group ID"),
42
+ }, async (params) => {
43
+ const res = await apiPost("/open-api/v1/ad-monitor-task/create-monitor-task", params);
44
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
45
+ });
46
+ server.tool("ppspy_ad_monitor_task_list", "List ad monitoring tasks with filters. Free.", {
47
+ keyword: z.string().optional().describe("Search keyword"),
48
+ monitor_task_type: z.number().optional().describe("Task type filter"),
49
+ country: z.string().optional().describe("Country filter"),
50
+ order_by: z.string().optional().describe("Sort field"),
51
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
52
+ page: z.number().optional().describe("Page number"),
53
+ per_page: z.number().optional().describe("Page size (1-100)"),
54
+ group_id: z.string().optional().describe("Group ID filter"),
55
+ }, async (params) => {
56
+ const res = await apiPost("/open-api/v1/ad-monitor-task/list", params);
57
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
58
+ });
59
+ server.tool("ppspy_ad_monitor_task_set_group", "Assign a monitoring task to a group. Free.", {
60
+ task_id: z.string().describe("Monitor task ID (required)"),
61
+ group_id: z.string().nullable().describe("Group ID (null to unset)"),
62
+ }, async ({ task_id, group_id }) => {
63
+ const res = await apiPost(`/open-api/v1/ad-monitor-task/set-group/${task_id}`, { group_id });
64
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
65
+ });
66
+ server.tool("ppspy_ad_monitor_task_cancel", "Cancel a monitoring task. Quotas will NOT be refunded. Free.", {
67
+ task_id: z.string().describe("Monitor task ID (required)"),
68
+ }, async ({ task_id }) => {
69
+ const res = await apiPut(`/open-api/v1/ad-monitor-task/cancel/${task_id}`);
70
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
71
+ });
72
+ // ===== Task Details & Overview =====
73
+ server.tool("ppspy_ad_monitor_task_details", "Get monitoring task details. Free.", {
74
+ task_id: z.string().describe("Monitor task ID (required)"),
75
+ }, async ({ task_id }) => {
76
+ const res = await apiPost(`/open-api/v1/ad-monitor-task/monitor-task-details/${task_id}`);
77
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
78
+ });
79
+ server.tool("ppspy_ad_monitor_task_real_time_overview", "Get real-time overview stats for a monitoring task. Free.", {
80
+ task_id: z.string().describe("Monitor task ID (required)"),
81
+ }, async ({ task_id }) => {
82
+ const res = await apiPost(`/open-api/v1/ad-monitor-task/real-time-overview/${task_id}`);
83
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
84
+ });
85
+ server.tool("ppspy_ad_monitor_task_daily_overview", "Get daily overview for a monitoring task. Free.", {
86
+ task_id: z.string().describe("Monitor task ID (required)"),
87
+ date: z.number().describe("UTC timestamp in seconds (required)"),
88
+ }, async (params) => {
89
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-daily-overview", params);
90
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
91
+ });
92
+ server.tool("ppspy_ad_monitor_task_running_overview", "Get aggregated dashboard stats for running tasks (or specific group). Free.", {
93
+ group_id: z.string().optional().describe("Group ID (optional, filter by group)"),
94
+ }, async (params) => {
95
+ const res = await apiPost("/open-api/v1/ad-monitor-task/board", params);
96
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
97
+ });
98
+ // ===== Landing Pages =====
99
+ server.tool("ppspy_ad_monitor_task_landing_page_overview", "Get landing page overview for a monitoring task. Free.", {
100
+ task_id: z.string().describe("Monitor task ID (required)"),
101
+ page: z.number().optional().describe("Page number"),
102
+ per_page: z.number().optional().describe("Page size"),
103
+ }, async (params) => {
104
+ const res = await apiPost("/open-api/v1/ad-monitor-task/landing-pages-overview", params);
105
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
106
+ });
107
+ server.tool("ppspy_ad_monitor_task_landing_page_list", "Get landing page list with filters for a monitoring task. Free.", {
108
+ task_id: z.string().describe("Monitor task ID (required)"),
109
+ keyword: z.string().optional().describe("Keyword filter"),
110
+ first_discovered_at_start: z.number().optional().describe("First discovery start (Unix timestamp)"),
111
+ first_discovered_at_end: z.number().optional().describe("First discovery end (Unix timestamp)"),
112
+ ad_count_start: z.number().optional().describe("Ad count minimum"),
113
+ ad_count_end: z.number().optional().describe("Ad count maximum"),
114
+ active_days_start: z.number().optional().describe("Active days minimum"),
115
+ active_days_end: z.number().optional().describe("Active days maximum"),
116
+ order_by: z.string().optional().describe("Sort field"),
117
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
118
+ page: z.number().optional().describe("Page number"),
119
+ per_page: z.number().optional().describe("Page size"),
120
+ }, async (params) => {
121
+ const res = await apiPost("/open-api/v1/ad-monitor-task/landing-page-list", params);
122
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
123
+ });
124
+ // ===== Products =====
125
+ server.tool("ppspy_ad_monitor_task_latest_ad_product", "Get latest advertised products for a monitoring task. Free.", {
126
+ task_id: z.string().describe("Monitor task ID (required)"),
127
+ limit: z.number().optional().describe("Limit number of results"),
128
+ }, async (params) => {
129
+ const res = await apiPost("/open-api/v1/ad-monitor-task/latest-products", params);
130
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
131
+ });
132
+ server.tool("ppspy_ad_monitor_task_ad_product_overview", "Get product overview (by ad count) for a monitoring task. Free.", {
133
+ task_id: z.string().describe("Monitor task ID (required)"),
134
+ page: z.number().optional().describe("Page number"),
135
+ per_page: z.number().optional().describe("Page size"),
136
+ }, async (params) => {
137
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-products-overview", params);
138
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
139
+ });
140
+ server.tool("ppspy_ad_monitor_task_ad_product_list", "Get product list with filters for a monitoring task. Free.", {
141
+ task_id: z.string().describe("Monitor task ID (required)"),
142
+ keyword: z.string().optional().describe("Keyword filter"),
143
+ first_discovered_at_start: z.number().optional().describe("First discovery start (Unix timestamp)"),
144
+ first_discovered_at_end: z.number().optional().describe("First discovery end (Unix timestamp)"),
145
+ ad_count_start: z.number().optional().describe("Ad count minimum"),
146
+ ad_count_end: z.number().optional().describe("Ad count maximum"),
147
+ price_start: z.number().optional().describe("Price minimum"),
148
+ price_end: z.number().optional().describe("Price maximum"),
149
+ active_days_start: z.number().optional().describe("Active days minimum"),
150
+ active_days_end: z.number().optional().describe("Active days maximum"),
151
+ order_by: z.string().optional().describe("Sort field"),
152
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
153
+ page: z.number().optional().describe("Page number"),
154
+ per_page: z.number().optional().describe("Page size"),
155
+ }, async (params) => {
156
+ const res = await apiPost("/open-api/v1/ad-monitor-task/product-list", params);
157
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
158
+ });
159
+ // ===== Ad Analytics =====
160
+ server.tool("ppspy_ad_monitor_task_ad_count_stats", "Get ad count statistics over time for a monitoring task. Free.", {
161
+ task_id: z.string().describe("Monitor task ID (required)"),
162
+ date_start: z.number().optional().describe("Date start (Unix timestamp)"),
163
+ date_end: z.number().optional().describe("Date end (Unix timestamp)"),
164
+ }, async (params) => {
165
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-count-statistics", params);
166
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
167
+ });
168
+ server.tool("ppspy_ad_monitor_task_ad_deep_analysis", "Get ad placement analysis (by day of week or time period). Free.", {
169
+ task_id: z.string().describe("Monitor task ID (required)"),
170
+ dimension: z.number().optional().describe("1=By day of week, 2=By time period"),
171
+ }, async (params) => {
172
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-deep-analysis", params);
173
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
174
+ });
175
+ server.tool("ppspy_ad_monitor_task_ad_content_most_used", "Get most used ad copy for a monitoring task. Free.", {
176
+ task_id: z.string().describe("Monitor task ID (required)"),
177
+ order_by: z.string().optional().describe("Sort field"),
178
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
179
+ page: z.number().optional().describe("Page number"),
180
+ per_page: z.number().optional().describe("Page size"),
181
+ }, async (params) => {
182
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-copy-most-used", params);
183
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
184
+ });
185
+ server.tool("ppspy_ad_monitor_task_ad_content_longest_run", "Get longest running ad content for a monitoring task. Free.", {
186
+ task_id: z.string().describe("Monitor task ID (required)"),
187
+ advertiser_id: z.string().optional().describe("Filter by advertiser ID"),
188
+ related_store_id: z.string().optional().describe("Filter by store ID"),
189
+ order_by: z.string().optional().describe("Sort field"),
190
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
191
+ page: z.number().optional().describe("Page number"),
192
+ per_page: z.number().optional().describe("Page size"),
193
+ }, async (params) => {
194
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-copy-longest-run", params);
195
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
196
+ });
197
+ // ===== Ad List & Details =====
198
+ server.tool("ppspy_ad_monitor_task_ad_list", "Get ad list for a monitoring task with extensive filters. Free.", {
199
+ task_id: z.string().describe("Monitor task ID (required)"),
200
+ ad_platform_list: z.array(z.string()).describe("Ad platforms (required): FACEBOOK, INSTAGRAM, AUDIENCE_NETWORK, MESSENGER, THREADS"),
201
+ ad_content: z.string().optional().describe("Ad content search"),
202
+ ad_audience_reach_start: z.number().optional().describe("Audience reach minimum"),
203
+ ad_audience_reach_end: z.number().optional().describe("Audience reach maximum"),
204
+ ad_cost_start: z.number().optional().describe("Ad spend start (USD)"),
205
+ ad_cost_end: z.number().optional().describe("Ad spend end (USD)"),
206
+ adset_count_start: z.number().optional().describe("Ad sets minimum"),
207
+ adset_count_end: z.number().optional().describe("Ad sets maximum"),
208
+ ad_forecast: z.array(z.number()).optional().describe("AI score: 1-Testing, 2-Growth, 3-Profitable"),
209
+ media_format: z.array(z.number()).optional().describe("Media: 1-Video, 2-Image, 3-Carousel"),
210
+ video_duration_start: z.number().optional().describe("Video duration start (seconds)"),
211
+ video_duration_end: z.number().optional().describe("Video duration end (seconds)"),
212
+ ad_started_at_start: z.number().optional().describe("Ad start time start (Unix timestamp)"),
213
+ ad_started_at_end: z.number().optional().describe("Ad start time end (Unix timestamp)"),
214
+ active_days_start: z.number().optional().describe("Active days minimum"),
215
+ active_days_end: z.number().optional().describe("Active days maximum"),
216
+ button_text: z.array(z.string()).optional().describe("Button text filter"),
217
+ ad_status: z.array(z.number()).optional().describe("Status: 1-Running, 2-Stopped, 3-Removed"),
218
+ landing_page_type: z.array(z.number()).optional().describe("Landing page: 1-Product, 2-Collection, 3-Other"),
219
+ keyword: z.string().optional().describe("Keyword filter"),
220
+ order_by: z.string().optional().describe("Sort field"),
221
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
222
+ page: z.number().optional().describe("Page number"),
223
+ per_page: z.number().optional().describe("Page size"),
224
+ }, async (params) => {
225
+ const res = await apiPost("/open-api/v1/ad-monitor-task/ad-library-list", params);
226
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
227
+ });
228
+ server.tool("ppspy_ad_monitor_task_ad_details", "Get detailed information for a specific ad in monitoring. Free.", {
229
+ ad_id: z.string().describe("Ad ID (required)"),
230
+ }, async ({ ad_id }) => {
231
+ const res = await apiPost(`/open-api/v1/ad-monitor-task/ad-details/${ad_id}`);
232
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
233
+ });
234
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAdProductTools(server: McpServer): void;
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { apiPost, apiGet } from "../client.js";
3
+ const keywordSchema = z.object({
4
+ field: z.string().describe("Search field"),
5
+ value: z.string().describe("Search value"),
6
+ logic_operator: z.string().describe("Logic operator: and, or"),
7
+ });
8
+ export function registerAdProductTools(server) {
9
+ server.tool("ppspy_ad_product_list", "Search ad library products with advanced filters. Cost: 2 credits per record.", {
10
+ extend_keywords: z.array(keywordSchema).optional().describe("Keyword search conditions"),
11
+ exclude_keywords: z.array(keywordSchema).optional().describe("Exclude keyword conditions"),
12
+ ad_forecast: z.array(z.number()).optional().describe("AI score: 1-Testing, 2-Growth, 3-Profitable"),
13
+ e_commerce_platform: z.array(z.string()).optional().describe("E-commerce platforms"),
14
+ creation_time_start: z.number().optional().describe("Product creation time start (Unix timestamp)"),
15
+ creation_time_end: z.number().optional().describe("Product creation time end (Unix timestamp)"),
16
+ category_id: z.array(z.string()).optional().describe("Product category IDs"),
17
+ store_country: z.array(z.string()).optional().describe("Store country codes"),
18
+ price_start: z.number().optional().describe("Price start (USD)"),
19
+ price_end: z.number().optional().describe("Price end (USD)"),
20
+ currency: z.string().optional().describe("Currency"),
21
+ language: z.array(z.string()).optional().describe("Language codes"),
22
+ ad_count_start: z.number().optional().describe("Ad count minimum"),
23
+ ad_count_end: z.number().optional().describe("Ad count maximum"),
24
+ adset_count_start: z.number().optional().describe("Ad sets minimum"),
25
+ adset_count_end: z.number().optional().describe("Ad sets maximum"),
26
+ ad_audience_reach_start: z.number().optional().describe("Audience reach minimum"),
27
+ ad_audience_reach_end: z.number().optional().describe("Audience reach maximum"),
28
+ ad_cost_start: z.number().optional().describe("Ad spend start (USD)"),
29
+ ad_cost_end: z.number().optional().describe("Ad spend end (USD)"),
30
+ ad_status: z.array(z.number()).optional().describe("Ad status: 1-Running, 2-Stopped, 3-Removed"),
31
+ store_id: z.string().optional().describe("Filter by store ID"),
32
+ order_by: z.string().optional().describe("Sort field"),
33
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
34
+ page: z.number().optional().describe("Page number"),
35
+ per_page: z.number().optional().describe("Page size (1-100)"),
36
+ }, async (params) => {
37
+ const res = await apiPost("/open-api/v1/ad-products/search", params);
38
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
39
+ });
40
+ server.tool("ppspy_ad_product_details", "Get detailed information for a specific ad product.", {
41
+ id: z.string().describe("Ad product ID (required)"),
42
+ }, async ({ id }) => {
43
+ const res = await apiGet(`/open-api/v1/ad-products/${id}`);
44
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAdStoreTools(server: McpServer): void;
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+ import { apiPost, apiGet } from "../client.js";
3
+ const keywordSchema = z.object({
4
+ field: z.string().describe("Search field: all, store_name, store_url"),
5
+ value: z.string().describe("Search value"),
6
+ logic_operator: z.string().describe("Logic operator: and, or"),
7
+ });
8
+ export function registerAdStoreTools(server) {
9
+ server.tool("ppspy_ad_store_list", "Search ad library stores with advanced filters. Cost: 2 credits per record.", {
10
+ extend_keywords: z.array(keywordSchema).optional().describe("Keyword search conditions"),
11
+ exclude_keywords: z.array(keywordSchema).optional().describe("Exclude keyword conditions"),
12
+ e_commerce_platform: z.array(z.string()).optional().describe("E-commerce platforms"),
13
+ ad_status: z.array(z.number()).optional().describe("Ad status: 1-Running, 2-Stopped, 3-Removed"),
14
+ category_ids: z.array(z.string()).optional().describe("Category IDs"),
15
+ ad_country: z.array(z.string()).optional().describe("Ad region codes"),
16
+ product_count_start: z.number().optional().describe("Product count minimum"),
17
+ product_count_end: z.number().optional().describe("Product count maximum"),
18
+ month_visits_start: z.number().optional().describe("Monthly visits minimum"),
19
+ month_visits_end: z.number().optional().describe("Monthly visits maximum"),
20
+ advertiser_count_start: z.number().optional().describe("Advertiser count minimum"),
21
+ advertiser_count_end: z.number().optional().describe("Advertiser count maximum"),
22
+ adset_count_start: z.number().optional().describe("Ad sets count minimum"),
23
+ adset_count_end: z.number().optional().describe("Ad sets count maximum"),
24
+ ad_count_start: z.number().optional().describe("Ad count minimum"),
25
+ ad_count_end: z.number().optional().describe("Ad count maximum"),
26
+ order_by: z.string().optional().describe("Sort field"),
27
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
28
+ page: z.number().optional().describe("Page number"),
29
+ per_page: z.number().optional().describe("Page size (1-100)"),
30
+ }, async (params) => {
31
+ const res = await apiPost("/open-api/v1/ad-stores/search", params);
32
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
33
+ });
34
+ server.tool("ppspy_ad_store_details", "Get detailed information for a specific ad store.", {
35
+ id: z.string().describe("Ad store ID (required)"),
36
+ }, async ({ id }) => {
37
+ const res = await apiGet(`/open-api/v1/ad-stores/${id}`);
38
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
39
+ });
40
+ server.tool("ppspy_ad_store_landing_page_stats", "Get landing page statistics for an ad store.", {
41
+ store_id: z.string().describe("Store ID (required)"),
42
+ landing_page: z.string().optional().describe("Landing page URL filter"),
43
+ keyword: z.string().optional().describe("Keyword filter"),
44
+ order_by: z.string().optional().describe("Sort field"),
45
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
46
+ page: z.number().optional().describe("Page number"),
47
+ per_page: z.number().optional().describe("Page size (1-100)"),
48
+ }, async (params) => {
49
+ const res = await apiGet("/open-api/v1/ad-stores/landing-pages-ad-count", params);
50
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
51
+ });
52
+ server.tool("ppspy_ad_store_advertising_trends", "Get advertising trends for an ad store over time.", {
53
+ store_id: z.string().describe("Store ID (required)"),
54
+ date_start: z.string().optional().describe("Date start (Y-m-d)"),
55
+ date_end: z.string().optional().describe("Date end (Y-m-d)"),
56
+ }, async (params) => {
57
+ const res = await apiGet("/open-api/v1/ad-stores/analysis", params);
58
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
59
+ });
60
+ server.tool("ppspy_ad_store_placement_analysis", "Get ad placement analysis (day of week and time period breakdown) for a store.", {
61
+ store_id: z.string().describe("Store ID (required)"),
62
+ }, async (params) => {
63
+ const res = await apiGet("/open-api/v1/ad-stores/deep-analysis", params);
64
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
65
+ });
66
+ server.tool("ppspy_ad_store_content_most_used", "Get most used ad copy content for a store.", {
67
+ store_id: z.string().describe("Store ID (required)"),
68
+ order_by: z.string().optional().describe("Sort field"),
69
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
70
+ page: z.number().optional().describe("Page number"),
71
+ per_page: z.number().optional().describe("Page size (1-100)"),
72
+ }, async (params) => {
73
+ const res = await apiGet("/open-api/v1/ad-stores/ad-content", params);
74
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
75
+ });
76
+ server.tool("ppspy_ad_store_content_longest_run", "Get longest running ad content for a store.", {
77
+ store_id: z.string().describe("Store ID (required)"),
78
+ order_by: z.string().optional().describe("Sort field"),
79
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
80
+ page: z.number().optional().describe("Page number"),
81
+ per_page: z.number().optional().describe("Page size (1-100)"),
82
+ }, async (params) => {
83
+ const res = await apiGet("/open-api/v1/ad-stores/ad-content-longest-run", params);
84
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
85
+ });
86
+ server.tool("ppspy_ad_store_advertiser_list", "Get advertisers associated with a store.", {
87
+ store_id: z.string().describe("Store ID (required)"),
88
+ page: z.number().optional().describe("Page number"),
89
+ per_page: z.number().optional().describe("Page size (1-100)"),
90
+ }, async (params) => {
91
+ const res = await apiGet("/open-api/v1/ad-stores/advertisers", params);
92
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
93
+ });
94
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerAdsLibraryTools(server: McpServer): void;
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+ import { apiPost, apiGet } from "../client.js";
3
+ const keywordSchema = z.object({
4
+ field: z.string().describe("Search field: all, ad_content, landing_pages, advertiser_name, product_name"),
5
+ value: z.string().describe("Search value"),
6
+ logic_operator: z.string().describe("Logic operator: and, or"),
7
+ });
8
+ export function registerAdsLibraryTools(server) {
9
+ server.tool("ppspy_advertisement_list", "Search advertisements with advanced filters. Cost: 2 credits per record.", {
10
+ extend_keywords: z.array(keywordSchema).optional().describe("Keyword search conditions"),
11
+ exclude_keywords: z.array(keywordSchema).optional().describe("Exclude keyword conditions"),
12
+ source_ad_id: z.array(z.string()).optional().describe("Ad Creative IDs"),
13
+ source_advertiser_id: z.string().optional().describe("Advertiser Page ID"),
14
+ advertiser_id: z.string().optional().describe("Advertiser ID"),
15
+ first_discovered_at_start: z.number().optional().describe("First discovery time start (Unix timestamp seconds)"),
16
+ first_discovered_at_end: z.number().optional().describe("First discovery time end (Unix timestamp seconds)"),
17
+ last_discovered_at_start: z.number().optional().describe("Last updated time start (Unix timestamp seconds)"),
18
+ last_discovered_at_end: z.number().optional().describe("Last updated time end (Unix timestamp seconds)"),
19
+ country: z.array(z.string()).optional().describe("Region codes"),
20
+ language: z.array(z.string()).optional().describe("Language codes"),
21
+ ad_audience_reach_start: z.number().optional().describe("Audience reach minimum"),
22
+ ad_audience_reach_end: z.number().optional().describe("Audience reach maximum"),
23
+ ad_cost_start: z.number().optional().describe("Ad spend start (USD)"),
24
+ ad_cost_end: z.number().optional().describe("Ad spend end (USD)"),
25
+ adset_count_start: z.number().optional().describe("Ad sets count minimum"),
26
+ adset_count_end: z.number().optional().describe("Ad sets count maximum"),
27
+ ad_forecast: z.array(z.number()).optional().describe("AI score: 1-Testing, 2-Growth, 3-Profitable"),
28
+ media_format: z.array(z.number()).optional().describe("Media format: 1-Video, 2-Image, 3-Carousel"),
29
+ video_duration_start: z.number().optional().describe("Video duration start (seconds)"),
30
+ video_duration_end: z.number().optional().describe("Video duration end (seconds)"),
31
+ e_commerce_platform: z.array(z.string()).optional().describe("E-commerce platforms"),
32
+ ad_started_at_start: z.number().optional().describe("Ad start time start (Unix timestamp)"),
33
+ ad_started_at_end: z.number().optional().describe("Ad start time end (Unix timestamp)"),
34
+ latest_actived_at_start: z.number().optional().describe("Last active time start (Unix timestamp)"),
35
+ latest_actived_at_end: z.number().optional().describe("Last active time end (Unix timestamp)"),
36
+ active_days_start: z.number().optional().describe("Active days minimum"),
37
+ active_days_end: z.number().optional().describe("Active days maximum"),
38
+ button_type: z.string().optional().describe("Button type"),
39
+ button_text: z.array(z.string()).optional().describe("Button text"),
40
+ ad_status: z.array(z.number()).optional().describe("Ad status: 1-Running, 2-Stopped, 3-Removed"),
41
+ ad_platform: z.array(z.string()).optional().describe("Ad platforms"),
42
+ landing_page_type: z.array(z.number()).optional().describe("Landing page type: 1-Product, 2-Collection, 3-Other"),
43
+ related_product_ids: z.array(z.string()).optional().describe("Product IDs"),
44
+ product_category_id: z.array(z.string()).optional().describe("Product category IDs"),
45
+ product_price_usd_start: z.number().optional().describe("Product price start (USD)"),
46
+ product_price_usd_end: z.number().optional().describe("Product price end (USD)"),
47
+ product_creation_time_start: z.number().optional().describe("Product creation time start (Unix timestamp)"),
48
+ product_creation_time_end: z.number().optional().describe("Product creation time end (Unix timestamp)"),
49
+ product_ad_count_start: z.number().optional().describe("Product ad count minimum"),
50
+ product_ad_count_end: z.number().optional().describe("Product ad count maximum"),
51
+ related_store_ids: z.array(z.string()).optional().describe("Store IDs"),
52
+ store_ad_count_start: z.number().optional().describe("Store ad count minimum"),
53
+ store_ad_count_end: z.number().optional().describe("Store ad count maximum"),
54
+ advertiser_ad_count_start: z.number().optional().describe("Advertiser ad count minimum"),
55
+ advertiser_ad_count_end: z.number().optional().describe("Advertiser ad count maximum"),
56
+ order_by: z.string().optional().describe("Sort: ad_started_at, adset_count, ad_audience_reach, last_discovered_at, ad_cost, advertiser_ad_count, product_price_usd, product_ad_count, product_creation_time, active_days"),
57
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
58
+ page: z.number().optional().describe("Page number (min: 1)"),
59
+ per_page: z.number().optional().describe("Page size (1-100)"),
60
+ with_ad_status_group: z.string().optional().describe("Include ad status stats (pass empty string to enable)"),
61
+ search_after: z.string().optional().describe("Deep pagination cursor (pass empty string for first page)"),
62
+ }, async (params) => {
63
+ const res = await apiPost("/open-api/v1/advertisements/search", params);
64
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
65
+ });
66
+ server.tool("ppspy_advertisement_details", "Get detailed information for a specific advertisement. Requires ad ID from advertisement list.", {
67
+ id: z.string().describe("Advertisement ID (required)"),
68
+ }, async ({ id }) => {
69
+ const res = await apiGet(`/open-api/v1/advertisements/${id}`);
70
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
71
+ });
72
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSalesMonitorTools(server: McpServer): void;
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ import { apiPost, apiGet, apiPut, apiDelete } from "../client.js";
3
+ export function registerSalesMonitorTools(server) {
4
+ // ===== Group CRUD =====
5
+ server.tool("ppspy_sales_monitor_create_group", "Create a sales monitoring group. Free.", {
6
+ name: z.string().describe("Group name (required)"),
7
+ }, async (params) => {
8
+ const res = await apiPost("/open-api/v1/sales-monitor-groups", params);
9
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
10
+ });
11
+ server.tool("ppspy_sales_monitor_group_list", "List all sales monitoring groups. Free.", {}, async () => {
12
+ const res = await apiGet("/open-api/v1/sales-monitor-groups");
13
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
14
+ });
15
+ server.tool("ppspy_sales_monitor_edit_group", "Edit a sales monitoring group name. Free.", {
16
+ id: z.string().describe("Group ID (required)"),
17
+ name: z.string().describe("New group name (required)"),
18
+ }, async ({ id, name }) => {
19
+ const res = await apiPut(`/open-api/v1/sales-monitor-groups/${id}`, { name });
20
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
21
+ });
22
+ server.tool("ppspy_sales_monitor_delete_group", "Delete a sales monitoring group. WARNING: All tasks under this group will be stopped. Free.", {
23
+ id: z.string().describe("Group ID (required)"),
24
+ }, async ({ id }) => {
25
+ const res = await apiDelete(`/open-api/v1/sales-monitor-groups/${id}`);
26
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
27
+ });
28
+ // ===== Task Operations =====
29
+ server.tool("ppspy_sales_monitor_create_task", "Create sales monitoring task(s). Cost: 1 monitoring quota per task. Supports batch up to 50 URLs.", {
30
+ urls: z.array(z.string()).describe("Store URLs or domains to monitor (required, max 50)"),
31
+ with_fake_sales: z.boolean().optional().describe("Monitor stores with fake sales (default: false)"),
32
+ group_id: z.string().optional().describe("Group ID"),
33
+ }, async (params) => {
34
+ const res = await apiPost("/open-api/v1/sales-monitor-task", params);
35
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
36
+ });
37
+ server.tool("ppspy_sales_monitor_task_list", "List sales monitoring tasks with filters. Free.", {
38
+ keyword: z.string().optional().describe("Search keyword"),
39
+ monitor_task_status: z.number().optional().describe("Status: 0-Stopped, 1-Monitoring"),
40
+ note: z.string().optional().describe("Note filter"),
41
+ order_by: z.string().optional().describe("Sort field"),
42
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
43
+ page: z.number().optional().describe("Page number (min: 1)"),
44
+ per_page: z.number().optional().describe("Page size (1-100)"),
45
+ group_id: z.string().optional().describe("Group ID filter"),
46
+ }, async (params) => {
47
+ const res = await apiGet("/open-api/v1/sales-monitor-task", params);
48
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
49
+ });
50
+ server.tool("ppspy_sales_monitor_task_details", "Get detailed sales monitoring task info including daily/hourly stats. Free.", {
51
+ site_domain: z.string().describe("Store domain (required)"),
52
+ period_id: z.string().optional().describe("Monitor period ID (defaults to latest)"),
53
+ }, async (params) => {
54
+ const res = await apiGet("/open-api/v1/sales-monitor-task/detail", params);
55
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
56
+ });
57
+ server.tool("ppspy_sales_monitor_task_real_time_sales", "Get real-time individual sales records. Free.", {
58
+ task_id: z.string().describe("Real-time sales task ID (required, from task details)"),
59
+ latest_id: z.string().optional().describe("Last record ID for pagination (omit for latest 100)"),
60
+ }, async (params) => {
61
+ const res = await apiGet("/open-api/v1/sales-monitor-task/real-time-sales", params);
62
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
63
+ });
64
+ server.tool("ppspy_sales_monitor_task_update", "Update sales monitoring task metadata (notes, group). Free.", {
65
+ site_domain: z.string().describe("Store domain (required)"),
66
+ note: z.string().nullable().optional().describe("Note"),
67
+ group_id: z.string().nullable().optional().describe("Group ID"),
68
+ }, async (params) => {
69
+ const res = await apiPut("/open-api/v1/sales-monitor-task", params);
70
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
71
+ });
72
+ server.tool("ppspy_sales_monitor_task_stop", "Stop a sales monitoring task. WARNING: Consumed quotas are NOT refunded. Free.", {
73
+ site_domain: z.string().describe("Store domain (required)"),
74
+ }, async (params) => {
75
+ const res = await apiPut("/open-api/v1/sales-monitor-task/stop", params);
76
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
77
+ });
78
+ server.tool("ppspy_sales_monitor_task_delete", "Delete sales monitoring task(s). Only stopped tasks can be deleted. Free.", {
79
+ site_domains: z.array(z.string()).describe("Store domains to delete (required)"),
80
+ }, async (params) => {
81
+ const res = await apiDelete("/open-api/v1/sales-monitor-task", params);
82
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
83
+ });
84
+ server.tool("ppspy_sales_monitor_task_overview", "Get aggregated sales overview (total, today, yesterday, 7-day) in USD. Free.", {
85
+ group_id: z.string().optional().describe("Group ID (optional, filter by group)"),
86
+ }, async (params) => {
87
+ const res = await apiGet("/open-api/v1/sales-monitor-task/overview", params);
88
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
89
+ });
90
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerShopifyProductTools(server: McpServer): void;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { apiGet } from "../client.js";
3
+ export function registerShopifyProductTools(server) {
4
+ server.tool("ppspy_shopify_product_list", "Search and filter Shopify products. Cost: 1 credit per record.", {
5
+ title: z.string().optional().describe("Product title, comma-separated for multiple"),
6
+ url: z.string().optional().describe("Store URL, comma-separated for multiple"),
7
+ pixel: z.string().optional().describe("Ad platform(s): snap, tiktok, pinterest"),
8
+ country: z.string().optional().describe("Store region(s), comma-separated"),
9
+ created_at_start: z.string().optional().describe("Creation time start (Y-m-d)"),
10
+ created_at_end: z.string().optional().describe("Creation time end (Y-m-d)"),
11
+ updated_at_start: z.string().optional().describe("Latest order time start (Y-m-d)"),
12
+ updated_at_end: z.string().optional().describe("Latest order time end (Y-m-d)"),
13
+ price_start: z.number().optional().describe("Price range start"),
14
+ price_end: z.number().optional().describe("Price range end"),
15
+ currency: z.string().optional().describe("Currency, comma-separated"),
16
+ language: z.string().optional().describe("Store language(s), comma-separated"),
17
+ is_bestselling: z.number().optional().describe("Bestseller flag: 1=Yes, 0=No"),
18
+ shop_id: z.string().optional().describe("Store ID"),
19
+ category_id: z.number().optional().describe("Product Category ID"),
20
+ monthly_sale_start: z.number().optional().describe("Monthly sales minimum"),
21
+ monthly_sale_end: z.number().optional().describe("Monthly sales maximum"),
22
+ monthly_revenue_start: z.number().optional().describe("Monthly revenue minimum"),
23
+ monthly_revenue_end: z.number().optional().describe("Monthly revenue maximum"),
24
+ published_products_count_start: z.number().optional().describe("Store product count min"),
25
+ published_products_count_end: z.number().optional().describe("Store product count max"),
26
+ exclude_title: z.string().optional().describe("Exclude product titles, comma-separated"),
27
+ exclude_url: z.string().optional().describe("Exclude store URLs, comma-separated"),
28
+ category_tags: z.string().optional().describe("Product types: 1-Dropshipping, 2-POD, 3-COD, 4-Digital"),
29
+ order_by: z.string().optional().describe("Sort: updated_at, created_at, published_products_count, price, monthly_sale, monthly_revenue"),
30
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
31
+ page: z.number().optional().describe("Page number (min: 1)"),
32
+ per_page: z.number().optional().describe("Items per page (1-100)"),
33
+ }, async (params) => {
34
+ const res = await apiGet("/open-api/v1/product", params);
35
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
36
+ });
37
+ server.tool("ppspy_shopify_bestselling_product_list", "Get bestselling products for a specific Shopify store. Requires shop_id.", {
38
+ shop_id: z.string().describe("Store ID (required)"),
39
+ page: z.number().optional().describe("Page number"),
40
+ per_page: z.number().optional().describe("Items per page (1-100)"),
41
+ }, async (params) => {
42
+ const res = await apiGet("/open-api/v1/bestsellers", params);
43
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
44
+ });
45
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerShopifyStoreTools(server: McpServer): void;
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ import { apiGet } from "../client.js";
3
+ export function registerShopifyStoreTools(server) {
4
+ server.tool("ppspy_shopify_store_list", "Search and filter Shopify stores. Cost: 1 credit per record.", {
5
+ url: z.string().optional().describe("Store URL(s), comma-separated"),
6
+ tags: z.string().optional().describe("Tag(s), comma-separated"),
7
+ pixel: z.string().optional().describe("Ad platform(s), comma-separated. Options: snap, tiktok, pinterest"),
8
+ last_sold_at_start: z.string().optional().describe("Latest order date start (Y-m-d)"),
9
+ last_sold_at_end: z.string().optional().describe("Latest order date end (Y-m-d)"),
10
+ month_visits_start: z.number().optional().describe("Monthly visits minimum"),
11
+ month_visits_end: z.number().optional().describe("Monthly visits maximum"),
12
+ published_products_count_start: z.number().optional().describe("Product count minimum"),
13
+ published_products_count_end: z.number().optional().describe("Product count maximum"),
14
+ language: z.string().optional().describe("Store language(s), comma-separated"),
15
+ country: z.string().optional().describe("Store region(s), comma-separated"),
16
+ last_product_published_at_start: z.string().optional().describe("Latest product publish date start (Y-m-d)"),
17
+ last_product_published_at_end: z.string().optional().describe("Latest product publish date end (Y-m-d)"),
18
+ first_product_published_at_start: z.string().optional().describe("First product publish date start (Y-m-d)"),
19
+ first_product_published_at_end: z.string().optional().describe("First product publish date end (Y-m-d)"),
20
+ currency: z.string().optional().describe("Currency/currencies, comma-separated"),
21
+ social_media: z.string().optional().describe("Social media platform(s), comma-separated. Options: facebook, instagram, tiktok, twitter, pinterest, youtube"),
22
+ themes: z.string().optional().describe("Search by theme name"),
23
+ app: z.string().optional().describe("Search by installed apps"),
24
+ pixel_text: z.string().optional().describe("Search by pixel name/ID"),
25
+ title: z.string().optional().describe("Store name(s), comma-separated"),
26
+ description: z.string().optional().describe("Store description(s), comma-separated"),
27
+ exclude_title: z.string().optional().describe("Exclude store name(s), comma-separated"),
28
+ exclude_url: z.string().optional().describe("Exclude store URL(s), comma-separated"),
29
+ exclude_description: z.string().optional().describe("Exclude store description(s), comma-separated"),
30
+ monthly_sale_start: z.number().optional().describe("Monthly sales minimum"),
31
+ monthly_sale_end: z.number().optional().describe("Monthly sales maximum"),
32
+ monthly_revenue_start: z.number().optional().describe("Monthly revenue minimum"),
33
+ monthly_revenue_end: z.number().optional().describe("Monthly revenue maximum"),
34
+ category_tags: z.string().optional().describe("Product types, comma-separated. Options: 1-Dropshipping, 2-POD, 3-COD, 4-Digital"),
35
+ shield_shop: z.number().optional().describe("Filter suspicious shops: 1-yes, 2-no"),
36
+ order_by: z.string().optional().describe("Sort field: last_sold_at, published_products_count, last_product_published_at, first_product_published_at, month_visits, monthly_sale, monthly_revenue"),
37
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
38
+ page: z.number().optional().describe("Page number (min: 1)"),
39
+ per_page: z.number().optional().describe("Items per page (1-100)"),
40
+ }, async (params) => {
41
+ const res = await apiGet("/open-api/v1/shop", params);
42
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
43
+ });
44
+ server.tool("ppspy_shopify_store_details", "Get detailed information for a specific Shopify store. Requires shop_id from store list.", {
45
+ shop_id: z.string().describe("Shop ID (required)"),
46
+ }, async (params) => {
47
+ const res = await apiGet("/open-api/v1/shop/store-details", params);
48
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
49
+ });
50
+ server.tool("ppspy_shopify_store_traffic_list", "Search Shopify stores with traffic data. Cost: 1 credit per record.", {
51
+ url: z.string().optional().describe("Store URL(s), comma-separated"),
52
+ pixel: z.string().optional().describe("Ad platform(s), comma-separated"),
53
+ last_sold_at_start: z.string().optional().describe("Latest order date start (Y-m-d)"),
54
+ last_sold_at_end: z.string().optional().describe("Latest order date end (Y-m-d)"),
55
+ month_visits_start: z.number().optional().describe("Monthly visits minimum"),
56
+ month_visits_end: z.number().optional().describe("Monthly visits maximum"),
57
+ language: z.string().optional().describe("Store language(s), comma-separated"),
58
+ country: z.string().optional().describe("Store region(s), comma-separated"),
59
+ currency: z.string().optional().describe("Currency, comma-separated"),
60
+ order_by: z.string().optional().describe("Sort field"),
61
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
62
+ page: z.number().optional().describe("Page number"),
63
+ per_page: z.number().optional().describe("Items per page (1-100)"),
64
+ }, async (params) => {
65
+ const res = await apiGet("/open-api/v1/shop/traffic-shop", params);
66
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
67
+ });
68
+ server.tool("ppspy_shopify_single_product_store_list", "Search single-product Shopify stores. Cost: 1 credit per record.", {
69
+ url: z.string().optional().describe("Store URL(s), comma-separated"),
70
+ tags: z.string().optional().describe("Tag(s), comma-separated"),
71
+ pixel: z.string().optional().describe("Ad platform(s), comma-separated"),
72
+ language: z.string().optional().describe("Store language(s), comma-separated"),
73
+ country: z.string().optional().describe("Store region(s), comma-separated"),
74
+ currency: z.string().optional().describe("Currency, comma-separated"),
75
+ themes: z.string().optional().describe("Theme name"),
76
+ order_by: z.string().optional().describe("Sort field"),
77
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
78
+ page: z.number().optional().describe("Page number"),
79
+ per_page: z.number().optional().describe("Items per page (1-100)"),
80
+ }, async (params) => {
81
+ const res = await apiGet("/open-api/v1/shop/single-product-shop", params);
82
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
83
+ });
84
+ server.tool("ppspy_shopify_theme_store_list", "Search Shopify stores by theme. Cost: 1 credit per record.", {
85
+ url: z.string().optional().describe("Store URL(s), comma-separated"),
86
+ tags: z.string().optional().describe("Tag(s), comma-separated"),
87
+ language: z.string().optional().describe("Store language(s), comma-separated"),
88
+ country: z.string().optional().describe("Store region(s), comma-separated"),
89
+ currency: z.string().optional().describe("Currency, comma-separated"),
90
+ themes_text: z.string().optional().describe("Theme name to search"),
91
+ order_by: z.string().optional().describe("Sort field"),
92
+ direction: z.string().optional().describe("Sort direction: desc, asc"),
93
+ page: z.number().optional().describe("Page number"),
94
+ per_page: z.number().optional().describe("Items per page (1-100)"),
95
+ }, async (params) => {
96
+ const res = await apiGet("/open-api/v1/shop/theme-shop", params);
97
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
98
+ });
99
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSupplementTools(server: McpServer): void;
@@ -0,0 +1,15 @@
1
+ import { apiGet } from "../client.js";
2
+ export function registerSupplementTools(server) {
3
+ server.tool("ppspy_product_category_list", "Get the list of product categories. Free, no credits consumed.", {}, async () => {
4
+ const res = await apiGet("/open-api/v1/product/categories");
5
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
6
+ });
7
+ server.tool("ppspy_ad_filter_options", "Get ad filter options (button types and button texts). Free, no credits consumed.", {}, async () => {
8
+ const res = await apiGet("/open-api/v1/advertisements/options");
9
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
10
+ });
11
+ server.tool("ppspy_shopify_theme_list", "Get the list of Shopify themes. Free, no credits consumed.", {}, async () => {
12
+ const res = await apiGet("/open-api/v1/shopify-themes");
13
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
14
+ });
15
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "ppspy-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for PPSPY Open API - Shopify spy, ad spy, ad monitor, sales monitor",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "ppspy-mcp-server": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "keywords": [
14
+ "mcp",
15
+ "mcp-server",
16
+ "ppspy",
17
+ "shopify",
18
+ "ecommerce",
19
+ "ad-spy",
20
+ "dropshipping",
21
+ "facebook-ads",
22
+ "sales-monitor"
23
+ ],
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/nicekid1/ppspy-mcp-server"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "prepublishOnly": "npm run build",
32
+ "start": "node dist/index.js",
33
+ "dev": "tsx src/index.ts"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.12.1",
37
+ "express": "^5.1.0",
38
+ "zod": "^3.24.4"
39
+ },
40
+ "devDependencies": {
41
+ "@types/express": "^5.0.2",
42
+ "@types/node": "^22.15.0",
43
+ "tsx": "^4.19.4",
44
+ "typescript": "^5.8.3"
45
+ }
46
+ }