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
| Name | Required | Description | Default |
|---|---|---|---|
| feature | Yes | The SDK feature to generate a hook for | |
| hookName | No | Custom hook name (optional, defaults to use{Feature}) | |
| includeLoading | No | Include loading state management (default: true) | |
| includeError | No | Include error state management (default: true) |
Implementation Reference
- src/tools/generate-hook.ts:557-603 (handler)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>; } \`\`\``, }, ], }; } - src/tools/generate-hook.ts:3-8 (schema)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; } - src/tools/index.ts:1-25 (registration)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", - src/tools/generate-hook.ts:10-555 (helper)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(), };