pabal-web-mcp 1.3.0 → 1.3.1
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/dist/bin/mcp-server.js +120 -17
- package/package.json +1 -1
package/dist/bin/mcp-server.js
CHANGED
|
@@ -1241,6 +1241,8 @@ ${researchSections.join("\n")}
|
|
|
1241
1241
|
prompt += `Apply the selected keywords to ALL fields:
|
|
1242
1242
|
`;
|
|
1243
1243
|
prompt += `- \`aso.title\` (\u226430): **"App Name: Primary Keyword"** format (app name in English, keyword in target language, keyword starts with uppercase after the colon)
|
|
1244
|
+
`;
|
|
1245
|
+
prompt += ` - **Do NOT translate/rename the app name**; keep the original English app name across all locales.
|
|
1244
1246
|
`;
|
|
1245
1247
|
prompt += `- \`aso.subtitle\` (\u226430): Complementary keywords
|
|
1246
1248
|
`;
|
|
@@ -1445,6 +1447,8 @@ ${researchSections.join(
|
|
|
1445
1447
|
prompt += ` - Example: "Aurora EOS: \uC624\uB85C\uB77C \uC608\uBCF4" (Korean), "Aurora EOS: \u30AA\u30FC\u30ED\u30E9\u4E88\u5831" (Japanese)
|
|
1446
1448
|
`;
|
|
1447
1449
|
prompt += ` - The keyword after the colon must start with an uppercase letter
|
|
1450
|
+
`;
|
|
1451
|
+
prompt += ` - **Do NOT translate/rename the app name**; keep the original English app name across all locales.
|
|
1448
1452
|
`;
|
|
1449
1453
|
prompt += `4. Swap keywords in sentences while keeping:
|
|
1450
1454
|
`;
|
|
@@ -2372,10 +2376,59 @@ Writing style reference for ${locale}: Found ${posts.length} existing post(s) us
|
|
|
2372
2376
|
}
|
|
2373
2377
|
|
|
2374
2378
|
// src/tools/keyword-research.ts
|
|
2375
|
-
import
|
|
2376
|
-
import
|
|
2379
|
+
import fs11 from "fs";
|
|
2380
|
+
import path11 from "path";
|
|
2377
2381
|
import { z as z6 } from "zod";
|
|
2378
2382
|
import { zodToJsonSchema as zodToJsonSchema6 } from "zod-to-json-schema";
|
|
2383
|
+
|
|
2384
|
+
// src/utils/registered-apps.util.ts
|
|
2385
|
+
import fs10 from "fs";
|
|
2386
|
+
import os from "os";
|
|
2387
|
+
import path10 from "path";
|
|
2388
|
+
var DEFAULT_REGISTERED_APPS_PATH = path10.join(
|
|
2389
|
+
os.homedir(),
|
|
2390
|
+
".config",
|
|
2391
|
+
"pabal-mcp",
|
|
2392
|
+
"registered-apps.json"
|
|
2393
|
+
);
|
|
2394
|
+
function safeReadJson(filePath) {
|
|
2395
|
+
if (!fs10.existsSync(filePath)) return null;
|
|
2396
|
+
try {
|
|
2397
|
+
const raw = fs10.readFileSync(filePath, "utf-8");
|
|
2398
|
+
const parsed = JSON.parse(raw);
|
|
2399
|
+
if (!parsed?.apps || !Array.isArray(parsed.apps)) {
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
return parsed;
|
|
2403
|
+
} catch {
|
|
2404
|
+
return null;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function loadRegisteredApps(filePath = DEFAULT_REGISTERED_APPS_PATH) {
|
|
2408
|
+
const data = safeReadJson(filePath);
|
|
2409
|
+
return { apps: data?.apps || [], path: filePath };
|
|
2410
|
+
}
|
|
2411
|
+
function findRegisteredApp(slug, filePath) {
|
|
2412
|
+
const { apps, path: usedPath } = loadRegisteredApps(filePath);
|
|
2413
|
+
const app = apps.find((a) => a.slug === slug);
|
|
2414
|
+
return { app, path: usedPath };
|
|
2415
|
+
}
|
|
2416
|
+
function getSupportedLocalesForSlug(slug, platform, filePath) {
|
|
2417
|
+
const { app, path: usedPath } = findRegisteredApp(slug, filePath);
|
|
2418
|
+
if (!app) return { supportedLocales: [], path: usedPath };
|
|
2419
|
+
if (platform === "ios") {
|
|
2420
|
+
return {
|
|
2421
|
+
supportedLocales: app.appStore?.supportedLocales || [],
|
|
2422
|
+
path: usedPath
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
return {
|
|
2426
|
+
supportedLocales: app.googlePlay?.supportedLocales || [],
|
|
2427
|
+
path: usedPath
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/tools/keyword-research.ts
|
|
2379
2432
|
var TOOL_NAME = "keyword-research";
|
|
2380
2433
|
var keywordResearchInputSchema = z6.object({
|
|
2381
2434
|
slug: z6.string().trim().describe("Product slug"),
|
|
@@ -2431,26 +2484,31 @@ function buildTemplate({
|
|
|
2431
2484
|
},
|
|
2432
2485
|
plan: {
|
|
2433
2486
|
steps: [
|
|
2434
|
-
"Start mcp-appstore server (
|
|
2487
|
+
"Start mcp-appstore server (node server.js in external-tools/mcp-appstore).",
|
|
2488
|
+
"Confirm app IDs/locales: get_app_details(appId from config/registered-apps) to lock country/lang and competitors.",
|
|
2435
2489
|
"Discover competitors: search_app(term=seed keyword), get_similar_apps(appId=known competitor).",
|
|
2436
|
-
"Collect candidates: suggest_keywords_by_seeds
|
|
2490
|
+
"Collect candidates: suggest_keywords_by_seeds/by_category/by_similarity/by_competition/by_search + suggest_keywords_by_apps(apps=[top competitors]).",
|
|
2437
2491
|
"Score shortlist: get_keyword_scores for 15\u201330 candidates per platform/country.",
|
|
2438
|
-
"Context check: analyze_reviews on top apps for language/tone cues."
|
|
2492
|
+
"Context check: analyze_reviews and fetch_reviews on top apps for language/tone cues."
|
|
2439
2493
|
],
|
|
2440
2494
|
note: "Run per platform/country. Save raw tool outputs plus curated top keywords."
|
|
2441
2495
|
},
|
|
2442
2496
|
data: {
|
|
2443
2497
|
raw: {
|
|
2444
2498
|
searchApp: [],
|
|
2499
|
+
getAppDetails: [],
|
|
2500
|
+
similarApps: [],
|
|
2445
2501
|
keywordSuggestions: {
|
|
2446
2502
|
bySeeds: [],
|
|
2447
2503
|
byCategory: [],
|
|
2448
2504
|
bySimilarity: [],
|
|
2449
2505
|
byCompetition: [],
|
|
2450
|
-
bySearchHints: []
|
|
2506
|
+
bySearchHints: [],
|
|
2507
|
+
byApps: []
|
|
2451
2508
|
},
|
|
2452
2509
|
keywordScores: [],
|
|
2453
|
-
reviewsAnalysis: []
|
|
2510
|
+
reviewsAnalysis: [],
|
|
2511
|
+
reviewsRaw: []
|
|
2454
2512
|
},
|
|
2455
2513
|
summary: {
|
|
2456
2514
|
recommendedKeywords: [],
|
|
@@ -2465,9 +2523,9 @@ function saveJsonFile({
|
|
|
2465
2523
|
fileName,
|
|
2466
2524
|
payload
|
|
2467
2525
|
}) {
|
|
2468
|
-
|
|
2469
|
-
const outputPath =
|
|
2470
|
-
|
|
2526
|
+
fs11.mkdirSync(researchDir, { recursive: true });
|
|
2527
|
+
const outputPath = path11.join(researchDir, fileName);
|
|
2528
|
+
fs11.writeFileSync(outputPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
|
|
2471
2529
|
return outputPath;
|
|
2472
2530
|
}
|
|
2473
2531
|
function normalizeKeywords(raw) {
|
|
@@ -2492,6 +2550,8 @@ async function handleKeywordResearch(input) {
|
|
|
2492
2550
|
const { config, locales } = loadProductLocales(slug);
|
|
2493
2551
|
const primaryLocale = resolvePrimaryLocale(config, locales);
|
|
2494
2552
|
const primaryLocaleData = locales[primaryLocale];
|
|
2553
|
+
const { app: registeredApp, path: registeredPath } = findRegisteredApp(slug);
|
|
2554
|
+
const { supportedLocales, path: supportedPath } = getSupportedLocalesForSlug(slug, platform);
|
|
2495
2555
|
const autoSeeds = [];
|
|
2496
2556
|
const autoCompetitors = [];
|
|
2497
2557
|
if (primaryLocaleData?.aso?.title) {
|
|
@@ -2501,19 +2561,43 @@ async function handleKeywordResearch(input) {
|
|
|
2501
2561
|
autoSeeds.push(...parsedKeywords.slice(0, 5));
|
|
2502
2562
|
if (config?.name) autoSeeds.push(config.name);
|
|
2503
2563
|
if (config?.tagline) autoSeeds.push(config.tagline);
|
|
2564
|
+
if (!config?.name && registeredApp?.name) autoSeeds.push(registeredApp.name);
|
|
2565
|
+
if (!primaryLocaleData?.aso?.title) {
|
|
2566
|
+
if (platform === "ios" && registeredApp?.appStore?.name) {
|
|
2567
|
+
autoSeeds.push(registeredApp.appStore.name);
|
|
2568
|
+
}
|
|
2569
|
+
if (platform === "android" && registeredApp?.googlePlay?.name) {
|
|
2570
|
+
autoSeeds.push(registeredApp.googlePlay.name);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2504
2573
|
if (platform === "ios") {
|
|
2505
2574
|
if (config?.appStoreAppId) {
|
|
2506
2575
|
autoCompetitors.push({ appId: String(config.appStoreAppId), platform });
|
|
2507
2576
|
} else if (config?.bundleId) {
|
|
2508
2577
|
autoCompetitors.push({ appId: config.bundleId, platform });
|
|
2578
|
+
} else if (registeredApp?.appStore?.appId) {
|
|
2579
|
+
autoCompetitors.push({
|
|
2580
|
+
appId: String(registeredApp.appStore.appId),
|
|
2581
|
+
platform
|
|
2582
|
+
});
|
|
2583
|
+
} else if (registeredApp?.appStore?.bundleId) {
|
|
2584
|
+
autoCompetitors.push({
|
|
2585
|
+
appId: registeredApp.appStore.bundleId,
|
|
2586
|
+
platform
|
|
2587
|
+
});
|
|
2509
2588
|
}
|
|
2510
2589
|
} else if (platform === "android" && config?.packageName) {
|
|
2511
2590
|
autoCompetitors.push({ appId: config.packageName, platform });
|
|
2591
|
+
} else if (platform === "android" && registeredApp?.googlePlay?.packageName) {
|
|
2592
|
+
autoCompetitors.push({
|
|
2593
|
+
appId: registeredApp.googlePlay.packageName,
|
|
2594
|
+
platform
|
|
2595
|
+
});
|
|
2512
2596
|
}
|
|
2513
2597
|
const resolvedSeeds = seedKeywords.length > 0 ? seedKeywords : Array.from(new Set(autoSeeds));
|
|
2514
2598
|
const resolvedCompetitors = competitorApps.length > 0 ? competitorApps : autoCompetitors;
|
|
2515
2599
|
const resolvedCountry = country || (locale?.includes("-") ? locale.split("-")[1].toLowerCase() : "us");
|
|
2516
|
-
const researchDir =
|
|
2600
|
+
const researchDir = path11.join(
|
|
2517
2601
|
getKeywordResearchDir(),
|
|
2518
2602
|
"products",
|
|
2519
2603
|
slug,
|
|
@@ -2522,7 +2606,7 @@ async function handleKeywordResearch(input) {
|
|
|
2522
2606
|
);
|
|
2523
2607
|
const defaultFileName = `keyword-research-${platform}-${resolvedCountry}.json`;
|
|
2524
2608
|
const fileName = filename || defaultFileName;
|
|
2525
|
-
let outputPath =
|
|
2609
|
+
let outputPath = path11.join(researchDir, fileName);
|
|
2526
2610
|
let fileAction;
|
|
2527
2611
|
if (writeTemplate || researchData) {
|
|
2528
2612
|
const payload = researchData ? (() => {
|
|
@@ -2560,6 +2644,22 @@ async function handleKeywordResearch(input) {
|
|
|
2560
2644
|
lines.push(`# Keyword research plan (${slug})`);
|
|
2561
2645
|
lines.push(`Locale: ${locale} | Platform: ${platform} | Country: ${resolvedCountry}`);
|
|
2562
2646
|
lines.push(`Primary locale detected: ${primaryLocale}`);
|
|
2647
|
+
if (supportedLocales.length > 0) {
|
|
2648
|
+
lines.push(
|
|
2649
|
+
`Registered supported locales (${platform}): ${supportedLocales.join(
|
|
2650
|
+
", "
|
|
2651
|
+
)} (source: ${supportedPath})`
|
|
2652
|
+
);
|
|
2653
|
+
if (!supportedLocales.includes(locale)) {
|
|
2654
|
+
lines.push(
|
|
2655
|
+
`WARNING: locale ${locale} not in registered supported locales. Confirm this locale or update registered-apps.json.`
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
} else {
|
|
2659
|
+
lines.push(
|
|
2660
|
+
`Registered supported locales not found for ${platform} (checked: ${supportedPath}).`
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2563
2663
|
lines.push(
|
|
2564
2664
|
`Seeds: ${resolvedSeeds.length > 0 ? resolvedSeeds.join(", ") : "(none set; add seedKeywords or ensure ASO keywords/title exist)"}`
|
|
2565
2665
|
);
|
|
@@ -2572,19 +2672,22 @@ async function handleKeywordResearch(input) {
|
|
|
2572
2672
|
`1) Start the local mcp-appstore server for this run: node server.js (cwd: /ABSOLUTE/PATH/TO/pabal-web-mcp/external-tools/mcp-appstore). LLM should start it before calling tools and stop it after, if the client supports process management; otherwise, start/stop manually.`
|
|
2573
2673
|
);
|
|
2574
2674
|
lines.push(
|
|
2575
|
-
`2)
|
|
2675
|
+
`2) Confirm IDs/locales: get_app_details(appId from config/registered-apps) to lock locale/country and competitor list.`
|
|
2676
|
+
);
|
|
2677
|
+
lines.push(
|
|
2678
|
+
`3) Discover apps: search_app(term=seed, platform=${platform}, country=${resolvedCountry}); get_similar_apps(appId=known competitor).`
|
|
2576
2679
|
);
|
|
2577
2680
|
lines.push(
|
|
2578
|
-
`
|
|
2681
|
+
`4) Expand keywords: suggest_keywords_by_seeds/by_category/by_similarity/by_competition/by_search + suggest_keywords_by_apps(apps=[top competitors]).`
|
|
2579
2682
|
);
|
|
2580
2683
|
lines.push(
|
|
2581
|
-
`
|
|
2684
|
+
`5) Score shortlist: get_keyword_scores for 15\u201330 candidates (note: scores are heuristic per README).`
|
|
2582
2685
|
);
|
|
2583
2686
|
lines.push(
|
|
2584
|
-
`
|
|
2687
|
+
`6) Context check: analyze_reviews and fetch_reviews on top apps to harvest native phrasing; keep snippets for improve-public.`
|
|
2585
2688
|
);
|
|
2586
2689
|
lines.push(
|
|
2587
|
-
`
|
|
2690
|
+
`7) Save all raw responses + your final top 10\u201315 keywords to: ${outputPath} (structure mirrors .aso/pullData/.aso/pushData under products/<slug>/locales/<locale>)`
|
|
2588
2691
|
);
|
|
2589
2692
|
if (fileAction) {
|
|
2590
2693
|
lines.push(`File: ${fileAction} at ${outputPath}`);
|