Skip to main content
Glama
cheungxin

JianDaoYun MCP Server

by cheungxin

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
NameRequiredDescriptionDefault
appIdYesThe JianDaoYun application ID
appKeyNoThe JianDaoYun application key (API key) (optional, will use JIANDAOYUN_APP_KEY from environment if not provided)
formIdYesThe form ID to submit data to (can be form ID or app ID)
dataYesThe data to submit (single object or array for batch)
autoMatchNoWhether to automatically match field types (default: true)
transactionIdNoOptional transaction ID for idempotent submissions

Implementation Reference

  • 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), }, ], }; } }
  • 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'], },
  • 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; } }
  • 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: [] }; } }
  • 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 }; }

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/cheungxin/jiandaoyun-mcp-server'

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