component-mcp-server
It's an MCP server that exposes design system component documentation to LLM clients over authenticated HTTP.
get_guidelines— return the shared design system rules and token catalog (API conventions, styling practices, design tokens) in a single call.list_components— list all documented component names fromcontexts/.get_component_context— fetch a component's parsed frontmatter, markdown body, and raw file content by name.Health check —
GET /healthconfirms the server is up andcontexts/is readable.Bearer-token authentication — all
/mcprequests requireAuthorization: Bearer <API_KEY>; missing/invalid keys get401.Read-only documentation serving — updates are made by editing Markdown files in
contexts/, with no database or restart needed.Stateless HTTP streaming — designed for concurrent LLM clients via MCP Streamable HTTP (typically behind a TLS reverse proxy).
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@component-mcp-serverwhat's the context for the button component?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
design-system-mcp
A standalone Model Context Protocol (MCP) server that exposes design system component documentation to LLM clients. It reads Markdown files with YAML frontmatter from contexts/ and serves them over three MCP tools — get_guidelines, list_components, and get_component_context — so an LLM can look up the cross-cutting API/styling rules and a real component's props and events before generating code against it.
This guide covers deploying it standalone (no Docker) to a fresh AWS EC2 instance, running under PM2.
1. What this service is
An MCP server, reachable over HTTP, that answers three questions for an LLM client: "what rules and practices apply to every component, regardless of which one I use?" (
get_guidelines), "what components are documented?" (list_components), and "give me everything you know about component X" (get_component_context).get_guidelinestakes no arguments and returnscontexts/_guidelines.mdverbatim — three things in one call: component API conventions (event handling via CustomEvent/.detail, boolean prop syntax, element-vs-service APIs, item/group component pairs, theming), styling practices (e.g. dimension/background defaults, typography tokens being composite font shorthand values rather than standalonefont-size/font-weight, no margin tokens — use padding/gap instead), and the full contextual (--ion-cont-*) design token catalog for color, spacing, sizing, border-radius, border-width, shadow, typography, motion, opacity, and layout. It exists so components get built against real API conventions and styling work never falls back to raw values (hex, px), literal (--ion-lit-*) tokens, or a token applied the wrong way. Some token entries are marked(auto)in the source file — their description was generated from the token name because the underlying CSS had no comment for it (mostly in the spacing/sizing/layout sections). The token names are exact and verified; treat(auto)descriptions as unverified until a human reviews them against Figma/source.get_guidelinesmerges what used to be two separate tools (get_conventionsandget_styling_rules/get_tokens) — both were "call once per session before writing code," and splitting them only helps if some sessions need one but not the other, which isn't the case here. It's meant to be called once per session, before any component-specific lookup or styling work —list_components' andget_guidelines' own tool descriptions say so, so a well-behaved client calls it first without being told.Stateless: every
/mcprequest spins up a fresh MCP server + transport internally, so it's safe to run behind a load balancer with multiple concurrent clients and no session affinity required.Read-only against
contexts/— there's no database, no write path; updating documentation means editing/adding a Markdown file in that directory (see §9).contexts/_guidelines.mdis the exception to "every file incontexts/is a component" — it's a shared reference doc, not a component, and is excluded fromlist_components' output and fromvalidate_component.py's per-component schema checks (any file starting with_is skipped by both).
Related MCP server: Markdown RAG MCP
2. Prerequisites
Node.js 18 or later (LTS recommended — 20.x or newer). The build uses
NodeNextmodule resolution and ES2022 target, which need a reasonably current Node.npm (ships with Node).
PM2, installed globally on the instance:
npm install -g pm2
3. Environment variables
Copy .env.example to .env and fill in real values — or export the same variables directly in the shell/systemd unit that starts PM2 (PM2 does not read .env files itself unless you load them into the shell first; see §5).
Variable | Required | Purpose | How to set it |
| Yes — the server exits immediately at startup if this is unset | Bearer token that every | Generate a random 32-byte hex token: |
| No (defaults to | TCP port the Node process listens on. | Pick anything free on the instance; |
4. Install and build
From the repo root, in order:
npm install
npm run buildnpm run build runs tsc and compiles src/ to dist/. Confirm it produced output:
ls dist/
# expect: http.js index.js server.js tools/The HTTP entrypoint is dist/http.js.
5. Starting the server under PM2
Export the environment variables from §3 into the shell before starting PM2 — PM2 captures and persists the environment of the process that starts it, reusing it on subsequent restart/resurrect:
export API_KEY=$(cat /path/to/your/secret) # or however you're sourcing it
export PORT=3000
pm2 start ecosystem.config.cjsThe config file is
ecosystem.config.cjs(not.js) — this package is"type": "module"inpackage.json, and PM2's config loader needs CommonJS, so.cjsis required, not a style choice.
Confirm it's actually running:
pm2 list
# expect a row for "design-system-mcp" with status "online"
pm2 logs design-system-mcp --lines 20
# expect: "design-system-mcp (Streamable HTTP) listening on port 3000"Surviving an instance reboot
Starting it once isn't enough — by default PM2 doesn't survive a reboot. Set up both of these, once:
pm2 save # snapshots the current process list
pm2 startup # prints an OS-specific command
# copy/paste and run the command pm2 startup prints, as root (e.g. via sudo) —
# this registers a systemd (or equivalent) service that resurrects
# "pm2 save"'s snapshot on bootAfter any future change to what's running under PM2 (new app, changed env, etc.), re-run pm2 save so the snapshot stays current.
Other PM2 commands you'll use
Action | Command |
Stop |
|
Restart (drops in-flight connections) |
|
Reload (zero-downtime) |
|
Remove from PM2 entirely |
|
Tail logs |
|
Logs also land in logs/out.log and logs/error.log in the repo root (gitignored), independent of pm2 logs.
6. Verifying it's working
A process showing "online" in pm2 list only proves Node is alive — it doesn't prove the service actually works. Do both of these:
Health check (confirms contexts/ is present and readable, not just that the process exists):
curl -s http://localhost:3000/health
# expect: {"status":"ok","components":20}
# (component count will match whatever's actually in contexts/)A 503 here means contexts/ is missing or unreadable — investigate before moving on.
A real end-to-end MCP call — this exercises the actual tool logic, not just connectivity:
curl -s -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_component_context",
"arguments": { "componentName": "button" }
}
}'Expect a text/event-stream response containing the button component's frontmatter, body, and raw content.
get_guidelines — same idea, no arguments:
curl -s -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_guidelines",
"arguments": {}
}
}'Expect the full contents of contexts/_guidelines.md back verbatim — API conventions (events/.detail, boolean props, apiTypes, relatedComponents, theming), styling practices (dimension/background defaults, typography-shorthand and no-margin-tokens notes), and the token catalog (border-radius/width, color, shadow, motion, opacity, sizing, spacing, layout, typography). Also confirm list_components doesn't include _guidelines in its output — it's a shared reference doc, not a component.
As a functional check, ask the connected coding assistant to build a form or similar UI and confirm: (a) it calls get_guidelines exactly once, without being reminded; (b) it doesn't set an explicit width/max-width/height/background-color on the outer container (those should come from the design system's own defaults per the styling practices in _guidelines.md, not be hardcoded); and (c) it uses the typography shorthand token and a padding/gap token for text sizing and vertical spacing — not raw font-size/margin values, and not a token named margin-* (there isn't one).
If you'd rather click through any of these interactively, point the MCP Inspector at http://localhost:3000/mcp with the same bearer token.
Also confirm auth is actually enforced — this should return 401, not the component data:
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
# expect: 4017. TLS/HTTPS — required before any public exposure
This server speaks plain HTTP only. It does not terminate TLS itself. Do not point a public DNS record or security-group rule directly at port 3000 — put a reverse proxy in front that handles HTTPS, and only expose that.
The simplest option is Caddy: it gets you automatic HTTPS via Let's Encrypt with a few lines of config and no manual certificate renewal.
# Install Caddy (Debian/Ubuntu example — see caddyserver.com for other distros)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddyMinimal Caddyfile (typically /etc/caddy/Caddyfile):
component-docs.yourdomain.com {
reverse_proxy localhost:3000
}Reload Caddy after editing:
sudo systemctl reload caddyThat's it — Caddy provisions and renews the Let's Encrypt certificate automatically as long as the domain's DNS points at this instance and ports 80/443 are open in your security group. Clients then connect to https://component-docs.yourdomain.com/mcp, never to the bare Node port.
8. Security notes
Rate limiting is enabled:
/mcpallows 100 requests/minute per IP;/healthallows 300/minute (it's expected to be polled more often by uptime checks).Bearer token auth is required on all
/mcprequests (POST,GET,DELETE) — a missing or incorrectAuthorizationheader gets a401, never a peek at component data./healthdoes not require auth (it reveals no component content, just a readiness signal).The
API_KEYvalue is the shared secret every consuming team needs. Distribute it out-of-band (a secrets manager, a password manager entry, a DM) — never in a committed config file, a Slack message that gets indexed, or a public repo. Rotate it by updating the deployed environment variable and restarting PM2 withpm2 restart design-system-mcp --update-env(after re-exporting the newAPI_KEY— see §5), then redistributing the new value to consumers.--update-envis not optional here. PM2 caches the environment a process was originally started with and reuses it on a plainrestart, even if you've re-exported a new value in your shell first — the old key keeps working and the new one is rejected until you pass--update-envto force PM2 to reread the environment. Confirmed by testing directly: after re-exportingAPI_KEYand running plainpm2 restart, the old key still returned200and the new key returned401; re-running withpm2 restart design-system-mcp --update-envreversed both — old key401, new key200.
9. Adding or updating a component
Gather the component's full source material (Angular source, TS interfaces, design tokens, docs, Storybook stories, usage samples).
Feed
extraction-prompt.mdto an LLM along with that material. It produces a single Markdown file (YAML frontmatter + body) in the format the rest ofcontexts/uses.Save it as
contexts/<componentName>.md.Run the validator — this is mandatory, not optional:
python3 validate_component.py contexts/<componentName>.mdA new or updated component file is not considered done until this exits
0. Fix every blocking issue it reports and re-run. Review warnings too — they're not always errors, but each should be a deliberate, explainable choice, not something overlooked.No deploy or restart needed —
contexts/is read at request time, so the change is live as soon as the file is saved on the running instance.
10. How teams connect once deployed
The server speaks MCP Streamable HTTP at POST https://<your-deployed-host>/mcp (through the reverse proxy from §7), authenticated with the shared bearer token from §8.
Claude Code
claude mcp add --transport http component-docs https://<your-deployed-host>/mcp \
--header "Authorization: Bearer <API_KEY>"Or directly in .mcp.json:
{
"mcpServers": {
"component-docs": {
"type": "http",
"url": "https://<your-deployed-host>/mcp",
"headers": {
"Authorization": "Bearer <API_KEY>"
}
}
}
}opencode
{
"mcp": {
"component-docs": {
"type": "remote",
"url": "https://<your-deployed-host>/mcp",
"headers": {
"Authorization": "Bearer <API_KEY>"
},
"enabled": true
}
}
}Replace <your-deployed-host> with the real domain from §7 and <API_KEY> with the real token from §3 — never commit either into a shared config file.
Available Tools
2 toolsget_component_contextA
Looks up contexts/.md (case-insensitive) and returns its parsed frontmatter, raw markdown body, and full raw file content. If the component isn't found, returns an error listing the currently available component names.
| Name | Required | Description | Default |
|---|---|---|---|
| componentName | Yes | The name of the component to fetch context for, e.g. 'button' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. It transparently explains the lookup is case-insensitive, the return structure (frontmatter, body, raw file), and error behavior. No contradictions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Single sentence efficiently conveys all essential information: action, file path, case-insensitivity, return structure, and error handling. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Tool has one parameter and no output schema, but description fully explains what the tool does and what it returns, including edge case. Complete for its simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Input schema has 100% description coverage for the single required parameter. The description adds no additional semantic detail beyond the schema's example and definition. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states the tool retrieves a component's context from a file, returning frontmatter, markdown body, and raw content. It also specifies error handling. This distinguishes it from sibling list_components, which lists components.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Description tells when to use (to fetch component context) and what happens on error (lists available components). It does not explicitly say when not to use, but the context is clear for a simple lookup tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_componentsA
Scans the contexts/ directory and returns the names of all available design system components (without the .md extension), so a caller can discover what's available before requesting details.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully describes the behavior: scans a directory, returns names without .md extension. It is transparent about the operation, though it could mention read-only nature or performance characteristics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no wasted words, front-loaded with the core action. Highly efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple discovery tool with no output schema, the description completely explains what is returned and why. No gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so schema coverage is 100%. The description adds no parameter info but does not need to. Baseline 4 for 0 params.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb (scans, returns) and the resource (design system component names from contexts/ directory). It distinguishes from sibling get_component_context by framing this as a discovery step before requesting details.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use: before requesting details with get_component_context. It provides clear context but does not explicitly state when not to use or list alternatives beyond the sibling.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
The two tools have clearly distinct purposes: one lists available components, the other retrieves detailed content for a specific component. There is no overlap or ambiguity.
Both tools follow a consistent verb_noun pattern with snake_case (list_components, get_component_context), making the naming predictable and clear.
With only two tools, the server feels minimal but still covers basic discovery and retrieval. However, for a server named 'component-mcp-server', a few more tools (e.g., search or create) might be expected, so the count is borderline.
The server only supports reading components (list and get). Missing create, update, and delete operations leave significant gaps for full component lifecycle management, limiting its usefulness for agents needing to modify components.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Public read-only MCP for products, frameworks, guides, methodology, and blog metadata.
Team docs served to AI agents over MCP - search, Markdown reads, version pinning, read audit.
Read-only MCP server for the OrchestKit docs: full-text search + Markdown fetch. No auth.
Read-only MCP over the Mzizi design system registry — nodes, components, ownership.
Related MCP Servers
- AlicenseAqualityCmaintenanceMCP server that exposes the @bunge/ds-components design system catalog, allowing AI assistants to list, search, and retrieve component details including inputs, outputs, usage examples, and import instructions.4MIT
- AlicenseNot gradedqualityDmaintenanceProvides semantic search over markdown documentation using RAG, allowing natural language queries and integration with MCP clients.1MIT
- AlicenseNot gradedqualityDmaintenanceProvides resources, tools, and prompts for a Design System via MCP protocol, enabling component search, reading, and related component discovery.225MIT
- AlicenseNot gradedqualityCmaintenanceExposes GOV.UK Frontend components and Design System patterns and styles as MCP resources for use with AI assistants.17MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Aniket-Sharma27/component-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server