create_form
Create Tally forms by converting simple field definitions into structured blocks automatically. Specify title, fields, and optional status to generate forms for surveys, feedback, or data collection.
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
| Name | Required | Description | Default |
|---|---|---|---|
| title | Yes | Form title (required) - will be displayed as the main form heading | |
| description | No | Optional form description - displayed below the title to provide context | |
| status | No | Form publication status. Use DRAFT for unpublished forms that are being worked on, or PUBLISHED for live forms. Defaults to DRAFT if not specified. | DRAFT |
| fields | Yes | Array of form fields/questions. Each field will be converted to appropriate Tally blocks automatically. |
Implementation Reference
- src/tools/form-creation-tool.ts:64-129 (handler)The main execution handler for form creation. Processes input arguments to generate a FormConfig (via direct config, template, or NLP), calls TallyApiService.createForm to create the form, extracts form URL/ID, and generates enriched field configurations using block builder utilities.public async execute(args: FormCreationArgs): Promise<FormCreationResult> { console.log(`Executing form creation tool with args: ${JSON.stringify(args)}`); let formConfig: FormConfig | undefined; // 1. Highest priority: direct FormConfig if (args.formConfig) { formConfig = { ...args.formConfig }; if (args.formTitle) { formConfig.title = args.formTitle; } } // 2. Template instantiation (if no direct config) if (!formConfig && args.templateId) { formConfig = this.templateService.instantiateTemplate(args.templateId, args.formTitle); if (!formConfig) { throw new Error(`Template with ID '${args.templateId}' not found.`); } } // 3. Natural-language prompt (if neither of the above) if (!formConfig && args.naturalLanguagePrompt) { formConfig = this.nlpService.generateFormConfig(args.naturalLanguagePrompt); if (args.formTitle) { formConfig.title = args.formTitle; } } if (!formConfig) { throw new Error('One of formConfig, naturalLanguagePrompt, or templateId must be provided.'); } let createdTallyForm; try { createdTallyForm = await this.tallyApiService.createForm(formConfig); } catch (err: any) { console.error('[FormCreationTool] createForm error', err?.response?.data || err?.message || err); throw err; } console.log('[FormCreationTool] Created form response:', JSON.stringify(createdTallyForm, null, 2)); // Try to capture any possible share link fields let formUrl: string | undefined = (createdTallyForm as any).url || (createdTallyForm as any).shareUrl || (createdTallyForm as any).share_url || (createdTallyForm as any).publicUrl; if (!formUrl && createdTallyForm.id) { formUrl = `https://tally.so/forms/${createdTallyForm.id}/edit`; } // Generate enriched field configurations using enhanced BlockBuilder const blockResult = buildBlocksForFormWithMapping(formConfig); const enrichedFieldConfigurations = generateEnrichedFieldConfigurations(formConfig, blockResult.fieldBlockMapping); return { formUrl, formId: createdTallyForm.id, formConfig, generatedFieldIds: blockResult.fieldIds, enrichedFieldConfigurations, }; }
- src/server.ts:1302-1384 (schema)JSON Schema definition for the 'create_form' tool input parameters, including title, optional description/status, and detailed fields array with supported types, used in MCP tools/list response.{ 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 form status 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"] } ] } ] }
- src/server.ts:1278-1294 (registration)Initializes and registers the FormCreationTool instance as 'form_creation' in the server's tools object, mapping it to handle 'create_form' tool calls (though switch case pending).private initializeTools(): void { this.log('info', 'Initializing tools...'); const apiClientConfig: TallyApiClientConfig = { accessToken: env.TALLY_API_KEY }; const tallyApiClient = new TallyApiClient(apiClientConfig); this.tools = { workspaceManagement: new WorkspaceManagementTool(apiClientConfig), template: new TemplateTool(), form_creation: new FormCreationTool(apiClientConfig), form_modification: new FormModificationTool(apiClientConfig), form_retrieval: new FormRetrievalTool(apiClientConfig), form_sharing: new FormSharingTool(tallyApiClient), form_permissions: new FormPermissionManager(apiClientConfig), submission_analysis: new SubmissionAnalysisTool(apiClientConfig), diagnostic: new DiagnosticTool(), }; this.log('info', 'Tools initialized.'); }
- Core helper method that performs the actual HTTP request to Tally API to create a form from FormConfig, called by FormCreationTool.execute.public async createForm(formConfig: FormConfig): Promise<TallyForm> { // The Tally API endpoint for creating forms const endpoint = '/forms'; // Map our FormConfig to the payload expected by the Tally API const payload = this.mapFormConfigToPayload(formConfig); // Validate the payload before sending to ensure it meets Tally API requirements const validatedPayload = TallyFormCreatePayloadSchema.parse(payload); return this.apiClient.requestWithValidation('POST', endpoint, TallyFormSchema, validatedPayload); } /** * Retrieves a specific form by its ID from Tally. * @param formId The ID of the form to retrieve. * @returns The Tally form data. */ public async getForm(formId: string): Promise<TallyForm> { return this.apiClient.getForm(formId); } /** * Retrieves a list of forms from Tally. * @param options Options for filtering and pagination. * @returns The list of forms response. */ public async getForms(options: { page?: number; limit?: number; workspaceId?: string; } = {}): Promise<TallyFormsResponse> { return this.apiClient.getForms(options); } /** * Updates an existing form in Tally. * @param formId The ID of the form to update. * @param formConfig The updated configuration of the form. * @returns The updated Tally form. */ public async updateForm(formId: string, formConfig: Partial<FormConfig>): Promise<TallyForm> { const endpoint = `/forms/${formId}`; // Map the FormConfig to the payload expected by the Tally API const payload = this.mapToTallyUpdatePayload(formConfig); // Validate the payload before sending to ensure it meets Tally API requirements const validatedPayload = TallyFormUpdatePayloadSchema.parse(payload); return this.apiClient.requestWithValidation('PUT', endpoint, TallyFormSchema, validatedPayload); } /** * Partially updates an existing form in Tally. * @param formId The ID of the form to update. * @param updates Partial updates to apply to the form. * @returns The updated Tally form. */ public async patchForm(formId: string, updates: Record<string, any>): Promise<TallyForm> { const endpoint = `/forms/${formId}`; return this.apiClient.requestWithValidation('PATCH', endpoint, TallyFormSchema, updates); } /** * Maps our internal FormConfig to the payload expected by the Tally API. * This is a placeholder and will be implemented in detail later. * @param formConfig The internal form configuration. * @returns The payload for the Tally API. */ private mapFormConfigToPayload(formConfig: FormConfig): TallyFormCreatePayload { // Build Tally-compatible blocks using our utility const blocks = buildBlocksForForm(formConfig); const payload = { status: 'PUBLISHED' as const, name: formConfig.title, blocks: blocks as any, // Cast to any to resolve type compatibility }; // DEBUG: log payload for development if (process.env.DEBUG_TALLY_PAYLOAD === '1') {
- The FormCreationTool class definition, implementing the Tool interface, with dependencies injected for NLP, Tally API, and templates, serving as the core handler for create_form operations.export class FormCreationTool implements Tool<FormCreationArgs, FormCreationResult> { public readonly name = 'form_creation_tool'; public readonly description = 'Creates a Tally form from a natural language description or a template.'; private nlpService: NlpService; private tallyApiService: TallyApiService; private templateService: TemplateService; constructor(apiClientConfig: TallyApiClientConfig) { this.nlpService = new NlpService(); this.tallyApiService = new TallyApiService(apiClientConfig); this.templateService = new TemplateService(); } public async execute(args: FormCreationArgs): Promise<FormCreationResult> { console.log(`Executing form creation tool with args: ${JSON.stringify(args)}`); let formConfig: FormConfig | undefined; // 1. Highest priority: direct FormConfig if (args.formConfig) { formConfig = { ...args.formConfig }; if (args.formTitle) { formConfig.title = args.formTitle; } } // 2. Template instantiation (if no direct config) if (!formConfig && args.templateId) { formConfig = this.templateService.instantiateTemplate(args.templateId, args.formTitle); if (!formConfig) { throw new Error(`Template with ID '${args.templateId}' not found.`); } } // 3. Natural-language prompt (if neither of the above) if (!formConfig && args.naturalLanguagePrompt) { formConfig = this.nlpService.generateFormConfig(args.naturalLanguagePrompt); if (args.formTitle) { formConfig.title = args.formTitle; } } if (!formConfig) { throw new Error('One of formConfig, naturalLanguagePrompt, or templateId must be provided.'); } let createdTallyForm; try { createdTallyForm = await this.tallyApiService.createForm(formConfig); } catch (err: any) { console.error('[FormCreationTool] createForm error', err?.response?.data || err?.message || err); throw err; } console.log('[FormCreationTool] Created form response:', JSON.stringify(createdTallyForm, null, 2)); // Try to capture any possible share link fields let formUrl: string | undefined = (createdTallyForm as any).url || (createdTallyForm as any).shareUrl || (createdTallyForm as any).share_url || (createdTallyForm as any).publicUrl; if (!formUrl && createdTallyForm.id) { formUrl = `https://tally.so/forms/${createdTallyForm.id}/edit`; } // Generate enriched field configurations using enhanced BlockBuilder const blockResult = buildBlocksForFormWithMapping(formConfig); const enrichedFieldConfigurations = generateEnrichedFieldConfigurations(formConfig, blockResult.fieldBlockMapping); return { formUrl, formId: createdTallyForm.id, formConfig, generatedFieldIds: blockResult.fieldIds, enrichedFieldConfigurations, }; }