Skip to main content
Glama
appolabs

Appo MCP

Official
by appolabs

generate_hook

Generates a custom React hook for an Appo SDK feature. Returns TypeScript code with types, error handling, and loading states.

Instructions

Generate a custom React hook for an @appolabs/appo SDK feature. Returns TypeScript code with types, error handling, and loading states.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
featureYesThe SDK feature to generate a hook for
hookNameNoCustom hook name (optional, defaults to use{Feature})
includeLoadingNoInclude loading state management (default: true)
includeErrorNoInclude error state management (default: true)

Implementation Reference

  • The main handler function for the generate_hook tool. Takes args (feature, hookName, includeLoading, includeError), validates the feature, generates hook code from templates, and returns formatted TypeScript code with usage example.
    export async function generateHook(
      args: Record<string, unknown>
    ): Promise<{ content: Array<{ type: "text"; text: string }> }> {
      const { feature, hookName, includeLoading = true, includeError = true } = args as unknown as GenerateHookArgs;
    
      if (!feature || !hookTemplates[feature]) {
        return {
          content: [
            {
              type: "text",
              text: `Invalid feature. Available features: ${Object.keys(hookTemplates).join(", ")}`,
            },
          ],
        };
      }
    
      const code = hookTemplates[feature]({
        feature,
        hookName,
        includeLoading,
        includeError,
      });
    
      return {
        content: [
          {
            type: "text",
            text: `\`\`\`typescript
    ${code}
    \`\`\`
    
    ## Usage Example
    
    \`\`\`tsx
    import { ${hookName || `use${feature.charAt(0).toUpperCase() + feature.slice(1)}`} } from './hooks/${feature}';
    
    function MyComponent() {
      const hook = ${hookName || `use${feature.charAt(0).toUpperCase() + feature.slice(1)}`}();
    
      // Use the hook methods and state
      return <div>...</div>;
    }
    \`\`\``,
          },
        ],
      };
    }
  • TypeScript interface defining input arguments for generateHook: feature (SdkFeature), hookName (optional), includeLoading (optional bool), includeError (optional bool).
    interface GenerateHookArgs {
      feature: SdkFeature;
      hookName?: string;
      includeLoading?: boolean;
      includeError?: boolean;
    }
  • Registration of the generate_hook tool in the tools array, including its name, description, and full JSON inputSchema (feature enum, hookName, includeLoading, includeError). Also includes the handler dispatch in handleToolCall switch statement (lines 186-187).
    import type { Tool } from "@modelcontextprotocol/sdk/types.js";
    import { generateHook } from "./generate-hook.js";
    import { generateComponent } from "./generate-component.js";
    import { scaffoldFeature } from "./scaffold-feature.js";
    import { validateSetup } from "./validate-setup.js";
    import { checkPermissions } from "./check-permissions.js";
    import { diagnoseIssue } from "./diagnose-issue.js";
    
    export const SDK_FEATURES = [
      "push",
      "biometrics",
      "camera",
      "location",
      "haptics",
      "storage",
      "share",
      "network",
      "device",
    ] as const;
    
    export type SdkFeature = (typeof SDK_FEATURES)[number];
    
    export const tools: Tool[] = [
      {
        name: "generate_hook",
  • The hookTemplates map - a Record<SdkFeature, function> containing template literal functions for each supported feature (push, biometrics, camera, location, haptics, storage, share, network, device). Each generates a complete React hook with useState/useCallback/useEffect, loading/error state, and SDK integration.
    const hookTemplates: Record<SdkFeature, (args: GenerateHookArgs) => string> = {
      push: ({ hookName = "usePushNotifications", includeLoading = true, includeError = true }) => `
    import { useState, useEffect, useCallback } from 'react';
    import { getAppo, type PushMessage, type PermissionStatus } from '@appolabs/appo';
    
    interface UsePushNotificationsReturn {
      permission: PermissionStatus | null;
      token: string | null;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      requestPermission: () => Promise<PermissionStatus>;
      getToken: () => Promise<string | null>;
    }
    
    export function ${hookName}(
      onMessage?: (message: PushMessage) => void
    ): UsePushNotificationsReturn {
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [token, setToken] = useState<string | null>(null);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      useEffect(() => {
        if (!onMessage) return;
        return appo.push.onMessage(onMessage);
      }, [onMessage]);
    
      const requestPermission = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const status = await appo.push.requestPermission();
          setPermission(status);
          return status;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to request permission'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      const getTokenFn = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const pushToken = await appo.push.getToken();
          setToken(pushToken);
          return pushToken;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to get token'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      return {
        permission,
        token,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        requestPermission,
        getToken: getTokenFn,
      };
    }
    `.trim(),
    
      biometrics: ({ hookName = "useBiometrics", includeLoading = true, includeError = true }) => `
    import { useState, useCallback } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface UseBiometricsReturn {
      isAvailable: boolean | null;
      isAuthenticated: boolean;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      checkAvailability: () => Promise<boolean>;
      authenticate: (reason: string) => Promise<boolean>;
    }
    
    export function ${hookName}(): UseBiometricsReturn {
      const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
      const [isAuthenticated, setIsAuthenticated] = useState(false);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      const checkAvailability = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const available = await appo.biometrics.isAvailable();
          setIsAvailable(available);
          return available;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to check biometrics'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      const authenticate = useCallback(async (reason: string) => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const success = await appo.biometrics.authenticate(reason);
          setIsAuthenticated(success);
          return success;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Biometric authentication failed'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      return {
        isAvailable,
        isAuthenticated,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        checkAvailability,
        authenticate,
      };
    }
    `.trim(),
    
      camera: ({ hookName = "useCamera", includeLoading = true, includeError = true }) => `
    import { useState, useCallback } from 'react';
    import { getAppo, type PermissionStatus } from '@appolabs/appo';
    
    interface Photo {
      uri: string;
      base64?: string;
      width: number;
      height: number;
    }
    
    interface UseCameraReturn {
      permission: PermissionStatus | null;
      photo: Photo | null;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      requestPermission: () => Promise<PermissionStatus>;
      takePicture: () => Promise<Photo | null>;
      clearPhoto: () => void;
    }
    
    export function ${hookName}(): UseCameraReturn {
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [photo, setPhoto] = useState<Photo | null>(null);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      const requestPermission = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const status = await appo.camera.requestPermission();
          setPermission(status);
          return status;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to request camera permission'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      const takePicture = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const result = await appo.camera.takePicture();
          if (result) {
            setPhoto(result);
          }
          return result;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to take picture'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      const clearPhoto = useCallback(() => {
        setPhoto(null);
      }, []);
    
      return {
        permission,
        photo,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        requestPermission,
        takePicture,
        clearPhoto,
      };
    }
    `.trim(),
    
      location: ({ hookName = "useLocation", includeLoading = true, includeError = true }) => `
    import { useState, useCallback } from 'react';
    import { getAppo, type PermissionStatus } from '@appolabs/appo';
    
    interface Position {
      latitude: number;
      longitude: number;
      altitude: number | null;
      accuracy: number;
      timestamp: number;
    }
    
    interface UseLocationReturn {
      permission: PermissionStatus | null;
      position: Position | null;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      requestPermission: () => Promise<PermissionStatus>;
      getCurrentPosition: () => Promise<Position | null>;
    }
    
    export function ${hookName}(): UseLocationReturn {
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [position, setPosition] = useState<Position | null>(null);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      const requestPermission = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const status = await appo.location.requestPermission();
          setPermission(status);
          return status;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to request location permission'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      const getCurrentPosition = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const pos = await appo.location.getCurrentPosition();
          if (pos) {
            setPosition(pos);
          }
          return pos;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to get location'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      return {
        permission,
        position,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        requestPermission,
        getCurrentPosition,
      };
    }
    `.trim(),
    
      haptics: ({ hookName = "useHaptics" }) => `
    import { useCallback } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    type ImpactStyle = 'light' | 'medium' | 'heavy';
    type NotificationType = 'success' | 'warning' | 'error';
    
    interface UseHapticsReturn {
      impact: (style?: ImpactStyle) => void;
      notification: (type: NotificationType) => void;
      success: () => void;
      warning: () => void;
      error: () => void;
    }
    
    export function ${hookName}(): UseHapticsReturn {
      const appo = getAppo();
    
      const impact = useCallback((style: ImpactStyle = 'medium') => {
        appo.haptics.impact(style);
      }, []);
    
      const notification = useCallback((type: NotificationType) => {
        appo.haptics.notification(type);
      }, []);
    
      const success = useCallback(() => {
        appo.haptics.notification('success');
      }, []);
    
      const warning = useCallback(() => {
        appo.haptics.notification('warning');
      }, []);
    
      const error = useCallback(() => {
        appo.haptics.notification('error');
      }, []);
    
      return {
        impact,
        notification,
        success,
        warning,
        error,
      };
    }
    `.trim(),
    
      storage: ({ hookName = "useStorage", includeLoading = true, includeError = true }) => `
    import { useState, useCallback } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface UseStorageReturn<T> {
      value: T | null;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      get: () => Promise<T | null>;
      set: (value: T) => Promise<void>;
      remove: () => Promise<void>;
    }
    
    export function ${hookName}<T = string>(key: string): UseStorageReturn<T> {
      const [value, setValue] = useState<T | null>(null);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      const get = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const stored = await appo.storage.get(key);
          const parsed = stored ? JSON.parse(stored) as T : null;
          setValue(parsed);
          return parsed;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to get storage value'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, [key]);
    
      const set = useCallback(async (newValue: T) => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          await appo.storage.set(key, JSON.stringify(newValue));
          setValue(newValue);
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to set storage value'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, [key]);
    
      const remove = useCallback(async () => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          await appo.storage.delete(key);
          setValue(null);
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to delete storage value'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, [key]);
    
      return {
        value,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        get,
        set,
        remove,
      };
    }
    `.trim(),
    
      share: ({ hookName = "useShare", includeLoading = true, includeError = true }) => `
    import { useState, useCallback } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface ShareOptions {
      title?: string;
      message?: string;
      url?: string;
    }
    
    interface ShareResult {
      success: boolean;
      action?: string;
    }
    
    interface UseShareReturn {
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
      share: (options: ShareOptions) => Promise<ShareResult>;
    }
    
    export function ${hookName}(): UseShareReturn {
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(false);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      const share = useCallback(async (options: ShareOptions) => {
        ${includeLoading ? "setIsLoading(true);" : ""}
        ${includeError ? "setError(null);" : ""}
        try {
          const result = await appo.share.open(options);
          return result;
        } catch (err) {
          ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to share'));" : ""}
          throw err;
        } finally {
          ${includeLoading ? "setIsLoading(false);" : ""}
        }
      }, []);
    
      return {
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
        share,
      };
    }
    `.trim(),
    
      network: ({ hookName = "useNetwork" }) => `
    import { useState, useEffect, useCallback } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface NetworkStatus {
      isConnected: boolean;
      type: 'wifi' | 'cellular' | 'unknown' | 'none';
    }
    
    interface UseNetworkReturn {
      status: NetworkStatus | null;
      isOnline: boolean;
      refresh: () => Promise<NetworkStatus | null>;
    }
    
    export function ${hookName}(): UseNetworkReturn {
      const [status, setStatus] = useState<NetworkStatus | null>(null);
    
      const appo = getAppo();
    
      const refresh = useCallback(async () => {
        const networkStatus = await appo.network.getStatus();
        setStatus(networkStatus);
        return networkStatus;
      }, []);
    
      useEffect(() => {
        refresh();
        return appo.network.onChange((newStatus) => {
          setStatus(newStatus);
        });
      }, []);
    
      return {
        status,
        isOnline: status?.isConnected ?? true,
        refresh,
      };
    }
    `.trim(),
    
      device: ({ hookName = "useDevice", includeLoading = true, includeError = true }) => `
    import { useState, useEffect } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface DeviceInfo {
      platform: 'ios' | 'android' | 'web';
      osVersion: string;
      appVersion: string;
      deviceId: string;
      deviceName: string;
      isTablet: boolean;
    }
    
    interface UseDeviceReturn {
      info: DeviceInfo | null;
      isNative: boolean;
      ${includeLoading ? "isLoading: boolean;" : ""}
      ${includeError ? "error: Error | null;" : ""}
    }
    
    export function ${hookName}(): UseDeviceReturn {
      const [info, setInfo] = useState<DeviceInfo | null>(null);
      ${includeLoading ? "const [isLoading, setIsLoading] = useState(true);" : ""}
      ${includeError ? "const [error, setError] = useState<Error | null>(null);" : ""}
    
      const appo = getAppo();
    
      useEffect(() => {
        const fetchInfo = async () => {
          ${includeLoading ? "setIsLoading(true);" : ""}
          ${includeError ? "setError(null);" : ""}
          try {
            const deviceInfo = await appo.device.getInfo();
            setInfo(deviceInfo);
          } catch (err) {
            ${includeError ? "setError(err instanceof Error ? err : new Error('Failed to get device info'));" : ""}
          } finally {
            ${includeLoading ? "setIsLoading(false);" : ""}
          }
        };
        fetchInfo();
      }, []);
    
      return {
        info,
        isNative: appo.isNative,
        ${includeLoading ? "isLoading," : ""}
        ${includeError ? "error," : ""}
      };
    }
    `.trim(),
    };
Behavior3/5

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

No annotations provided; description carries full burden. It discloses that the tool returns TypeScript code with types, error handling, and loading states, but does not mention side effects, permissions, or any destructive actions. Adequate but not comprehensive.

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

Conciseness4/5

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

The description is a single sentence that gets straight to the point. It could benefit from structural separation (e.g., when to use), but it is concise and front-loaded with the essential purpose.

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

Completeness4/5

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

Given no output schema and 4 parameters (1 required), the description adequately covers the tool's behavior and output. It explains the return value, which compensates for missing output schema. Lacks only minor usage context.

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 coverage is 100%, and each parameter is well-described in the schema. The description adds no additional parameter-level meaning beyond the schema, so baseline 3 is appropriate.

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

Purpose5/5

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

The description clearly states 'Generate a custom React hook for an @appolabs/appo SDK feature' with a specific verb and resource. It distinguishes from siblings like generate_component by focusing on hooks and mentions return value.

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

Usage Guidelines3/5

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

The description does not explicitly state when to use this tool versus alternatives like generate_component or scaffold_feature. The usage is implied by the hook-specific output, but no guidance on exclusions or when not to use.

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/appolabs/appo-mcp'

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