Skip to main content
Glama
appolabs

Appo MCP

Official
by appolabs

generate_component

Generate a React component integrated with Appo SDK features like push, biometrics, and camera. Choose from button, card, form, or status variants with optional styling.

Instructions

Generate a UI component that uses @appolabs/appo SDK features. Returns a complete React component with SDK integration.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
featureYesThe SDK feature to build the component around
componentNameNoComponent name (optional, defaults to {Feature}Button or similar)
stylingNoStyling approach (default: tailwind)
variantNoComponent variant/type

Implementation Reference

  • The main handler function `generateComponent` that executes the tool logic. It validates the feature argument, looks up the template from componentTemplates, generates the React component code as a string, and returns it formatted with usage instructions.
    export async function generateComponent(
      args: Record<string, unknown>
    ): Promise<{ content: Array<{ type: "text"; text: string }> }> {
      const {
        feature,
        componentName,
        styling = "tailwind",
        variant,
      } = args as unknown as GenerateComponentArgs;
    
      if (!feature || !componentTemplates[feature]) {
        return {
          content: [
            {
              type: "text",
              text: `Invalid feature. Available features: ${Object.keys(componentTemplates).join(", ")}`,
            },
          ],
        };
      }
    
      const code = componentTemplates[feature]({
        feature,
        componentName,
        styling,
        variant,
      });
    
      const defaultName =
        componentName ||
        feature.charAt(0).toUpperCase() + feature.slice(1) + "Component";
    
      return {
        content: [
          {
            type: "text",
            text: `\`\`\`tsx
    ${code}
    \`\`\`
    
    ## Usage
    
    \`\`\`tsx
    import { ${defaultName} } from './components/${feature}';
    
    function App() {
      return <${defaultName} />;
    }
    \`\`\``,
          },
        ],
      };
    }
  • The `GenerateComponentArgs` interface defines the input schema for the tool: `feature` (SdkFeature, required), `componentName` (optional), `styling` (tailwind/css/none, optional), and `variant` (button/card/form/status, optional).
    interface GenerateComponentArgs {
      feature: SdkFeature;
      componentName?: string;
      styling?: "tailwind" | "css" | "none";
      variant?: "button" | "card" | "form" | "status";
    }
  • The `componentTemplates` record mapping each SDK feature (push, biometrics, camera, location, haptics, storage, share, network, device) to a function that returns a complete React component template as a string. Each template generates a fully functional component with SDK integration, TypeScript types, and optional Tailwind CSS styling.
    const componentTemplates: Record<
      SdkFeature,
      (args: GenerateComponentArgs) => string
    > = {
      push: ({ componentName = "PushNotificationButton", styling = "tailwind" }) => {
        const buttonClass =
          styling === "tailwind"
            ? 'className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"'
            : styling === "css"
              ? 'className="push-button"'
              : "";
    
        return `
    import { useState } from 'react';
    import { getAppo, type PermissionStatus } from '@appolabs/appo';
    
    interface ${componentName}Props {
      onTokenReceived?: (token: string) => void;
      onPermissionChange?: (status: PermissionStatus) => void;
    }
    
    export function ${componentName}({ onTokenReceived, onPermissionChange }: ${componentName}Props) {
      const [isLoading, setIsLoading] = useState(false);
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [token, setToken] = useState<string | null>(null);
    
      const appo = getAppo();
    
      const handleEnablePush = async () => {
        setIsLoading(true);
        try {
          const status = await appo.push.requestPermission();
          setPermission(status);
          onPermissionChange?.(status);
    
          if (status === 'granted') {
            const pushToken = await appo.push.getToken();
            if (pushToken) {
              setToken(pushToken);
              onTokenReceived?.(pushToken);
            }
          }
        } catch (error) {
          console.error('Failed to enable push notifications:', error);
        } finally {
          setIsLoading(false);
        }
      };
    
      if (token) {
        return (
          <div ${styling === "tailwind" ? 'className="flex items-center gap-2 text-green-600"' : ""}>
            <svg ${styling === "tailwind" ? 'className="h-5 w-5"' : ""} fill="currentColor" viewBox="0 0 20 20">
              <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
            </svg>
            <span>Push notifications enabled</span>
          </div>
        );
      }
    
      return (
        <button
          onClick={handleEnablePush}
          disabled={isLoading || permission === 'denied'}
          ${buttonClass}
        >
          {isLoading ? (
            <>
              <svg ${styling === "tailwind" ? 'className="h-4 w-4 animate-spin"' : ""} fill="none" viewBox="0 0 24 24">
                <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
              </svg>
              Enabling...
            </>
          ) : permission === 'denied' ? (
            'Notifications blocked'
          ) : (
            'Enable push notifications'
          )}
        </button>
      );
    }
    `.trim();
      },
    
      biometrics: ({ componentName = "BiometricAuthButton", styling = "tailwind" }) => {
        const buttonClass =
          styling === "tailwind"
            ? 'className="inline-flex items-center gap-2 rounded-lg bg-purple-600 px-4 py-2 text-white hover:bg-purple-700 disabled:opacity-50"'
            : "";
    
        return `
    import { useState, useEffect } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface ${componentName}Props {
      reason?: string;
      onSuccess?: () => void;
      onError?: (error: Error) => void;
    }
    
    export function ${componentName}({
      reason = 'Authenticate to continue',
      onSuccess,
      onError,
    }: ${componentName}Props) {
      const [isAvailable, setIsAvailable] = useState<boolean | null>(null);
      const [isLoading, setIsLoading] = useState(false);
    
      const appo = getAppo();
    
      useEffect(() => {
        appo.biometrics.isAvailable().then(setIsAvailable);
      }, []);
    
      const handleAuthenticate = async () => {
        setIsLoading(true);
        try {
          const success = await appo.biometrics.authenticate(reason);
          if (success) {
            onSuccess?.();
          } else {
            onError?.(new Error('Authentication failed'));
          }
        } catch (error) {
          onError?.(error instanceof Error ? error : new Error('Authentication error'));
        } finally {
          setIsLoading(false);
        }
      };
    
      if (isAvailable === null) {
        return <div ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>Checking biometrics...</div>;
      }
    
      if (!isAvailable) {
        return <div ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>Biometric auth not available</div>;
      }
    
      return (
        <button
          onClick={handleAuthenticate}
          disabled={isLoading}
          ${buttonClass}
        >
          {isLoading ? (
            'Authenticating...'
          ) : (
            <>
              <svg ${styling === "tailwind" ? 'className="h-5 w-5"' : ""} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04l.054-.09A13.916 13.916 0 008 11a4 4 0 118 0c0 1.017-.07 2.019-.203 3m-2.118 6.844A21.88 21.88 0 0015.171 17m3.839 1.132c.645-2.266.99-4.659.99-7.132A8 8 0 008 4.07M3 15.364c.64-1.319 1-2.8 1-4.364 0-1.457.39-2.823 1.07-4" />
              </svg>
              Authenticate with biometrics
            </>
          )}
        </button>
      );
    }
    `.trim();
      },
    
      camera: ({ componentName = "CameraCapture", styling = "tailwind" }) => `
    import { useState } from 'react';
    import { getAppo, type PermissionStatus } from '@appolabs/appo';
    
    interface Photo {
      uri: string;
      base64?: string;
      width: number;
      height: number;
    }
    
    interface ${componentName}Props {
      onCapture?: (photo: Photo) => void;
    }
    
    export function ${componentName}({ onCapture }: ${componentName}Props) {
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [photo, setPhoto] = useState<Photo | null>(null);
      const [isLoading, setIsLoading] = useState(false);
    
      const appo = getAppo();
    
      const handleRequestPermission = async () => {
        const status = await appo.camera.requestPermission();
        setPermission(status);
      };
    
      const handleTakePicture = async () => {
        setIsLoading(true);
        try {
          const result = await appo.camera.takePicture();
          if (result) {
            setPhoto(result);
            onCapture?.(result);
          }
        } finally {
          setIsLoading(false);
        }
      };
    
      if (permission === null) {
        return (
          <button
            onClick={handleRequestPermission}
            ${styling === "tailwind" ? 'className="rounded-lg bg-gray-600 px-4 py-2 text-white hover:bg-gray-700"' : ""}
          >
            Allow camera access
          </button>
        );
      }
    
      if (permission === 'denied') {
        return <p ${styling === "tailwind" ? 'className="text-red-500"' : ""}>Camera access denied</p>;
      }
    
      return (
        <div ${styling === "tailwind" ? 'className="space-y-4"' : ""}>
          {photo ? (
            <div ${styling === "tailwind" ? 'className="space-y-2"' : ""}>
              <img
                src={photo.uri}
                alt="Captured"
                ${styling === "tailwind" ? 'className="max-w-full rounded-lg"' : ""}
              />
              <button
                onClick={() => setPhoto(null)}
                ${styling === "tailwind" ? 'className="rounded bg-gray-200 px-3 py-1 text-sm hover:bg-gray-300"' : ""}
              >
                Clear
              </button>
            </div>
          ) : (
            <button
              onClick={handleTakePicture}
              disabled={isLoading}
              ${styling === "tailwind" ? 'className="inline-flex items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:opacity-50"' : ""}
            >
              {isLoading ? 'Capturing...' : 'Take photo'}
            </button>
          )}
        </div>
      );
    }
    `.trim(),
    
      location: ({ componentName = "LocationButton", styling = "tailwind" }) => `
    import { useState } from 'react';
    import { getAppo, type PermissionStatus } from '@appolabs/appo';
    
    interface Position {
      latitude: number;
      longitude: number;
      accuracy: number;
    }
    
    interface ${componentName}Props {
      onLocation?: (position: Position) => void;
    }
    
    export function ${componentName}({ onLocation }: ${componentName}Props) {
      const [permission, setPermission] = useState<PermissionStatus | null>(null);
      const [position, setPosition] = useState<Position | null>(null);
      const [isLoading, setIsLoading] = useState(false);
    
      const appo = getAppo();
    
      const handleGetLocation = async () => {
        setIsLoading(true);
        try {
          if (permission !== 'granted') {
            const status = await appo.location.requestPermission();
            setPermission(status);
            if (status !== 'granted') return;
          }
    
          const pos = await appo.location.getCurrentPosition();
          if (pos) {
            setPosition(pos);
            onLocation?.(pos);
          }
        } finally {
          setIsLoading(false);
        }
      };
    
      return (
        <div ${styling === "tailwind" ? 'className="space-y-2"' : ""}>
          <button
            onClick={handleGetLocation}
            disabled={isLoading || permission === 'denied'}
            ${styling === "tailwind" ? 'className="inline-flex items-center gap-2 rounded-lg bg-teal-600 px-4 py-2 text-white hover:bg-teal-700 disabled:opacity-50"' : ""}
          >
            <svg ${styling === "tailwind" ? 'className="h-5 w-5"' : ""} fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
            </svg>
            {isLoading ? 'Getting location...' : 'Get my location'}
          </button>
          {position && (
            <p ${styling === "tailwind" ? 'className="text-sm text-gray-600"' : ""}>
              {position.latitude.toFixed(6)}, {position.longitude.toFixed(6)}
            </p>
          )}
        </div>
      );
    }
    `.trim(),
    
      haptics: ({ componentName = "HapticButtons", styling = "tailwind" }) => `
    import { getAppo } from '@appolabs/appo';
    
    export function ${componentName}() {
      const appo = getAppo();
    
      return (
        <div ${styling === "tailwind" ? 'className="flex flex-wrap gap-2"' : ""}>
          <button
            onClick={() => appo.haptics.impact('light')}
            ${styling === "tailwind" ? 'className="rounded bg-gray-100 px-3 py-2 text-sm hover:bg-gray-200"' : ""}
          >
            Light tap
          </button>
          <button
            onClick={() => appo.haptics.impact('medium')}
            ${styling === "tailwind" ? 'className="rounded bg-gray-200 px-3 py-2 text-sm hover:bg-gray-300"' : ""}
          >
            Medium tap
          </button>
          <button
            onClick={() => appo.haptics.impact('heavy')}
            ${styling === "tailwind" ? 'className="rounded bg-gray-300 px-3 py-2 text-sm hover:bg-gray-400"' : ""}
          >
            Heavy tap
          </button>
          <button
            onClick={() => appo.haptics.notification('success')}
            ${styling === "tailwind" ? 'className="rounded bg-green-100 px-3 py-2 text-sm text-green-700 hover:bg-green-200"' : ""}
          >
            Success
          </button>
          <button
            onClick={() => appo.haptics.notification('warning')}
            ${styling === "tailwind" ? 'className="rounded bg-yellow-100 px-3 py-2 text-sm text-yellow-700 hover:bg-yellow-200"' : ""}
          >
            Warning
          </button>
          <button
            onClick={() => appo.haptics.notification('error')}
            ${styling === "tailwind" ? 'className="rounded bg-red-100 px-3 py-2 text-sm text-red-700 hover:bg-red-200"' : ""}
          >
            Error
          </button>
        </div>
      );
    }
    `.trim(),
    
      storage: ({ componentName = "StorageDemo", styling = "tailwind" }) => `
    import { useState } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface ${componentName}Props {
      storageKey?: string;
    }
    
    export function ${componentName}({ storageKey = 'demo-value' }: ${componentName}Props) {
      const [value, setValue] = useState('');
      const [stored, setStored] = useState<string | null>(null);
    
      const appo = getAppo();
    
      const handleSave = async () => {
        await appo.storage.set(storageKey, value);
        setStored(value);
      };
    
      const handleLoad = async () => {
        const loaded = await appo.storage.get(storageKey);
        setStored(loaded);
        if (loaded) setValue(loaded);
      };
    
      const handleDelete = async () => {
        await appo.storage.delete(storageKey);
        setStored(null);
        setValue('');
      };
    
      return (
        <div ${styling === "tailwind" ? 'className="space-y-4"' : ""}>
          <div ${styling === "tailwind" ? 'className="flex gap-2"' : ""}>
            <input
              type="text"
              value={value}
              onChange={(e) => setValue(e.target.value)}
              placeholder="Enter a value"
              ${styling === "tailwind" ? 'className="flex-1 rounded border px-3 py-2"' : ""}
            />
            <button onClick={handleSave} ${styling === "tailwind" ? 'className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"' : ""}>
              Save
            </button>
          </div>
          <div ${styling === "tailwind" ? 'className="flex gap-2"' : ""}>
            <button onClick={handleLoad} ${styling === "tailwind" ? 'className="rounded bg-gray-200 px-3 py-2 hover:bg-gray-300"' : ""}>
              Load
            </button>
            <button onClick={handleDelete} ${styling === "tailwind" ? 'className="rounded bg-red-100 px-3 py-2 text-red-700 hover:bg-red-200"' : ""}>
              Delete
            </button>
          </div>
          {stored !== null && (
            <p ${styling === "tailwind" ? 'className="text-sm text-gray-600"' : ""}>Stored: {stored}</p>
          )}
        </div>
      );
    }
    `.trim(),
    
      share: ({ componentName = "ShareButton", styling = "tailwind" }) => `
    import { useState } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface ${componentName}Props {
      title?: string;
      message?: string;
      url?: string;
    }
    
    export function ${componentName}({
      title = 'Check this out!',
      message = '',
      url = window.location.href,
    }: ${componentName}Props) {
      const [isLoading, setIsLoading] = useState(false);
    
      const appo = getAppo();
    
      const handleShare = async () => {
        setIsLoading(true);
        try {
          await appo.share.open({ title, message, url });
        } finally {
          setIsLoading(false);
        }
      };
    
      return (
        <button
          onClick={handleShare}
          disabled={isLoading}
          ${styling === "tailwind" ? 'className="inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-700 disabled:opacity-50"' : ""}
        >
          <svg ${styling === "tailwind" ? 'className="h-5 w-5"' : ""} fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
          </svg>
          {isLoading ? 'Sharing...' : 'Share'}
        </button>
      );
    }
    `.trim(),
    
      network: ({ componentName = "NetworkStatus", styling = "tailwind" }) => `
    import { useState, useEffect } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface NetworkState {
      isConnected: boolean;
      type: string;
    }
    
    export function ${componentName}() {
      const [network, setNetwork] = useState<NetworkState | null>(null);
    
      const appo = getAppo();
    
      useEffect(() => {
        appo.network.getStatus().then(setNetwork);
        return appo.network.onChange(setNetwork);
      }, []);
    
      if (!network) return null;
    
      return (
        <div ${styling === "tailwind" ? `className={\`inline-flex items-center gap-2 rounded-full px-3 py-1 text-sm \${network.isConnected ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}\`}` : ""}>
          <span ${styling === "tailwind" ? `className={\`h-2 w-2 rounded-full \${network.isConnected ? 'bg-green-500' : 'bg-red-500'}\`}` : ""} />
          {network.isConnected ? \`Online (\${network.type})\` : 'Offline'}
        </div>
      );
    }
    `.trim(),
    
      device: ({ componentName = "DeviceInfo", styling = "tailwind" }) => `
    import { useState, useEffect } from 'react';
    import { getAppo } from '@appolabs/appo';
    
    interface DeviceData {
      platform: string;
      osVersion: string;
      appVersion: string;
      deviceName: string;
      isTablet: boolean;
    }
    
    export function ${componentName}() {
      const [device, setDevice] = useState<DeviceData | null>(null);
    
      const appo = getAppo();
    
      useEffect(() => {
        appo.device.getInfo().then(setDevice);
      }, []);
    
      if (!device) {
        return <div ${styling === "tailwind" ? 'className="animate-pulse text-gray-400"' : ""}>Loading device info...</div>;
      }
    
      return (
        <div ${styling === "tailwind" ? 'className="rounded-lg bg-gray-50 p-4"' : ""}>
          <h3 ${styling === "tailwind" ? 'className="mb-3 font-semibold"' : ""}>Device Information</h3>
          <dl ${styling === "tailwind" ? 'className="space-y-2 text-sm"' : ""}>
            <div ${styling === "tailwind" ? 'className="flex justify-between"' : ""}>
              <dt ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>Platform</dt>
              <dd ${styling === "tailwind" ? 'className="font-medium"' : ""}>{device.platform}</dd>
            </div>
            <div ${styling === "tailwind" ? 'className="flex justify-between"' : ""}>
              <dt ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>OS Version</dt>
              <dd ${styling === "tailwind" ? 'className="font-medium"' : ""}>{device.osVersion}</dd>
            </div>
            <div ${styling === "tailwind" ? 'className="flex justify-between"' : ""}>
              <dt ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>App Version</dt>
              <dd ${styling === "tailwind" ? 'className="font-medium"' : ""}>{device.appVersion}</dd>
            </div>
            <div ${styling === "tailwind" ? 'className="flex justify-between"' : ""}>
              <dt ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>Device</dt>
              <dd ${styling === "tailwind" ? 'className="font-medium"' : ""}>{device.deviceName}</dd>
            </div>
            <div ${styling === "tailwind" ? 'className="flex justify-between"' : ""}>
              <dt ${styling === "tailwind" ? 'className="text-gray-500"' : ""}>Type</dt>
              <dd ${styling === "tailwind" ? 'className="font-medium"' : ""}>{device.isTablet ? 'Tablet' : 'Phone'}</dd>
            </div>
          </dl>
          {appo.isNative && (
            <p ${styling === "tailwind" ? 'className="mt-3 text-xs text-green-600"' : ""}>Running in native app</p>
          )}
        </div>
      );
    }
    `.trim(),
    };
  • Registration of the 'generate_component' tool in the `tools` array. Defines the tool name, description, and inputSchema with properties for feature, componentName, styling, and variant. The handler dispatch is at line 188-189 where `case 'generate_component'` calls `generateComponent(args)`.
    {
      name: "generate_component",
      description:
        "Generate a UI component that uses @appolabs/appo SDK features. Returns a complete React component with SDK integration.",
      inputSchema: {
        type: "object",
        properties: {
          feature: {
            type: "string",
            enum: SDK_FEATURES,
            description: "The SDK feature to build the component around",
          },
          componentName: {
            type: "string",
            description: "Component name (optional, defaults to {Feature}Button or similar)",
          },
          styling: {
            type: "string",
            enum: ["tailwind", "css", "none"],
            description: "Styling approach (default: tailwind)",
          },
          variant: {
            type: "string",
            enum: ["button", "card", "form", "status"],
            description: "Component variant/type",
          },
        },
        required: ["feature"],
      },
    },
Behavior2/5

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

No annotations are provided, so the description must disclose behavioral traits. It states it generates and returns a React component, but lacks details on side effects (e.g., file creation), authorization needs, or any limitations. For a code generation tool, more transparency is needed.

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?

Two sentences, front-loaded with the main purpose. No wasted words, efficient and clear.

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

Completeness2/5

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

Given the tool's complex task of generating SDK-integrated components and no output schema, the description should provide more context about the output format, defaults, or constraints. It is too brief to fully guide an agent.

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%, with each parameter well-described in the schema. The description adds no additional meaning beyond the schema, so the baseline of 3 applies.

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 the verb 'generate' and resource 'UI component', specifies it uses @appolabs/appo SDK features, and returns a complete React component with SDK integration. This differentiates it from siblings like generate_hook or scaffold_feature.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It does not mention when not to use it, prerequisites, or contrast with sibling tools like check_permissions or diagnose_issue.

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