Skip to main content
Glama

analyze_submissions

Analyze form submissions to generate summaries, identify trends, evaluate responses, and calculate completion rates, enabling data-driven decisions for Tally.so forms.

Instructions

Analyze form submissions and provide insights

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
analysisTypeYesType of analysis to perform
formIdYesID of the form to analyze

Implementation Reference

  • Registration of the 'analyze_submissions' tool in the _handleToolsList method, including full input schema definition. This is returned in MCP tools/list responses.
    name: 'analyze_submissions', description: 'Analyze submission data for a Tally form to provide insights and statistics', inputSchema: { type: 'object', properties: { formId: { type: 'string', description: 'ID of the form to analyze submissions for' }, analysisType: { type: 'string', enum: ['basic_stats', 'response_patterns', 'completion_rates', 'field_analysis'], description: 'Type of analysis to perform' }, dateRange: { type: 'object', properties: { start: { type: 'string', description: 'Start date for analysis (ISO 8601)' }, end: { type: 'string', description: 'End date for analysis (ISO 8601)' } } } }, required: ['formId', 'analysisType'] } },
  • Exact schema definition for 'analyze_submissions' tool used for validation purposes. Mirrors the production schema from server.ts
    name: 'analyze_submissions', description: 'Analyze submission data for a Tally form to provide insights and statistics', inputSchema: { type: 'object', properties: { formId: { type: 'string', description: 'ID of the form to analyze submissions for' }, analysisType: { type: 'string', enum: ['basic_stats', 'response_patterns', 'completion_rates', 'field_analysis'], description: 'Type of analysis to perform' }, dateRange: { type: 'object', properties: { start: { type: 'string', description: 'Start date for analysis (ISO 8601)' }, end: { type: 'string', description: 'End date for analysis (ISO 8601)' } } } }, required: ['formId', 'analysisType'] } },
  • SubmissionAnalysisTool class provides core functionality for analyzing form submissions (analyze(), getResponseDistribution(), etc.). Likely supporting implementation or intended handler logic for 'analyze_submissions' tool despite schema differences.
    export class SubmissionAnalysisTool { private submissionService: SubmissionService; constructor(apiClientConfig: TallyApiClientConfig = {}) { this.submissionService = new SubmissionService(apiClientConfig); } public async analyze(formId: string, filters: SubmissionFilters = {}): Promise<SubmissionAnalysis> { const response = await this.submissionService.getFormSubmissions(formId, filters); const totalSubmissions = response.totalNumberOfSubmissionsPerFilter.all; const completedSubmissions = response.totalNumberOfSubmissionsPerFilter.completed; const completionRate = totalSubmissions > 0 ? (completedSubmissions / totalSubmissions) * 100 : 0; return { totalSubmissions, completionRate, }; } public async list(formId: string, filters: SubmissionFilters = {}): Promise<TallySubmission[]> { const response = await this.submissionService.getFormSubmissions(formId, filters); return response.submissions; } public async filterByDateRange(formId: string, startDate: string, endDate: string): Promise<TallySubmission[]> { return this.list(formId, { startDate, endDate }); } public async filterByStatus(formId: string, status: SubmissionStatusFilter): Promise<TallySubmission[]> { return this.list(formId, { status }); } public async formatForAnalysis(formId: string, filters: SubmissionFilters = {}): Promise<FormattedSubmission[]> { const response = await this.submissionService.getFormSubmissions(formId, filters); const questions = response.questions; const submissions = response.submissions; const questionMap = new Map<string, string>(questions.map(q => [q.id, q.title])); return submissions.map(sub => ({ submissionId: sub.id, submittedAt: sub.submittedAt, isCompleted: sub.isCompleted, responses: sub.responses.map(res => ({ questionTitle: questionMap.get(res.questionId) || 'Unknown Question', answer: res.value, })), })); } public async getAverageRating(formId: string, questionTitle: string, filters: SubmissionFilters = {}): Promise<number | null> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); let totalRating = 0; let ratingCount = 0; for (const sub of formattedSubmissions) { for (const res of sub.responses) { if (res.questionTitle === questionTitle && typeof res.answer === 'number') { totalRating += res.answer; ratingCount++; } } } return ratingCount > 0 ? totalRating / ratingCount : null; } public async getResponseDistribution(formId: string, questionTitle: string, filters: SubmissionFilters = {}): Promise<Record<string, number>> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); const distribution: Record<string, number> = {}; for (const sub of formattedSubmissions) { for (const res of sub.responses) { if (res.questionTitle === questionTitle) { const answer = String(res.answer); distribution[answer] = (distribution[answer] || 0) + 1; } } } return distribution; } public async exportToCSV(formId: string, filePath: string, filters: SubmissionFilters = {}): Promise<void> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); if (formattedSubmissions.length === 0) { return; } // Flatten the data for CSV const flattenedData = formattedSubmissions.flatMap(sub => sub.responses.map(res => ({ submissionId: sub.submissionId, submittedAt: sub.submittedAt, isCompleted: sub.isCompleted, questionTitle: res.questionTitle, answer: res.answer })) ); const csv = Papa.unparse(flattenedData); // Ensure the directory exists const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, csv); } public async exportToJSON(formId: string, filePath: string, filters: SubmissionFilters = {}): Promise<void> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); // Ensure the directory exists const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, JSON.stringify(formattedSubmissions, null, 2)); } public async search(formId: string, query: string, filters: SubmissionFilters = {}): Promise<FormattedSubmission[]> { const formattedSubmissions = await this.formatForAnalysis(formId, filters); const lowerCaseQuery = query.toLowerCase(); return formattedSubmissions.filter(sub => sub.responses.some(res => String(res.answer).toLowerCase().includes(lowerCaseQuery) ) ); } }

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