submit_form_data
Submit data to JianDaoYun forms with automatic field type matching for accurate data entry and batch processing.
Instructions
Submit data to a JianDaoYun form with automatic field type matching
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| appId | Yes | The JianDaoYun application ID | |
| appKey | No | The JianDaoYun application key (API key) (optional, will use JIANDAOYUN_APP_KEY from environment if not provided) | |
| formId | Yes | The form ID to submit data to (can be form ID or app ID) | |
| data | Yes | The data to submit (single object or array for batch) | |
| autoMatch | No | Whether to automatically match field types (default: true) | |
| transactionId | No | Optional transaction ID for idempotent submissions |
Implementation Reference
- src/index.ts:569-713 (handler)MCP tool handler for 'submit_form_data': validates inputs, resolves form ID intelligently (app ID or form ID), applies optional smart field mapping, submits data (single or batch) via JianDaoYunClient, handles errors with detailed feedback.case 'submit_form_data': { const { formId, data, autoMatch = true, transactionId } = args as { formId: string; data: FormData | FormData[]; autoMatch?: boolean; transactionId?: string; }; const { appId, appKey, baseUrl } = getDefaultParams(args); // 验证必需参数 if (!appKey) { throw new Error('appKey is required. Please set JIANDAOYUN_APP_KEY in MCP server configuration.'); } if (!appId) { throw new Error('appId is required. Please provide it as parameter.'); } // 创建客户端实例 const jdyClient = new JianDaoYunClient({ appId, appKey, baseUrl }); let resolved: any; let processedData = data; let fieldMappingInfo = null; let submitResult: any; let errorDetails: any = null; try { resolved = await resolveFormId(formId, appKey); } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: true, message: `表单ID解析失败: ${error instanceof Error ? error.message : String(error)}`, formUsed: null, appId: appId, originalData: data, processedData: null }, null, 2), }, ], }; } if (autoMatch) { try { // 使用智能字段映射 if (Array.isArray(data)) { const mappedResults = []; for (const item of data) { const mappingResult = await smartFieldMapping(resolved.formId, item, appKey, resolved.appId || appId); mappedResults.push(mappingResult.mappedData); if (!fieldMappingInfo) fieldMappingInfo = mappingResult.fieldInfo; } processedData = mappedResults; } else { const mappingResult = await smartFieldMapping(resolved.formId, data, appKey, resolved.appId || appId); processedData = mappingResult.mappedData; fieldMappingInfo = mappingResult.fieldInfo; } } catch (error) { console.log('字段映射失败,使用原始数据:', error instanceof Error ? error.message : String(error)); } } try { submitResult = await jdyClient.submitData({ formId: resolved.formId, data: processedData, transactionId, }); let message = `成功提交 ${Array.isArray(data) ? data.length : 1} 条记录`; if (resolved.suggestions && resolved.suggestions.length > 1) { message += `\n注意: 检测到应用下有多个表单,已使用第一个表单进行提交`; } return { content: [ { type: 'text', text: JSON.stringify({ success: true, result: submitResult, message, formUsed: resolved.formId, appId: resolved.appId || appId, originalData: data, processedData, fieldMapping: fieldMappingInfo }, null, 2), }, ], }; } catch (error) { // 返回详细的错误信息而不是抛出错误 errorDetails = { success: false, error: true, message: '提交表单数据失败', formUsed: resolved?.formId || null, appId: appId, originalData: data, processedData: processedData }; if (error && typeof error === 'object' && 'response' in error && (error as any).response?.data) { // 简道云API错误 const apiError = (error as any).response.data; errorDetails.apiError = { code: apiError.code, message: apiError.msg, details: apiError }; errorDetails.message = `API错误 ${apiError.code}: ${apiError.msg}`; // 根据错误代码提供更详细的说明 if (apiError.code === 3005) { errorDetails.suggestion = '请求参数无效,请检查表单ID、字段名称和数据格式是否正确'; } else if (apiError.code === 3000) { errorDetails.suggestion = '表单不存在,请检查表单ID是否正确'; } else if (apiError.code === 4000) { errorDetails.suggestion = '数据提交失败,请检查字段值是否符合表单要求'; } } else if (error instanceof Error) { errorDetails.message = error.message; } return { content: [ { type: 'text', text: JSON.stringify(errorDetails, null, 2), }, ], }; } }
- src/index.ts:269-302 (schema)Tool registration including name, description, and input schema definition for 'submit_form_data'.{ name: 'submit_form_data', description: 'Submit data to a JianDaoYun form with automatic field type matching', inputSchema: { type: 'object', properties: { appId: { type: 'string', description: 'The JianDaoYun application ID', }, appKey: { type: 'string', description: 'The JianDaoYun application key (API key) (optional, will use JIANDAOYUN_APP_KEY from environment if not provided)', }, formId: { type: 'string', description: 'The form ID to submit data to (can be form ID or app ID)', }, data: { type: ['object', 'array'], description: 'The data to submit (single object or array for batch)', }, autoMatch: { type: 'boolean', description: 'Whether to automatically match field types (default: true)', default: true, }, transactionId: { type: 'string', description: 'Optional transaction ID for idempotent submissions', }, }, required: ['appId', 'formId', 'data'], },
- src/client.ts:100-161 (handler)Core implementation of data submission to JianDaoYun API: handles single/batch create, formats data structure, makes POST request to /v5/app/entry/data/create or batch_create, processes response and errors.async submitData(options: SubmitDataOptions): Promise<any> { try { const dataArray = Array.isArray(options.data) ? options.data : [options.data]; if (dataArray.length > 100) { throw new Error('Batch submission limit is 100 records'); } const isBatch = dataArray.length > 1; const endpoint = isBatch ? '/v5/app/entry/data/batch_create' : '/v5/app/entry/data/create'; const requestData: any = { app_id: this.config.appId, entry_id: options.formId }; if (isBatch) { requestData.data_list = dataArray.map(record => this.formatDataForSubmission(record)); } else { requestData.data = this.formatDataForSubmission(dataArray[0]); } if (options.transactionId) { requestData.transaction_id = options.transactionId; } if (options.dataCreator) { requestData.data_creator = options.dataCreator; } if (options.isStartWorkflow !== undefined) { requestData.is_start_workflow = options.isStartWorkflow; } if (options.isStartTrigger !== undefined) { requestData.is_start_trigger = options.isStartTrigger; } console.log('提交请求数据:', JSON.stringify(requestData, null, 2)); const response = await this.axios.post<ApiResponse>(endpoint, requestData); console.log('API响应:', JSON.stringify(response.data, null, 2)); if (response.data.code !== undefined && response.data.code !== 0) { // 创建包含详细错误信息的错误对象 const error = new Error(`Failed to submit data: ${response.data.msg || 'Unknown error'}`); (error as any).response = { data: response.data }; throw error; } return response.data.data || response.data; } catch (error) { console.error('submitData错误详情:', error); if (axios.isAxiosError(error)) { console.error('Axios错误响应:', error.response?.data); // 创建包含详细错误信息的错误对象,但不重新抛出 const enhancedError = new Error(`API request failed: ${error.response?.data?.msg || error.message}`); (enhancedError as any).response = error.response; throw enhancedError; } throw error; } }
- src/index.ts:134-218 (helper)Smart field mapping helper: fetches form widgets, matches user-provided field names to backend field names using exact, partial, name, and common mapping strategies.async function smartFieldMapping(formId: string, userData: any, appKey: string, appId?: string): Promise<any> { try { // 获取表单字段信息 const response = await axios.post( `${process.env.JIANDAOYUN_BASE_URL || 'https://api.jiandaoyun.com'}/api/v5/app/entry/widget/list`, { app_id: appId, entry_id: formId }, { headers: { 'Authorization': `Bearer ${appKey}`, 'Content-Type': 'application/json' } } ); // API返回格式: {widgets: [...], sysWidgets: ...} const widgets = response.data?.widgets || []; const mappedData: any = {}; // 为每个用户输入的字段找到对应的后台字段名 for (const [userKey, value] of Object.entries(userData)) { let matchedField = null; // 1. 精确匹配label matchedField = widgets.find((w: any) => w.label === userKey); // 2. 如果没找到,尝试包含匹配 if (!matchedField) { matchedField = widgets.find((w: any) => w.label?.includes(userKey) || userKey.includes(w.label || '') ); } // 3. 如果还没找到,尝试name匹配 if (!matchedField) { matchedField = widgets.find((w: any) => w.name === userKey); } // 4. 常见字段名映射 if (!matchedField) { const commonMappings: { [key: string]: string[] } = { '姓名': ['name', 'username', '用户名', '姓名'], '电话': ['phone', 'tel', 'mobile', '手机', '电话'], '邮箱': ['email', 'mail', '邮件', '邮箱'], '地址': ['address', '地址', '住址'], '备注': ['remark', 'note', 'comment', '备注', '说明'] }; for (const [cnName, enNames] of Object.entries(commonMappings)) { if (userKey === cnName || enNames.includes(userKey)) { matchedField = widgets.find((w: any) => enNames.some(en => w.label?.includes(en) || w.name?.includes(en)) ); if (matchedField) break; } } } if (matchedField) { mappedData[matchedField.name] = value; console.log(`字段映射: "${userKey}" -> "${matchedField.name}" (${matchedField.label})`); } else { // 如果找不到匹配字段,保持原样 mappedData[userKey] = value; console.log(`字段未映射: "${userKey}" 保持原样`); } } return { mappedData, fieldInfo: widgets.map((w: any) => ({ name: w.name, label: w.label, type: w.type, required: w.required })) }; } catch (error) { console.error('字段映射失败:', error); // 如果获取字段信息失败,返回原始数据 return { mappedData: userData, fieldInfo: [] }; } }
- src/index.ts:75-129 (helper)Resolves input ID to form ID: detects if it's direct form ID, app ID (fetches forms, picks first or suggests), or uses as-is.async function resolveFormId(inputId: string, appKey: string): Promise<{ formId: string; appId?: string; suggestions?: string[] }> { // 如果输入看起来像表单ID(通常24位字符),直接返回 if (inputId.length === 24 && /^[a-f0-9]{24}$/i.test(inputId)) { return { formId: inputId }; } // 尝试作为应用ID处理 const appList = await getAppList(appKey); const targetApp = appList.find(app => app.app_id === inputId); if (targetApp) { // 这是一个应用ID,需要获取其下的表单列表 try { const response = await axios.post( `${process.env.JIANDAOYUN_BASE_URL || 'https://api.jiandaoyun.com'}/api/v1/app/${inputId}/entry/list`, {}, { headers: { 'Authorization': `Bearer ${appKey}`, 'Content-Type': 'application/json' } } ); const forms = response.data || []; if (forms.length === 0) { throw new Error(`应用 "${targetApp.name}" 下没有找到可用的表单`); } // 如果只有一个表单,直接返回 if (forms.length === 1) { return { formId: forms[0].entry_id || forms[0]._id, appId: inputId }; } // 多个表单时,返回建议列表 const suggestions = forms.map((form: any) => `${form.name || '未命名表单'} (${form.entry_id || form._id})` ); return { formId: forms[0].entry_id || forms[0]._id, // 默认返回第一个 appId: inputId, suggestions }; } catch (error) { throw new Error(`无法获取应用 "${targetApp.name}" 下的表单列表: ${error instanceof Error ? error.message : '未知错误'}`); } } // 既不是标准表单ID也不是已知应用ID,直接尝试使用 return { formId: inputId }; }