@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 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<TextContent[]>;
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
- export const searchHeroDesignComponentExamples = async (query, limit = 5, threshold = 0.4) => {
12
- try {
13
- const response = await fetch(`${HERO_DESIGN_COMPONENT_EXAMPLES_HOST}/retrieve_hero_design`, {
14
- method: "POST",
15
- headers: {
16
- "Content-Type": "application/json",
17
- },
18
- body: JSON.stringify({ query, limit, threshold }),
19
- });
20
- if (!response.ok) {
21
- throw new Error(`HTTP error status: ${response.status}`);
22
- }
23
- const examples = await response.json();
24
- if (!examples.length) {
25
- return [
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
- const responseParts = [];
33
- examples.forEach((example, index) => {
34
- responseParts.push(`\n${index + 1}. ${example.component_name} - ${example.example_name}`, `\nSimilarity Score: ${example.similarity_score.toFixed(2)}`, `\nComponent Description: ${example.component_description}`);
35
- if (example.purpose_description) {
36
- responseParts.push(`\nPurpose: ${example.purpose_description}`);
37
- }
38
- if (example.technical_description) {
39
- responseParts.push(`\nTechnical Details: ${example.technical_description}`);
40
- }
41
- if (example.code) {
42
- responseParts.push(`\nCode Example:\n\`\`\`jsx\n${example.code}\n\`\`\``);
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 (error) {
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) || num <= 0)
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) || num <= 0)
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 1.0) for returned examples. Lower values return more results but might be less relevant"),
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 content = await searchHeroDesignComponentExamples(query, limit, threshold);
111
- return { content };
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ehrocks/fe-mcp-server",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "MCP server for searching Hero Design System components",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",