Skip to main content
Glama

create_form

Generate Tally forms with predefined fields and configurations. Convert simple field definitions into Tally's blocks-based structure automatically, with optional status defaulting to DRAFT. Ideal for creating structured forms quickly.

Instructions

Create a new Tally form with specified fields and configuration. This tool converts simple field definitions into Tally's complex blocks-based structure automatically. The status field is optional and defaults to DRAFT if not specified.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionNoOptional form description - displayed below the title to provide context
fieldsYesArray of form fields/questions. Each field will be converted to appropriate Tally blocks automatically.
statusNoForm publication status. Use DRAFT for unpublished forms that are being worked on, or PUBLISHED for live forms. Defaults to DRAFT if not specified.DRAFT
titleYesForm title (required) - will be displayed as the main form heading

Implementation Reference

  • src/worker.ts:53-136 (registration)
    Registration of the 'create_form' tool in the MCP TOOLS array, including full input schema definition with field types, validation, and examples.
    { name: 'create_form', description: 'Create a new Tally form with specified fields and configuration. This tool converts simple field definitions into Tally\'s complex blocks-based structure automatically. The status field is optional and defaults to DRAFT if not specified.', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Form title (required) - will be displayed as the main form heading', minLength: 1, maxLength: 100 }, description: { type: 'string', description: 'Optional form description - displayed below the title to provide context' }, status: { type: 'string', enum: ['DRAFT', 'PUBLISHED'], description: 'Form publication status. Use DRAFT for unpublished forms that are being worked on, or PUBLISHED for live forms. Defaults to DRAFT if not specified.', default: 'DRAFT' }, fields: { type: 'array', description: 'Array of form fields/questions. Each field will be converted to appropriate Tally blocks automatically.', minItems: 1, items: { type: 'object', properties: { type: { type: 'string', enum: ['text', 'email', 'number', 'textarea', 'select', 'checkbox', 'radio'], description: 'Field input type. Maps to Tally blocks: text→INPUT_TEXT, email→INPUT_EMAIL, number→INPUT_NUMBER, textarea→TEXTAREA, select→DROPDOWN, checkbox→CHECKBOXES, radio→MULTIPLE_CHOICE' }, label: { type: 'string', description: 'Field label/question text - what the user will see', minLength: 1 }, required: { type: 'boolean', description: 'Whether this field must be filled out before form submission', default: false }, options: { type: 'array', items: { type: 'string' }, description: 'Available options for select, checkbox, or radio field types. Required for select/checkbox/radio fields.' } }, required: ['type', 'label'], additionalProperties: false } } }, required: ['title', 'fields'], additionalProperties: false, examples: [ { title: "Customer Feedback Survey", description: "Help us improve our service", status: "DRAFT", fields: [ { type: "text", label: "What is your name?", required: true }, { type: "email", label: "Email address", required: true }, { type: "select", label: "How would you rate our service?", required: false, options: ["Excellent", "Good", "Fair", "Poor"] } ] } ] } },
  • Detailed JSON Schema for 'create_form' tool input validation, defining supported field types (text, email, etc.), requirements, and examples.
    type: 'object', properties: { title: { type: 'string', description: 'Form title (required) - will be displayed as the main form heading', minLength: 1, maxLength: 100 }, description: { type: 'string', description: 'Optional form description - displayed below the title to provide context' }, status: { type: 'string', enum: ['DRAFT', 'PUBLISHED'], description: 'Form publication status. Use DRAFT for unpublished forms that are being worked on, or PUBLISHED for live forms. Defaults to DRAFT if not specified.', default: 'DRAFT' }, fields: { type: 'array', description: 'Array of form fields/questions. Each field will be converted to appropriate Tally blocks automatically.', minItems: 1, items: { type: 'object', properties: { type: { type: 'string', enum: ['text', 'email', 'number', 'textarea', 'select', 'checkbox', 'radio'], description: 'Field input type. Maps to Tally blocks: text→INPUT_TEXT, email→INPUT_EMAIL, number→INPUT_NUMBER, textarea→TEXTAREA, select→DROPDOWN, checkbox→CHECKBOXES, radio→MULTIPLE_CHOICE' }, label: { type: 'string', description: 'Field label/question text - what the user will see', minLength: 1 }, required: { type: 'boolean', description: 'Whether this field must be filled out before form submission', default: false }, options: { type: 'array', items: { type: 'string' }, description: 'Available options for select, checkbox, or radio field types. Required for select/checkbox/radio fields.' } }, required: ['type', 'label'], additionalProperties: false } } }, required: ['title', 'fields'], additionalProperties: false, examples: [ { title: "Customer Feedback Survey", description: "Help us improve our service", status: "DRAFT", fields: [ { type: "text", label: "What is your name?", required: true }, { type: "email", label: "Email address", required: true }, { type: "select", label: "How would you rate our service?", required: false, options: ["Excellent", "Good", "Fair", "Poor"] } ] } ] }
  • Core handler logic for 'create_form': normalizes fields, builds Tally-compatible blocks using utility functions, sends POST to Tally API /forms endpoint.
    case 'create_form': // Build blocks using BlockBuilder utility for consistency with main codebase const blocks: any[] = []; // Title block (FORM_TITLE) blocks.push(createFormTitleBlock(args.title)); // Optional description block – still TEXT; Tally supports plain TEXT blocks for content sections if (args.description) { blocks.push({ uuid: crypto.randomUUID(), type: 'TEXT', groupUuid: crypto.randomUUID(), groupType: 'TEXT', title: args.description, payload: { text: args.description, html: args.description, }, }); } // Field blocks if (Array.isArray(args.fields)) { args.fields.forEach((field: any) => { const questionConfig = normalizeField(field); createQuestionBlocks(questionConfig).forEach((b) => blocks.push(b)); }); } const payload = { status: args.status || 'DRAFT', // Use provided status or default to DRAFT blocks: blocks }; // Debug logging console.log('=== TALLY API PAYLOAD ==='); console.log(JSON.stringify(payload, null, 2)); console.log('========================'); const createResponse = await globalThis.fetch(`${baseURL}/forms`, { method: 'POST', headers, body: JSON.stringify(payload) }); if (!createResponse.ok) { const errorText = await createResponse.text(); throw new Error(`Tally API error ${createResponse.status}: ${errorText}`); } return await createResponse.json();
  • Helper function to create FORM_TITLE Tally block from form title, used in create_form handler.
    export function createFormTitleBlock(title: string): TallyBlock { return { uuid: randomUUID(), type: 'FORM_TITLE', groupUuid: randomUUID(), groupType: 'TEXT', title, payload: { html: title, }, }; }
  • Helper to generate Tally blocks for a single question/field, including title, input, and option blocks. Called in loop for each field in handler.
    export function createQuestionBlocks(question: QuestionConfig): TallyBlock[] { const blocks: TallyBlock[] = []; const groupUuid = randomUUID(); // Title block for question label blocks.push({ uuid: randomUUID(), type: 'TITLE', groupUuid, groupType: 'QUESTION', title: question.label, payload: { html: question.label, }, }); // Input block const blockType = mapQuestionTypeToBlockType(question.type); const payload: Record<string, any> = { isRequired: question.required ?? false, placeholder: 'placeholder' in question && question.placeholder ? question.placeholder : '', }; if (questionHasOptions(question)) { payload.options = question.options.map((opt) => ({ id: randomUUID(), text: opt.text ?? opt.value ?? opt.id ?? String(opt), })); } // For choice-based questions where Tally expects each option as its own block if (questionHasOptions(question) && ['DROPDOWN', 'MULTIPLE_CHOICE', 'CHECKBOXES'].includes(blockType)) { const optionBlocks: TallyBlock[] = []; const optionGroupUuid = randomUUID(); question.options.forEach((opt, idx) => { let optionType: TallyBlockType | 'DROPDOWN_OPTION' | 'MULTIPLE_CHOICE_OPTION' | 'CHECKBOX'; switch (blockType) { case 'DROPDOWN': optionType = 'DROPDOWN_OPTION'; break; case 'MULTIPLE_CHOICE': optionType = 'MULTIPLE_CHOICE_OPTION'; break; case 'CHECKBOXES': optionType = 'CHECKBOX'; break; default: optionType = 'DROPDOWN_OPTION'; } optionBlocks.push({ uuid: randomUUID(), type: optionType as TallyBlockType, // cast for index signature; runtime value is correct per docs groupUuid: optionGroupUuid, groupType: blockType, title: opt.text ?? opt.value ?? String(opt), payload: { index: idx, text: opt.text ?? opt.value ?? String(opt), }, } as unknown as TallyBlock); }); blocks.push(...optionBlocks); return blocks; } blocks.push({ uuid: randomUUID(), type: blockType, groupUuid, groupType: blockType, title: question.label, payload, }); return blocks; }
  • Normalizes simplified input field objects from tool args to full QuestionConfig expected by block-builder utilities.
    function normalizeField(field: any): import('./models/form-config').QuestionConfig { const { QuestionType } = require('./models/form-config'); const typeMap: Record<string, import('./models/form-config').QuestionType> = { text: QuestionType.TEXT, email: QuestionType.EMAIL, number: QuestionType.NUMBER, select: QuestionType.DROPDOWN, // accept both singular & plural spellings dropdown: QuestionType.DROPDOWN, radio: QuestionType.MULTIPLE_CHOICE, 'multiple_choice': QuestionType.MULTIPLE_CHOICE, checkbox: QuestionType.CHECKBOXES, checkboxes: QuestionType.CHECKBOXES, textarea: QuestionType.TEXTAREA, 'long answer': QuestionType.TEXTAREA, }; const qType = typeMap[(field.type || '').toLowerCase()] ?? QuestionType.TEXT; const qc: any = { id: crypto.randomUUID(), type: qType, label: field.label || 'Untitled', required: field.required ?? false, placeholder: field.placeholder, }; if (field.options) { qc.options = field.options.map((opt: any) => ({ text: opt, value: opt })); } return qc; }

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/learnwithcc/tally-mcp'

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