/**
* 認証関連ツールハンドラ(複数DB対応版)
*
* FileMaker Data API の認証機能を提供するツール群
* - fm_list_databases: 利用可能なデータベース一覧(新規追加)
* - fm_login: セッション確立
* - fm_logout: セッション終了
* - fm_validate_session: セッション有効性確認
*
* 設計書 3.2.1 セクション準拠
* 実装計画書 Phase 3 準拠
*/
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { ErrorCodes } from '../api/error-mapper.js';
import { getRegistry, isRegistryInitialized } from '../api/session.js';
import { loggers } from '../utils/logger.js';
import type {
ErrorResponse,
ListDatabasesOutput,
LoginInput,
LoginOutput,
LogoutInput,
LogoutOutput,
ValidateSessionInput,
ValidateSessionOutput,
} from '../types/tools.js';
const logger = loggers.tools;
// ============================================================================
// ツール定義
// ============================================================================
/**
* 認証関連ツール定義
*/
export const AUTH_TOOLS: Tool[] = [
{
name: 'fm_list_databases',
description:
'利用可能なFileMakerデータベース一覧を取得します。環境変数で設定されたデータベースのエイリアス、サーバー、データベース名を返します。',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'fm_login',
description:
'指定されたエイリアスのFileMakerサーバーにログインしてセッションを確立します。認証情報は環境変数から自動的に読み込まれます。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
},
required: ['alias'],
},
},
{
name: 'fm_logout',
description: '指定されたエイリアスのセッションを終了します。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
},
required: ['alias'],
},
},
{
name: 'fm_validate_session',
description: '指定されたエイリアスのセッションが有効かどうかを確認します。',
inputSchema: {
type: 'object',
properties: {
alias: {
type: 'string',
description: 'データベースエイリアス(必須)',
},
},
required: ['alias'],
},
},
];
// ============================================================================
// ヘルパー関数
// ============================================================================
/**
* レジストリが初期化されていない場合のエラーレスポンスを生成
*
* @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_list_databases ハンドラ
*
* 環境変数で設定された全てのデータベース接続情報を返す。
* パスワードなどの機密情報は含まない。
*
* @returns データベース一覧
*/
export async function handleListDatabases(): Promise<ListDatabasesOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
const databases = registry.getAllDatabaseInfo();
logger.debug(`Listed ${databases.length} database(s)`);
return {
success: true,
databases,
count: databases.length,
};
}
/**
* fm_login ハンドラ
*
* 指定されたエイリアスのFileMaker Data APIにログインしてセッションを確立する。
* 認証情報は環境変数から取得される。
*
* @param args - エイリアスを含む引数
* @returns ログイン結果(success: true の場合は sessionInfo を含む)
*/
export async function handleLogin(args: LoginInput): Promise<LoginOutput | ErrorResponse> {
// レジストリ初期化チェック
if (!isRegistryInitialized()) {
return createRegistryNotInitializedError();
}
const registry = getRegistry();
// エイリアス存在チェック
if (!registry.hasAlias(args.alias)) {
return createAliasNotFoundError(args.alias, registry.getAliases());
}
// 正規化されたエイリアスを取得(ログ出力用)
const normalizedAlias = args.alias.toUpperCase();
// SessionManagerとFileMakerConfigを取得
const sessionManager = registry.getManager(args.alias);
const config = registry.getFileMakerConfig(args.alias);
logger.debug(`Login attempt for alias: ${normalizedAlias}`);
// ログイン実行
const result = await sessionManager.login(config);
if (!result.success) {
return result as ErrorResponse;
}
return {
success: true,
message: `Login successful to ${normalizedAlias}`,
alias: normalizedAlias,
sessionInfo: result.sessionInfo,
};
}
/**
* fm_logout ハンドラ
*
* 指定されたエイリアスのセッションを終了し、トークンを無効化する。
*
* @param args - エイリアスを含む引数
* @returns ログアウト結果
*/
export async function handleLogout(args: LogoutInput): Promise<LogoutOutput | 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);
logger.debug(`Logout attempt for alias: ${normalizedAlias}`);
const result = await sessionManager.logout();
if (!result.success) {
return result as ErrorResponse;
}
return {
success: true,
message: result.message,
alias: normalizedAlias,
};
}
/**
* fm_validate_session ハンドラ
*
* 指定されたエイリアスのセッションが有効かどうかを確認する。
*
* @param args - エイリアスを含む引数
* @returns セッション有効性情報
*/
export async function handleValidateSession(
args: ValidateSessionInput
): Promise<ValidateSessionOutput | 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.validateSession();
return {
...result,
alias: normalizedAlias,
};
}