Skip to main content
Glama

Kintone MCP Server

by r3-yamauchi
LayoutValidator.js15.8 kB
// src/repositories/validators/LayoutValidator.js import { LoggingUtils } from '../../utils/LoggingUtils.js'; function logLayoutWarning(message, metadata = {}) { LoggingUtils.warn('layout_validator', 'warning', { message, ...metadata }); } // レイアウト要素のタイプを検証する関数 export function validateLayoutElementType(element, allowedTypes) { // typeプロパティが指定されていない場合は自動的に補完 if (!element.type) { // デフォルトのタイプを設定 if (allowedTypes && allowedTypes.length > 0) { element.type = allowedTypes[0]; // 許可されているタイプの最初のものを使用 logLayoutWarning(`レイアウト要素に type プロパティが指定されていません。自動的に "${element.type}" を設定します。`); } else { // 許可されているタイプが指定されていない場合は "ROW" をデフォルトとして使用 element.type = "ROW"; logLayoutWarning('レイアウト要素に type プロパティが指定されていません。自動的に "ROW" を設定します。'); } } if (allowedTypes && !allowedTypes.includes(element.type)) { throw new Error( `レイアウト要素のタイプが不正です: "${element.type}"\n` + `指定可能な値: ${allowedTypes.join(', ')}` ); } return true; } // ROW要素を検証する関数 export function validateRowElement(row) { validateLayoutElementType(row, ["ROW"]); // fieldsプロパティが指定されていない場合は自動的に補完 if (!row.fields) { row.fields = []; logLayoutWarning('ROW要素に fields プロパティが指定されていません。空の配列を設定します。'); } // fieldsプロパティが配列でない場合は配列に変換 if (!Array.isArray(row.fields)) { logLayoutWarning('ROW要素の fields プロパティが配列ではありません。自動的に配列に変換します。'); row.fields = [row.fields]; } // GROUP要素が含まれる場合は、それが唯一の要素であることを確認 const groupFields = row.fields.filter(field => field.type === "GROUP"); if (groupFields.length > 0 && row.fields.length > groupFields.length) { throw new Error('GROUP要素を含む行には他のフィールドを配置できません。kintoneの仕様により、グループフィールドはトップレベルに配置する必要があります。'); } // ROW要素内にSUBTABLE要素が含まれていないことを確認 const subtableFields = row.fields.filter(field => field.type === "SUBTABLE"); if (subtableFields.length > 0) { throw new Error('ROW要素内にはSUBTABLE要素を配置できません。kintoneの仕様により、テーブルはトップレベルに配置する必要があります。'); } // 各フィールド要素を検証 row.fields.forEach((field, index) => { // フィールド要素のタイプは実際のフィールドタイプ("NUMBER"など)または特殊タイプ("LABEL"など) // 特殊タイプのみを検証し、実際のフィールドタイプは許容する if (["LABEL", "SPACER", "HR", "REFERENCE_TABLE"].includes(field.type)) { validateLayoutElementType(field, ["LABEL", "SPACER", "HR", "REFERENCE_TABLE"]); } // フィールドタイプに応じた検証 if (field.type === "LABEL") { if (!field.value) { throw new Error(`ROW要素内のLABEL要素[${index}]には value プロパティが必須です。`); } } else if (field.type === "REFERENCE_TABLE") { if (!field.code) { throw new Error(`ROW要素内のREFERENCE_TABLE要素[${index}]には code プロパティが必須です。`); } } }); return true; } // GROUP要素を検証する関数 export function validateGroupElement(group) { validateLayoutElementType(group, ["GROUP"]); if (!group.code) { throw new Error('GROUP要素には code プロパティが必須です。'); } // fieldsプロパティが指定されている場合は明確なエラーメッセージを表示 if (group.fields !== undefined) { throw new Error( `GROUP要素 "${group.code}" には fields プロパティではなく layout プロパティを使用してください。\n` + `GROUP要素の正しい構造:\n` + `{\n` + ` "type": "GROUP",\n` + ` "code": "グループコード",\n` + ` "label": "グループ名",\n` + ` "layout": [] // ここに行要素を配置\n` + `}` ); } // フィールド追加時には label プロパティが必須だが、 // レイアウト更新時には label プロパティを省略する必要があるため、 // ここではチェックを行わない // openGroup プロパティが指定されていない場合は true を設定 // kintoneの仕様では省略すると false になるが、このMCP Serverでは明示的に true を設定 if (group.openGroup === undefined) { group.openGroup = true; logLayoutWarning(`GROUP要素 "${group.code}" の openGroup プロパティが指定されていません。自動的に true を設定します。`, { groupCode: group.code }); } // layout プロパティが存在しない場合は空の配列を設定 if (group.layout === undefined) { group.layout = []; logLayoutWarning(`GROUP要素 "${group.code}" に layout プロパティが指定されていません。空の配列を設定します。`, { groupCode: group.code }); } // layout プロパティが配列でない場合は配列に変換 if (!Array.isArray(group.layout)) { logLayoutWarning(`GROUP要素 "${group.code}" の layout プロパティが配列ではありません。自動的に配列に変換します。`, { groupCode: group.code }); group.layout = [group.layout]; } // グループ内の各レイアウト要素を検証 group.layout.forEach((item, index) => { validateLayoutElementType(item, ["ROW"]); // グループ内にはSUBTABLEやGROUPを配置できない制約を追加 if (item.type === "SUBTABLE") { throw new Error(`GROUP要素 "${group.code}" 内にはSUBTABLE要素を配置できません。kintoneの仕様により、グループフィールド内にテーブルを入れることはできません。`); } if (item.type === "GROUP") { throw new Error(`GROUP要素 "${group.code}" 内にはGROUP要素を配置できません。kintoneの仕様により、グループフィールド内にグループフィールドを入れることはできません。`); } // 要素タイプに応じた検証 if (item.type === "ROW") { validateRowElement(item); } else { throw new Error(`GROUP要素 "${group.code}" 内には ROW 要素のみ配置できます。`); } }); return true; } // SUBTABLE要素を検証する関数 export function validateSubtableElement(subtable) { validateLayoutElementType(subtable, ["SUBTABLE"]); if (!subtable.code) { throw new Error('SUBTABLE要素には code プロパティが必須です。'); } // テーブル内のフィールドを検証(テーブルのフィールド定義を取得できる場合) if (subtable.fields) { // GROUP要素がテーブル内に含まれていないことを確認 const hasGroupField = Object.values(subtable.fields).some(field => field.type === "GROUP"); if (hasGroupField) { throw new Error('SUBTABLE要素内にはGROUP要素を配置できません。kintoneの仕様により、グループフィールドはテーブル化できません。'); } } return true; } // フォームレイアウト全体を検証する関数 export function validateFormLayout(layout) { if (!Array.isArray(layout)) { throw new Error('フォームレイアウトは配列形式で指定する必要があります。'); } // 各レイアウト要素を検証 layout.forEach((item, index) => { validateLayoutElementType(item, ["ROW", "GROUP", "SUBTABLE"]); // 要素タイプに応じた検証 if (item.type === "ROW") { validateRowElement(item); } else if (item.type === "GROUP") { validateGroupElement(item); } else if (item.type === "SUBTABLE") { validateSubtableElement(item); } }); return true; } // フィールドサイズを検証する関数 export function validateFieldSize(size) { if (!size) return true; if (typeof size !== 'object') { throw new Error('size はオブジェクト形式で指定する必要があります。'); } // 幅の検証 if (size.width !== undefined) { // 文字列形式の場合は数値に変換 if (typeof size.width === 'string') { // 数値以外の文字(単位など)を除去して数値のみを抽出 const numericPart = size.width.replace(/[^0-9.]/g, ''); const numWidth = Number(numericPart); if (isNaN(numWidth)) { throw new Error('size.width は数値または数値形式の文字列で指定する必要があります。'); } // 元の文字列に数値以外の文字が含まれていた場合は警告 if (size.width !== numericPart) { logLayoutWarning(`size.width に単位または数値以外の文字が含まれています。kintoneでは単位の指定はできません。数値部分のみを使用します: "${size.width}" → ${numWidth}`); } else { logLayoutWarning(`size.width が文字列形式で指定されています。自動的に数値に変換しました: "${size.width}" → ${numWidth}`); } // 数値に変換して置き換え size.width = numWidth; } else if (typeof size.width !== 'number') { throw new Error('size.width は数値または数値形式の文字列で指定する必要があります。'); } // 有効な範囲かチェック if (size.width <= 0) { throw new Error('size.width は正の数値で指定する必要があります。'); } } // 高さの検証 if (size.height !== undefined) { // 文字列形式の場合は数値に変換 if (typeof size.height === 'string') { // 数値以外の文字(単位など)を除去して数値のみを抽出 const numericPart = size.height.replace(/[^0-9.]/g, ''); const numHeight = Number(numericPart); if (isNaN(numHeight)) { throw new Error('size.height は数値または数値形式の文字列で指定する必要があります。'); } // 元の文字列に数値以外の文字が含まれていた場合は警告 if (size.height !== numericPart) { logLayoutWarning(`size.height に単位または数値以外の文字が含まれています。kintoneでは単位の指定はできません。数値部分のみを使用します: "${size.height}" → ${numHeight}`); } else { logLayoutWarning(`size.height が文字列形式で指定されています。自動的に数値に変換しました: "${size.height}" → ${numHeight}`); } // 数値に変換して置き換え size.height = numHeight; } else if (typeof size.height !== 'number') { throw new Error('size.height は数値または数値形式の文字列で指定する必要があります。'); } // 有効な範囲かチェック if (size.height <= 0) { throw new Error('size.height は正の数値で指定する必要があります。'); } } // 内部高さの検証 if (size.innerHeight !== undefined) { // 文字列形式の場合は数値に変換 if (typeof size.innerHeight === 'string') { // 数値以外の文字(単位など)を除去して数値のみを抽出 const numericPart = size.innerHeight.replace(/[^0-9.]/g, ''); const numInnerHeight = Number(numericPart); if (isNaN(numInnerHeight)) { throw new Error('size.innerHeight は数値または数値形式の文字列で指定する必要があります。'); } // 元の文字列に数値以外の文字が含まれていた場合は警告 if (size.innerHeight !== numericPart) { logLayoutWarning(`size.innerHeight に単位または数値以外の文字が含まれています。kintoneでは単位の指定はできません。数値部分のみを使用します: "${size.innerHeight}" → ${numInnerHeight}`); } else { logLayoutWarning(`size.innerHeight が文字列形式で指定されています。自動的に数値に変換しました: "${size.innerHeight}" → ${numInnerHeight}`); } // 数値に変換して置き換え size.innerHeight = numInnerHeight; } else if (typeof size.innerHeight !== 'number') { throw new Error('size.innerHeight は数値または数値形式の文字列で指定する必要があります。'); } // 有効な範囲かチェック if (size.innerHeight <= 0) { throw new Error('size.innerHeight は正の数値で指定する必要があります。'); } } return true; } // レイアウト要素の位置指定を検証する関数 export function validateElementPosition(position) { if (!position) return true; if (typeof position !== 'object') { throw new Error('position はオブジェクト形式で指定する必要があります。'); } // インデックス指定の検証 if (position.index !== undefined) { if (typeof position.index !== 'number' || position.index < 0) { throw new Error('position.index は 0 以上の整数で指定する必要があります。'); } // グループ内への挿入の場合 if (position.type === "GROUP") { if (!position.groupCode) { throw new Error('グループ内への挿入には position.groupCode の指定が必須です。'); } } } // 前後指定の検証 else if (position.after !== undefined || position.before !== undefined) { if (position.after !== undefined && position.before !== undefined) { throw new Error('position.after と position.before は同時に指定できません。'); } const targetCode = position.after || position.before; if (!targetCode || typeof targetCode !== 'string') { throw new Error('position.after または position.before には有効なフィールドコードを指定する必要があります。'); } } return true; }

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/r3-yamauchi/kintone-mcp-server'

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