/**
* メタデータ関連ツールハンドラ(複数DB対応版)
*
* FileMaker Data API のメタデータ取得機能を提供するツール群
* - fm_get_layouts: レイアウト一覧取得
* - fm_get_layout_metadata: レイアウトのフィールド/ポータル情報取得
* - fm_get_scripts: スクリプト一覧取得
* - fm_list_value_lists: 値一覧取得
*
* 設計書 3.2.2 セクション準拠
* 実装計画書 Phase 3 準拠
*/
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { isErrorResponse } from '../api/client.js';
import { ErrorCodes, createErrorResponse } from '../api/error-mapper.js';
import { getRegistry, isRegistryInitialized } from '../api/session.js';
import type {
FMLayoutMetadataResponse,
FMLayoutsResponse,
FMScriptsResponse,
} from '../types/filemaker.js';
import type {
ErrorResponse,
GetLayoutMetadataInput,
GetLayoutMetadataOutput,
GetLayoutsInput,
GetLayoutsOutput,
GetScriptsInput,
GetScriptsOutput,
ListValueListsInput,
ListValueListsOutput,
} from '../types/tools.js';
// ============================================================================
// ツール定義
// ============================================================================
/**
* メタデータ関連ツール定義
*/
export const METADATA_TOOLS: Tool[] = [
{
name: 'fm_get_layouts',
description: '指定されたエイリアスのデータベース内のすべてのレイアウト一覧を取得します。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
},
required: ['alias'],
},
},
{
name: 'fm_get_layout_metadata',
description: '指定されたレイアウトのフィールド定義、ポータル情報、値一覧を取得します。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
layout: {
type: 'string',
description: 'レイアウト名',
},
},
required: ['alias', 'layout'],
},
},
{
name: 'fm_get_scripts',
description: '指定されたエイリアスのデータベース内のすべてのスクリプト一覧を取得します。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
},
required: ['alias'],
},
},
{
name: 'fm_list_value_lists',
description:
'指定されたレイアウトで利用可能な値一覧(Value Lists)を取得します。フィールドに設定されたドロップダウン選択肢などを確認できます。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
layout: {
type: 'string',
description: 'レイアウト名',
},
},
required: ['alias', 'layout'],
},
},
];
// ============================================================================
// ヘルパー関数
// ============================================================================
/**
* レジストリが初期化されていない場合のエラーレスポンスを生成
*
* @returns エラーレスポンス
*/
function createRegistryNotInitializedError(): ErrorResponse {
return {
success: false,
error: {
code: ErrorCodes.INTERNAL_CONFIG_ERROR,
message: 'Server not initialized',
details: 'SessionManagerRegistry has not been initialized. This is a server startup error.',
retryable: false,
},
};
}
/**
* エイリアスが存在しない場合のエラーレスポンスを生成
*
* @param alias - 指定されたエイリアス
* @param availableAliases - 利用可能なエイリアス一覧
* @returns エラーレスポンス
*/
function createAliasNotFoundError(alias: string, availableAliases: string[]): ErrorResponse {
return {
success: false,
error: {
code: ErrorCodes.INTERNAL_CONFIG_ERROR,
message: `Database alias "${alias}" not found`,
details: `Available aliases: ${availableAliases.join(', ')}`,
retryable: false,
},
};
}
// ============================================================================
// ツールハンドラ
// ============================================================================
/**
* fm_get_layouts ハンドラ
*
* 指定されたエイリアスのデータベース内のすべてのレイアウト一覧を取得する。
* レイアウト名とフォルダかどうかの情報を返す。
*
* @param args - エイリアスを含む引数
* @returns レイアウト一覧(name, isFolder を含む配列)
*/
export async function handleGetLayouts(
args: GetLayoutsInput
): Promise<GetLayoutsOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
// エイリアス存在チェック
if (!registry.hasAlias(args.alias)) {
return createAliasNotFoundError(args.alias, registry.getAliases());
}
const normalizedAlias = args.alias.toUpperCase();
const sessionManager = registry.getManager(args.alias);
const result = await sessionManager.withSession(async (client, token) => {
return client.get<FMLayoutsResponse>('/layouts', token);
});
// エラーレスポンスの場合はそのまま返す
if (isErrorResponse(result)) {
return result;
}
// 型ガードでAPIレスポンスを確認
if ('data' in result) {
return {
success: true,
alias: normalizedAlias,
layouts: result.data.layouts.map((layout: { name: string; isFolder?: boolean }) => ({
name: layout.name,
isFolder: layout.isFolder,
})),
};
}
return createErrorResponse(500, undefined, 'Unexpected response format');
}
/**
* fm_get_layout_metadata ハンドラ
*
* 指定されたレイアウトのフィールド定義、ポータル情報、値一覧を取得する。
*
* @param args - エイリアスとレイアウト名を含む引数
* @returns レイアウトメタデータ(fieldMetaData, portalMetaData, valueLists)
*/
export async function handleGetLayoutMetadata(
args: GetLayoutMetadataInput
): Promise<GetLayoutMetadataOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
// エイリアス存在チェック
if (!registry.hasAlias(args.alias)) {
return createAliasNotFoundError(args.alias, registry.getAliases());
}
const normalizedAlias = args.alias.toUpperCase();
const sessionManager = registry.getManager(args.alias);
const result = await sessionManager.withSession(async (client, token) => {
return client.get<FMLayoutMetadataResponse>(
`/layouts/${encodeURIComponent(args.layout)}`,
token
);
});
// エラーレスポンスの場合はそのまま返す
if (isErrorResponse(result)) {
return result;
}
// 型ガードでAPIレスポンスを確認
if ('data' in result) {
return {
success: true,
alias: normalizedAlias,
layout: args.layout,
fieldMetaData: result.data.fieldMetaData,
portalMetaData: result.data.portalMetaData,
valueLists: result.data.valueLists,
};
}
return createErrorResponse(500, undefined, 'Unexpected response format');
}
/**
* fm_get_scripts ハンドラ
*
* 指定されたエイリアスのデータベース内のすべてのスクリプト一覧を取得する。
* スクリプト名とフォルダかどうかの情報を返す。
*
* @param args - エイリアスを含む引数
* @returns スクリプト一覧(name, isFolder を含む配列)
*/
export async function handleGetScripts(
args: GetScriptsInput
): Promise<GetScriptsOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
// エイリアス存在チェック
if (!registry.hasAlias(args.alias)) {
return createAliasNotFoundError(args.alias, registry.getAliases());
}
const normalizedAlias = args.alias.toUpperCase();
const sessionManager = registry.getManager(args.alias);
const result = await sessionManager.withSession(async (client, token) => {
return client.get<FMScriptsResponse>('/scripts', token);
});
// エラーレスポンスの場合はそのまま返す
if (isErrorResponse(result)) {
return result;
}
// 型ガードでAPIレスポンスを確認
if ('data' in result) {
return {
success: true,
alias: normalizedAlias,
scripts: result.data.scripts.map((script: { name: string; isFolder?: boolean }) => ({
name: script.name,
isFolder: script.isFolder,
})),
};
}
return createErrorResponse(500, undefined, 'Unexpected response format');
}
/**
* fm_list_value_lists ハンドラ
*
* 指定されたレイアウトで利用可能な値一覧(Value Lists)を取得する。
* レイアウトメタデータ API を使用して valueLists 部分のみを抽出して返す。
*
* 用途:
* - フィールドに設定されたドロップダウン選択肢の確認
* - 入力補完やバリデーションのための選択肢取得
*
* @param args - エイリアスとレイアウト名を含む引数
* @returns 値一覧のマップ(値一覧名 → 値の配列)
*/
export async function handleListValueLists(
args: ListValueListsInput
): Promise<ListValueListsOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
// エイリアス存在チェック
if (!registry.hasAlias(args.alias)) {
return createAliasNotFoundError(args.alias, registry.getAliases());
}
const normalizedAlias = args.alias.toUpperCase();
const sessionManager = registry.getManager(args.alias);
// レイアウトメタデータAPIを使用して値一覧を取得
const result = await sessionManager.withSession(async (client, token) => {
return client.get<FMLayoutMetadataResponse>(
`/layouts/${encodeURIComponent(args.layout)}`,
token
);
});
// エラーレスポンスの場合はそのまま返す
if (isErrorResponse(result)) {
return result;
}
// 型ガードでAPIレスポンスを確認
if ('data' in result) {
// valueLists はすでに Record<string, string[]> 形式
// そのまま返却する(データがない場合は空オブジェクト)
const valueLists = result.data.valueLists ?? {};
return {
success: true,
alias: normalizedAlias,
layout: args.layout,
valueLists,
};
}
return createErrorResponse(500, undefined, 'Unexpected response format');
}