listingbureau-mcp 0.1.6 → 0.1.7
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 +1 -1
- package/dist/index.js +16 -0
- package/dist/tools/cost.tools.js +24 -5
- package/dist/tools/feedback.tools.js +1 -1
- package/dist/tools/projects.tools.js +7 -14
- package/dist/tools/schedule.tools.js +68 -10
- package/dist/utils/regions.d.ts +24 -0
- package/dist/utils/regions.js +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ Create an account at [listingbureau.com](https://listingbureau.com/mcp?utm_sourc
|
|
|
164
164
|
|
|
165
165
|
| Tool | Description |
|
|
166
166
|
|------|-------------|
|
|
167
|
-
| `lb_feedback_submit` | Submit feedback (10-5000 characters) |
|
|
167
|
+
| `lb_feedback_submit` | Submit feedback, feature requests, or suggestions (10-5000 characters) |
|
|
168
168
|
|
|
169
169
|
## 🛠️ Development
|
|
170
170
|
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,22 @@ const client = new LBClient(apiKey, baseUrl);
|
|
|
33
33
|
const server = new McpServer({
|
|
34
34
|
name: "listingbureau",
|
|
35
35
|
version: pkg.version,
|
|
36
|
+
}, {
|
|
37
|
+
instructions: [
|
|
38
|
+
"Listing Bureau provides Amazon organic ranking services: SFB (Search Find Buy), ATC (Add to Cart), and PGV (page views).",
|
|
39
|
+
"SFB drives purchases that directly boost organic ranking. ATC and PGV support the funnel but alone rarely move ranking unless the keyword is very uncompetitive.",
|
|
40
|
+
"When a user schedules only ATC/PGV with no SFB, note that these are typically paired with SFB to maintain a healthy conversion ratio.",
|
|
41
|
+
"",
|
|
42
|
+
"When advising on service volumes, aim for a natural Amazon funnel with realistic conversion rates (10-15%). These are starting points — adjust based on the user's product, price point, and keyword competitiveness:",
|
|
43
|
+
"Default ratio per SFB: ~2 ATC, ~8-10 PGV.",
|
|
44
|
+
"Cheaper/high-intent keywords (e.g. branded): ~1-1.5 ATC, ~5-7 PGV per SFB.",
|
|
45
|
+
"Expensive/competitive keywords: ~2-3 ATC, ~10-15 PGV per SFB to reflect more browsing.",
|
|
46
|
+
"Avoid SFB-only schedules with near-100% conversion. Keep ratios consistent per keyword so traffic looks like natural shopper behavior.",
|
|
47
|
+
"",
|
|
48
|
+
"When the user asks about something outside current capabilities (other marketplaces, unsupported features), offer to submit their input as feedback via lb_feedback_submit.",
|
|
49
|
+
"",
|
|
50
|
+
"SFB units are real Amazon purchases of the user's product. The retail price in the SFB cost is not a net expense — the seller receives it back as normal Amazon sale proceeds. The actual out-of-pocket cost per SFB is the service fee plus the 11% tax/transfer overhead on the retail price.",
|
|
51
|
+
].join("\n"),
|
|
36
52
|
});
|
|
37
53
|
registerAccountTools(server, client);
|
|
38
54
|
registerWalletTools(server, client);
|
package/dist/tools/cost.tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { sfbUnitCost, estimateCost, round2 } from "../utils/cost.js";
|
|
3
3
|
import { formatResult, formatErrorResult } from "../utils/response.js";
|
|
4
|
+
import { ACCEPTED_REGIONS, normalizeRegion, assertSfbAllowed } from "../utils/regions.js";
|
|
4
5
|
const scheduleItemSchema = z.object({
|
|
5
6
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be YYYY-MM-DD format").describe("Date in YYYY-MM-DD format"),
|
|
6
7
|
atc: z.number().int().min(0).optional().describe("Add-to-cart volume (default 0)"),
|
|
@@ -8,9 +9,13 @@ const scheduleItemSchema = z.object({
|
|
|
8
9
|
pgv: z.number().int().min(0).optional().describe("Page view volume (default 0)"),
|
|
9
10
|
});
|
|
10
11
|
const estimateCostShape = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
region: z
|
|
13
|
+
.enum(ACCEPTED_REGIONS)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Amazon region code — if provided with SFB volumes, validates SFB is allowed (US only). GB accepted as alias for UK."),
|
|
16
|
+
atc: z.number().int().min(0).optional().describe("Uniform daily add-to-cart volume (all regions; lower execution rate outside US)"),
|
|
17
|
+
sfb: z.number().int().min(0).optional().describe("Uniform daily Search Find Buy (SFB) volume (US-region projects only)"),
|
|
18
|
+
pgv: z.number().int().min(0).optional().describe("Uniform daily page view volume (all regions; lower execution rate outside US)"),
|
|
14
19
|
num_days: z.number().int().min(1).max(365).optional().describe("Number of days for uniform volumes"),
|
|
15
20
|
schedule: z
|
|
16
21
|
.array(scheduleItemSchema)
|
|
@@ -25,7 +30,7 @@ const estimateCostShape = {
|
|
|
25
30
|
.describe("Product retail price in USD — needed for accurate SFB cost including product price and fees"),
|
|
26
31
|
};
|
|
27
32
|
export function registerCostTools(server, client) {
|
|
28
|
-
server.tool("lb_estimate_cost", "Estimate campaign cost before committing. Fetches current rates and wallet balance, then computes total cost, daily averages, and wallet sustainability. Provide either uniform daily volumes (atc/sfb/pgv + num_days) or a per-day schedule array. Include retail_price for accurate SFB costs.", estimateCostShape, { readOnlyHint: true }, async (params) => {
|
|
33
|
+
server.tool("lb_estimate_cost", "Estimate campaign cost before committing. Fetches current rates and wallet balance, then computes total cost, daily averages, and wallet sustainability. Provide either uniform daily volumes (atc/sfb/pgv + num_days) or a per-day schedule array. Include retail_price for accurate SFB costs. SFB is US-region only; ATC/PGV work in all regions (lower execution rate outside US). Pass region to validate SFB eligibility.", estimateCostShape, { readOnlyHint: true }, async (params) => {
|
|
29
34
|
try {
|
|
30
35
|
// Validate: either schedule array, or (volumes + num_days)
|
|
31
36
|
const hasSchedule = params.schedule && params.schedule.length > 0;
|
|
@@ -33,6 +38,16 @@ export function registerCostTools(server, client) {
|
|
|
33
38
|
if (!hasSchedule && !(hasVolume && params.num_days != null)) {
|
|
34
39
|
return formatErrorResult(new Error("Provide either a 'schedule' array, OR at least one volume (atc/sfb/pgv > 0) with 'num_days'"));
|
|
35
40
|
}
|
|
41
|
+
// Normalize region once for all downstream use
|
|
42
|
+
const normalizedRegion = params.region ? normalizeRegion(params.region) : undefined;
|
|
43
|
+
// SFB region validation
|
|
44
|
+
const hasSfbInput = params.schedule
|
|
45
|
+
? params.schedule.some((d) => (d.sfb ?? 0) > 0)
|
|
46
|
+
: (params.sfb ?? 0) > 0;
|
|
47
|
+
if (normalizedRegion) {
|
|
48
|
+
// Hard reject: region provided + non-US + has SFB
|
|
49
|
+
assertSfbAllowed(normalizedRegion, hasSfbInput);
|
|
50
|
+
}
|
|
36
51
|
const [ratesRes, walletRes] = await Promise.all([
|
|
37
52
|
client.request("GET", "/api/v1/account/service-rates", undefined, undefined, "lb_estimate_cost"),
|
|
38
53
|
client.request("GET", "/api/v1/wallet", undefined, undefined, "lb_estimate_cost"),
|
|
@@ -72,8 +87,12 @@ export function registerCostTools(server, client) {
|
|
|
72
87
|
if (hasSchedule && (hasVolume || params.num_days != null)) {
|
|
73
88
|
warnings.push("schedule array provided — num_days and uniform volumes (atc/sfb/pgv) were ignored.");
|
|
74
89
|
}
|
|
75
|
-
// SFB without
|
|
90
|
+
// SFB without region warning
|
|
76
91
|
const hasSfb = schedule.some((d) => d.sfb > 0);
|
|
92
|
+
if (hasSfb && !params.region) {
|
|
93
|
+
warnings.push("SFB volumes provided without region — estimate assumes US pricing. Pass region to validate SFB eligibility.");
|
|
94
|
+
}
|
|
95
|
+
// SFB without retail_price warning (0 is effectively the same as omitting)
|
|
77
96
|
if (hasSfb && (params.retail_price == null || params.retail_price === 0)) {
|
|
78
97
|
warnings.push("SFB volumes provided without retail_price — estimate uses service fee only ($" +
|
|
79
98
|
rates.sfb_service_fee.toFixed(2) +
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult, formatErrorResult } from "../utils/response.js";
|
|
3
3
|
export function registerFeedbackTools(server, client) {
|
|
4
|
-
server.tool("lb_feedback_submit", "Submit feedback
|
|
4
|
+
server.tool("lb_feedback_submit", "Submit feedback, feature requests, or suggestions (10-5000 characters)", {
|
|
5
5
|
feedback: z
|
|
6
6
|
.string()
|
|
7
7
|
.min(10)
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult, formatErrorResult } from "../utils/response.js";
|
|
3
|
-
|
|
4
|
-
const VALID_REGIONS = [
|
|
5
|
-
"US", "CA", "MX", // Americas
|
|
6
|
-
"UK", "DE", "FR", "IT", // Europe
|
|
7
|
-
"ES", "NL", "SE", "TR", // Europe cont.
|
|
8
|
-
"JP", "AU", "IN", // Asia-Pacific
|
|
9
|
-
"AE", "BR", "SG", "SA", // Rest of world
|
|
10
|
-
];
|
|
3
|
+
import { ACCEPTED_REGIONS, normalizeRegion } from "../utils/regions.js";
|
|
11
4
|
export function registerProjectsTools(server, client) {
|
|
12
5
|
server.tool("lb_projects_list", "List Listing Bureau Amazon projects with optional filters. Returns ASIN, keyword, active status, and current service volumes.", {
|
|
13
6
|
region: z
|
|
14
|
-
.enum(
|
|
7
|
+
.enum(ACCEPTED_REGIONS)
|
|
15
8
|
.optional()
|
|
16
|
-
.describe("Filter by Amazon region"),
|
|
9
|
+
.describe("Filter by Amazon region code (GB accepted as alias for UK)"),
|
|
17
10
|
active: z.boolean().optional().describe("Filter by active status"),
|
|
18
11
|
}, { readOnlyHint: true }, async (params) => {
|
|
19
12
|
try {
|
|
20
13
|
const query = { marketplace: "amazon" };
|
|
21
14
|
if (params.region !== undefined)
|
|
22
|
-
query.region = params.region;
|
|
15
|
+
query.region = normalizeRegion(params.region);
|
|
23
16
|
if (params.active !== undefined)
|
|
24
17
|
query.active = String(params.active);
|
|
25
18
|
const res = await client.request("GET", "/api/v1/projects", undefined, query, "lb_projects_list");
|
|
@@ -31,8 +24,8 @@ export function registerProjectsTools(server, client) {
|
|
|
31
24
|
});
|
|
32
25
|
server.tool("lb_projects_create", "Create a new Listing Bureau Amazon project. If a project with the same ASIN+keyword+region was previously archived, it will be reactivated instead (returns 201). ASIN must be a valid Amazon ASIN. Keyword must be 3-200 characters.", {
|
|
33
26
|
region: z
|
|
34
|
-
.enum(
|
|
35
|
-
.describe("Amazon region code"),
|
|
27
|
+
.enum(ACCEPTED_REGIONS)
|
|
28
|
+
.describe("Amazon region code (GB accepted as alias for UK)"),
|
|
36
29
|
asin: z
|
|
37
30
|
.string()
|
|
38
31
|
.describe("Amazon ASIN (e.g. 'B01MTJK06C')"),
|
|
@@ -50,7 +43,7 @@ export function registerProjectsTools(server, client) {
|
|
|
50
43
|
try {
|
|
51
44
|
const body = {
|
|
52
45
|
marketplace: "amazon",
|
|
53
|
-
region: params.region,
|
|
46
|
+
region: normalizeRegion(params.region),
|
|
54
47
|
asin: params.asin,
|
|
55
48
|
keyword: params.keyword,
|
|
56
49
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult, formatErrorResult } from "../utils/response.js";
|
|
3
|
+
import { assertSfbAllowed, SfbRegionError } from "../utils/regions.js";
|
|
3
4
|
import { sfbUnitCost, estimateCost, mapScheduleEntries, round2 } from "../utils/cost.js";
|
|
4
5
|
// Write schema: only YYYY-MM-DD dates (backend manages 'ongoing' entries internally)
|
|
5
6
|
const scheduleEntrySchema = z.object({
|
|
@@ -7,9 +8,9 @@ const scheduleEntrySchema = z.object({
|
|
|
7
8
|
.string()
|
|
8
9
|
.regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD format")
|
|
9
10
|
.describe("Date in YYYY-MM-DD format"),
|
|
10
|
-
atc: z.number().int().min(0).describe("Add-to-cart volume"),
|
|
11
|
-
sfb: z.number().int().min(0).describe("Search Find Buy (SFB) volume"),
|
|
12
|
-
pgv: z.number().int().min(0).describe("Page view volume"),
|
|
11
|
+
atc: z.number().int().min(0).describe("Add-to-cart volume (all regions; lower execution rate outside US)"),
|
|
12
|
+
sfb: z.number().int().min(0).describe("Search Find Buy (SFB) volume (US-region projects only)"),
|
|
13
|
+
pgv: z.number().int().min(0).describe("Page view volume (all regions; lower execution rate outside US)"),
|
|
13
14
|
});
|
|
14
15
|
/**
|
|
15
16
|
* Best-effort: fetch rates and compute cost summary for a schedule response.
|
|
@@ -25,7 +26,8 @@ async function appendCostSummary(data, client) {
|
|
|
25
26
|
if (dated.length === 0 && !ongoing)
|
|
26
27
|
return result;
|
|
27
28
|
const sfbNote = "SFB costs use service fee only ($" + rates.sfb_service_fee.toFixed(2) +
|
|
28
|
-
"), matching backend balance check behavior. Use lb_estimate_cost with retail_price for full SFB cost."
|
|
29
|
+
"), matching backend balance check behavior. Use lb_estimate_cost with retail_price for full SFB cost." +
|
|
30
|
+
" Note: the retail price portion is not a net expense — it returns as Amazon sale proceeds.";
|
|
29
31
|
if (dated.length > 0 && ongoing) {
|
|
30
32
|
// Mixed: dated entries + ongoing
|
|
31
33
|
const est = estimateCost(dated, rates);
|
|
@@ -99,7 +101,7 @@ async function appendCostSummary(data, client) {
|
|
|
99
101
|
return result;
|
|
100
102
|
}
|
|
101
103
|
export function registerScheduleTools(server, client) {
|
|
102
|
-
server.tool("lb_schedule_get", "Get the current schedule for a Listing Bureau project. Shows per-day service volumes (atc, sfb, pgv).", {
|
|
104
|
+
server.tool("lb_schedule_get", "Get the current schedule for a Listing Bureau project. Shows per-day service volumes (atc, sfb, pgv). Note: SFB is only available for US-region projects.", {
|
|
103
105
|
ui_id: z.string().describe("Project unique identifier"),
|
|
104
106
|
}, { readOnlyHint: true }, async (params) => {
|
|
105
107
|
try {
|
|
@@ -111,7 +113,7 @@ export function registerScheduleTools(server, client) {
|
|
|
111
113
|
return formatErrorResult(e);
|
|
112
114
|
}
|
|
113
115
|
});
|
|
114
|
-
server.tool("lb_schedule_set", "Set the full per-day schedule for a Listing Bureau project. Replaces any existing schedule. Each entry represents one day with volumes for atc, sfb, and pgv. Max 365 entries.", {
|
|
116
|
+
server.tool("lb_schedule_set", "Set the full per-day schedule for a Listing Bureau project. Replaces any existing schedule. Each entry represents one day with volumes for atc, sfb, and pgv. Max 365 entries. SFB is US-region only; ATC/PGV work in all regions (lower execution rate outside US).", {
|
|
115
117
|
ui_id: z.string().describe("Project unique identifier"),
|
|
116
118
|
schedule: z
|
|
117
119
|
.array(scheduleEntrySchema)
|
|
@@ -120,21 +122,64 @@ export function registerScheduleTools(server, client) {
|
|
|
120
122
|
.describe("Array of daily schedule entries"),
|
|
121
123
|
}, { destructiveHint: true, idempotentHint: true }, async (params) => {
|
|
122
124
|
try {
|
|
125
|
+
// SFB region gate: only fetch project when schedule contains SFB > 0
|
|
126
|
+
let regionWarning;
|
|
127
|
+
if (params.schedule.some((e) => e.sfb > 0)) {
|
|
128
|
+
try {
|
|
129
|
+
const projRes = await client.request("GET", `/api/v1/projects/${encodeURIComponent(params.ui_id)}`, undefined, undefined, "lb_schedule_set");
|
|
130
|
+
assertSfbAllowed(projRes.data.region, true);
|
|
131
|
+
}
|
|
132
|
+
catch (fetchErr) {
|
|
133
|
+
// If assertSfbAllowed threw, re-throw (it's a validation error, not a fetch failure)
|
|
134
|
+
if (fetchErr instanceof SfbRegionError) {
|
|
135
|
+
throw fetchErr;
|
|
136
|
+
}
|
|
137
|
+
// Fetch failure: warn and proceed — backend enforces the real restriction
|
|
138
|
+
regionWarning = "Could not verify project region for SFB eligibility. The backend will enforce region restrictions.";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
123
141
|
const res = await client.request("PUT", `/api/v1/projects/${encodeURIComponent(params.ui_id)}/schedule`, { schedule: params.schedule }, undefined, "lb_schedule_set");
|
|
124
142
|
const augmented = await appendCostSummary(res.data, client);
|
|
143
|
+
if (regionWarning) {
|
|
144
|
+
augmented.region_warning = regionWarning;
|
|
145
|
+
}
|
|
146
|
+
// Note when schedule has ATC/PGV but no SFB
|
|
147
|
+
const hasAtcOrPgv = params.schedule.some((e) => e.atc > 0 || e.pgv > 0);
|
|
148
|
+
const hasSfb = params.schedule.some((e) => e.sfb > 0);
|
|
149
|
+
if (hasAtcOrPgv && !hasSfb) {
|
|
150
|
+
augmented.service_note =
|
|
151
|
+
"This schedule has ATC/PGV but no SFB. " +
|
|
152
|
+
"These services alone rarely move ranking unless the keyword is very uncompetitive. " +
|
|
153
|
+
"They're typically paired with SFB to maintain a healthy organic conversion ratio. " +
|
|
154
|
+
"You can update the schedule to add SFB if needed.";
|
|
155
|
+
}
|
|
125
156
|
return formatResult(augmented);
|
|
126
157
|
}
|
|
127
158
|
catch (e) {
|
|
128
159
|
return formatErrorResult(e);
|
|
129
160
|
}
|
|
130
161
|
});
|
|
131
|
-
server.tool("lb_schedule_quick_set", "Quick-set uniform daily volumes for a Listing Bureau project. WARNING: This clears any existing per-day schedule and replaces it with uniform values. All omitted fields default to 0.", {
|
|
162
|
+
server.tool("lb_schedule_quick_set", "Quick-set uniform daily volumes for a Listing Bureau project. WARNING: This clears any existing per-day schedule and replaces it with uniform values. All omitted fields default to 0. SFB is US-region only; ATC/PGV work in all regions (lower execution rate outside US).", {
|
|
132
163
|
ui_id: z.string().describe("Project unique identifier"),
|
|
133
|
-
atc: z.number().int().min(0).optional().describe("Add-to-cart volume per day (default 0)"),
|
|
134
|
-
sfb: z.number().int().min(0).optional().describe("Search Find Buy (SFB) volume per day (default 0)"),
|
|
135
|
-
pgv: z.number().int().min(0).optional().describe("Page view volume per day (default 0)"),
|
|
164
|
+
atc: z.number().int().min(0).optional().describe("Add-to-cart volume per day (default 0) (all regions; lower execution rate outside US)"),
|
|
165
|
+
sfb: z.number().int().min(0).optional().describe("Search Find Buy (SFB) volume per day (default 0) (US-region projects only)"),
|
|
166
|
+
pgv: z.number().int().min(0).optional().describe("Page view volume per day (default 0) (all regions; lower execution rate outside US)"),
|
|
136
167
|
}, { destructiveHint: true, idempotentHint: true }, async (params) => {
|
|
137
168
|
try {
|
|
169
|
+
// SFB region gate: only fetch project when SFB > 0
|
|
170
|
+
let regionWarning;
|
|
171
|
+
if ((params.sfb ?? 0) > 0) {
|
|
172
|
+
try {
|
|
173
|
+
const projRes = await client.request("GET", `/api/v1/projects/${encodeURIComponent(params.ui_id)}`, undefined, undefined, "lb_schedule_quick_set");
|
|
174
|
+
assertSfbAllowed(projRes.data.region, true);
|
|
175
|
+
}
|
|
176
|
+
catch (fetchErr) {
|
|
177
|
+
if (fetchErr instanceof SfbRegionError) {
|
|
178
|
+
throw fetchErr;
|
|
179
|
+
}
|
|
180
|
+
regionWarning = "Could not verify project region for SFB eligibility. The backend will enforce region restrictions.";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
138
183
|
const body = {
|
|
139
184
|
atc: params.atc ?? 0,
|
|
140
185
|
sfb: params.sfb ?? 0,
|
|
@@ -142,6 +187,19 @@ export function registerScheduleTools(server, client) {
|
|
|
142
187
|
};
|
|
143
188
|
const res = await client.request("POST", `/api/v1/projects/${encodeURIComponent(params.ui_id)}/schedule/quick`, body, undefined, "lb_schedule_quick_set");
|
|
144
189
|
const augmented = await appendCostSummary(res.data, client);
|
|
190
|
+
if (regionWarning) {
|
|
191
|
+
augmented.region_warning = regionWarning;
|
|
192
|
+
}
|
|
193
|
+
// Note when schedule has ATC/PGV but no SFB
|
|
194
|
+
const hasAtcOrPgv = (params.atc ?? 0) > 0 || (params.pgv ?? 0) > 0;
|
|
195
|
+
const hasSfb = (params.sfb ?? 0) > 0;
|
|
196
|
+
if (hasAtcOrPgv && !hasSfb) {
|
|
197
|
+
augmented.service_note =
|
|
198
|
+
"This schedule has ATC/PGV but no SFB. " +
|
|
199
|
+
"These services alone rarely move ranking unless the keyword is very uncompetitive. " +
|
|
200
|
+
"They're typically paired with SFB to maintain a healthy organic conversion ratio. " +
|
|
201
|
+
"You can update the schedule to add SFB if needed.";
|
|
202
|
+
}
|
|
145
203
|
return formatResult(augmented);
|
|
146
204
|
}
|
|
147
205
|
catch (e) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Canonical 18 backend region codes (matches CONSTANTS['MARKETPLACES'] in Flask). */
|
|
2
|
+
export declare const VALID_REGIONS: readonly ["US", "CA", "MX", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "TR", "JP", "AU", "IN", "AE", "BR", "SG", "SA"];
|
|
3
|
+
/**
|
|
4
|
+
* All accepted region codes for MCP tool input (19-element tuple).
|
|
5
|
+
* Includes GB as an alias for UK. Declared as a flat literal tuple
|
|
6
|
+
* because z.enum() requires a literal non-empty tuple type.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ACCEPTED_REGIONS: readonly ["US", "CA", "MX", "UK", "DE", "FR", "IT", "ES", "NL", "SE", "TR", "JP", "AU", "IN", "AE", "BR", "SG", "SA", "GB"];
|
|
9
|
+
/** Regions where SFB (Search Find Buy) is available. */
|
|
10
|
+
export declare const SFB_REGIONS: Set<"US" | "CA" | "MX" | "UK" | "DE" | "FR" | "IT" | "ES" | "NL" | "SE" | "TR" | "JP" | "AU" | "IN" | "AE" | "BR" | "SG" | "SA">;
|
|
11
|
+
/** Normalize user-supplied region code: GB -> UK, all others passthrough. */
|
|
12
|
+
export declare function normalizeRegion(region: string): string;
|
|
13
|
+
/** Typed error thrown by assertSfbAllowed for non-US SFB requests. */
|
|
14
|
+
export declare class SfbRegionError extends Error {
|
|
15
|
+
constructor(region: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Assert that SFB is allowed for the given region.
|
|
19
|
+
* Throws SfbRegionError if the region is not in SFB_REGIONS.
|
|
20
|
+
*
|
|
21
|
+
* @param region - Canonical region code (already normalized)
|
|
22
|
+
* @param hasSfb - Whether the request includes SFB volumes > 0
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertSfbAllowed(region: string, hasSfb: boolean): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Centralized region constants and validation for Listing Bureau MCP tools.
|
|
2
|
+
/** Canonical 18 backend region codes (matches CONSTANTS['MARKETPLACES'] in Flask). */
|
|
3
|
+
export const VALID_REGIONS = [
|
|
4
|
+
"US", "CA", "MX", // Americas
|
|
5
|
+
"UK", "DE", "FR", "IT", // Europe
|
|
6
|
+
"ES", "NL", "SE", "TR", // Europe cont.
|
|
7
|
+
"JP", "AU", "IN", // Asia-Pacific
|
|
8
|
+
"AE", "BR", "SG", "SA", // Rest of world
|
|
9
|
+
];
|
|
10
|
+
/**
|
|
11
|
+
* All accepted region codes for MCP tool input (19-element tuple).
|
|
12
|
+
* Includes GB as an alias for UK. Declared as a flat literal tuple
|
|
13
|
+
* because z.enum() requires a literal non-empty tuple type.
|
|
14
|
+
*/
|
|
15
|
+
export const ACCEPTED_REGIONS = [
|
|
16
|
+
"US", "CA", "MX",
|
|
17
|
+
"UK", "DE", "FR", "IT",
|
|
18
|
+
"ES", "NL", "SE", "TR",
|
|
19
|
+
"JP", "AU", "IN",
|
|
20
|
+
"AE", "BR", "SG", "SA",
|
|
21
|
+
"GB",
|
|
22
|
+
];
|
|
23
|
+
/** Regions where SFB (Search Find Buy) is available. */
|
|
24
|
+
export const SFB_REGIONS = new Set(["US"]);
|
|
25
|
+
/** Normalize user-supplied region code: GB -> UK, all others passthrough. */
|
|
26
|
+
export function normalizeRegion(region) {
|
|
27
|
+
return region === "GB" ? "UK" : region;
|
|
28
|
+
}
|
|
29
|
+
/** Typed error thrown by assertSfbAllowed for non-US SFB requests. */
|
|
30
|
+
export class SfbRegionError extends Error {
|
|
31
|
+
constructor(region) {
|
|
32
|
+
super(`SFB (Search Find Buy) is only available for US-region projects. This project's region is ${region}. Remove SFB volumes or use a US-region project.`);
|
|
33
|
+
this.name = "SfbRegionError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Assert that SFB is allowed for the given region.
|
|
38
|
+
* Throws SfbRegionError if the region is not in SFB_REGIONS.
|
|
39
|
+
*
|
|
40
|
+
* @param region - Canonical region code (already normalized)
|
|
41
|
+
* @param hasSfb - Whether the request includes SFB volumes > 0
|
|
42
|
+
*/
|
|
43
|
+
export function assertSfbAllowed(region, hasSfb) {
|
|
44
|
+
if (hasSfb && !SFB_REGIONS.has(region)) {
|
|
45
|
+
throw new SfbRegionError(region);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "listingbureau-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Amazon organic ranking MCP server. Run ranking campaigns, track keyword positions, and monitor rank movement from any AI assistant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|