@ehrocks/fe-mcp-server 1.0.6 → 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/README.md CHANGED
@@ -22,7 +22,7 @@ An MCP server for searching Hero Design System components.
22
22
  ## Troubleshooting
23
23
  - MCP connection gets `Client closed` error: *Ensure that Node.js is accessible globally.* Example: If you're using `asdf`, you can set the default Node.js version with:
24
24
  ```
25
- asdf set -u nodejs 20.19.0
25
+ asdf set -u nodejs 22.22.0
26
26
  ```
27
27
  - Connect error or timeout: Because our MCP service is deployed to Employment Hero staging cluster, ensure you have the connection via VPN. [Read more](https://employmenthero.atlassian.net/wiki/spaces/PLAT/pages/1648525859/VPN+-+Internal+services+and+sandbox+accessibility)
28
28
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  export interface TextContent {
3
3
  [key: string]: unknown;
4
- type: 'text';
4
+ type: "text";
5
5
  text: string;
6
6
  }
7
7
  export interface HeroDesignComponentExample {
@@ -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
@@ -1,120 +1,144 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
2
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
5
  import { z } from "zod";
5
- const HERO_DESIGN_COMPONENT_EXAMPLES_HOST = process.env.ENVIRONMENT === 'LOCAL' ? 'http://localhost:8000' : 'https://fe-mcp-service.staging.ehrocks.com';
6
- export const searchHeroDesignComponentExamples = async (query, limit = 5, threshold = 0.4) => {
7
- try {
8
- const response = await fetch(`${HERO_DESIGN_COMPONENT_EXAMPLES_HOST}/retrieve_hero_design`, {
9
- method: 'POST',
10
- headers: {
11
- 'Content-Type': 'application/json'
12
- },
13
- body: JSON.stringify({ query, limit, threshold })
14
- });
15
- if (!response.ok) {
16
- throw new Error(`HTTP error status: ${response.status}`);
6
+ const require = createRequire(import.meta.url);
7
+ const { version } = require("../package.json");
8
+ const HERO_DESIGN_COMPONENT_EXAMPLES_HOST = process.env.ENVIRONMENT === "LOCAL"
9
+ ? "http://localhost:8000"
10
+ : "https://fe-mcp-service.staging.ehrocks.com";
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: [
43
+ {
44
+ type: "text",
45
+ text: "I couldn't find any relevant component examples for your query.",
46
+ },
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}`);
17
58
  }
18
- const examples = await response.json();
19
- if (!examples.length) {
20
- return [{
21
- type: 'text',
22
- text: "I couldn't find any relevant component examples for your query."
23
- }];
59
+ if (example.code) {
60
+ responseParts.push(`\nCode Example:\n\`\`\`jsx\n${example.code}\n\`\`\``);
24
61
  }
25
- const responseParts = [];
26
- examples.forEach((example, index) => {
27
- responseParts.push(`\n${index + 1}. ${example.component_name} - ${example.example_name}`, `\nSimilarity Score: ${example.similarity_score.toFixed(2)}`, `\nComponent Description: ${example.component_description}`);
28
- if (example.purpose_description) {
29
- responseParts.push(`\nPurpose: ${example.purpose_description}`);
30
- }
31
- if (example.technical_description) {
32
- responseParts.push(`\nTechnical Details: ${example.technical_description}`);
33
- }
34
- if (example.code) {
35
- responseParts.push(`\nCode Example:\n\`\`\`jsx\n${example.code}\n\`\`\``);
36
- }
37
- if (example.props_description) {
38
- responseParts.push(`\nProps Description: ${example.props_description}`);
39
- }
40
- if (example.matched_embedding_type) {
41
- responseParts.push(`\nMatched Embedding Type: ${example.matched_embedding_type}`);
42
- }
43
- responseParts.push('\n' + '='.repeat(80) + '\n');
44
- });
45
- return [{
46
- type: 'text',
47
- text: 'Here are the most relevant component examples I found:\n' + responseParts.join('\n')
48
- }];
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: [
73
+ {
74
+ type: "text",
75
+ text: "Here are the most relevant component examples I found:\n" +
76
+ responseParts.join("\n"),
77
+ },
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));
49
86
  }
50
- catch (error) {
51
- return [{
52
- type: 'text',
53
- text: `An error occurred while processing your request: ${error}`
54
- }];
87
+ catch (err) {
88
+ return { ok: false, message: classifyError(err) };
55
89
  }
56
90
  };
57
91
  async function startServer() {
58
- const server = new McpServer({
59
- name: "hero-design-mcp-server",
60
- version: "1.0.0"
92
+ const server = new McpServer({ name: "hero-design-mcp-server", version }, {
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.",
61
94
  });
62
- server.tool("search_hero_design_component_examples", `Search for React component examples from the Hero Design System using semantic search.
95
+ server.tool("search_hero_design_component_examples", `Retrieve Hero Design System examples, patterns, and references using natural language.
63
96
 
64
- This tool performs semantic search across multiple specialized collections to find the most relevant component examples based on your query. The search covers:
97
+ 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.
65
98
 
66
- 1. Full Component Information: Searches across complete component documentation including names, descriptions, and examples
67
- 2. Code Examples: Specifically searches through code implementations and syntax
68
- 3. Component Descriptions: Focuses on component usage descriptions and explanations
69
- 4. Purpose & Use Cases: Matches queries about component purposes and intended use cases
70
- 5. Technical Details: Searches through technical implementation details and specifications
71
- 6. Props & Configuration: Finds examples based on prop usage and configuration options
72
- 7. Design Patterns: Matches React patterns and best practices demonstrated in examples
73
- 8. Use Case Scenarios: Searches through real-world application scenarios
99
+ Common queries: button loading state, form validation error, dropdown with search, empty state, date picker range, modal confirmation dialog, toast notification.
74
100
 
75
- You can use this tool to:
76
- - Find examples of specific components (e.g., "Button with icon")
77
- - Search for implementation patterns (e.g., "how to handle form validation")
78
- - Look up prop usage (e.g., "Button with custom colors")
79
- - Find examples of specific features (e.g., "dropdown with search")
80
- - Search for specific use cases (e.g., "loading state in forms")
81
- - Find technical implementations (e.g., "async data loading pattern")
82
-
83
- The tool will return the most relevant examples, including:
84
- - Component name and description
85
- - Example code with explanations
86
- - Technical details and implementation notes
87
- - Purpose and use case descriptions
88
- - Props and configuration details
89
- - Design patterns and best practices`, {
101
+ Returns for each match:
102
+ - Component name and similarity score
103
+ - Component and example descriptions
104
+ - Code example (JSX)
105
+ - Technical details, design patterns, and props context when available`, {
90
106
  query: z
91
107
  .string()
108
+ .trim()
109
+ .min(1, "query must not be empty")
92
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.)"),
93
111
  limit: z
94
112
  .string()
95
- .transform(val => {
113
+ .transform((val) => {
96
114
  const num = Number(val);
97
- if (isNaN(num) || num <= 0)
115
+ if (isNaN(num))
98
116
  return 5;
99
- return num;
117
+ return Math.min(Math.max(Math.round(num), 1), 5);
100
118
  })
101
119
  .optional()
102
120
  .default("5")
103
- .describe("Maximum number of distinct component examples to return"),
121
+ .describe("Maximum number of distinct component examples to return (1–5)"),
104
122
  threshold: z
105
123
  .string()
106
- .transform(val => {
124
+ .transform((val) => {
107
125
  const num = parseFloat(val);
108
- if (isNaN(num) || num <= 0)
126
+ if (isNaN(num))
109
127
  return 0.4;
110
- return num;
128
+ return Math.min(Math.max(num, 0), 0.99);
111
129
  })
112
130
  .optional()
113
131
  .default("0.4")
114
- .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"),
115
133
  }, async ({ query, limit, threshold }) => {
116
- const content = await searchHeroDesignComponentExamples(query, limit, threshold);
117
- 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
+ };
118
142
  });
119
143
  const transport = new StdioServerTransport();
120
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.6",
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",