@ehrocks/fe-mcp-server 1.0.7 → 1.0.8
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 +83 -53
- package/package.json +1 -1
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,62 +8,84 @@ 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() {
|
|
@@ -83,32 +105,40 @@ Returns for each match:
|
|
|
83
105
|
- Technical details, design patterns, and props context when available`, {
|
|
84
106
|
query: z
|
|
85
107
|
.string()
|
|
108
|
+
.trim()
|
|
109
|
+
.min(1, "query must not be empty")
|
|
86
110
|
.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.)"),
|
|
87
111
|
limit: z
|
|
88
112
|
.string()
|
|
89
113
|
.transform((val) => {
|
|
90
114
|
const num = Number(val);
|
|
91
|
-
if (isNaN(num)
|
|
115
|
+
if (isNaN(num))
|
|
92
116
|
return 5;
|
|
93
|
-
return num;
|
|
117
|
+
return Math.min(Math.max(Math.round(num), 1), 5);
|
|
94
118
|
})
|
|
95
119
|
.optional()
|
|
96
120
|
.default("5")
|
|
97
|
-
.describe("Maximum number of distinct component examples to return"),
|
|
121
|
+
.describe("Maximum number of distinct component examples to return (1–5)"),
|
|
98
122
|
threshold: z
|
|
99
123
|
.string()
|
|
100
124
|
.transform((val) => {
|
|
101
125
|
const num = parseFloat(val);
|
|
102
|
-
if (isNaN(num)
|
|
126
|
+
if (isNaN(num))
|
|
103
127
|
return 0.4;
|
|
104
|
-
return num;
|
|
128
|
+
return Math.min(Math.max(num, 0), 0.99);
|
|
105
129
|
})
|
|
106
130
|
.optional()
|
|
107
131
|
.default("0.4")
|
|
108
|
-
.describe("Minimum similarity score (0.0 to
|
|
132
|
+
.describe("Minimum similarity score (0.0 to 0.99) for returned examples. Lower values return more results but might be less relevant"),
|
|
109
133
|
}, async ({ query, limit, threshold }) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
134
|
+
const result = await searchHeroDesignComponentExamples(query, limit, threshold);
|
|
135
|
+
if (result.ok) {
|
|
136
|
+
return { content: result.content };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: result.message }],
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
112
142
|
});
|
|
113
143
|
const transport = new StdioServerTransport();
|
|
114
144
|
await server.connect(transport);
|