apps-init
Initialize app registration by querying store APIs to automatically register apps from App Store or Google Play, requiring package names for Google Play setup.
Instructions
Query app list from store API and register automatically. App Store: auto-register all apps, Google Play: packageName required.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| store | No | Target store (default: appStore) | |
| packageName | No | Google Play package name (required when setting up Google Play) |
Input Schema (JSON Schema)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"packageName": {
"description": "Google Play package name (required when setting up Google Play)",
"type": "string"
},
"store": {
"description": "Target store (default: appStore)",
"enum": [
"appStore",
"googlePlay"
],
"type": "string"
}
},
"type": "object"
}
Implementation Reference
- src/tools/apps/init.ts:48-485 (handler)Main handler function that implements 'apps-init': automatically discovers and registers apps from App Store (all apps) or Google Play (specific package), generates slugs, fetches supported locales, handles both stores, updates existing registrations with locale info.export async function handleSetupApps(options: SetupAppsOptions) { const { store = "both", packageName } = options; let config; try { config = loadConfig(); } catch (error) { const message = error instanceof Error ? error.message : String(error); return { content: [ { type: "text" as const, text: `β Failed to load config: ${message}`, }, ], isError: true, }; } console.error(`[MCP] π± Initializing apps (store: ${store})`); // both: Query App Store apps then check Play Store if (store === "both" || store === "appStore") { if (!config.appStore) { return { content: [ { type: "text" as const, text: "β App Store authentication not configured. Please check ~/.config/pabal-mcp/config.json.", }, ], }; } const clientResult = appStoreService.createClient("dummy"); // listAllApps() does not use bundleId if (!clientResult.success) { return { content: [ { type: "text" as const, text: `β Failed to create App Store client: ${clientResult.error.message}`, }, ], }; } try { console.error(`[MCP] π Fetching app list from App Store...`); const apps = await clientResult.data.listAllApps({ onlyReleased: true, }); console.error(`[MCP] β Found ${apps.length} apps`); // λͺ¨λ μ±μ μΈμ΄ μ 보λ₯Ό 미리 κ°μ Έμ€κΈ° μν ν΄λΌμ΄μΈνΈ μΈμ€ν΄μ€ const appInfoClientResult = appStoreService.createClient("dummy"); if (!appInfoClientResult.success) { return { content: [ { type: "text" as const, text: `β Failed to create App Store info client: ${appInfoClientResult.error.message}`, }, ], }; } const appInfoClient = appInfoClientResult.data; if (apps.length === 0) { return { content: [ { type: "text" as const, text: "π± No apps registered in App Store.", }, ], }; } // Prepare Play Store service account const playStoreEnabled = store === "both" && !!config.playStore?.serviceAccountJson; // Auto-register const registered: Array<{ name: string; slug: string; appStoreLocales?: string[]; googlePlayLocales?: string[]; }> = []; const skipped: string[] = []; const playStoreFound: string[] = []; const playStoreNotFound: string[] = []; for (let i = 0; i < apps.length; i++) { const app = apps[i]; // Use only last part of bundleId as slug (com.quartz.postblackbelt -> postblackbelt) const parts = app.bundleId.split("."); const slug = parts[parts.length - 1].toLowerCase(); console.error( `[MCP] [${i + 1}/${apps.length}] Processing: ${app.name} (${ app.bundleId })` ); // Check if already registered (findApp searches by slug, bundleId, packageName) let existing; try { existing = findApp(app.bundleId); } catch (error) { console.error( `[MCP] β Failed to load registered apps: ${ error instanceof Error ? error.message : String(error) }` ); continue; } if (existing) { // Update language info for existing apps let appsConfig; try { appsConfig = loadRegisteredApps(); } catch (error) { console.error( `[MCP] β Failed to load registered apps: ${ error instanceof Error ? error.message : String(error) }` ); continue; } const appIndex = appsConfig.apps.findIndex( (a) => a.slug === existing.slug ); if (appIndex >= 0) { let updated = false; // Update App Store language info if (existing.appStore) { const appStoreInfo = await appStoreService.fetchAppInfo( app.bundleId, appInfoClient ); if (appStoreInfo.found && appStoreInfo.supportedLocales) { if (!appsConfig.apps[appIndex].appStore) { appsConfig.apps[appIndex].appStore = { bundleId: app.bundleId, appId: app.id, name: app.name, }; } appsConfig.apps[appIndex].appStore!.supportedLocales = appStoreInfo.supportedLocales; updated = true; } } // Update Google Play info (when in both mode) if (playStoreEnabled) { const playResult = await checkPlayStoreAccess(app.bundleId); if (playResult.accessible) { if (!appsConfig.apps[appIndex].googlePlay) { appsConfig.apps[appIndex].googlePlay = { packageName: app.bundleId, name: playResult.title, }; } appsConfig.apps[appIndex].googlePlay!.supportedLocales = playResult.supportedLocales; appsConfig.apps[appIndex].googlePlay!.name = playResult.title; updated = true; playStoreFound.push(app.name); } else { playStoreNotFound.push(app.name); } } if (updated) { saveRegisteredApps(appsConfig); skipped.push( `${app.name} (${app.bundleId}) - language info updated` ); } else { skipped.push( `${app.name} (${app.bundleId}) - already registered` ); } } else { skipped.push(`${app.name} (${app.bundleId}) - already registered`); } continue; } // App Store μ 보 κ°μ Έμ€κΈ° (μΈμ΄ μ 보 ν¬ν¨) const appStoreInfo = await appStoreService.fetchAppInfo( app.bundleId, appInfoClient ); // Check Play Store (when in both mode) let googlePlayInfo: RegisteredApp["googlePlay"] = undefined; if (playStoreEnabled) { const playResult = await checkPlayStoreAccess(app.bundleId); if (playResult.accessible) { googlePlayInfo = { packageName: app.bundleId, name: playResult.title, supportedLocales: playResult.supportedLocales, }; playStoreFound.push(app.name); } else { playStoreNotFound.push(app.name); } } try { const registeredAppStoreInfo = toRegisteredAppStoreInfo({ bundleId: app.bundleId, appInfo: appStoreInfo, }) || { bundleId: app.bundleId, appId: app.id, name: app.name, }; registerApp({ slug, name: app.name, appStore: registeredAppStoreInfo, googlePlay: googlePlayInfo, }); console.error(`[MCP] β Registered: ${slug}`); registered.push({ name: app.name, slug, appStoreLocales: registeredAppStoreInfo.supportedLocales, googlePlayLocales: googlePlayInfo?.supportedLocales, }); } catch (error) { skipped.push(`${app.name} (${app.bundleId}) - registration failed`); } } const lines = [`π± **App Setup Complete**\n`]; if (registered.length > 0) { lines.push(`β **Registered** (${registered.length}):`); for (const r of registered) { const storeInfo = r.googlePlayLocales ? " (π+π€)" : " (π)"; let localeInfo = ""; if (r.appStoreLocales && r.appStoreLocales.length > 0) { localeInfo += `\n π App Store: ${r.appStoreLocales.join(", ")}`; } if (r.googlePlayLocales && r.googlePlayLocales.length > 0) { localeInfo += `\n π€ Google Play: ${r.googlePlayLocales.join( ", " )}`; } lines.push( ` β’ ${r.name}${storeInfo} β slug: "${r.slug}"${localeInfo}` ); } lines.push(""); } if (skipped.length > 0) { lines.push(`βοΈ **Skipped** (${skipped.length}):`); for (const s of skipped) { lines.push(` β’ ${s}`); } lines.push(""); } if (playStoreEnabled) { lines.push(`**Play Store Check Results:**`); lines.push(` π€ Found: ${playStoreFound.length}`); if (playStoreFound.length > 0) { for (const name of playStoreFound) { lines.push(` β’ ${name}`); } } lines.push(` β Not found: ${playStoreNotFound.length}`); if (playStoreNotFound.length > 0) { for (const name of playStoreNotFound) { lines.push(` β’ ${name}`); } } lines.push(""); } lines.push( 'You can now reference apps in other tools using the `app: "slug"` parameter.' ); return { content: [{ type: "text" as const, text: lines.join("\n") }], _meta: { registered: registered.length, skipped: skipped.length, playStoreFound: playStoreFound.length, playStoreNotFound: playStoreNotFound.length, apps, }, }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); return { content: [ { type: "text" as const, text: `β Failed to query App Store apps: ${msg}`, }, ], }; } } if (store === "googlePlay") { console.error( `[MCP] π Processing Google Play app: ${packageName || "N/A"}` ); if (!config.playStore) { return { content: [ { type: "text" as const, text: "β Google Play authentication not configured. Please check ~/.config/pabal-mcp/config.json.", }, ], }; } if (!packageName) { return { content: [ { type: "text" as const, text: `β οΈ Google Play API does not support listing apps. Provide packageName to verify and register that app: \`\`\`json { "store": "googlePlay", "packageName": "com.example.app" } \`\`\``, }, ], }; } try { // Google Play μ 보 κ°μ Έμ€κΈ° (μΈμ΄ μ 보 ν¬ν¨) console.error(`[MCP] π Fetching Google Play app info...`); const googlePlayInfo = await googlePlayService.fetchAppInfo(packageName); if (!googlePlayInfo.found) { throw new Error("Failed to access Google Play app"); } // Use only last part of packageName as slug (com.quartz.postblackbelt -> postblackbelt) const parts = packageName.split("."); const slug = parts[parts.length - 1].toLowerCase(); // Check if already registered (findApp searches by slug, bundleId, packageName) const existing = findApp(packageName); if (existing) { return { content: [ { type: "text" as const, text: `βοΈ App is already registered: "${existing.slug}"`, }, ], _meta: { app: existing }, }; } // Register const registeredGooglePlayInfo = toRegisteredGooglePlayInfo({ packageName, appInfo: googlePlayInfo, }); console.error(`[MCP] πΎ Registering app with slug: ${slug}`); const newApp = registerApp({ slug, name: googlePlayInfo.name || packageName, googlePlay: registeredGooglePlayInfo, }); console.error(`[MCP] β App registered successfully`); const localeInfo = registeredGooglePlayInfo?.supportedLocales && registeredGooglePlayInfo.supportedLocales.length > 0 ? `\nβ’ Supported Languages: ${registeredGooglePlayInfo.supportedLocales.join( ", " )}` : ""; return { content: [ { type: "text" as const, text: `β Google Play app registration complete β’ Package Name: \`${packageName}\` β’ Slug: \`${slug}\` β’ Name: ${newApp.name}${localeInfo} You can now reference this app in other tools using the \`app: "${slug}"\` parameter.`, }, ], _meta: { app: newApp }, }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); return { content: [ { type: "text" as const, text: `β Failed to access Google Play app: ${msg}`, }, ], }; } } return { content: [ { type: "text" as const, text: "β store parameter must be 'appStore', 'googlePlay', or 'both'.", }, ], }; }
- src/index.ts:162-182 (registration)Registers the 'apps-init' MCP tool with description, Zod input schema, and references the handleSetupApps handler function.registerToolWithInfo( "apps-init", { description: "Query app list from store API and register automatically. App Store: auto-register all apps, Google Play: packageName required.", inputSchema: z.object({ store: z .enum(["appStore", "googlePlay"]) .optional() .describe("Target store (default: appStore)"), packageName: z .string() .optional() .describe( "Google Play package name (required when setting up Google Play)" ), }), }, handleSetupApps, "App Management" );
- src/tools/apps/init.ts:23-26 (schema)TypeScript interface defining input options for the handler, matching the Zod schema (note: handler defaults store to 'both').interface SetupAppsOptions { store?: "appStore" | "googlePlay" | "both"; packageName?: string; // For Google Play - list query not supported, so used for specific app verification }
- src/tools/apps/init.ts:31-46 (helper)Helper function to check Google Play app accessibility and fetch basic info (used when processing App Store apps in 'both' mode).async function checkPlayStoreAccess(packageName: string): Promise<{ accessible: boolean; title?: string; supportedLocales?: string[]; }> { const appInfo = await googlePlayService.fetchAppInfo(packageName); if (!appInfo.found) { return { accessible: false }; } return { accessible: true, title: appInfo.name, supportedLocales: appInfo.supportedLocales, }; }