@vadenai/mcp-server 0.1.0

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.
@@ -0,0 +1,36 @@
1
+ interface ComponentInfo {
2
+ name: string;
3
+ title: string;
4
+ description: string;
5
+ categories: string[];
6
+ }
7
+ /**
8
+ * コンポーネント使用ガイドを生成する
9
+ *
10
+ * 出力例:
11
+ * ```
12
+ * # Button — Component Usage Guide
13
+ *
14
+ * > Displays a button or a component that looks like a button.
15
+ *
16
+ * **Category**: form
17
+ *
18
+ * ## Structure
19
+ * Single element component (cva-based).
20
+ *
21
+ * ## Variants
22
+ * - `variant`: default | destructive | outline | secondary | ghost | link
23
+ * - `size`: default | sm | lg | icon
24
+ *
25
+ * ## Accessibility
26
+ * - Use <button> for actions, <a> for navigation ...
27
+ *
28
+ * ## States
29
+ * hover, focus-visible, active, disabled
30
+ *
31
+ * ## Do NOT
32
+ * - Do not use color props — use variant instead
33
+ * ```
34
+ */
35
+ export declare function generateComponentGuide(info: ComponentInfo): string | null;
36
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * コンポーネントメタデータから LLM 向け使用ガイドを生成する
3
+ *
4
+ * design-tokens の generateTokenUsageGuide と同じパターン。
5
+ * JSON 仕様だけでは不足する「どう使うべきか」を自然言語で補完する。
6
+ */
7
+ import { componentMetadata } from "./component-metadata.js";
8
+ /**
9
+ * コンポーネント使用ガイドを生成する
10
+ *
11
+ * 出力例:
12
+ * ```
13
+ * # Button — Component Usage Guide
14
+ *
15
+ * > Displays a button or a component that looks like a button.
16
+ *
17
+ * **Category**: form
18
+ *
19
+ * ## Structure
20
+ * Single element component (cva-based).
21
+ *
22
+ * ## Variants
23
+ * - `variant`: default | destructive | outline | secondary | ghost | link
24
+ * - `size`: default | sm | lg | icon
25
+ *
26
+ * ## Accessibility
27
+ * - Use <button> for actions, <a> for navigation ...
28
+ *
29
+ * ## States
30
+ * hover, focus-visible, active, disabled
31
+ *
32
+ * ## Do NOT
33
+ * - Do not use color props — use variant instead
34
+ * ```
35
+ */
36
+ export function generateComponentGuide(info) {
37
+ if (!Object.hasOwn(componentMetadata, info.name))
38
+ return null;
39
+ const meta = componentMetadata[info.name];
40
+ const lines = [`# ${info.title} — Component Usage Guide`, ""];
41
+ if (info.description.trim()) {
42
+ lines.push(`> ${info.description}`, "");
43
+ }
44
+ lines.push(`**Categories**: ${info.categories.join(", ") || "unknown"}`, "");
45
+ // Structure
46
+ lines.push("## Structure");
47
+ if (meta.type === "single") {
48
+ const hasVariants = meta.variants && Object.keys(meta.variants).length > 0;
49
+ lines.push(hasVariants
50
+ ? "Single element component (cva-based) with variants."
51
+ : "Single element component (cva-based), no variants.");
52
+ }
53
+ else {
54
+ lines.push(`Multipart component with ${meta.slots?.length ?? 0} slots.`);
55
+ if (meta.slots && meta.slots.length > 0) {
56
+ lines.push(`Slots: \`${meta.slots.join("` | `")}\``);
57
+ }
58
+ }
59
+ lines.push("");
60
+ // Variants
61
+ if (meta.variants && Object.keys(meta.variants).length > 0) {
62
+ lines.push("## Variants");
63
+ for (const [key, options] of Object.entries(meta.variants)) {
64
+ lines.push(`- \`${key}\`: ${options.join(" | ")}`);
65
+ }
66
+ lines.push("");
67
+ }
68
+ // Dependencies
69
+ if (meta.dependencies && meta.dependencies.length > 0) {
70
+ lines.push("## Dependencies");
71
+ lines.push(meta.dependencies.map((d) => `- \`${d}\``).join("\n"));
72
+ lines.push("");
73
+ }
74
+ // Accessibility
75
+ if (meta.a11y && meta.a11y.length > 0) {
76
+ lines.push("## Accessibility");
77
+ for (const rule of meta.a11y) {
78
+ lines.push(`- ${rule}`);
79
+ }
80
+ lines.push("");
81
+ }
82
+ // States
83
+ if (meta.states && meta.states.length > 0) {
84
+ lines.push("## States");
85
+ lines.push(`Supported: ${meta.states.join(", ")}`);
86
+ lines.push("");
87
+ }
88
+ // Do NOT
89
+ if (meta.doNot && meta.doNot.length > 0) {
90
+ lines.push("## Do NOT");
91
+ for (const rule of meta.doNot) {
92
+ lines.push(`- ${rule}`);
93
+ }
94
+ lines.push("");
95
+ }
96
+ // Important rules (common to all)
97
+ lines.push("## Important Rules");
98
+ lines.push("- Always use design token CSS variables — never hardcode colors or spacing.");
99
+ lines.push("- Follow the variant system — do not create ad-hoc style overrides.");
100
+ if (meta.type === "multipart") {
101
+ lines.push("- Use the slot-based structure — each slot has specific styling responsibilities.");
102
+ }
103
+ return lines.join("\n");
104
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Vaden MCP Server エントリポイント
4
+ *
5
+ * 起動方法:
6
+ * node dist/index.js --project-id <id> --token <token> [--api-url <url>]
7
+ *
8
+ * 環境変数フォールバック:
9
+ * VADEN_PROJECT_ID, VADEN_TOKEN, VADEN_API_URL
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Vaden MCP Server エントリポイント
4
+ *
5
+ * 起動方法:
6
+ * node dist/index.js --project-id <id> --token <token> [--api-url <url>]
7
+ *
8
+ * 環境変数フォールバック:
9
+ * VADEN_PROJECT_ID, VADEN_TOKEN, VADEN_API_URL
10
+ */
11
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
14
+ import { VadenMcpApiClient } from "./api-client.js";
15
+ import { handleGetComponentList, handleGetComponentSpec, handleSearchComponents, } from "./tools/components.js";
16
+ import { handleGetConcept, handleGetDesignRationale } from "./tools/concept.js";
17
+ import { handleGetDesignTokens, handleGetThemeCss, } from "./tools/design-tokens.js";
18
+ import { handleGetWireframeDetail, handleGetWireframes, } from "./tools/wireframes.js";
19
+ // --- コマンドライン引数パース ---
20
+ const VERSION = "0.1.0";
21
+ function parseArgs() {
22
+ const args = process.argv.slice(2);
23
+ if (args.includes("--help") || args.includes("-h")) {
24
+ console.log(`vaden-mcp-server v${VERSION}
25
+
26
+ Usage: vaden-mcp-server [options]
27
+
28
+ Options:
29
+ --project-id <id> Vaden project ID (or VADEN_PROJECT_ID env)
30
+ --token <jwt> Registry token (or VADEN_TOKEN env)
31
+ --api-url <url> API endpoint (default: https://app.vaden.ai)
32
+ --help, -h Show this help message
33
+ --version, -v Show version number`);
34
+ process.exit(0);
35
+ }
36
+ if (args.includes("--version") || args.includes("-v")) {
37
+ console.log(VERSION);
38
+ process.exit(0);
39
+ }
40
+ let projectId = "";
41
+ let token = "";
42
+ let apiUrl = "";
43
+ for (let i = 0; i < args.length; i++) {
44
+ if (args[i] === "--project-id" && args[i + 1]) {
45
+ projectId = args[++i] ?? "";
46
+ }
47
+ else if (args[i] === "--token" && args[i + 1]) {
48
+ token = args[++i] ?? "";
49
+ }
50
+ else if (args[i] === "--api-url" && args[i + 1]) {
51
+ apiUrl = args[++i] ?? "";
52
+ }
53
+ }
54
+ // 環境変数フォールバック
55
+ projectId ||= process.env.VADEN_PROJECT_ID ?? "";
56
+ token ||= process.env.VADEN_TOKEN ?? "";
57
+ apiUrl ||= process.env.VADEN_API_URL ?? "https://app.vaden.ai";
58
+ if (!projectId)
59
+ throw new Error("--project-id または VADEN_PROJECT_ID が必要です");
60
+ if (!token)
61
+ throw new Error("--token または VADEN_TOKEN が必要です");
62
+ return { projectId, token, apiUrl };
63
+ }
64
+ // --- ツール定義 ---
65
+ const TOOLS = [
66
+ {
67
+ name: "get_design_tokens",
68
+ description: "デザイントークン(色・フォント・spacing・radius 等)を返します",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {},
72
+ required: [],
73
+ },
74
+ },
75
+ {
76
+ name: "get_theme_css",
77
+ description: "CSS 変数形式のテーマ(theme.css)を返します",
78
+ inputSchema: {
79
+ type: "object",
80
+ properties: {},
81
+ required: [],
82
+ },
83
+ },
84
+ {
85
+ name: "get_component_list",
86
+ description: "利用可能なコンポーネントの一覧を返します",
87
+ inputSchema: {
88
+ type: "object",
89
+ properties: {},
90
+ required: [],
91
+ },
92
+ },
93
+ {
94
+ name: "get_component_spec",
95
+ description: "指定したコンポーネントの仕様(props・バリアント等)を返します",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ component: {
100
+ type: "string",
101
+ description: "コンポーネント名(例: button, card)",
102
+ },
103
+ },
104
+ required: ["component"],
105
+ },
106
+ },
107
+ {
108
+ name: "search_components",
109
+ description: "キーワードでコンポーネントを検索します(日本語対応)。'button', 'form', 'ボタン', 'primary' 等で検索できます",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ query: {
114
+ type: "string",
115
+ description: "検索クエリ(コンポーネント名・カテゴリ・バリアント・日本語キーワード)",
116
+ },
117
+ },
118
+ required: ["query"],
119
+ },
120
+ },
121
+ {
122
+ name: "get_wireframes",
123
+ description: "ワイヤーフレームの画面一覧と遷移フローを返します。画面の目的・グループ・ジャーニーステージ・コンポーネント数と、画面間の遷移情報が含まれます",
124
+ inputSchema: {
125
+ type: "object",
126
+ properties: {},
127
+ required: [],
128
+ },
129
+ },
130
+ {
131
+ name: "get_wireframe_detail",
132
+ description: "指定した画面の詳細情報を返します。コンポーネント一覧・UI状態・ビヘイビア・モックデータ・関連画面が含まれます",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ screen_id: {
137
+ type: "string",
138
+ description: "画面ID(get_wireframes で取得した id)",
139
+ },
140
+ },
141
+ required: ["screen_id"],
142
+ },
143
+ },
144
+ {
145
+ name: "get_concept",
146
+ description: "コンセプト情報(ブランド人格・ムード・ビジュアルスタイル・ターゲットユーザー・デザイン原則)を返します",
147
+ inputSchema: {
148
+ type: "object",
149
+ properties: {},
150
+ required: [],
151
+ },
152
+ },
153
+ {
154
+ name: "get_design_rationale",
155
+ description: "デザイン哲学・意思決定理由・UX原則・カラー戦略・タイポグラフィ選択の根拠を返します",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {},
159
+ required: [],
160
+ },
161
+ },
162
+ ];
163
+ // --- メイン ---
164
+ async function main() {
165
+ const { projectId, token, apiUrl } = parseArgs();
166
+ const client = new VadenMcpApiClient(apiUrl, token);
167
+ const server = new Server({ name: "vaden-mcp-server", version: VERSION }, { capabilities: { tools: {} } });
168
+ // ツール一覧を返す
169
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
170
+ tools: TOOLS.map((t) => ({
171
+ name: t.name,
172
+ description: t.description,
173
+ inputSchema: t.inputSchema,
174
+ })),
175
+ }));
176
+ // ツール呼び出し
177
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
178
+ const { name, arguments: args } = request.params;
179
+ switch (name) {
180
+ case "get_design_tokens": {
181
+ const content = await handleGetDesignTokens(client, projectId);
182
+ return { content };
183
+ }
184
+ case "get_theme_css": {
185
+ const content = await handleGetThemeCss(client, projectId);
186
+ return { content };
187
+ }
188
+ case "get_component_list": {
189
+ const content = await handleGetComponentList(client, projectId);
190
+ return { content };
191
+ }
192
+ case "get_component_spec": {
193
+ const componentName = String(args?.component ?? "");
194
+ if (!componentName) {
195
+ throw new Error("component パラメータが必要です");
196
+ }
197
+ const content = await handleGetComponentSpec(client, projectId, componentName);
198
+ return { content };
199
+ }
200
+ case "search_components": {
201
+ const query = String(args?.query ?? "");
202
+ if (!query) {
203
+ throw new Error("query パラメータが必要です");
204
+ }
205
+ const content = await handleSearchComponents(client, projectId, query);
206
+ return { content };
207
+ }
208
+ case "get_wireframes": {
209
+ const content = await handleGetWireframes(client, projectId);
210
+ return { content };
211
+ }
212
+ case "get_wireframe_detail": {
213
+ const screenId = String(args?.screen_id ?? "").trim();
214
+ if (!screenId) {
215
+ throw new Error("screen_id パラメータが必要です");
216
+ }
217
+ const content = await handleGetWireframeDetail(client, projectId, screenId);
218
+ return { content };
219
+ }
220
+ case "get_concept": {
221
+ const content = await handleGetConcept(client, projectId);
222
+ return { content };
223
+ }
224
+ case "get_design_rationale": {
225
+ const content = await handleGetDesignRationale(client, projectId);
226
+ return { content };
227
+ }
228
+ default:
229
+ throw new Error(`Unknown tool: ${name}`);
230
+ }
231
+ });
232
+ const transport = new StdioServerTransport();
233
+ await server.connect(transport);
234
+ }
235
+ main().catch((err) => {
236
+ console.error(err);
237
+ process.exit(1);
238
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * コンポーネント関連 MCP ツールハンドラー
3
+ *
4
+ * レジストリ API: GET /api/projects/{projectId}/design-system/registry
5
+ * レスポンス形式: shadcn/ui registry.json 互換
6
+ */
7
+ import type { VadenMcpApiClient } from "../api-client.js";
8
+ export declare function handleGetComponentList(client: VadenMcpApiClient, projectId: string): Promise<{
9
+ type: "text";
10
+ text: string;
11
+ }[]>;
12
+ export declare function handleGetComponentSpec(client: VadenMcpApiClient, projectId: string, componentName: string): Promise<{
13
+ type: "text";
14
+ text: string;
15
+ }[]>;
16
+ export declare function handleSearchComponents(client: VadenMcpApiClient, projectId: string, query: string): Promise<{
17
+ type: "text";
18
+ text: string;
19
+ }[]>;
20
+ /** テスト用にキャッシュをクリアする */
21
+ export declare function clearRegistryCache(): void;
@@ -0,0 +1,234 @@
1
+ import { componentMetadata } from "../guides/component-metadata.js";
2
+ import { generateComponentGuide } from "../guides/generate-guide.js";
3
+ let cache = new WeakMap();
4
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5分
5
+ function getClientCache(client) {
6
+ let clientCache = cache.get(client);
7
+ if (!clientCache) {
8
+ clientCache = new Map();
9
+ cache.set(client, clientCache);
10
+ }
11
+ return clientCache;
12
+ }
13
+ function getCached(client, projectId) {
14
+ const entry = getClientCache(client).get(projectId);
15
+ if (!entry)
16
+ return null;
17
+ if (Date.now() > entry.expiresAt) {
18
+ getClientCache(client).delete(projectId);
19
+ return null;
20
+ }
21
+ return entry.data;
22
+ }
23
+ function setCached(client, projectId, data) {
24
+ getClientCache(client).set(projectId, {
25
+ data,
26
+ expiresAt: Date.now() + CACHE_TTL_MS,
27
+ });
28
+ }
29
+ let pending = new WeakMap();
30
+ function getClientPending(client) {
31
+ let clientPending = pending.get(client);
32
+ if (!clientPending) {
33
+ clientPending = new Map();
34
+ pending.set(client, clientPending);
35
+ }
36
+ return clientPending;
37
+ }
38
+ async function fetchRegistry(client, projectId) {
39
+ const cached = getCached(client, projectId);
40
+ if (cached)
41
+ return cached;
42
+ const inFlight = getClientPending(client).get(projectId);
43
+ if (inFlight)
44
+ return inFlight;
45
+ const request = client
46
+ .get(`/api/projects/${encodeURIComponent(projectId)}/design-system/registry`)
47
+ .then((data) => {
48
+ setCached(client, projectId, data);
49
+ return data;
50
+ })
51
+ .finally(() => {
52
+ getClientPending(client).delete(projectId);
53
+ });
54
+ getClientPending(client).set(projectId, request);
55
+ return request;
56
+ }
57
+ export async function handleGetComponentList(client, projectId) {
58
+ const registry = await fetchRegistry(client, projectId);
59
+ const components = registry.items.map((item) => ({
60
+ name: item.name,
61
+ title: item.title ?? item.name,
62
+ description: item.description ?? "",
63
+ categories: item.categories ?? [],
64
+ }));
65
+ return [{ type: "text", text: JSON.stringify({ components }, null, 2) }];
66
+ }
67
+ export async function handleGetComponentSpec(client, projectId, componentName) {
68
+ const registry = await fetchRegistry(client, projectId);
69
+ const item = registry.items.find((i) => i.name === componentName);
70
+ if (!item) {
71
+ const available = registry.items.map((i) => i.name).join(", ");
72
+ throw new Error(`Component "${componentName}" not found. Available: ${available}`);
73
+ }
74
+ const spec = {
75
+ name: item.name,
76
+ type: item.type,
77
+ title: item.title ?? item.name,
78
+ description: item.description ?? "",
79
+ categories: item.categories ?? [],
80
+ registryDependencies: item.registryDependencies ?? [],
81
+ dependencies: item.dependencies ?? [],
82
+ files: item.files ?? [],
83
+ cssVars: {
84
+ light: item.cssVars?.light ?? {},
85
+ dark: item.cssVars?.dark ?? {},
86
+ },
87
+ meta: item.meta ?? {},
88
+ };
89
+ // ガイドを生成(メタデータがあれば)
90
+ const guide = generateComponentGuide({
91
+ name: spec.name,
92
+ title: spec.title,
93
+ description: spec.description,
94
+ categories: spec.categories,
95
+ });
96
+ const parts = [];
97
+ if (guide) {
98
+ parts.push(guide);
99
+ parts.push("", "---", "");
100
+ }
101
+ parts.push("## Registry Spec (JSON)");
102
+ parts.push("```json");
103
+ parts.push(JSON.stringify(spec, null, 2));
104
+ parts.push("```");
105
+ return [{ type: "text", text: parts.join("\n") }];
106
+ }
107
+ // --- search_components ---
108
+ const JA_TO_EN = {
109
+ ボタン: ["button"],
110
+ 入力: ["input", "textarea"],
111
+ カード: ["card"],
112
+ テーブル: ["table", "data-table"],
113
+ ダイアログ: ["dialog", "alert-dialog"],
114
+ モーダル: ["dialog"],
115
+ メニュー: ["menubar", "dropdown-menu", "context-menu"],
116
+ ナビ: ["navigation-menu", "breadcrumb", "sidebar"],
117
+ フォーム: ["form", "input", "select", "checkbox", "radio-group"],
118
+ タブ: ["tabs"],
119
+ アコーディオン: ["accordion", "collapsible"],
120
+ 通知: ["toast", "alert", "sonner"],
121
+ アバター: ["avatar"],
122
+ バッジ: ["badge"],
123
+ スイッチ: ["switch"],
124
+ スライダー: ["slider"],
125
+ ツールチップ: ["tooltip"],
126
+ };
127
+ function scoreComponent(item, query) {
128
+ const q = query.toLowerCase();
129
+ const name = item.name.toLowerCase();
130
+ const title = (item.title ?? item.name).toLowerCase();
131
+ const description = (item.description ?? "").toLowerCase();
132
+ const categories = (item.categories ?? []).map((c) => c.toLowerCase());
133
+ const meta = componentMetadata[item.name];
134
+ const variantValues = [];
135
+ const variantKeys = [];
136
+ if (meta?.variants) {
137
+ for (const [key, vals] of Object.entries(meta.variants)) {
138
+ variantKeys.push(key.toLowerCase());
139
+ for (const v of vals) {
140
+ variantValues.push(v.toLowerCase());
141
+ }
142
+ }
143
+ }
144
+ const slots = (meta?.slots ?? []).map((s) => s.toLowerCase());
145
+ const type = meta?.type ?? "";
146
+ let score = 0;
147
+ // 完全一致(100) > 前方一致(80) > 部分一致(60) > カテゴリ一致(40)
148
+ const checkField = (field) => {
149
+ if (field === q)
150
+ return 100;
151
+ if (field.startsWith(q))
152
+ return 80;
153
+ if (field.includes(q))
154
+ return 60;
155
+ return 0;
156
+ };
157
+ score = Math.max(score, checkField(name));
158
+ score = Math.max(score, checkField(title));
159
+ if (description.includes(q)) {
160
+ score = Math.max(score, 60);
161
+ }
162
+ for (const cat of categories) {
163
+ if (cat.includes(q) || q.includes(cat)) {
164
+ score = Math.max(score, 40);
165
+ }
166
+ }
167
+ for (const vk of variantKeys) {
168
+ if (vk === q || vk.includes(q))
169
+ score = Math.max(score, 60);
170
+ }
171
+ for (const vv of variantValues) {
172
+ if (vv === q || vv.includes(q))
173
+ score = Math.max(score, 60);
174
+ }
175
+ for (const slot of slots) {
176
+ if (slot === q || slot.includes(q))
177
+ score = Math.max(score, 40);
178
+ }
179
+ if (type === q)
180
+ score = Math.max(score, 60);
181
+ return score;
182
+ }
183
+ export async function handleSearchComponents(client, projectId, query) {
184
+ const normalizedQuery = query.trim();
185
+ if (!normalizedQuery) {
186
+ return [
187
+ {
188
+ type: "text",
189
+ text: `No components found matching "${query}". Use \`get_component_list\` to see all available components.`,
190
+ },
191
+ ];
192
+ }
193
+ const registry = await fetchRegistry(client, projectId);
194
+ // 日本語→英語マッピング展開
195
+ const queries = [normalizedQuery];
196
+ for (const [ja, ens] of Object.entries(JA_TO_EN)) {
197
+ if (normalizedQuery.includes(ja)) {
198
+ queries.push(...ens);
199
+ }
200
+ }
201
+ const scored = registry.items.map((item) => {
202
+ const maxScore = Math.max(...queries.map((q) => scoreComponent(item, q)));
203
+ return {
204
+ name: item.name,
205
+ title: item.title ?? item.name,
206
+ description: item.description ?? "",
207
+ categories: item.categories ?? [],
208
+ matchScore: maxScore,
209
+ };
210
+ });
211
+ const matched = scored
212
+ .filter((c) => c.matchScore > 0)
213
+ .sort((a, b) => b.matchScore - a.matchScore)
214
+ .slice(0, 10);
215
+ if (matched.length === 0) {
216
+ return [
217
+ {
218
+ type: "text",
219
+ text: `No components found matching "${query}". Use \`get_component_list\` to see all available components.`,
220
+ },
221
+ ];
222
+ }
223
+ const header = "| Component | Title | Categories | Score |\n|-----------|-------|------------|-------|\n";
224
+ const rows = matched
225
+ .map((c) => `| \`${c.name}\` | ${c.title} | ${c.categories.join(", ")} | ${c.matchScore} |`)
226
+ .join("\n");
227
+ const text = `## Search Results for "${query}"\n\n${header}${rows}\n\nUse \`get_component_spec\` with a component name for full details.`;
228
+ return [{ type: "text", text }];
229
+ }
230
+ /** テスト用にキャッシュをクリアする */
231
+ export function clearRegistryCache() {
232
+ cache = new WeakMap();
233
+ pending = new WeakMap();
234
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * コンセプト関連 MCP ツールハンドラー
3
+ *
4
+ * コンセプト API: GET /api/projects/{projectId}/concept
5
+ */
6
+ import type { VadenMcpApiClient } from "../api-client.js";
7
+ /**
8
+ * コンセプト情報を返す
9
+ *
10
+ * @param client - Vaden MCP API クライアント
11
+ * @param projectId - プロジェクトID
12
+ */
13
+ export declare function handleGetConcept(client: VadenMcpApiClient, projectId: string): Promise<{
14
+ type: "text";
15
+ text: string;
16
+ }[]>;
17
+ /**
18
+ * デザイン意思決定理由を返す
19
+ *
20
+ * @param client - Vaden MCP API クライアント
21
+ * @param projectId - プロジェクトID
22
+ */
23
+ export declare function handleGetDesignRationale(client: VadenMcpApiClient, projectId: string): Promise<{
24
+ type: "text";
25
+ text: string;
26
+ }[]>;