@vadenai/mcp-server 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,4 +12,7 @@ export declare class VadenMcpApiClient {
12
12
  private headers;
13
13
  private buildUrl;
14
14
  get<T>(path: string): Promise<T>;
15
+ post<T>(path: string, body: unknown): Promise<T>;
16
+ patch<T>(path: string, body: unknown): Promise<T>;
17
+ private request;
15
18
  }
@@ -24,18 +24,28 @@ export class VadenMcpApiClient {
24
24
  return new URL(cleanPath, base).toString();
25
25
  }
26
26
  async get(path) {
27
+ return this.request("GET", path);
28
+ }
29
+ async post(path, body) {
30
+ return this.request("POST", path, body);
31
+ }
32
+ async patch(path, body) {
33
+ return this.request("PATCH", path, body);
34
+ }
35
+ async request(method, path, body) {
27
36
  const url = this.buildUrl(path);
28
37
  const controller = new AbortController();
29
38
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
30
39
  try {
31
40
  const response = await fetch(url, {
32
- method: "GET",
41
+ method,
33
42
  headers: this.headers(),
34
43
  signal: controller.signal,
44
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
35
45
  });
36
46
  if (!response.ok) {
37
- const body = await response.text();
38
- throw new Error(`API error (${response.status}): ${body}`);
47
+ const text = await response.text();
48
+ throw new Error(`API error (${response.status}): ${text}`);
39
49
  }
40
50
  return (await response.json());
41
51
  }
package/dist/index.js CHANGED
@@ -13,11 +13,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
14
14
  import { VadenMcpApiClient } from "./api-client.js";
15
15
  import { handleGetComponentList, handleGetComponentSpec, handleSearchComponents, } from "./tools/components.js";
16
- import { handleGetConcept, handleGetDesignRationale } from "./tools/concept.js";
16
+ import { handleGetConcept, handleGetDesignRationale, handleUpdateConcept, } from "./tools/concept.js";
17
17
  import { handleGetDesignTokens, handleGetThemeCss, } from "./tools/design-tokens.js";
18
- import { handleGetWireframeDetail, handleGetWireframes, } from "./tools/wireframes.js";
18
+ import { handleGetWireframeDetail, handleGetWireframes, handlePushWireframes, handleUpdateWireframe, } from "./tools/wireframes.js";
19
+ import { handleUpdateComponentStyle } from "./tools/write-components.js";
20
+ import { handleUpdateDesignTokens } from "./tools/write-tokens.js";
21
+ import { validateArrayAddArgs, validateArrayRemoveArgs, validateFieldUpdateArgs, validatePushWireframesArgs, } from "./validation.js";
19
22
  // --- コマンドライン引数パース ---
20
- const VERSION = "0.2.1";
23
+ const VERSION = "0.3.0";
21
24
  function parseArgs() {
22
25
  const args = process.argv.slice(2);
23
26
  if (args.includes("--help") || args.includes("-h")) {
@@ -159,6 +162,187 @@ const TOOLS = [
159
162
  required: [],
160
163
  },
161
164
  },
165
+ // --- Write ツール ---
166
+ {
167
+ name: "update_design_tokens",
168
+ description: "デザイントークン(色・spacing・radius等)を部分更新します。指定したフィールドのみ deep merge で上書きされます",
169
+ inputSchema: {
170
+ type: "object",
171
+ properties: {
172
+ tokens: {
173
+ type: "object",
174
+ description: "更新するトークン(deep merge)。例: { colors: { light: { semantic: { primary: '#3B82F6' } } } }",
175
+ },
176
+ },
177
+ required: ["tokens"],
178
+ },
179
+ annotations: {
180
+ readOnlyHint: false,
181
+ destructiveHint: false,
182
+ idempotentHint: true,
183
+ openWorldHint: false,
184
+ },
185
+ },
186
+ {
187
+ name: "update_component_style",
188
+ description: "指定したコンポーネントのスタイル設定をオーバーライドします。ベーススタイルは保持され、オーバーライドレイヤーとして保存されます",
189
+ inputSchema: {
190
+ type: "object",
191
+ properties: {
192
+ component: {
193
+ type: "string",
194
+ description: "コンポーネント名(例: button, card)",
195
+ },
196
+ updatedStyleConfig: {
197
+ type: "object",
198
+ description: "更新するスタイル設定。Single型: { baseStyles, variants }、Multipart型: { slots, variants }",
199
+ },
200
+ },
201
+ required: ["component", "updatedStyleConfig"],
202
+ },
203
+ annotations: {
204
+ readOnlyHint: false,
205
+ destructiveHint: false,
206
+ idempotentHint: true,
207
+ openWorldHint: false,
208
+ },
209
+ },
210
+ {
211
+ name: "update_concept",
212
+ description: "コンセプトの特定フィールドをドット区切りパスで更新します。例: serviceName, brandPersonality.0, targetUsers.2",
213
+ inputSchema: {
214
+ type: "object",
215
+ properties: {
216
+ field_path: {
217
+ type: "string",
218
+ description: "更新するフィールドのドット区切りパス(例: 'serviceName', 'brandPersonality.0')",
219
+ },
220
+ value: {
221
+ oneOf: [
222
+ { type: "string" },
223
+ { type: "number" },
224
+ { type: "boolean" },
225
+ { type: "null" },
226
+ ],
227
+ description: "新しい値(文字列・数値・真偽値・null)",
228
+ },
229
+ },
230
+ required: ["field_path", "value"],
231
+ },
232
+ annotations: {
233
+ readOnlyHint: false,
234
+ destructiveHint: false,
235
+ idempotentHint: true,
236
+ openWorldHint: false,
237
+ },
238
+ },
239
+ {
240
+ name: "update_wireframe",
241
+ description: "ワイヤーフレームの特定フィールドをドット区切りパスで更新します。例: screens.0.name, transitions.1.label",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ field_path: {
246
+ type: "string",
247
+ description: "更新するフィールドのドット区切りパス(例: 'screens.0.name')",
248
+ },
249
+ value: {
250
+ oneOf: [
251
+ { type: "string" },
252
+ { type: "number" },
253
+ { type: "boolean" },
254
+ { type: "null" },
255
+ ],
256
+ description: "新しい値(文字列・数値・真偽値・null)",
257
+ },
258
+ },
259
+ required: ["field_path", "value"],
260
+ },
261
+ annotations: {
262
+ readOnlyHint: false,
263
+ destructiveHint: false,
264
+ idempotentHint: true,
265
+ openWorldHint: false,
266
+ },
267
+ },
268
+ {
269
+ name: "add_wireframe_element",
270
+ description: "ワイヤーフレームの配列に要素を追加します。画面追加(screens)、コンポーネント追加(screens.0.components)、遷移追加(transitions)などに使用",
271
+ inputSchema: {
272
+ type: "object",
273
+ properties: {
274
+ field_path: {
275
+ type: "string",
276
+ description: "追加先の配列パス(例: 'screens', 'screens.0.components', 'transitions')",
277
+ },
278
+ value: {
279
+ type: "object",
280
+ description: "追加する要素。画面: { id, name, purpose, width: 800, components: [], group?, journeyStage? }、コンポーネント: { id, type, label }、遷移: { id, fromScreenId, toScreenId, triggerAction?, label? }",
281
+ },
282
+ },
283
+ required: ["field_path", "value"],
284
+ },
285
+ annotations: {
286
+ readOnlyHint: false,
287
+ destructiveHint: false,
288
+ idempotentHint: false,
289
+ openWorldHint: false,
290
+ },
291
+ },
292
+ {
293
+ name: "remove_wireframe_element",
294
+ description: "ワイヤーフレームの配列から要素を削除します。画面削除(screens.2)、コンポーネント削除(screens.0.components.3)、遷移削除(transitions.1)などに使用",
295
+ inputSchema: {
296
+ type: "object",
297
+ properties: {
298
+ field_path: {
299
+ type: "string",
300
+ description: "削除する要素のパス(最後のセグメントが配列インデックス。例: 'screens.2', 'screens.0.components.3', 'transitions.1')",
301
+ },
302
+ },
303
+ required: ["field_path"],
304
+ },
305
+ annotations: {
306
+ readOnlyHint: false,
307
+ destructiveHint: true,
308
+ idempotentHint: false,
309
+ openWorldHint: false,
310
+ },
311
+ },
312
+ {
313
+ name: "push_wireframes",
314
+ description: "UXIR(YAML形式)ファイルを一括プッシュしてワイヤーフレームを更新します。既存のワイヤーフレームは置換されます",
315
+ inputSchema: {
316
+ type: "object",
317
+ properties: {
318
+ files: {
319
+ type: "array",
320
+ items: {
321
+ type: "object",
322
+ properties: {
323
+ filename: {
324
+ type: "string",
325
+ description: "ファイル名(.uxir 拡張子必須)",
326
+ },
327
+ content: {
328
+ type: "string",
329
+ description: "UXIR YAML コンテンツ",
330
+ },
331
+ },
332
+ required: ["filename", "content"],
333
+ },
334
+ description: "UXIR ファイル一覧(最大50ファイル、各1MB以下)",
335
+ },
336
+ },
337
+ required: ["files"],
338
+ },
339
+ annotations: {
340
+ readOnlyHint: false,
341
+ destructiveHint: true,
342
+ idempotentHint: false,
343
+ openWorldHint: false,
344
+ },
345
+ },
162
346
  ];
163
347
  // --- メイン ---
164
348
  async function main() {
@@ -171,6 +355,7 @@ async function main() {
171
355
  name: t.name,
172
356
  description: t.description,
173
357
  inputSchema: t.inputSchema,
358
+ ...("annotations" in t ? { annotations: t.annotations } : {}),
174
359
  })),
175
360
  }));
176
361
  // ツール呼び出し
@@ -225,6 +410,53 @@ async function main() {
225
410
  const content = await handleGetDesignRationale(client, projectId);
226
411
  return { content };
227
412
  }
413
+ // --- Write ツール ---
414
+ case "update_design_tokens": {
415
+ const tokens = args?.tokens;
416
+ if (!tokens || typeof tokens !== "object") {
417
+ throw new Error("tokens パラメータが必要です(オブジェクト)");
418
+ }
419
+ const content = await handleUpdateDesignTokens(client, projectId, tokens);
420
+ return { content };
421
+ }
422
+ case "update_component_style": {
423
+ const componentName = String(args?.component ?? "");
424
+ const updatedStyleConfig = args
425
+ ?.updatedStyleConfig;
426
+ if (!componentName) {
427
+ throw new Error("component パラメータが必要です");
428
+ }
429
+ if (!updatedStyleConfig || typeof updatedStyleConfig !== "object") {
430
+ throw new Error("updatedStyleConfig パラメータが必要です(オブジェクト)");
431
+ }
432
+ const content = await handleUpdateComponentStyle(client, projectId, componentName, updatedStyleConfig);
433
+ return { content };
434
+ }
435
+ case "update_concept": {
436
+ const { fieldPath, value } = validateFieldUpdateArgs(args);
437
+ const content = await handleUpdateConcept(client, projectId, fieldPath, value);
438
+ return { content };
439
+ }
440
+ case "update_wireframe": {
441
+ const { fieldPath, value } = validateFieldUpdateArgs(args);
442
+ const content = await handleUpdateWireframe(client, projectId, fieldPath, value);
443
+ return { content };
444
+ }
445
+ case "add_wireframe_element": {
446
+ const { fieldPath, value } = validateArrayAddArgs(args);
447
+ const content = await handleUpdateWireframe(client, projectId, fieldPath, value, "add");
448
+ return { content };
449
+ }
450
+ case "remove_wireframe_element": {
451
+ const { fieldPath } = validateArrayRemoveArgs(args);
452
+ const content = await handleUpdateWireframe(client, projectId, fieldPath, null, "remove");
453
+ return { content };
454
+ }
455
+ case "push_wireframes": {
456
+ const validatedFiles = validatePushWireframesArgs(args);
457
+ const content = await handlePushWireframes(client, projectId, validatedFiles);
458
+ return { content };
459
+ }
228
460
  default:
229
461
  throw new Error(`Unknown tool: ${name}`);
230
462
  }
@@ -1,5 +1,6 @@
1
1
  import { componentMetadata } from "../guides/component-metadata.js";
2
2
  import { generateComponentGuide } from "../guides/generate-guide.js";
3
+ import { resolveVisualProperties, } from "../utils/tailwind-resolver.js";
3
4
  let cache = new WeakMap();
4
5
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5分
5
6
  function getClientCache(client) {
@@ -307,8 +308,156 @@ function formatStyleSection(data) {
307
308
  }
308
309
  lines.push("");
309
310
  }
311
+ // --- Resolved Styles ---
312
+ const resolvedStyleEntries = [];
313
+ if (type === "single") {
314
+ const baseStylesValue = data.baseStyles;
315
+ if (typeof baseStylesValue === "string" && baseStylesValue.trim()) {
316
+ resolvedStyleEntries.push({
317
+ label: "baseStyles",
318
+ props: resolveVisualProperties(baseStylesValue, resolvedColors ?? {}),
319
+ });
320
+ }
321
+ }
322
+ else if (type === "multipart") {
323
+ const slotsObj = data.slots;
324
+ if (slotsObj && typeof slotsObj === "object") {
325
+ for (const [slotName, classes] of Object.entries(slotsObj)) {
326
+ if (typeof classes !== "string" || !classes.trim())
327
+ continue;
328
+ resolvedStyleEntries.push({
329
+ label: slotName,
330
+ props: resolveVisualProperties(classes, resolvedColors ?? {}),
331
+ });
332
+ }
333
+ }
334
+ }
335
+ // --- Variant Resolved Styles ---
336
+ const resolvedVariantEntries = [];
337
+ const variants = data.variants;
338
+ if (variants && typeof variants === "object") {
339
+ for (const [groupName, groupVariants] of Object.entries(variants)) {
340
+ if (!groupVariants || typeof groupVariants !== "object")
341
+ continue;
342
+ for (const [variantName, classes] of Object.entries(groupVariants)) {
343
+ if (typeof classes !== "string" || !classes.trim())
344
+ continue;
345
+ resolvedVariantEntries.push({
346
+ group: groupName,
347
+ name: variantName,
348
+ props: resolveVisualProperties(classes, resolvedColors ?? {}),
349
+ });
350
+ }
351
+ }
352
+ }
353
+ if (resolvedStyleEntries.length > 0 || resolvedVariantEntries.length > 0) {
354
+ lines.push("## Resolved Styles");
355
+ lines.push("");
356
+ lines.push("Visual properties resolved from Style Config classes and design tokens.");
357
+ lines.push("Use these values for accurate rendering in design tools (Figma, Framer, etc.).");
358
+ lines.push("");
359
+ for (const { label, props } of resolvedStyleEntries) {
360
+ const propLines = formatResolvedProps(props);
361
+ if (propLines.length > 0) {
362
+ lines.push(`**${label}**:`);
363
+ for (const line of propLines) {
364
+ lines.push(`- ${line}`);
365
+ }
366
+ lines.push("");
367
+ }
368
+ }
369
+ if (resolvedVariantEntries.length > 0) {
370
+ lines.push("### Variants");
371
+ lines.push("");
372
+ let currentGroup = "";
373
+ for (const { group, name, props } of resolvedVariantEntries) {
374
+ const propLines = formatResolvedProps(props);
375
+ if (propLines.length === 0)
376
+ continue;
377
+ if (group !== currentGroup) {
378
+ lines.push(`**${group}**:`);
379
+ currentGroup = group;
380
+ }
381
+ lines.push(` *${name}*:`);
382
+ for (const line of propLines) {
383
+ lines.push(` - ${line}`);
384
+ }
385
+ }
386
+ lines.push("");
387
+ }
388
+ }
310
389
  return lines.join("\n");
311
390
  }
391
+ function formatResolvedProps(props) {
392
+ const result = [];
393
+ if (props.display)
394
+ result.push(`display: ${props.display}`);
395
+ if (props.width)
396
+ result.push(`width: ${props.width}`);
397
+ if (props.height)
398
+ result.push(`height: ${props.height}`);
399
+ if (props.aspectRatio)
400
+ result.push(`aspect-ratio: ${props.aspectRatio}`);
401
+ if (props.overflow)
402
+ result.push(`overflow: ${props.overflow}`);
403
+ if (props.borderRadius)
404
+ result.push(`border-radius: ${props.borderRadius}`);
405
+ if (props.border) {
406
+ const style = props.borderStyle ?? (props.border.color ? "solid" : undefined);
407
+ const parts = [
408
+ props.border.width,
409
+ style,
410
+ props.border.color || undefined,
411
+ ].filter(Boolean);
412
+ result.push(`border: ${parts.join(" ")}`);
413
+ }
414
+ if (props.backgroundColor)
415
+ result.push(`background-color: ${props.backgroundColor}`);
416
+ if (props.color)
417
+ result.push(`color: ${props.color}`);
418
+ if (props.fontWeight)
419
+ result.push(`font-weight: ${props.fontWeight}`);
420
+ if (props.fontSize)
421
+ result.push(`font-size: ${props.fontSize}`);
422
+ if (props.objectFit)
423
+ result.push(`object-fit: ${props.objectFit}`);
424
+ if (props.alignItems)
425
+ result.push(`align-items: ${props.alignItems}`);
426
+ if (props.justifyContent)
427
+ result.push(`justify-content: ${props.justifyContent}`);
428
+ if (props.opacity)
429
+ result.push(`opacity: ${props.opacity}`);
430
+ if (props.boxShadow)
431
+ result.push(`box-shadow: ${props.boxShadow}`);
432
+ if (props.padding)
433
+ result.push(`padding: ${props.padding}`);
434
+ if (props.paddingX)
435
+ result.push(`padding-inline: ${props.paddingX}`);
436
+ if (props.paddingY)
437
+ result.push(`padding-block: ${props.paddingY}`);
438
+ if (props.paddingTop)
439
+ result.push(`padding-top: ${props.paddingTop}`);
440
+ if (props.paddingBottom)
441
+ result.push(`padding-bottom: ${props.paddingBottom}`);
442
+ if (props.paddingLeft)
443
+ result.push(`padding-left: ${props.paddingLeft}`);
444
+ if (props.paddingRight)
445
+ result.push(`padding-right: ${props.paddingRight}`);
446
+ if (props.gap)
447
+ result.push(`gap: ${props.gap}`);
448
+ if (props.letterSpacing)
449
+ result.push(`letter-spacing: ${props.letterSpacing}`);
450
+ if (props.flexShrink)
451
+ result.push(`flex-shrink: ${props.flexShrink}`);
452
+ if (props.borderStyle && !props.border)
453
+ result.push(`border-style: ${props.borderStyle}`);
454
+ if (props.fontFamily)
455
+ result.push(`font-family: ${props.fontFamily}`);
456
+ if (props.gradient) {
457
+ result.push(`background: ${props.gradient}`);
458
+ }
459
+ return result;
460
+ }
312
461
  /** テスト用にキャッシュをクリアする */
313
462
  export function clearRegistryCache() {
314
463
  cache = new WeakMap();
@@ -4,6 +4,18 @@
4
4
  * コンセプト API: GET /api/projects/{projectId}/concept
5
5
  */
6
6
  import type { VadenMcpApiClient } from "../api-client.js";
7
+ /**
8
+ * コンセプトの特定フィールドを更新する
9
+ *
10
+ * @param client - Vaden MCP API クライアント
11
+ * @param projectId - プロジェクトID
12
+ * @param fieldPath - ドット区切りのフィールドパス(例: 'brandPersonality.0', 'serviceName')
13
+ * @param value - 新しい値
14
+ */
15
+ export declare function handleUpdateConcept(client: VadenMcpApiClient, projectId: string, fieldPath: string, value: unknown, operation?: "update" | "add" | "remove"): Promise<{
16
+ type: "text";
17
+ text: string;
18
+ }[]>;
7
19
  /**
8
20
  * コンセプト情報を返す
9
21
  *
@@ -48,6 +48,24 @@ function generateConceptGuide(data) {
48
48
  }
49
49
  lines.push("");
50
50
  }
51
+ if (data.colorEmotions) {
52
+ lines.push("## Color Emotions");
53
+ lines.push("");
54
+ if (data.colorEmotions.primary) {
55
+ lines.push(`**Primary**: ${data.colorEmotions.primary}`);
56
+ }
57
+ if (data.colorEmotions.secondary) {
58
+ lines.push(`**Secondary**: ${data.colorEmotions.secondary}`);
59
+ }
60
+ if (data.colorEmotions.emotional?.length) {
61
+ lines.push("");
62
+ lines.push("### Emotional Effects");
63
+ for (const effect of data.colorEmotions.emotional) {
64
+ lines.push(`- ${effect}`);
65
+ }
66
+ }
67
+ lines.push("");
68
+ }
51
69
  lines.push("## Design Principles");
52
70
  lines.push("");
53
71
  for (const principle of data.designPrinciples) {
@@ -66,6 +84,32 @@ function generateConceptGuide(data) {
66
84
  lines.push(`- ${journey}`);
67
85
  }
68
86
  lines.push("");
87
+ if (data.layoutPatterns?.length) {
88
+ lines.push("## Layout Patterns");
89
+ lines.push("");
90
+ for (const pattern of data.layoutPatterns) {
91
+ lines.push(`### ${pattern.name}`);
92
+ lines.push("");
93
+ lines.push(pattern.description);
94
+ if (pattern.basedOn) {
95
+ if (pattern.basedOn.interfaceApproach?.length) {
96
+ lines.push(`- **Interface Approach**: ${pattern.basedOn.interfaceApproach.join(", ")}`);
97
+ }
98
+ if (pattern.basedOn.layoutStrategy?.length) {
99
+ lines.push(`- **Layout Strategy**: ${pattern.basedOn.layoutStrategy.join(", ")}`);
100
+ }
101
+ }
102
+ if (pattern.specs) {
103
+ if (pattern.specs.grid) {
104
+ lines.push(`- **Grid**: ${pattern.specs.grid.columns ?? "?"}col, gap ${String(pattern.specs.grid.gap ?? "?")}`);
105
+ }
106
+ if (pattern.specs.nav?.type) {
107
+ lines.push(`- **Navigation**: ${pattern.specs.nav.type}`);
108
+ }
109
+ }
110
+ lines.push("");
111
+ }
112
+ }
69
113
  lines.push("> Use `get_design_rationale` to understand the reasoning behind design decisions.");
70
114
  return lines.join("\n");
71
115
  }
@@ -120,6 +164,86 @@ function generateDesignRationaleGuide(data) {
120
164
  lines.push(`- ${principle}`);
121
165
  }
122
166
  lines.push("");
167
+ if (da.visualEffects?.length) {
168
+ lines.push("## Visual Effects");
169
+ lines.push("");
170
+ for (const effect of da.visualEffects) {
171
+ lines.push(`- **${effect.name}**: ${effect.intent}`);
172
+ }
173
+ lines.push("");
174
+ }
175
+ if (da.interactionDesign?.length) {
176
+ lines.push("## Interaction Design");
177
+ lines.push("");
178
+ for (const item of da.interactionDesign) {
179
+ lines.push(`- **${item.feature}**: ${item.purpose}`);
180
+ }
181
+ lines.push("");
182
+ }
183
+ if (da.layoutStrategy?.length) {
184
+ lines.push("## Layout Strategy");
185
+ lines.push("");
186
+ for (const item of da.layoutStrategy) {
187
+ lines.push(`- **${item.aspect}**: ${item.rationale}`);
188
+ }
189
+ lines.push("");
190
+ }
191
+ if (da.motionDesign?.length) {
192
+ lines.push("## Motion Design");
193
+ lines.push("");
194
+ for (const item of da.motionDesign) {
195
+ lines.push(`- ${item}`);
196
+ }
197
+ lines.push("");
198
+ }
199
+ if (da.differentiation?.length) {
200
+ lines.push("## Differentiation");
201
+ lines.push("");
202
+ for (const item of da.differentiation) {
203
+ lines.push(`- vs **${item.competitor}**: ${item.distinction}`);
204
+ }
205
+ lines.push("");
206
+ }
207
+ if (da.behavioralPsychology?.length) {
208
+ lines.push("## Behavioral Psychology");
209
+ lines.push("");
210
+ for (const item of da.behavioralPsychology) {
211
+ lines.push(`- **${item.principle}**: ${item.application}`);
212
+ }
213
+ lines.push("");
214
+ }
215
+ if (da.iconography?.length) {
216
+ lines.push("## Iconography");
217
+ lines.push("");
218
+ for (const item of da.iconography) {
219
+ lines.push(`- **${item.element}**: ${item.symbolism}`);
220
+ }
221
+ lines.push("");
222
+ }
223
+ if (da.culturalResonance?.length) {
224
+ lines.push("## Cultural Resonance");
225
+ lines.push("");
226
+ for (const item of da.culturalResonance) {
227
+ lines.push(`- ${item}`);
228
+ }
229
+ lines.push("");
230
+ }
231
+ if (da.informationArchitecture?.length) {
232
+ lines.push("## Information Architecture");
233
+ lines.push("");
234
+ for (const item of da.informationArchitecture) {
235
+ lines.push(`- ${item}`);
236
+ }
237
+ lines.push("");
238
+ }
239
+ if (da.brandConsistency?.length) {
240
+ lines.push("## Brand Consistency");
241
+ lines.push("");
242
+ for (const item of da.brandConsistency) {
243
+ lines.push(`- ${item}`);
244
+ }
245
+ lines.push("");
246
+ }
123
247
  lines.push("## Success Metrics");
124
248
  lines.push("");
125
249
  for (const metric of data.successMetrics) {
@@ -134,7 +258,29 @@ function generateDesignRationaleGuide(data) {
134
258
  lines.push("");
135
259
  return lines.join("\n");
136
260
  }
137
- // --- ツールハンドラー ---
261
+ /**
262
+ * コンセプトの特定フィールドを更新する
263
+ *
264
+ * @param client - Vaden MCP API クライアント
265
+ * @param projectId - プロジェクトID
266
+ * @param fieldPath - ドット区切りのフィールドパス(例: 'brandPersonality.0', 'serviceName')
267
+ * @param value - 新しい値
268
+ */
269
+ export async function handleUpdateConcept(client, projectId, fieldPath, value, operation = "update") {
270
+ await client.patch(`/api/projects/${encodeURIComponent(projectId)}/concept`, { fieldPath, value, operation });
271
+ const actionLabel = operation === "add"
272
+ ? "added to"
273
+ : operation === "remove"
274
+ ? "removed from"
275
+ : "updated in";
276
+ return [
277
+ {
278
+ type: "text",
279
+ text: `Concept: element ${actionLabel} "${fieldPath}"${operation !== "remove" ? ` — ${JSON.stringify(value)}` : ""}`,
280
+ },
281
+ ];
282
+ }
283
+ // --- Read ハンドラー ---
138
284
  /**
139
285
  * コンセプト情報を返す
140
286
  *