preference.hear
Collect user preferences for web design by presenting samples and updating profiles based on feedback to personalize recommendations.
Instructions
ユーザー嗜好ヒアリングセッション。feedbackなしでサンプル提示、feedbackありで嗜好プロファイル更新。User preference hearing session. Present samples without feedback, update profile with feedback.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| profile_id | No | プロファイルID(省略時は新規作成) / Profile ID (create new if omitted) | |
| feedback | No | フィードバック配列(存在する場合はモードB) / Feedback array (Mode B if present) | |
| preference_text | No | 嗜好テキスト(Claudeエージェントが自然言語フィードバックから生成、10-1000文字) / Preference text (generated by Claude agent from natural language feedback, 10-1000 chars) | |
| limit | No | 返却サンプル数(デフォルト1件) / Number of samples to return (default 1) | |
| offset | No | スキップ数 / Number of samples to skip | |
| exclude_ids | No | 除外するサンプルID配列(既評価済み) / Sample IDs to exclude (already evaluated) |
Implementation Reference
- The main handler function for the 'preference.hear' tool, which processes either a request for design samples (Mode A) or feedback submission (Mode B) by delegating to the Preference Service.
export async function preferenceHearHandler(input: unknown): Promise<PreferenceHearOutput> { if (isDevelopment()) { logger.info("[MCP Tool] preference.hear called", { hasFeedback: !!(input as Record<string, unknown>)?.feedback, }); } // 入力バリデーション / Input validation let validated: PreferenceHearInput; try { validated = preferenceHearInputSchema.parse(input); } catch (error) { if (error instanceof ZodError) { const errorMessage = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", "); logger.warn("[MCP Tool] preference.hear validation error", { errors: error.errors, }); return { success: false, error: { code: PREFERENCE_MCP_ERROR_CODES.VALIDATION_ERROR, message: `Validation error: ${errorMessage}`, }, }; } throw error; } // サービスファクトリーチェック / Service factory check if (!preferenceServiceFactory) { logger.warn("[MCP Tool] preference.hear service factory not set"); return { success: false, error: { code: PREFERENCE_MCP_ERROR_CODES.SERVICE_UNAVAILABLE, message: "Preference service is not available", }, }; } const service = preferenceServiceFactory(); try { // モード判定: feedbackの有無で分岐 // Mode determination: branch on feedback presence if ( validated.feedback && validated.feedback.length > 0 && validated.preference_text && validated.profile_id ) { // モードB: フィードバック受信 / Mode B: Receive feedback if (isDevelopment()) { logger.info("[MCP Tool] preference.hear Mode B: processing feedback", { feedbackCount: validated.feedback.length, preferenceTextLength: validated.preference_text.length, }); } const result = await service.processFeedback( validated.profile_id, validated.feedback, validated.preference_text ); if (isDevelopment()) { logger.info("[MCP Tool] preference.hear Mode B completed", { profileId: truncateId(result.profile_id), interactionCount: result.interaction_count, }); } return { success: true, data: result, }; } else { // モードA: サンプル提示 / Mode A: Present samples if (isDevelopment()) { logger.info("[MCP Tool] preference.hear Mode A: getting samples", { profileId: truncateId(validated.profile_id), limit: validated.limit, offset: validated.offset, excludeIds: validated.exclude_ids, }); } const result = await service.getSamples({ profileId: validated.profile_id, limit: validated.limit, offset: validated.offset, excludeIds: validated.exclude_ids, }); if (isDevelopment()) { logger.info("[MCP Tool] preference.hear Mode A completed", { profileId: truncateId(result.profile_id), sampleCount: result.samples.length, }); } return { success: true, data: result, }; } } catch (error) { const errorInstance = error instanceof Error ? error : new Error(String(error)); const errorCode = mapErrorToCode(errorInstance); // 全環境でログ出力(isDevelopmentガードなし) // Log in all environments (no isDevelopment guard) logger.warn("[MCP Tool] preference.hear error", { code: errorCode, error: errorInstance.message, }); return { success: false, error: { code: errorCode, message: sanitizeErrorMessage(errorCode), }, }; } } - apps/mcp-server/src/tools/preference/hear.tool.ts:421-496 (registration)The definition of the 'preference.hear' tool, including its name, description, and input schema.
export const preferenceHearToolDefinition = { name: "preference.hear", description: "ユーザー嗜好ヒアリングセッション。feedbackなしでサンプル提示、feedbackありで嗜好プロファイル更新。" + "User preference hearing session. Present samples without feedback, update profile with feedback.", annotations: { title: "Preference Hearing", readOnlyHint: false, idempotentHint: false, openWorldHint: false, }, inputSchema: { type: "object" as const, properties: { profile_id: { type: "string", format: "uuid", description: "プロファイルID(省略時は新規作成) / Profile ID (create new if omitted)", }, feedback: { type: "array", description: "フィードバック配列(存在する場合はモードB) / Feedback array (Mode B if present)", items: { type: "object", properties: { sample_id: { type: "string", format: "uuid", description: "サンプルID / Sample ID", }, rating: { type: "string", enum: ["positive", "negative", "neutral"], description: "評価 / Rating", }, comment: { type: "string", maxLength: 500, description: "コメント(最大500文字) / Comment (max 500 characters)", }, }, required: ["sample_id", "rating"], }, }, preference_text: { type: "string", minLength: 10, maxLength: 1000, description: "嗜好テキスト(Claudeエージェントが自然言語フィードバックから生成、10-1000文字) / " + "Preference text (generated by Claude agent from natural language feedback, 10-1000 chars)", }, limit: { type: "integer", minimum: 1, maximum: 10, default: 1, description: "返却サンプル数(デフォルト1件) / Number of samples to return (default 1)", }, offset: { type: "integer", minimum: 0, default: 0, description: "スキップ数 / Number of samples to skip", }, exclude_ids: { type: "array", items: { type: "string", format: "uuid" }, maxItems: 50, description: "除外するサンプルID配列(既評価済み) / Sample IDs to exclude (already evaluated)", }, }, }, };