submit_form_data
Submit data to JianDaoYun forms with automatic field type matching, enabling efficient data entry and batch processing via app ID, form ID, and optional transaction ID.
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) | |
| autoMatch | No | Whether to automatically match field types (default: true) | |
| data | Yes | The data to submit (single object or array for batch) | |
| formId | Yes | The form ID to submit data to (can be form ID or app ID) | |
| transactionId | No | Optional transaction ID for idempotent submissions |
Implementation Reference
- src/index.ts:569-713 (handler)MCP tool handler implementation for submit_form_data: validates params, resolves form ID, optional smart field mapping, calls client.submitData, handles success/error responses.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 (registration)Registration of the submit_form_data tool in the tools list, including full input schema definition.{ 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/index.ts:272-301 (schema)Input schema definition for the submit_form_data tool.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 (helper)JianDaoYunClient.submitData method: formats data, makes batch/single API POST to JianDaoYun for form submission.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:75-129 (helper)resolveFormId helper: intelligently resolves input to actual formId, handling appId to form fallback with suggestions.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 }; }
- src/index.ts:134-218 (helper)smartFieldMapping helper: fetches form widgets and maps user-friendly field names to backend field names using multiple matching 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: [] }; } }