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 };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but only mentions automatic field type matching. It fails to disclose critical behavioral aspects such as authentication requirements (implied by appKey but not explained), whether submissions are idempotent (hinted by transactionId but not clarified), error handling, rate limits, or what happens on success/failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose without unnecessary details. Every word contributes to understanding the tool's function, making it appropriately concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 6 parameters, no annotations, and no output schema, the description is inadequate. It lacks information on authentication, idempotency, error handling, return values, and how it differs from sibling tools, leaving significant gaps for an AI agent to understand and use it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so parameters are well-documented in the schema. The description adds minimal value beyond the schema by mentioning 'automatic field type matching,' which loosely relates to the 'autoMatch' parameter but doesn't elaborate on its implications or how it affects data submission.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('submit data') and target resource ('JianDaoYun form'), with the specific capability of 'automatic field type matching' distinguishing it from basic submission. However, it doesn't explicitly differentiate from sibling tools like 'update_form_data' or 'delete_form_data' in terms of when to use each.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'update_form_data' for modifications or 'query_form_data' for retrieval. It mentions automatic field matching but doesn't specify scenarios where this is beneficial or when manual handling might be preferred.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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