@rio-cloud/uikit-mcp 1.0.0
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/LICENSE +202 -0
- package/README.md +91 -0
- package/bin/uikit-mcp.mjs +23 -0
- package/data/pages/Components/components/accentbar.json +207 -0
- package/data/pages/Components/components/activity.json +87 -0
- package/data/pages/Components/components/animatednumber.json +99 -0
- package/data/pages/Components/components/animations.json +87 -0
- package/data/pages/Components/components/appheader.json +291 -0
- package/data/pages/Components/components/applayout.json +1198 -0
- package/data/pages/Components/components/appnavigationbar.json +327 -0
- package/data/pages/Components/components/areacharts.json +563 -0
- package/data/pages/Components/components/aspectratioplaceholder.json +75 -0
- package/data/pages/Components/components/assettree.json +3080 -0
- package/data/pages/Components/components/autosuggests.json +710 -0
- package/data/pages/Components/components/avatar.json +157 -0
- package/data/pages/Components/components/banner.json +599 -0
- package/data/pages/Components/components/barcharts.json +1507 -0
- package/data/pages/Components/components/barlist.json +223 -0
- package/data/pages/Components/components/basicmap.json +68 -0
- package/data/pages/Components/components/bottomsheet.json +601 -0
- package/data/pages/Components/components/button.json +583 -0
- package/data/pages/Components/components/buttontoolbar.json +63 -0
- package/data/pages/Components/components/calendarstripe.json +235 -0
- package/data/pages/Components/components/card.json +69 -0
- package/data/pages/Components/components/carousel.json +39 -0
- package/data/pages/Components/components/chartcolors.json +34 -0
- package/data/pages/Components/components/chartsgettingstarted.json +32 -0
- package/data/pages/Components/components/chat.json +39 -0
- package/data/pages/Components/components/checkbox.json +847 -0
- package/data/pages/Components/components/clearableinput.json +789 -0
- package/data/pages/Components/components/collapse.json +175 -0
- package/data/pages/Components/components/composedcharts.json +159 -0
- package/data/pages/Components/components/contentloader.json +233 -0
- package/data/pages/Components/components/datatabs.json +680 -0
- package/data/pages/Components/components/datepickers.json +287 -0
- package/data/pages/Components/components/dialogs.json +1492 -0
- package/data/pages/Components/components/divider.json +93 -0
- package/data/pages/Components/components/dropdowns.json +936 -0
- package/data/pages/Components/components/editablecontent.json +1117 -0
- package/data/pages/Components/components/expander.json +377 -0
- package/data/pages/Components/components/fade.json +403 -0
- package/data/pages/Components/components/fadeexpander.json +75 -0
- package/data/pages/Components/components/fadeup.json +127 -0
- package/data/pages/Components/components/feedback.json +269 -0
- package/data/pages/Components/components/filepickers.json +269 -0
- package/data/pages/Components/components/formlabel.json +115 -0
- package/data/pages/Components/components/fullscreenmap.json +22 -0
- package/data/pages/Components/components/groupeditemlist.json +323 -0
- package/data/pages/Components/components/iconlist.json +45 -0
- package/data/pages/Components/components/imagepreloader.json +81 -0
- package/data/pages/Components/components/labeledelement.json +75 -0
- package/data/pages/Components/components/licenseplate.json +69 -0
- package/data/pages/Components/components/linecharts.json +987 -0
- package/data/pages/Components/components/listmenu.json +313 -0
- package/data/pages/Components/components/loadmore.json +175 -0
- package/data/pages/Components/components/mainnavigation.json +39 -0
- package/data/pages/Components/components/mapcircle.json +34 -0
- package/data/pages/Components/components/mapcluster.json +51 -0
- package/data/pages/Components/components/mapcontext.json +105 -0
- package/data/pages/Components/components/mapdraggablemarker.json +34 -0
- package/data/pages/Components/components/mapgettingstarted.json +27 -0
- package/data/pages/Components/components/mapgroup.json +1198 -0
- package/data/pages/Components/components/mapinfobubble.json +34 -0
- package/data/pages/Components/components/maplayergroup.json +34 -0
- package/data/pages/Components/components/mapmarker.json +700 -0
- package/data/pages/Components/components/mappolygon.json +45 -0
- package/data/pages/Components/components/maproute.json +623 -0
- package/data/pages/Components/components/maproutegenerator.json +16 -0
- package/data/pages/Components/components/mapsettings.json +51 -0
- package/data/pages/Components/components/maputils.json +34 -0
- package/data/pages/Components/components/multiselects.json +1451 -0
- package/data/pages/Components/components/nodata.json +139 -0
- package/data/pages/Components/components/notifications.json +65 -0
- package/data/pages/Components/components/numbercontrol.json +301 -0
- package/data/pages/Components/components/onboarding.json +302 -0
- package/data/pages/Components/components/page.json +197 -0
- package/data/pages/Components/components/pager.json +93 -0
- package/data/pages/Components/components/piecharts.json +731 -0
- package/data/pages/Components/components/popover.json +251 -0
- package/data/pages/Components/components/position.json +69 -0
- package/data/pages/Components/components/radialbarcharts.json +1304 -0
- package/data/pages/Components/components/radiobutton.json +1105 -0
- package/data/pages/Components/components/releasenotes.json +44 -0
- package/data/pages/Components/components/resizer.json +93 -0
- package/data/pages/Components/components/responsivecolumnstripe.json +123 -0
- package/data/pages/Components/components/responsivevideo.json +75 -0
- package/data/pages/Components/components/rioglyph.json +93 -0
- package/data/pages/Components/components/rules.json +410 -0
- package/data/pages/Components/components/saveableinput.json +703 -0
- package/data/pages/Components/components/selects.json +701 -0
- package/data/pages/Components/components/sidebar.json +243 -0
- package/data/pages/Components/components/sliders.json +235 -0
- package/data/pages/Components/components/smoothscrollbars.json +335 -0
- package/data/pages/Components/components/spinners.json +343 -0
- package/data/pages/Components/components/states.json +1705 -0
- package/data/pages/Components/components/statswidgets.json +314 -0
- package/data/pages/Components/components/statusbar.json +177 -0
- package/data/pages/Components/components/stepbutton.json +57 -0
- package/data/pages/Components/components/steppedprogressbars.json +417 -0
- package/data/pages/Components/components/subnavigation.json +107 -0
- package/data/pages/Components/components/supportmarker.json +45 -0
- package/data/pages/Components/components/svgimage.json +81 -0
- package/data/pages/Components/components/switch.json +111 -0
- package/data/pages/Components/components/tables.json +144 -0
- package/data/pages/Components/components/tagmanager.json +86 -0
- package/data/pages/Components/components/tags.json +146 -0
- package/data/pages/Components/components/teaser.json +188 -0
- package/data/pages/Components/components/timeline.json +45 -0
- package/data/pages/Components/components/timepicker.json +163 -0
- package/data/pages/Components/components/togglebutton.json +247 -0
- package/data/pages/Components/components/tooltip.json +270 -0
- package/data/pages/Components/components/virtuallist.json +175 -0
- package/data/pages/Foundations/foundations.json +2475 -0
- package/data/pages/Getting-started/start/changelog.json +22 -0
- package/data/pages/Getting-started/start/goodtoknow.json +32 -0
- package/data/pages/Getting-started/start/guidelines/color-combinations.json +58 -0
- package/data/pages/Getting-started/start/guidelines/custom-css.json +27 -0
- package/data/pages/Getting-started/start/guidelines/custom-rioglyph.json +22 -0
- package/data/pages/Getting-started/start/guidelines/formatting.json +97 -0
- package/data/pages/Getting-started/start/guidelines/iframe.json +93 -0
- package/data/pages/Getting-started/start/guidelines/obfuscate-data.json +22 -0
- package/data/pages/Getting-started/start/guidelines/print-css.json +37 -0
- package/data/pages/Getting-started/start/guidelines/spinner.json +144 -0
- package/data/pages/Getting-started/start/guidelines/supported-browsers.json +22 -0
- package/data/pages/Getting-started/start/guidelines/writing.json +242 -0
- package/data/pages/Getting-started/start/howto.json +72 -0
- package/data/pages/Getting-started/start/intro.json +37 -0
- package/data/pages/Getting-started/start/responsiveness.json +52 -0
- package/data/pages/Templates/templates/common-table.json +39 -0
- package/data/pages/Templates/templates/detail-views.json +71 -0
- package/data/pages/Templates/templates/expandable-details.json +39 -0
- package/data/pages/Templates/templates/feature-cards.json +103 -0
- package/data/pages/Templates/templates/form-summary.json +39 -0
- package/data/pages/Templates/templates/form-toggle.json +39 -0
- package/data/pages/Templates/templates/list-blocks.json +119 -0
- package/data/pages/Templates/templates/loading-progress.json +39 -0
- package/data/pages/Templates/templates/options-panel.json +39 -0
- package/data/pages/Templates/templates/panel-variants.json +39 -0
- package/data/pages/Templates/templates/progress-cards.json +71 -0
- package/data/pages/Templates/templates/progress-success.json +39 -0
- package/data/pages/Templates/templates/settings-form.json +39 -0
- package/data/pages/Templates/templates/stats-blocks.json +135 -0
- package/data/pages/Templates/templates/table-panel.json +39 -0
- package/data/pages/Templates/templates/table-row-animation.json +39 -0
- package/data/pages/Templates/templates/usage-cards.json +39 -0
- package/data/pages/Utilities/utilities/deviceutils.json +39 -0
- package/data/pages/Utilities/utilities/featuretoggles.json +42 -0
- package/data/pages/Utilities/utilities/fueltypeutils.json +118 -0
- package/data/pages/Utilities/utilities/routeutils.json +34 -0
- package/data/pages/Utilities/utilities/useaftermount.json +63 -0
- package/data/pages/Utilities/utilities/useaverage.json +86 -0
- package/data/pages/Utilities/utilities/useclickoutside.json +69 -0
- package/data/pages/Utilities/utilities/useclipboard.json +57 -0
- package/data/pages/Utilities/utilities/usecount.json +92 -0
- package/data/pages/Utilities/utilities/usedarkmode.json +50 -0
- package/data/pages/Utilities/utilities/usedebuginfo.json +63 -0
- package/data/pages/Utilities/utilities/useeffectonce.json +57 -0
- package/data/pages/Utilities/utilities/useelapsedtime.json +57 -0
- package/data/pages/Utilities/utilities/useelementsize.json +63 -0
- package/data/pages/Utilities/utilities/useesc.json +57 -0
- package/data/pages/Utilities/utilities/useevent.json +75 -0
- package/data/pages/Utilities/utilities/usefocustrap.json +57 -0
- package/data/pages/Utilities/utilities/usefullscreen.json +197 -0
- package/data/pages/Utilities/utilities/usehover.json +57 -0
- package/data/pages/Utilities/utilities/useinterval.json +63 -0
- package/data/pages/Utilities/utilities/useisfocuswithin.json +75 -0
- package/data/pages/Utilities/utilities/usekey.json +75 -0
- package/data/pages/Utilities/utilities/uselocalstorage.json +69 -0
- package/data/pages/Utilities/utilities/uselocationsuggestions.json +110 -0
- package/data/pages/Utilities/utilities/usemax.json +86 -0
- package/data/pages/Utilities/utilities/usemin.json +86 -0
- package/data/pages/Utilities/utilities/usemutationobserver.json +69 -0
- package/data/pages/Utilities/utilities/useonlinestatus.json +39 -0
- package/data/pages/Utilities/utilities/useonscreen.json +63 -0
- package/data/pages/Utilities/utilities/usepostmessage.json +80 -0
- package/data/pages/Utilities/utilities/useprevious.json +63 -0
- package/data/pages/Utilities/utilities/useresizeobserver.json +65 -0
- package/data/pages/Utilities/utilities/usescrollposition.json +103 -0
- package/data/pages/Utilities/utilities/usesearch.json +197 -0
- package/data/pages/Utilities/utilities/usesorting.json +139 -0
- package/data/pages/Utilities/utilities/usestatewithvalidation.json +69 -0
- package/data/pages/Utilities/utilities/usesum.json +86 -0
- package/data/pages/Utilities/utilities/usetableexport.json +87 -0
- package/data/pages/Utilities/utilities/usetableselection.json +311 -0
- package/data/pages/Utilities/utilities/usetimeout.json +63 -0
- package/data/pages/Utilities/utilities/usetoggle.json +75 -0
- package/data/pages/Utilities/utilities/usewindowresize.json +63 -0
- package/data/version.json +4 -0
- package/docs/content-schema.md +147 -0
- package/docs/navigation-inventory.json +1310 -0
- package/docs/search-synonyms.json +43 -0
- package/package.json +38 -0
- package/server/index.mjs +268 -0
- package/server/lib/load-docs.mjs +48 -0
- package/server/lib/normalise-doc.mjs +220 -0
- package/server/lib/render-markdown.mjs +82 -0
- package/server/lib/search-index.mjs +49 -0
- package/server/lib/types.js +99 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"synonyms": {
|
|
3
|
+
"install": ["installation", "setup", "how to", "howto", "getting started"],
|
|
4
|
+
"version": ["release"],
|
|
5
|
+
"button": ["btn", "cta", "buttons"],
|
|
6
|
+
"dropdown": ["select", "combobox"],
|
|
7
|
+
"select": ["dropdown", "combobox"],
|
|
8
|
+
"table": ["datagrid", "grid"],
|
|
9
|
+
"datagrid": ["table", "grid"],
|
|
10
|
+
"form": ["input", "field"],
|
|
11
|
+
"modal": ["dialog"],
|
|
12
|
+
"dialog": ["modal"],
|
|
13
|
+
"toast": ["alert", "notification"],
|
|
14
|
+
"alert": ["toast", "notification"],
|
|
15
|
+
"datepicker": ["date picker", "calendar"],
|
|
16
|
+
"date picker": ["datepicker", "calendar"],
|
|
17
|
+
"accordion": ["expander"],
|
|
18
|
+
"expander": ["accordion"],
|
|
19
|
+
"chip": ["tag", "badge"],
|
|
20
|
+
"tag": ["chip", "badge"]
|
|
21
|
+
},
|
|
22
|
+
"preferredDocIds": {
|
|
23
|
+
"install": ["start/howto"],
|
|
24
|
+
"getting started": ["start/howto"],
|
|
25
|
+
"setup": ["start/howto"],
|
|
26
|
+
"version": ["start/howto"],
|
|
27
|
+
"button": ["components/button"],
|
|
28
|
+
"dropdown": ["components/dropdowns"],
|
|
29
|
+
"select": ["components/dropdowns"],
|
|
30
|
+
"table": ["components/tables"],
|
|
31
|
+
"datagrid": ["components/tables"],
|
|
32
|
+
"modal": ["components/dialogs"],
|
|
33
|
+
"dialog": ["components/dialogs"],
|
|
34
|
+
"toast": ["components/notifications"],
|
|
35
|
+
"alert": ["components/notifications"],
|
|
36
|
+
"datepicker": ["components/datepickers"],
|
|
37
|
+
"date picker": ["components/datepickers"],
|
|
38
|
+
"accordion": ["components/expander"],
|
|
39
|
+
"expander": ["components/expander"],
|
|
40
|
+
"chip": ["components/tags"],
|
|
41
|
+
"tag": ["components/tags"]
|
|
42
|
+
}
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rio-cloud/uikit-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server that serves the captured RIO UI Kit documentation to Model Context Protocol clients.",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"author": "TB Digital Services GmbH",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"bin": {
|
|
12
|
+
"uikit-mcp": "./bin/uikit-mcp.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"server/",
|
|
17
|
+
"data/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"package.json",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test",
|
|
25
|
+
"setup:crawler": "npx playwright install chromium",
|
|
26
|
+
"crawl:full": "node tools/crawl-full.mjs",
|
|
27
|
+
"crawl:navigation": "node crawler/cli/crawl-navigation.mjs",
|
|
28
|
+
"capture:all": "node crawler/cli/capture-all.mjs",
|
|
29
|
+
"mcp:server": "node server/index.mjs",
|
|
30
|
+
"link:global": "npm link"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
34
|
+
"flexsearch": "^0.8.212",
|
|
35
|
+
"playwright": "^1.45.0",
|
|
36
|
+
"zod": "^3.25.76"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/server/index.mjs
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import { loadDocumentation } from './lib/load-docs.mjs';
|
|
10
|
+
import { renderDocMarkdown } from './lib/render-markdown.mjs';
|
|
11
|
+
import { buildSearchIndex } from './lib/search-index.mjs';
|
|
12
|
+
|
|
13
|
+
function readCapturedVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const versionPath = path.resolve(new URL('../data/version.json', import.meta.url).pathname);
|
|
16
|
+
return JSON.parse(readFileSync(versionPath, 'utf8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function loadSearchSynonyms() {
|
|
23
|
+
try {
|
|
24
|
+
const synonymsPath = path.resolve(new URL('../docs/search-synonyms.json', import.meta.url).pathname);
|
|
25
|
+
return JSON.parse(readFileSync(synonymsPath, 'utf8'));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.warn('Warning: failed to load search synonyms:', error.message);
|
|
28
|
+
return { synonyms: {}, preferredDocIds: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const VERSION_INFO = readCapturedVersion();
|
|
33
|
+
const CURRENT_UIKIT_VERSION = VERSION_INFO?.version ?? null;
|
|
34
|
+
const SEARCH_SYNONYMS = loadSearchSynonyms();
|
|
35
|
+
|
|
36
|
+
const server = new McpServer(
|
|
37
|
+
{
|
|
38
|
+
name: 'uikit-mcp-server',
|
|
39
|
+
version: '0.1.0'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
instructions: `UI Kit MCP usage guidelines:
|
|
43
|
+
- Always discover documentation with search_docs; listResources is a static inventory and must not be used for guessing. Call readResource on the returned uikit-doc:// URI before using anything.
|
|
44
|
+
- Coding rules: import components from '@rio-cloud/rio-uikit', prefer styling via provided classes instead of custom CSS files, and wrap the app/content in ApplicationLayout.
|
|
45
|
+
- Follow documented props tables and examples; avoid inventing undocumented variants.
|
|
46
|
+
- Treat the captured metadata (Category, Section, Captured) as the source of truth. If you need newer upstream info, say so explicitly—live crawling/fetching is not available here.
|
|
47
|
+
- This server serves offline captures only; do not attempt to run Playwright or crawling scripts from this MCP session.
|
|
48
|
+
- Captured UI Kit version: ${CURRENT_UIKIT_VERSION ?? 'unknown'} (from data/version.json). Ask if you require a newer version.`
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
function buildResourceUri(docId) {
|
|
53
|
+
return new URL(`uikit-doc://${docId}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function expandDomainSynonyms(rawQuery) {
|
|
57
|
+
const config = SEARCH_SYNONYMS;
|
|
58
|
+
const query = rawQuery.toLowerCase();
|
|
59
|
+
const expansions = new Set();
|
|
60
|
+
const preferred = new Set();
|
|
61
|
+
const hints = new Set();
|
|
62
|
+
|
|
63
|
+
for (const [key, values] of Object.entries(config.synonyms ?? {})) {
|
|
64
|
+
if (query.includes(key.toLowerCase())) {
|
|
65
|
+
(values ?? []).forEach((value) => {
|
|
66
|
+
if (value.toLowerCase() !== query) {
|
|
67
|
+
expansions.add(value);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const ids = config.preferredDocIds?.[key] ?? [];
|
|
71
|
+
ids.forEach((id) => preferred.add(id));
|
|
72
|
+
if (config.hints?.[key]) {
|
|
73
|
+
hints.add(config.hints[key]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
expansions: Array.from(expansions),
|
|
80
|
+
preferredDocIds: Array.from(preferred),
|
|
81
|
+
hints: Array.from(hints)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function registerDocResources(docs) {
|
|
86
|
+
for (const doc of docs.values()) {
|
|
87
|
+
const uri = buildResourceUri(doc.id);
|
|
88
|
+
const versionNote = CURRENT_UIKIT_VERSION
|
|
89
|
+
? `\n\n> Captured UI Kit version: ${CURRENT_UIKIT_VERSION}`
|
|
90
|
+
: '';
|
|
91
|
+
const markdown = renderDocMarkdown(doc) + versionNote;
|
|
92
|
+
|
|
93
|
+
server.registerResource(
|
|
94
|
+
doc.id,
|
|
95
|
+
uri.href,
|
|
96
|
+
{
|
|
97
|
+
title: doc.title ?? doc.id,
|
|
98
|
+
description: doc.summary,
|
|
99
|
+
mimeType: 'text/markdown'
|
|
100
|
+
},
|
|
101
|
+
async () => ({
|
|
102
|
+
contents: [
|
|
103
|
+
{
|
|
104
|
+
uri: uri.href,
|
|
105
|
+
mimeType: 'text/markdown',
|
|
106
|
+
text: markdown
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.error(`Registered ${docs.size} documentation resources.`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function registerSearchTool(docs, index) {
|
|
117
|
+
const inputSchema = {
|
|
118
|
+
query: z.string().min(1, 'Query is required'),
|
|
119
|
+
limit: z.number().int().min(1).max(20).optional()
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const outputSchema = {
|
|
123
|
+
results: z.array(
|
|
124
|
+
z.object({
|
|
125
|
+
resource: z.string(),
|
|
126
|
+
title: z.string(),
|
|
127
|
+
summary: z.string().optional(),
|
|
128
|
+
category: z.string().nullish(),
|
|
129
|
+
section: z.string().nullish()
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
server.registerTool(
|
|
135
|
+
'search_docs',
|
|
136
|
+
{
|
|
137
|
+
title: 'Search UI Kit docs',
|
|
138
|
+
description:
|
|
139
|
+
'Full-text search across the captured UI Kit documentation. Always call this before readResource; it is the only supported way to discover resource IDs.',
|
|
140
|
+
inputSchema,
|
|
141
|
+
outputSchema
|
|
142
|
+
},
|
|
143
|
+
async ({ query, limit = 5 }) => {
|
|
144
|
+
const searchQueue = [query];
|
|
145
|
+
const domainSynonyms = expandDomainSynonyms(query);
|
|
146
|
+
for (const extra of domainSynonyms.expansions) {
|
|
147
|
+
searchQueue.push(extra);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
const resolved = [];
|
|
152
|
+
const preferredDocIds = [];
|
|
153
|
+
const hints = [];
|
|
154
|
+
|
|
155
|
+
preferredDocIds.push(...domainSynonyms.preferredDocIds);
|
|
156
|
+
hints.push(...domainSynonyms.hints);
|
|
157
|
+
|
|
158
|
+
for (const id of preferredDocIds) {
|
|
159
|
+
if (seen.has(id)) continue;
|
|
160
|
+
const doc = docs.get(id);
|
|
161
|
+
if (!doc) continue;
|
|
162
|
+
seen.add(id);
|
|
163
|
+
resolved.push({
|
|
164
|
+
resource: buildResourceUri(id).href,
|
|
165
|
+
title: doc.title ?? id,
|
|
166
|
+
summary: doc.summary ?? '',
|
|
167
|
+
category: doc.category ?? null,
|
|
168
|
+
section: doc.section ?? null
|
|
169
|
+
});
|
|
170
|
+
if (resolved.length >= limit) break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const searchQuery of searchQueue) {
|
|
174
|
+
const hits = index.search(searchQuery, {
|
|
175
|
+
limit: Math.max(limit * 2, 10),
|
|
176
|
+
suggest: true
|
|
177
|
+
});
|
|
178
|
+
for (const group of hits) {
|
|
179
|
+
for (const id of group.result ?? []) {
|
|
180
|
+
if (seen.has(id)) continue;
|
|
181
|
+
seen.add(id);
|
|
182
|
+
const doc = docs.get(id);
|
|
183
|
+
if (!doc) continue;
|
|
184
|
+
resolved.push({
|
|
185
|
+
resource: buildResourceUri(id).href,
|
|
186
|
+
title: doc.title ?? id,
|
|
187
|
+
summary: doc.summary ?? '',
|
|
188
|
+
category: doc.category ?? null,
|
|
189
|
+
section: doc.section ?? null
|
|
190
|
+
});
|
|
191
|
+
if (resolved.length >= limit) break;
|
|
192
|
+
}
|
|
193
|
+
if (resolved.length >= limit) break;
|
|
194
|
+
}
|
|
195
|
+
if (resolved.length >= limit) break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let text =
|
|
199
|
+
resolved.length > 0
|
|
200
|
+
? resolved
|
|
201
|
+
.map(
|
|
202
|
+
(entry, idx) =>
|
|
203
|
+
`${idx + 1}. ${entry.title} (${entry.resource})${
|
|
204
|
+
entry.summary ? `\n ${entry.summary}` : ''
|
|
205
|
+
}`
|
|
206
|
+
)
|
|
207
|
+
.join('\n\n')
|
|
208
|
+
.concat(
|
|
209
|
+
'\n\nAlways call readResource on one of the URIs above instead of guessing component routes.'
|
|
210
|
+
)
|
|
211
|
+
: `No matches for "${query}".`;
|
|
212
|
+
|
|
213
|
+
const uniqueHints = Array.from(new Set(hints.filter(Boolean)));
|
|
214
|
+
if (uniqueHints.length) {
|
|
215
|
+
text += `\n\n${uniqueHints.join('\n')}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (CURRENT_UIKIT_VERSION) {
|
|
219
|
+
text += `\n\nCaptured UI Kit version: ${CURRENT_UIKIT_VERSION}. Read "How to use the UIKIT" → "Install the npm package" for installation details.`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
structuredContent: { results: resolved },
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
text
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function initialiseServer() {
|
|
236
|
+
const docs = await loadDocumentation();
|
|
237
|
+
const index = buildSearchIndex(docs);
|
|
238
|
+
registerDocResources(docs);
|
|
239
|
+
registerSearchTool(docs, index);
|
|
240
|
+
|
|
241
|
+
const docIds = new Set(docs.keys());
|
|
242
|
+
const missingPreferred = new Set();
|
|
243
|
+
for (const ids of Object.values(SEARCH_SYNONYMS.preferredDocIds ?? {})) {
|
|
244
|
+
for (const id of ids ?? []) {
|
|
245
|
+
if (!docIds.has(id)) {
|
|
246
|
+
missingPreferred.add(id);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (missingPreferred.size) {
|
|
251
|
+
console.warn(
|
|
252
|
+
`Warning: preferredDocIds not found in dataset: ${Array.from(missingPreferred).join(', ')}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return { docs, index };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function main() {
|
|
260
|
+
await initialiseServer();
|
|
261
|
+
const transport = new StdioServerTransport();
|
|
262
|
+
await server.connect(transport);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
main().catch((error) => {
|
|
266
|
+
console.error(error);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { normaliseDocument } from './normalise-doc.mjs';
|
|
6
|
+
|
|
7
|
+
const DATA_ROOT = path.resolve(new URL('../../data/pages', import.meta.url).pathname);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Recursively collect JSON file paths.
|
|
11
|
+
* @param {string} dir
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
function walk(dir) {
|
|
15
|
+
const entries = readdirSync(dir);
|
|
16
|
+
const files = [];
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const fullPath = path.join(dir, entry);
|
|
19
|
+
const stats = statSync(fullPath);
|
|
20
|
+
if (stats.isDirectory()) {
|
|
21
|
+
files.push(...walk(fullPath));
|
|
22
|
+
} else if (stats.isFile() && entry.endsWith('.json')) {
|
|
23
|
+
files.push(fullPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load all documentation artefacts and return a map keyed by doc id.
|
|
31
|
+
* @param {{ root?: string } } [options]
|
|
32
|
+
* @returns {Promise<Map<string, UiKitDoc>>}
|
|
33
|
+
*/
|
|
34
|
+
export async function loadDocumentation(options = {}) {
|
|
35
|
+
const root = options.root ? path.resolve(options.root) : DATA_ROOT;
|
|
36
|
+
const files = walk(root);
|
|
37
|
+
const docs = new Map();
|
|
38
|
+
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const rawText = await readFile(file, 'utf8');
|
|
41
|
+
const rawDoc = JSON.parse(rawText);
|
|
42
|
+
const normalised = normaliseDocument(rawDoc);
|
|
43
|
+
docs.set(normalised.id, normalised);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return docs;
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collapse excessive whitespace and normalise newlines.
|
|
5
|
+
* @param {string} value
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
function normaliseWhitespace(value) {
|
|
9
|
+
return value
|
|
10
|
+
.replace(/\r\n?/g, '\n')
|
|
11
|
+
.split('\n')
|
|
12
|
+
.map((line) => line.trim())
|
|
13
|
+
.filter((line, index, all) => line.length > 0 || (index > 0 && all[index - 1].length > 0))
|
|
14
|
+
.join('\n')
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BLOCK_BREAK_REGEXP = /<\/(p|div|section|article|li|ul|ol|pre|table|thead|tbody|tr|h[1-6])>/gi;
|
|
19
|
+
const BR_REGEXP = /<br\s*\/?>/gi;
|
|
20
|
+
const TAG_REGEXP = /<[^>]+>/g;
|
|
21
|
+
const ENTITY_MAP = new Map([
|
|
22
|
+
[' ', ' '],
|
|
23
|
+
['&', '&'],
|
|
24
|
+
['<', '<'],
|
|
25
|
+
['>', '>'],
|
|
26
|
+
['"', '"'],
|
|
27
|
+
[''', "'"],
|
|
28
|
+
[''', "'"]
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert HTML into best-effort plaintext suitable for indexing.
|
|
33
|
+
* @param {string} html
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export function htmlToPlainText(html) {
|
|
37
|
+
if (!html) return '';
|
|
38
|
+
let value = html;
|
|
39
|
+
value = value.replace(BLOCK_BREAK_REGEXP, '\n');
|
|
40
|
+
value = value.replace(BR_REGEXP, '\n');
|
|
41
|
+
value = value.replace(TAG_REGEXP, '');
|
|
42
|
+
for (const [entity, replacement] of ENTITY_MAP.entries()) {
|
|
43
|
+
value = value.replace(new RegExp(entity, 'g'), replacement);
|
|
44
|
+
}
|
|
45
|
+
return normaliseWhitespace(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert rudimentary markdown-ish copy into plain text.
|
|
50
|
+
* @param {string} markdown
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function markdownToPlainText(markdown) {
|
|
54
|
+
if (!markdown) return '';
|
|
55
|
+
return normaliseWhitespace(
|
|
56
|
+
markdown
|
|
57
|
+
.replace(/`{1,3}([^`]+)`{1,3}/g, '$1')
|
|
58
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
59
|
+
.replace(/\*([^*]+)\*/g, '$1')
|
|
60
|
+
.replace(/__([^_]+)__|_([^_]+)_/g, (_, bold, italic) => bold ?? italic ?? '')
|
|
61
|
+
.replace(/> ?/g, '')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Escape Markdown table cell content.
|
|
67
|
+
* @param {string} value
|
|
68
|
+
* @returns {string}
|
|
69
|
+
*/
|
|
70
|
+
function escapeTableCell(value) {
|
|
71
|
+
return value
|
|
72
|
+
.replace(/\|/g, '\\|')
|
|
73
|
+
.replace(/\n+/g, '<br />')
|
|
74
|
+
.trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Turn props table data into Markdown.
|
|
79
|
+
* @param {RawPropsTable} table
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function propsTableToMarkdown(table) {
|
|
83
|
+
const header = '| Name | Type | Default | Description |';
|
|
84
|
+
const divider = '| --- | --- | --- | --- |';
|
|
85
|
+
const rows = table.rows
|
|
86
|
+
.map((row) => {
|
|
87
|
+
const name = escapeTableCell(row.name);
|
|
88
|
+
const type = escapeTableCell(row.type);
|
|
89
|
+
const defaultValue = escapeTableCell(row.default || '—');
|
|
90
|
+
const description = escapeTableCell(row.description);
|
|
91
|
+
return `| ${name} | ${type} | ${defaultValue} | ${description} |`;
|
|
92
|
+
})
|
|
93
|
+
.join('\n');
|
|
94
|
+
|
|
95
|
+
const heading = table.heading ? `### ${table.heading}\n\n` : '';
|
|
96
|
+
return `${heading}${header}\n${divider}\n${rows}`.trim();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Derive hash route and identifier from metadata.
|
|
101
|
+
* @param {RawDoc['metadata']} metadata
|
|
102
|
+
* @returns {{ route: string, id: string }}
|
|
103
|
+
*/
|
|
104
|
+
function deriveRoute(metadata) {
|
|
105
|
+
const source = metadata?.source ?? '';
|
|
106
|
+
const hashIndex = source.indexOf('#');
|
|
107
|
+
const fallback = metadata?.slug ? `#${metadata.slug}` : '';
|
|
108
|
+
const route = hashIndex >= 0 ? source.slice(hashIndex) : fallback;
|
|
109
|
+
const id = route.startsWith('#') ? route.slice(1) : route;
|
|
110
|
+
return { route, id };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build a normalised document ready for MCP consumption.
|
|
115
|
+
* @param {RawDoc} raw
|
|
116
|
+
* @returns {UiKitDoc}
|
|
117
|
+
*/
|
|
118
|
+
export function normaliseDocument(raw) {
|
|
119
|
+
if (!raw || typeof raw !== 'object') {
|
|
120
|
+
throw new TypeError('Expected raw document object');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { route, id } = deriveRoute(raw.metadata ?? {});
|
|
124
|
+
const title = raw.title ?? null;
|
|
125
|
+
const sections = [];
|
|
126
|
+
const textAccumulator = [];
|
|
127
|
+
|
|
128
|
+
if (title) textAccumulator.push(title);
|
|
129
|
+
if (raw.lead) textAccumulator.push(raw.lead);
|
|
130
|
+
|
|
131
|
+
for (const section of raw.content ?? []) {
|
|
132
|
+
const heading = section.heading ?? '';
|
|
133
|
+
const bodyMarkdown = section.body ? normaliseWhitespace(section.body) : '';
|
|
134
|
+
const bodyPlainText = markdownToPlainText(bodyMarkdown);
|
|
135
|
+
|
|
136
|
+
if (heading) textAccumulator.push(heading);
|
|
137
|
+
if (bodyPlainText) textAccumulator.push(bodyPlainText);
|
|
138
|
+
|
|
139
|
+
const examples = [];
|
|
140
|
+
for (const example of section.examples ?? []) {
|
|
141
|
+
const caption = example.caption ?? heading ?? title ?? 'Example';
|
|
142
|
+
const html = example.rendered_html ?? '';
|
|
143
|
+
const plainText = htmlToPlainText(html);
|
|
144
|
+
if (plainText) textAccumulator.push(plainText);
|
|
145
|
+
|
|
146
|
+
const codeTabs = [];
|
|
147
|
+
const propsTables = [];
|
|
148
|
+
|
|
149
|
+
for (const tab of example.tabs ?? []) {
|
|
150
|
+
if (tab.code && tab.code.trim().length) {
|
|
151
|
+
codeTabs.push({
|
|
152
|
+
label: tab.label,
|
|
153
|
+
language: tab.language,
|
|
154
|
+
code: tab.code.trim()
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (Array.isArray(tab.props) && tab.props.length) {
|
|
159
|
+
for (const table of tab.props) {
|
|
160
|
+
const markdown = propsTableToMarkdown(table);
|
|
161
|
+
propsTables.push({
|
|
162
|
+
heading: table.heading ?? null,
|
|
163
|
+
rows: table.rows,
|
|
164
|
+
markdown
|
|
165
|
+
});
|
|
166
|
+
textAccumulator.push(markdownToPlainText(markdown));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
examples.push({
|
|
172
|
+
caption,
|
|
173
|
+
html,
|
|
174
|
+
plainText,
|
|
175
|
+
codeTabs,
|
|
176
|
+
propsTables
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
sections.push({
|
|
181
|
+
heading,
|
|
182
|
+
bodyMarkdown,
|
|
183
|
+
bodyPlainText,
|
|
184
|
+
examples
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const summaryCandidate =
|
|
189
|
+
raw.lead ||
|
|
190
|
+
(sections.find((section) => section.bodyPlainText)?.bodyPlainText ?? '') ||
|
|
191
|
+
(sections.find((section) => section.examples.length)?.examples[0].plainText ?? '');
|
|
192
|
+
|
|
193
|
+
const summary = summaryCandidate
|
|
194
|
+
? summaryCandidate.split('\n')[0]
|
|
195
|
+
: `Documentation entry for ${id}`;
|
|
196
|
+
|
|
197
|
+
const seeAlso = (raw.see_also ?? []).map((link) => ({
|
|
198
|
+
label: link.label,
|
|
199
|
+
href: link.href
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
const searchText = normaliseWhitespace(textAccumulator.join('\n'));
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
id,
|
|
206
|
+
route,
|
|
207
|
+
category: raw.metadata?.category ?? null,
|
|
208
|
+
section: raw.metadata?.section ?? null,
|
|
209
|
+
title,
|
|
210
|
+
lead: raw.lead ?? null,
|
|
211
|
+
sections,
|
|
212
|
+
seeAlso,
|
|
213
|
+
sourceUrl: raw.metadata?.source ?? '',
|
|
214
|
+
capturedAt: raw.metadata?.captured_at ?? '',
|
|
215
|
+
slug: raw.metadata?.slug ?? id,
|
|
216
|
+
summary,
|
|
217
|
+
searchText
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import './types.js';
|
|
2
|
+
import { htmlToPlainText } from './normalise-doc.mjs';
|
|
3
|
+
|
|
4
|
+
function heading(level, text) {
|
|
5
|
+
const prefix = '#'.repeat(level);
|
|
6
|
+
return `${prefix} ${text}`.trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function codeBlock(language, code) {
|
|
10
|
+
const fence = '```';
|
|
11
|
+
return `${fence}${language ?? ''}\n${code}\n${fence}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert a UiKitDoc to Markdown for MCP resource responses.
|
|
16
|
+
* @param {UiKitDoc} doc
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function renderDocMarkdown(doc) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
|
|
22
|
+
const title = doc.title ?? doc.id;
|
|
23
|
+
parts.push(heading(1, title));
|
|
24
|
+
|
|
25
|
+
parts.push([
|
|
26
|
+
`*Category:* ${doc.category ?? '—'}`,
|
|
27
|
+
`*Section:* ${doc.section ?? '—'}`,
|
|
28
|
+
`*Source:* ${doc.sourceUrl}`,
|
|
29
|
+
`*Captured:* ${doc.capturedAt}`
|
|
30
|
+
].join('\n'));
|
|
31
|
+
|
|
32
|
+
if (doc.lead) {
|
|
33
|
+
parts.push(doc.lead.trim());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const section of doc.sections) {
|
|
37
|
+
if (section.heading) {
|
|
38
|
+
parts.push(heading(2, section.heading));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (section.bodyMarkdown) {
|
|
42
|
+
parts.push(section.bodyMarkdown);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const example of section.examples) {
|
|
46
|
+
parts.push(heading(3, `Example: ${example.caption}`));
|
|
47
|
+
|
|
48
|
+
if (example.plainText) {
|
|
49
|
+
parts.push(example.plainText);
|
|
50
|
+
} else if (example.html) {
|
|
51
|
+
parts.push(htmlToPlainText(example.html));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (example.plainText || example.html) {
|
|
55
|
+
parts.push(heading(4, 'Summary'));
|
|
56
|
+
const summary = example.plainText || htmlToPlainText(example.html);
|
|
57
|
+
parts.push(summary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const tab of example.codeTabs) {
|
|
61
|
+
parts.push(heading(4, `${tab.label} (${tab.language})`));
|
|
62
|
+
parts.push(codeBlock(tab.language, tab.code));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const table of example.propsTables) {
|
|
66
|
+
parts.push(heading(4, table.heading ? `Props: ${table.heading}` : 'Props'));
|
|
67
|
+
parts.push(table.markdown);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (doc.seeAlso.length) {
|
|
73
|
+
parts.push(heading(2, 'See Also'));
|
|
74
|
+
const list = doc.seeAlso
|
|
75
|
+
.map((link) => `- [${link.label}](${link.href})`)
|
|
76
|
+
.join('\n');
|
|
77
|
+
parts.push(list);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
parts.push('\n');
|
|
81
|
+
return parts.join('\n\n').trim();
|
|
82
|
+
}
|