Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
storeNoTarget store (default: appStore)
packageNameNoGoogle Play package name (required when setting up Google Play)

Implementation Reference

  • 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"
    );
  • 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
    }
  • 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,
      };
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It describes the tool's behavior: querying and automatic registration, with store-specific rules. However, it lacks details on permissions needed, rate limits, error handling, or what 'register automatically' entails (e.g., side effects). This is a moderate disclosure but leaves gaps for a tool with potential mutations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is highly concise and front-loaded: two sentences that directly state the purpose and usage rules without any fluff. Every sentence earns its place by providing essential information, making it efficient and well-structured for quick understanding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (a tool that queries and registers apps, with store-specific behavior), no annotations, and no output schema, the description is moderately complete. It covers the core action and store rules but lacks details on output format, error cases, or deeper behavioral traits. For a tool with potential side effects and no structured output, it should do more to be fully adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters ('store' and 'packageName') with descriptions and enums. The description adds some context: it clarifies that 'packageName' is required for Google Play, which is implied but not explicit in the schema (which says 'required when setting up Google Play'). This provides marginal value beyond the schema, aligning with the baseline for high coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Query app list from store API and register automatically.' It specifies the verb ('query' and 'register') and resource ('app list'), making the action clear. However, it doesn't explicitly differentiate from sibling tools like 'apps-add' or 'apps-search', which might have overlapping functionality, so it doesn't reach the highest score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context on when to use this tool: 'App Store: auto-register all apps, Google Play: packageName required.' This gives explicit guidance for different store scenarios, including a requirement for Google Play. However, it doesn't mention when not to use it or name alternatives among sibling tools, such as 'apps-add' for manual registration, so it's not a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quartz-labs-dev/pabal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server