Skip to main content
Glama

scaffold_consumer

Generate type-safe client code from API schemas with full type inference for TypeScript functions, React hooks, or Zustand actions.

Instructions

Generate type-safe client code from API schemas. Creates TypeScript functions, React hooks, or Zustand actions with full type inference.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
producerDirYesPath to API source directory
toolNameYesName of the endpoint/tool to scaffold
targetNoOutput target format
includeErrorHandlingNoInclude try/catch error handling
includeTypesNoInclude TypeScript type definitions

Implementation Reference

  • src/index.ts:193-207 (registration)
    Tool registration for 'scaffold_consumer' as an MCP tool with name, description, and input schema definition.
    {
      name: 'scaffold_consumer',
      description: 'Generate type-safe client code from API schemas. Creates TypeScript functions, React hooks, or Zustand actions with full type inference.',
      inputSchema: {
        type: 'object',
        properties: {
          producerDir: { type: 'string', description: 'Path to API source directory' },
          toolName: { type: 'string', description: 'Name of the endpoint/tool to scaffold' },
          target: { type: 'string', enum: ['typescript', 'javascript', 'react-hook', 'zustand-action'], description: 'Output target format' },
          includeErrorHandling: { type: 'boolean', description: 'Include try/catch error handling' },
          includeTypes: { type: 'boolean', description: 'Include TypeScript type definitions' },
        },
        required: ['producerDir', 'toolName'],
      },
    },
  • Zod schema (ScaffoldConsumerInput) defining the input parameters for scaffold_consumer: producerDir, toolName, target, includeErrorHandling, includeTypes.
    const ScaffoldConsumerInput = z.object({
      producerDir: z.string().describe('Path to MCP server source directory'),
      toolName: z.string().describe('Name of the tool to scaffold consumer for'),
      target: z.enum(['typescript', 'javascript', 'react-hook', 'zustand-action']).optional().describe('Output target format (default: typescript)'),
      includeErrorHandling: z.boolean().optional().describe('Include try/catch error handling (default: true)'),
      includeTypes: z.boolean().optional().describe('Include TypeScript type definitions (default: true)'),
    });
  • Handler logic: extracts producer schemas, finds the requested tool, calls scaffoldConsumerFromProducer, and returns generated code.
    case 'scaffold_consumer': {
      const input = ScaffoldConsumerInput.parse(args);
      log(`Scaffolding consumer for tool: ${input.toolName}`);
      
      // Extract producer schemas to find the requested tool
      const producers = await extractProducerSchemas({ rootDir: input.producerDir });
      const producer = producers.find(p => p.toolName === input.toolName);
      
      if (!producer) {
        throw new Error(`Tool "${input.toolName}" not found in ${input.producerDir}`);
      }
      
      const result = scaffoldConsumerFromProducer(producer, {
        target: input.target || 'typescript',
        includeErrorHandling: input.includeErrorHandling ?? true,
        includeTypes: input.includeTypes ?? true,
        includeJSDoc: true,
      });
      
      log(`Generated ${input.target || 'typescript'} consumer code`);
      
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: true,
              toolName: input.toolName,
              target: input.target || 'typescript',
              suggestedFilename: result.suggestedFilename,
              code: result.code,
              types: result.types,
              example: result.example,
            }, null, 2),
          },
        ],
      };
    }
  • Core implementation of scaffoldConsumerFromProducer: generates consumer code (TypeScript, JavaScript, React hook, or Zustand action) from a producer schema.
    export function scaffoldConsumerFromProducer(
      producer: ProducerSchema,
      options: ScaffoldOptions = { target: 'typescript' }
    ): ScaffoldResult {
      const { target, includeErrorHandling = true, includeTypes = true, functionPrefix = '', includeJSDoc = true } = options;
      
      const toolName = producer.toolName;
      const functionName = `${functionPrefix}${toCamelCase(toolName)}`;
      const inputProps = producer.inputSchema.properties || {};
      const requiredArgs = producer.inputSchema.required || [];
      
      // Generate TypeScript interface for args
      const argsInterface = generateArgsInterface(toolName, inputProps, requiredArgs);
      
      // Generate the function based on target
      let code: string;
      let types: string | undefined;
      let example: string;
      
      switch (target) {
        case 'react-hook':
          ({ code, types, example } = generateReactHook(toolName, functionName, inputProps, requiredArgs, { includeErrorHandling, includeTypes, includeJSDoc, producer }));
          break;
        case 'zustand-action':
          ({ code, types, example } = generateZustandAction(toolName, functionName, inputProps, requiredArgs, { includeErrorHandling, includeTypes, includeJSDoc, producer }));
          break;
        case 'javascript':
          const jsResult = generateJavaScript(toolName, functionName, inputProps, requiredArgs, { includeErrorHandling, includeJSDoc, producer });
          code = jsResult.code;
          example = jsResult.example;
          types = undefined;
          break;
        case 'typescript':
        default:
          ({ code, types, example } = generateTypeScript(toolName, functionName, inputProps, requiredArgs, { includeErrorHandling, includeTypes, includeJSDoc, producer }));
          break;
      }
      
      return {
        code,
        suggestedFilename: `use-${toKebabCase(toolName)}.${target === 'javascript' ? 'js' : 'ts'}`,
        types,
        example,
      };
    }
  • Helper generator functions: generateTypeScript, generateJavaScript, generateReactHook, generateZustandAction — all called by scaffoldConsumerFromProducer to produce target-specific code.
    // ============================================================================
    // Generator Functions
    // ============================================================================
    
    function generateTypeScript(
      toolName: string,
      functionName: string,
      inputProps: Record<string, JSONSchema>,
      requiredArgs: string[],
      options: { includeErrorHandling: boolean; includeTypes: boolean; includeJSDoc: boolean; producer: ProducerSchema }
    ): { code: string; types?: string; example: string } {
      const { includeErrorHandling, includeTypes, includeJSDoc, producer } = options;
      
      const argsType = `${toPascalCase(toolName)}Args`;
      const resultType = `${toPascalCase(toolName)}Result`;
      
      const types = includeTypes ? `
    export interface ${argsType} {
    ${Object.entries(inputProps)
      .map(([key, schema]) => `  ${key}${requiredArgs.includes(key) ? '' : '?'}: ${jsonSchemaToTsType(schema)};`)
      .join('\n')}
    }
    
    export interface ${resultType} {
      // TODO: Define based on actual response
      [key: string]: unknown;
    }
    `.trim() : undefined;
    
      const jsdoc = includeJSDoc ? `
    /**
     * ${producer.description || `Call the ${toolName} tool`}
     * @trace-contract CONSUMER
     * Producer: ${producer.location.file}:${producer.location.line}
     */` : '';
    
      const code = `
    ${jsdoc}
    export async function ${functionName}(
      client: McpClient,
      args: ${includeTypes ? argsType : `{ ${Object.keys(inputProps).join(', ')} }`}
    ): Promise<${includeTypes ? resultType : 'unknown'}> {
    ${includeErrorHandling ? `  try {
        const result = await client.callTool('${toolName}', args);
        return JSON.parse(result.content[0].text);
      } catch (error) {
        console.error('Error calling ${toolName}:', error);
        throw error;
      }` : `  const result = await client.callTool('${toolName}', args);
      return JSON.parse(result.content[0].text);`}
    }
    `.trim();
    
      const example = `
    // Usage:
    const result = await ${functionName}(client, {
    ${requiredArgs.map(arg => `  ${arg}: /* ${jsonSchemaToTsType(inputProps[arg])} */,`).join('\n')}
    });
    `.trim();
    
      return { code, types, example };
    }
    
    function generateJavaScript(
      toolName: string,
      functionName: string,
      inputProps: Record<string, JSONSchema>,
      requiredArgs: string[],
      options: { includeErrorHandling: boolean; includeJSDoc: boolean; producer: ProducerSchema }
    ): { code: string; example: string } {
      const { includeErrorHandling, includeJSDoc, producer } = options;
      
      const jsdoc = includeJSDoc ? `
    /**
     * ${producer.description || `Call the ${toolName} tool`}
     * @param {Object} client - MCP client
     * @param {Object} args - Tool arguments
    ${Object.entries(inputProps)
      .map(([key, schema]) => ` * @param {${jsonSchemaToJsType(schema)}} args.${key}`)
      .join('\n')}
     * @returns {Promise<Object>}
     */` : '';
    
      const code = `
    ${jsdoc}
    export async function ${functionName}(client, args) {
    ${includeErrorHandling ? `  try {
        const result = await client.callTool('${toolName}', args);
        return JSON.parse(result.content[0].text);
      } catch (error) {
        console.error('Error calling ${toolName}:', error);
        throw error;
      }` : `  const result = await client.callTool('${toolName}', args);
      return JSON.parse(result.content[0].text);`}
    }
    `.trim();
    
      const example = `
    // Usage:
    const result = await ${functionName}(client, {
    ${requiredArgs.map(arg => `  ${arg}: /* value */,`).join('\n')}
    });
    `.trim();
    
      return { code, example };
    }
    
    function generateReactHook(
      toolName: string,
      _functionName: string,
      inputProps: Record<string, JSONSchema>,
      requiredArgs: string[],
      options: { includeErrorHandling: boolean; includeTypes: boolean; includeJSDoc: boolean; producer: ProducerSchema }
    ): { code: string; types?: string; example: string } {
      const { includeTypes, producer } = options;
      const hookName = `use${toPascalCase(toolName)}`;
      const argsType = `${toPascalCase(toolName)}Args`;
      
      const types = includeTypes ? `
    export interface ${argsType} {
    ${Object.entries(inputProps)
      .map(([key, schema]) => `  ${key}${requiredArgs.includes(key) ? '' : '?'}: ${jsonSchemaToTsType(schema)};`)
      .join('\n')}
    }
    `.trim() : undefined;
    
      const code = `
    import { useState, useCallback } from 'react';
    import { useMcpClient } from './mcp-context'; // Adjust import
    
    /**
     * ${producer.description || `Hook for ${toolName} tool`}
     * @trace-contract CONSUMER (React Hook)
     * Producer: ${producer.location.file}:${producer.location.line}
     */
    export function ${hookName}() {
      const client = useMcpClient();
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState<Error | null>(null);
      const [data, setData] = useState<unknown>(null);
    
      const execute = useCallback(async (args: ${includeTypes ? argsType : 'Record<string, unknown>'}) => {
        setLoading(true);
        setError(null);
        try {
          const result = await client.callTool('${toolName}', args);
          const parsed = JSON.parse(result.content[0].text);
          setData(parsed);
          return parsed;
        } catch (err) {
          setError(err instanceof Error ? err : new Error(String(err)));
          throw err;
        } finally {
          setLoading(false);
        }
      }, [client]);
    
      return { execute, loading, error, data };
    }
    `.trim();
    
      const example = `
    // Usage in React component:
    function MyComponent() {
      const { execute, loading, error, data } = ${hookName}();
      
      const handleClick = async () => {
        await execute({
    ${requiredArgs.map(arg => `      ${arg}: /* value */,`).join('\n')}
        });
      };
      
      if (loading) return <div>Loading...</div>;
      if (error) return <div>Error: {error.message}</div>;
      return <div>{JSON.stringify(data)}</div>;
    }
    `.trim();
    
      return { code, types, example };
    }
    
    function generateZustandAction(
      toolName: string,
      _functionName: string,
      inputProps: Record<string, JSONSchema>,
      requiredArgs: string[],
      options: { includeErrorHandling: boolean; includeTypes: boolean; includeJSDoc: boolean; producer: ProducerSchema }
    ): { code: string; types?: string; example: string } {
      const { includeTypes, producer } = options;
      const actionName = toCamelCase(toolName);
      const argsType = `${toPascalCase(toolName)}Args`;
      
      const types = includeTypes ? `
    export interface ${argsType} {
    ${Object.entries(inputProps)
      .map(([key, schema]) => `  ${key}${requiredArgs.includes(key) ? '' : '?'}: ${jsonSchemaToTsType(schema)};`)
      .join('\n')}
    }
    `.trim() : undefined;
    
      const code = `
    /**
     * Zustand action for ${toolName}
     * @trace-contract CONSUMER (Zustand)
     * Producer: ${producer.location.file}:${producer.location.line}
     */
    export const create${toPascalCase(toolName)}Slice = (set: any, get: any) => ({
      ${actionName}Loading: false,
      ${actionName}Error: null as Error | null,
      ${actionName}Data: null as unknown,
      
      ${actionName}: async (args: ${includeTypes ? argsType : 'Record<string, unknown>'}) => {
        const { mcpClient } = get();
        set({ ${actionName}Loading: true, ${actionName}Error: null });
        
        try {
          const result = await mcpClient.callTool('${toolName}', args);
          const data = JSON.parse(result.content[0].text);
          set({ ${actionName}Data: data, ${actionName}Loading: false });
          return data;
        } catch (error) {
          set({ 
            ${actionName}Error: error instanceof Error ? error : new Error(String(error)),
            ${actionName}Loading: false 
          });
          throw error;
        }
      },
    });
    `.trim();
    
      const example = `
    // Add to your Zustand store:
    import { create } from 'zustand';
    
    const useStore = create((set, get) => ({
      mcpClient: null,
      setMcpClient: (client) => set({ mcpClient: client }),
      ...create${toPascalCase(toolName)}Slice(set, get),
    }));
    
    // Usage:
    const { ${actionName}, ${actionName}Loading, ${actionName}Data } = useStore();
    await ${actionName}({
    ${requiredArgs.map(arg => `  ${arg}: /* value */,`).join('\n')}
    });
    `.trim();
    
      return { code, types, example };
    }
    
    // ============================================================================
    // Utilities
    // ============================================================================
    
    function generateArgsInterface(toolName: string, props: Record<string, JSONSchema>, required: string[]): string {
      const lines = Object.entries(props).map(([key, schema]) => {
        const optional = required.includes(key) ? '' : '?';
        const type = jsonSchemaToTsType(schema);
        return `  ${key}${optional}: ${type};`;
      });
      
      return `interface ${toPascalCase(toolName)}Args {\n${lines.join('\n')}\n}`;
    }
    
    function jsonSchemaToTsType(schema: JSONSchema): string {
      if (!schema.type) return 'unknown';
      
      switch (schema.type) {
        case 'string':
          if (schema.enum && Array.isArray(schema.enum)) {
            return (schema.enum as unknown[]).map((v: unknown) => `'${v}'`).join(' | ');
          }
          return 'string';
        case 'number':
        case 'integer':
          return 'number';
        case 'boolean':
          return 'boolean';
        case 'array':
          return `${jsonSchemaToTsType(schema.items || {})}[]`;
        case 'object':
          return 'Record<string, unknown>';
        default:
          return 'unknown';
      }
    }
    
    function jsonSchemaToJsType(schema: JSONSchema): string {
      if (!schema.type) return '*';
      
      switch (schema.type) {
        case 'string': return 'string';
        case 'number':
        case 'integer': return 'number';
        case 'boolean': return 'boolean';
        case 'array': return 'Array';
        case 'object': return 'Object';
        default: return '*';
      }
    }
    
    function inferSchemaFromArgs(args: Record<string, unknown>): Record<string, string> {
      const schema: Record<string, string> = {};
      
      for (const [key, value] of Object.entries(args)) {
        if (typeof value === 'string' || value === '<value>') {
          schema[key] = 'z.string()';
        } else if (typeof value === 'number') {
          schema[key] = 'z.number()';
        } else if (typeof value === 'boolean') {
          schema[key] = 'z.boolean()';
        } else if (Array.isArray(value)) {
          schema[key] = 'z.array(z.unknown())';
        } else if (typeof value === 'object') {
          schema[key] = 'z.object({})';
        } else {
          schema[key] = 'z.unknown()';
        }
      }
      
      return schema;
    }
    
    function toCamelCase(str: string): string {
      return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
    }
    
    function toPascalCase(str: string): string {
      const camel = toCamelCase(str);
      return camel.charAt(0).toUpperCase() + camel.slice(1);
    }
    
    function toKebabCase(str: string): string {
      return str.replace(/_/g, '-').toLowerCase();
    }
Behavior2/5

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

No annotations are present, so the description must carry the transparency burden. It fails to disclose side effects (e.g., file overwriting), permissions, or whether the generated code is saved somewhere. Only the basic output types are mentioned.

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 two concise sentences with the core action front-loaded. Every word adds value; no redundancy or filler. It is efficiently structured.

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?

Despite having 5 parameters and no output schema or annotations, the description omits important details like return values, file system effects, required directory structure, or error handling behavior. It is insufficient for a code generation tool of this complexity.

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 input schema already documents all parameters. The description adds minimal extra meaning by explaining the output formats (TypeScript, React hooks, Zustand), which aligns with the 'target' enum. Baseline 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 tool's purpose: generating type-safe client code from API schemas, and lists concrete outputs (TypeScript functions, React hooks, Zustand actions). This differentiates it from the sibling 'scaffold_producer' which presumably generates server-side code.

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 implies usage for generating client code after having API schemas, but does not explicitly state when to use this over alternatives like scaffold_producer or other tools. No exclusion criteria or prerequisite conditions are provided.

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/Mnehmos/mnehmos.trace.mcp'

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