@ehrocks/fe-mcp-server 1.0.7 → 1.0.9
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.
- package/dist/index.d.ts +7 -1
- package/dist/index.js +114 -74
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,10 @@ export interface HeroDesignComponentExample {
|
|
|
18
18
|
similarity_score: number;
|
|
19
19
|
matched_embedding_type: string;
|
|
20
20
|
}
|
|
21
|
-
export declare const searchHeroDesignComponentExamples: (query: string, limit?: number, threshold?: number) => Promise<
|
|
21
|
+
export declare const searchHeroDesignComponentExamples: (query: string, limit?: number, threshold?: number) => Promise<{
|
|
22
|
+
ok: true;
|
|
23
|
+
content: TextContent[];
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
message: string;
|
|
27
|
+
}>;
|
package/dist/index.js
CHANGED
|
@@ -8,69 +8,92 @@ const { version } = require("../package.json");
|
|
|
8
8
|
const HERO_DESIGN_COMPONENT_EXAMPLES_HOST = process.env.ENVIRONMENT === "LOCAL"
|
|
9
9
|
? "http://localhost:8000"
|
|
10
10
|
: "https://fe-mcp-service.staging.ehrocks.com";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
const TIMEOUT_MS = 15000;
|
|
12
|
+
const IS_LOCAL = process.env.ENVIRONMENT === "LOCAL";
|
|
13
|
+
const CONNECTION_ERROR = IS_LOCAL
|
|
14
|
+
? "fe-mcp-service is unreachable. Is the local service running?"
|
|
15
|
+
: "fe-mcp-service is unreachable. VPN connection is required.";
|
|
16
|
+
const TIMEOUT_ERROR = IS_LOCAL
|
|
17
|
+
? "fe-mcp-service timed out. Is the local service running?"
|
|
18
|
+
: "fe-mcp-service timed out. Check VPN. If already connected, the service may be degraded — retry.";
|
|
19
|
+
const classifyError = (err) => {
|
|
20
|
+
if (err instanceof Error && err.name === "TimeoutError")
|
|
21
|
+
return TIMEOUT_ERROR;
|
|
22
|
+
if (err instanceof TypeError && err.message === "fetch failed")
|
|
23
|
+
return CONNECTION_ERROR;
|
|
24
|
+
return `An error occurred while processing your request: ${err}`;
|
|
25
|
+
};
|
|
26
|
+
const fetchWithTimeout = async (url, body) => {
|
|
27
|
+
const response = await fetch(url, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
body,
|
|
31
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`HTTP error status: ${response.status}`);
|
|
35
|
+
}
|
|
36
|
+
return response.json();
|
|
37
|
+
};
|
|
38
|
+
const buildContent = (examples) => {
|
|
39
|
+
if (!examples.length) {
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
content: [
|
|
26
43
|
{
|
|
27
44
|
type: "text",
|
|
28
45
|
text: "I couldn't find any relevant component examples for your query.",
|
|
29
46
|
},
|
|
30
|
-
]
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const responseParts = [];
|
|
51
|
+
examples.forEach((example, index) => {
|
|
52
|
+
responseParts.push(`\n${index + 1}. ${example.component_name} - ${example.example_name}`, `\nSimilarity Score: ${example.similarity_score.toFixed(2)}`, `\nComponent Description: ${example.component_description}`);
|
|
53
|
+
if (example.purpose_description) {
|
|
54
|
+
responseParts.push(`\nPurpose: ${example.purpose_description}`);
|
|
55
|
+
}
|
|
56
|
+
if (example.technical_description) {
|
|
57
|
+
responseParts.push(`\nTechnical Details: ${example.technical_description}`);
|
|
58
|
+
}
|
|
59
|
+
if (example.code) {
|
|
60
|
+
responseParts.push(`\nCode Example:\n\`\`\`jsx\n${example.code}\n\`\`\``);
|
|
31
61
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
if (example.props_description) {
|
|
45
|
-
responseParts.push(`\nProps Description: ${example.props_description}`);
|
|
46
|
-
}
|
|
47
|
-
if (example.matched_embedding_type) {
|
|
48
|
-
responseParts.push(`\nMatched Embedding Type: ${example.matched_embedding_type}`);
|
|
49
|
-
}
|
|
50
|
-
responseParts.push("\n" + "=".repeat(80) + "\n");
|
|
51
|
-
});
|
|
52
|
-
return [
|
|
62
|
+
if (example.props_description) {
|
|
63
|
+
responseParts.push(`\nProps Description: ${example.props_description}`);
|
|
64
|
+
}
|
|
65
|
+
if (example.matched_embedding_type) {
|
|
66
|
+
responseParts.push(`\nMatched Embedding Type: ${example.matched_embedding_type}`);
|
|
67
|
+
}
|
|
68
|
+
responseParts.push("\n" + "=".repeat(80) + "\n");
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
content: [
|
|
53
73
|
{
|
|
54
74
|
type: "text",
|
|
55
75
|
text: "Here are the most relevant component examples I found:\n" +
|
|
56
76
|
responseParts.join("\n"),
|
|
57
77
|
},
|
|
58
|
-
]
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export const searchHeroDesignComponentExamples = async (query, limit = 5, threshold = 0.4) => {
|
|
82
|
+
const url = `${HERO_DESIGN_COMPONENT_EXAMPLES_HOST}/retrieve_hero_design`;
|
|
83
|
+
const body = JSON.stringify({ query, limit, threshold });
|
|
84
|
+
try {
|
|
85
|
+
return buildContent(await fetchWithTimeout(url, body));
|
|
59
86
|
}
|
|
60
|
-
catch (
|
|
61
|
-
return
|
|
62
|
-
{
|
|
63
|
-
type: "text",
|
|
64
|
-
text: `An error occurred while processing your request: ${error}`,
|
|
65
|
-
},
|
|
66
|
-
];
|
|
87
|
+
catch (err) {
|
|
88
|
+
return { ok: false, message: classifyError(err) };
|
|
67
89
|
}
|
|
68
90
|
};
|
|
69
91
|
async function startServer() {
|
|
70
92
|
const server = new McpServer({ name: "hero-design-mcp-server", version }, {
|
|
71
93
|
instructions: "Invoke for any @hero-design/react implementation task: selecting the right component for a UI need, retrieving JSX code examples, or exploring design patterns and design system resources.",
|
|
72
94
|
});
|
|
73
|
-
server.
|
|
95
|
+
server.registerTool("search_hero_design_component_examples", {
|
|
96
|
+
description: `Retrieve Hero Design System examples, patterns, and references using natural language.
|
|
74
97
|
|
|
75
98
|
Describe the UI behavior, component, or pattern you need. Covers component selection guidance, JSX code examples, design patterns, and use cases across @hero-design/react. Not a prop API reference — use the component library docs for precise prop signatures.
|
|
76
99
|
|
|
@@ -80,35 +103,52 @@ Returns for each match:
|
|
|
80
103
|
- Component name and similarity score
|
|
81
104
|
- Component and example descriptions
|
|
82
105
|
- Code example (JSX)
|
|
83
|
-
- Technical details, design patterns, and props context when available`,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
- Technical details, design patterns, and props context when available`,
|
|
107
|
+
inputSchema: {
|
|
108
|
+
query: z
|
|
109
|
+
.string()
|
|
110
|
+
.trim()
|
|
111
|
+
.min(1, "query must not be empty")
|
|
112
|
+
.describe("The search query describing what you're looking for in the component examples. Be specific about what aspects you're interested in (code, usage, props, patterns, etc.)"),
|
|
113
|
+
limit: z
|
|
114
|
+
.string()
|
|
115
|
+
.transform((val) => {
|
|
116
|
+
const num = Number(val);
|
|
117
|
+
if (isNaN(num))
|
|
118
|
+
return 5;
|
|
119
|
+
return Math.min(Math.max(Math.round(num), 1), 5);
|
|
120
|
+
})
|
|
121
|
+
.optional()
|
|
122
|
+
.default("5")
|
|
123
|
+
.describe("Maximum number of distinct component examples to return (1–5)"),
|
|
124
|
+
threshold: z
|
|
125
|
+
.string()
|
|
126
|
+
.transform((val) => {
|
|
127
|
+
const num = parseFloat(val);
|
|
128
|
+
if (isNaN(num))
|
|
129
|
+
return 0.4;
|
|
130
|
+
return Math.min(Math.max(num, 0), 0.99);
|
|
131
|
+
})
|
|
132
|
+
.optional()
|
|
133
|
+
.default("0.4")
|
|
134
|
+
.describe("Minimum similarity score (0.0 to 0.99) for returned examples. Lower values return more results but might be less relevant"),
|
|
135
|
+
},
|
|
136
|
+
annotations: {
|
|
137
|
+
title: "Search Hero Design Component Examples",
|
|
138
|
+
readOnlyHint: true,
|
|
139
|
+
destructiveHint: false,
|
|
140
|
+
idempotentHint: true,
|
|
141
|
+
openWorldHint: false,
|
|
142
|
+
},
|
|
109
143
|
}, async ({ query, limit, threshold }) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
144
|
+
const result = await searchHeroDesignComponentExamples(query, limit, threshold);
|
|
145
|
+
if (result.ok) {
|
|
146
|
+
return { content: result.content };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
content: [{ type: "text", text: result.message }],
|
|
150
|
+
isError: true,
|
|
151
|
+
};
|
|
112
152
|
});
|
|
113
153
|
const transport = new StdioServerTransport();
|
|
114
154
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ehrocks/fe-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "MCP server for searching Hero Design System components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"author": "",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
|
+
"zod": "^3.25.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^22.14.1",
|