@aleph-alpha/lib-mcp 2.0.0 → 2.1.1

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
@@ -1,17 +1,30 @@
1
1
  # @aleph-alpha/lib-mcp
2
2
 
3
- MCP (Model Context Protocol) Server for UI Library component discovery tools.
3
+ MCP (Model Context Protocol) Server for UI Library and ChatKit component discovery tools.
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides an MCP server that enables AI assistants (like Claude and Cursor) to discover and understand UI Library components. It offers tools to list available components and retrieve detailed documentation including props, examples, and usage guidance.
7
+ This package provides an MCP server that enables AI assistants (like Claude and Cursor) to discover and understand components from:
8
+
9
+ - [`@aleph-alpha/ui-library`](../ui-library) — `Ui*` components
10
+ - [`@aleph-alpha/chat-kit`](../chat-kit) — `Ck*` components
11
+
12
+ It offers tools to list available components and retrieve detailed documentation including props, examples, and usage guidance.
8
13
 
9
14
  ## Installation
10
15
 
16
+ Install `@aleph-alpha/lib-mcp` together with the component packages whose docs you want to query. UI Library is required; ChatKit is optional and only needed if you want `Ck*` component docs.
17
+
11
18
  ```bash
19
+ # UI Library only
12
20
  pnpm add @aleph-alpha/lib-mcp @aleph-alpha/ui-library
21
+
22
+ # UI Library + ChatKit
23
+ pnpm add @aleph-alpha/lib-mcp @aleph-alpha/ui-library @aleph-alpha/chat-kit
13
24
  ```
14
25
 
26
+ > ChatKit docs require `@aleph-alpha/chat-kit` to publish its `src/` directory (component `types.ts` and `*.stories.ts` files). If `get_chat_kit_docs` returns "Could not find chat-kit source directory", upgrade `@aleph-alpha/chat-kit` to a version that includes `src/` in its published files.
27
+
15
28
  ## Usage
16
29
 
17
30
  ### Configure MCP Server
@@ -24,6 +37,9 @@ npx @aleph-alpha/lib-mcp mcp-install
24
37
 
25
38
  # For both Claude Code and Cursor
26
39
  npx @aleph-alpha/lib-mcp mcp-install --cursor
40
+
41
+ # For VS Code
42
+ npx @aleph-alpha/lib-mcp mcp-install --vscode
27
43
  ```
28
44
 
29
45
  After installation, restart your IDE to activate the MCP tools.
@@ -32,9 +48,11 @@ After installation, restart your IDE to activate the MCP tools.
32
48
 
33
49
  Once configured, the following tools are available to your AI assistant:
34
50
 
35
- #### `list_components`
51
+ #### UI Library tools
52
+
53
+ ##### `list_components`
36
54
 
37
- Lists all available UI components. Supports optional filtering by name, category, or description.
55
+ Lists all available UI Library components. Supports optional filtering by name, category, or description.
38
56
 
39
57
  ```
40
58
  list_components
@@ -42,9 +60,9 @@ list_components --filter="button"
42
60
  list_components --filter="form"
43
61
  ```
44
62
 
45
- #### `get_component_docs`
63
+ ##### `get_component_docs`
46
64
 
47
- Retrieves full documentation for a specific component including:
65
+ Retrieves full documentation for a specific UI Library component including:
48
66
 
49
67
  - Component description
50
68
  - Props with types, defaults, and descriptions
@@ -57,6 +75,45 @@ get_component_docs UiButton
57
75
  get_component_docs Button # "Ui" prefix is optional
58
76
  ```
59
77
 
78
+ #### ChatKit tools
79
+
80
+ Available when `@aleph-alpha/chat-kit` is installed alongside this package.
81
+
82
+ ##### `list_chat_kit_components`
83
+
84
+ Lists all available ChatKit (`Ck*`) components. Supports optional filtering by name, category, or description.
85
+
86
+ ```
87
+ list_chat_kit_components
88
+ list_chat_kit_components --filter="history"
89
+ list_chat_kit_components --filter="message"
90
+ ```
91
+
92
+ ##### `get_chat_kit_docs`
93
+
94
+ Retrieves full documentation for a specific ChatKit component, with the same shape as `get_component_docs`:
95
+
96
+ - Component description
97
+ - Props with types, defaults, and descriptions
98
+ - Sub-components (if applicable)
99
+ - Usage guidance
100
+ - Code examples
101
+
102
+ ```
103
+ get_chat_kit_docs CkHistory
104
+ get_chat_kit_docs History # "Ck" prefix is optional
105
+ ```
106
+
107
+ #### Setup tool
108
+
109
+ ##### `get_setup_guide`
110
+
111
+ Returns the setup and installation guide for integrating the UI library into a project (UnoCSS config, Vite config, required dependencies, and MCP setup instructions).
112
+
113
+ ```
114
+ get_setup_guide
115
+ ```
116
+
60
117
  ## Development
61
118
 
62
119
  ### Building
@@ -67,6 +124,8 @@ pnpm build
67
124
 
68
125
  ### Generating Component Metadata
69
126
 
127
+ Regenerates `components-meta.json` (UI Library) and `chat-kit-components-meta.json` (ChatKit) by scanning each package's source files.
128
+
70
129
  ```bash
71
130
  pnpm generate:component-meta
72
131
  ```
@@ -79,21 +138,23 @@ pnpm test
79
138
 
80
139
  ## How It Works
81
140
 
82
- The MCP server reads component metadata from:
141
+ The MCP server reads component metadata from each package's source layout:
83
142
 
84
143
  1. **`types.ts`**: Extracts props, their types, descriptions, and component-level JSDoc tags (`@category`, `@useCases`, `@keywords`, `@related`)
85
144
  2. **`*.stories.ts`**: Extracts code examples and usage guidance from Storybook stories
86
- 3. **`components-meta.json`**: Pre-generated metadata for faster lookups
145
+ 3. **`index.ts`**: Detects sub-component exports for the documented component
146
+ 4. **`components-meta.json` / `chat-kit-components-meta.json`**: Pre-generated metadata for faster lookups
147
+
148
+ UI Library components are read from `@aleph-alpha/ui-library/src/**`, and ChatKit components from `@aleph-alpha/chat-kit/src/components/**`. Both packages must publish their `src/` directory for docs to resolve when consumed from `node_modules`.
87
149
 
88
150
  ## CLI Commands
89
151
 
90
- | Command | Description |
91
- |---------|-------------|
92
- | `mcp-install` | Configure MCP server for IDE |
93
- | `serve` | Start the MCP server (used internally by IDE) |
94
- | `--help` | Show help message |
152
+ | Command | Description |
153
+ | ------------- | ----------------------------------------------------------------------- |
154
+ | `mcp-install` | Configure MCP server for IDE (`--cursor`, `--vscode` to add extra IDEs) |
155
+ | `serve` | Start the MCP server (used internally by IDE) |
156
+ | `--help` | Show help message |
95
157
 
96
158
  ## License
97
159
 
98
160
  Apache-2.0
99
-
@@ -103,6 +103,95 @@
103
103
  ],
104
104
  "_autoGenerated": true
105
105
  },
106
+ "CkFeedbackButton": {
107
+ "description": "Binary thumbs up / thumbs down feedback control for assistant messages. Fully prop-driven: the host application owns the selection and lock state and decides when (or if) to persist it to a backend. Labels default to those provided by `useChatKitLabels` (which can be translated via `provideChatKitLabels`). The `ariaLabel*` props override the injected labels on a per-instance basis when set.",
108
+ "category": "Feedback",
109
+ "useCases": [
110
+ "like or dislike an assistant reply",
111
+ "collect binary message rating",
112
+ "lock feedback after submission",
113
+ "rate AI response"
114
+ ],
115
+ "keywords": [
116
+ "feedback",
117
+ "thumbs up",
118
+ "thumbs down",
119
+ "like",
120
+ "dislike",
121
+ "rating",
122
+ "vote",
123
+ "action",
124
+ "message",
125
+ "button"
126
+ ],
127
+ "related": [
128
+ "CkMessageActionButton",
129
+ "CkCopyMessageActionButton",
130
+ "CkAgentMessage",
131
+ "CkConversation"
132
+ ],
133
+ "props": [
134
+ "value",
135
+ "locked",
136
+ "disabled",
137
+ "ariaLabelThumbsUp",
138
+ "ariaLabelThumbsDown",
139
+ "ariaLabelGroup"
140
+ ],
141
+ "examples": [
142
+ "Default",
143
+ "SelectedUp",
144
+ "SelectedDown",
145
+ "Locked",
146
+ "Disabled",
147
+ "SubmitOnSelect"
148
+ ],
149
+ "_autoGenerated": true
150
+ },
151
+ "CkFileItemsList": {
152
+ "description": "Horizontal list of file attachments; collapses to a popover once the count exceeds `maxInline`.",
153
+ "category": "Form Inputs",
154
+ "useCases": [
155
+ "user message attachments",
156
+ "prompt input previews",
157
+ "chat file row"
158
+ ],
159
+ "keywords": [
160
+ "files",
161
+ "attachments",
162
+ "file list",
163
+ "popover",
164
+ "chat",
165
+ "file",
166
+ "items",
167
+ "list"
168
+ ],
169
+ "related": [
170
+ "UiFileItem",
171
+ "UiPopover"
172
+ ],
173
+ "props": [
174
+ "files",
175
+ "maxInline",
176
+ "size",
177
+ "isRemovable",
178
+ "removeLabel",
179
+ "disabled",
180
+ "triggerLabel",
181
+ "triggerAriaLabel",
182
+ "listLabel",
183
+ "popoverClass"
184
+ ],
185
+ "examples": [
186
+ "Inline",
187
+ "Collapsed",
188
+ "Removable",
189
+ "CustomItem",
190
+ "Clickable",
191
+ "Disabled"
192
+ ],
193
+ "_autoGenerated": true
194
+ },
106
195
  "CkHistoryItem": {
107
196
  "description": "A single chat history entry with title, optional meta line, active indicator, and a trailing action control (plain button or popover). Sidebar-agnostic: can be used anywhere, not only inside a sidebar.",
108
197
  "category": "Actions",
@@ -443,6 +532,7 @@
443
532
  ],
444
533
  "examples": [
445
534
  "Default",
535
+ "ChainOfThought",
446
536
  "WithIcon",
447
537
  "WithCustomAvatarFallback",
448
538
  "NoIcon",
@@ -62,14 +62,26 @@
62
62
  "_autoGenerated": true
63
63
  },
64
64
  "UiAlertDialog": {
65
- "description": "Root alert dialog. Uses core **`Dialog`** (`components/core/dialog/Dialog.vue` → **`DialogRoot`**) with **`modal: true`** Root uses core **`Dialog`** with modal enabled. Subcomponents use **`UiAlertDialog*`** / alert **`DialogContent`** for **`alertdialog`** semantics. Use **`v-model:model`** for controlled mode (unlike **`UiDialog`**, which uses **`v-model:open`**).",
66
- "category": "Uncategorized",
67
- "useCases": [],
65
+ "description": "A modal dialog for confirmations that interrupt the user. Use for destructive actions, important decisions, or critical confirmations.",
66
+ "category": "Overlays",
67
+ "useCases": [
68
+ "confirm delete",
69
+ "destructive action",
70
+ "important confirmation",
71
+ "warning"
72
+ ],
68
73
  "keywords": [
69
74
  "alert",
70
- "dialog"
75
+ "dialog",
76
+ "modal",
77
+ "confirm",
78
+ "warning",
79
+ "destructive",
80
+ "popup"
81
+ ],
82
+ "related": [
83
+ "UiPopover"
71
84
  ],
72
- "related": [],
73
85
  "props": [],
74
86
  "examples": [
75
87
  "Default",
@@ -102,9 +114,10 @@
102
114
  "props": [],
103
115
  "examples": [
104
116
  "Default",
117
+ "Sizes",
105
118
  "Fallback",
106
119
  "Multiple",
107
- "Stacked"
120
+ "AvatarGroup"
108
121
  ],
109
122
  "_autoGenerated": true
110
123
  },
@@ -205,6 +218,7 @@
205
218
  "WithIconSlot",
206
219
  "AsChild",
207
220
  "AllVariants",
221
+ "DisabledHoverBleed",
208
222
  "IconSizes",
209
223
  "AllVariantsAndSizes"
210
224
  ],
@@ -526,7 +540,7 @@
526
540
  "WithRadioItems",
527
541
  "WithDestructiveAction",
528
542
  "WithSubmenu",
529
- "WithIconButton",
543
+ "WithIconTrigger",
530
544
  "WithSoftToggleTrigger",
531
545
  "WithSoftToggleTriggerAndText",
532
546
  "WithAvatarTrigger"
@@ -633,12 +647,15 @@
633
647
  "upload",
634
648
  "attachment",
635
649
  "progress",
650
+ "success",
651
+ "error",
636
652
  "item"
637
653
  ],
638
654
  "related": [
639
655
  "UiFileUploadBox",
640
656
  "UiItem",
641
- "UiButton"
657
+ "UiButton",
658
+ "UiProgress"
642
659
  ],
643
660
  "props": [
644
661
  "filename",
@@ -647,24 +664,64 @@
647
664
  "progress",
648
665
  "error",
649
666
  "removeLabel",
650
- "disabled"
667
+ "progressLabel",
668
+ "removable",
669
+ "disabled",
670
+ "size",
671
+ "mediaTone",
672
+ "mediaIcon"
651
673
  ],
652
674
  "examples": [
653
675
  "Default",
654
- "WithProgress",
655
- "Complete",
656
- "WithError",
676
+ "DefaultWithProgress",
677
+ "DefaultSuccess",
678
+ "DefaultError",
679
+ "SmallWithProgress",
680
+ "SmallSuccess",
681
+ "SmallError",
682
+ "NonRemovable",
657
683
  "Disabled",
658
- "DisabledWithError",
659
- "ProgressAndError",
660
- "FilenameOnly",
661
- "AllStates"
684
+ "AllVariants"
685
+ ],
686
+ "_autoGenerated": true
687
+ },
688
+ "UiFileType": {
689
+ "description": "Round 32×32 badge that visualises a file type with a tone + icon. Mirrors the Figma `Filetype` component (`AA26 / File Upload / Filetype`, node id `26438:42144`): - `Type=Text` (`pdf` / `doc` / `docx` / `txt` / `rtf`) → `accent-pink` + `file` - `Type=Markdown` (`html` / `htm` / `md` / `mdx`) → `accent-soft` + `code-xml` - `Type=Tabular` (`csv` / `tsv` / `xls` / `xlsx`) → `accent-yellow` + `table-2` - everything else → `default` + `file`",
690
+ "category": "Data Display",
691
+ "useCases": [
692
+ "file-type indicator",
693
+ "attachment badge"
694
+ ],
695
+ "keywords": [
696
+ "file",
697
+ "badge",
698
+ "attachment",
699
+ "filetype",
700
+ "icon",
701
+ "type"
702
+ ],
703
+ "related": [
704
+ "UiFileItem",
705
+ "UiFileUploadBox"
706
+ ],
707
+ "props": [
708
+ "filename",
709
+ "tone",
710
+ "icon",
711
+ "error",
712
+ "loading"
713
+ ],
714
+ "examples": [
715
+ "Default",
716
+ "ErrorState",
717
+ "AllTypes",
718
+ "ToneOverrides"
662
719
  ],
663
720
  "_autoGenerated": true
664
721
  },
665
722
  "UiFileUploadBox": {
666
723
  "description": "Drop zone + file picker for selecting files with optional type/size validation. **Emits:** see {@link UiFileUploadBoxEmits}.",
667
- "category": "Components",
724
+ "category": "Form Inputs",
668
725
  "useCases": [
669
726
  "file upload area",
670
727
  "drag and drop attachments"
@@ -698,69 +755,34 @@
698
755
  "_autoGenerated": true
699
756
  },
700
757
  "UiIcon": {
701
- "description": "UiIcon component",
702
- "category": "Uncategorized",
703
- "useCases": [],
704
- "keywords": [
705
- "icon"
706
- ],
707
- "related": [],
708
- "props": [
709
- "name",
710
- "size",
711
- "strokeWidth",
712
- "ariaLabel"
713
- ],
714
- "examples": [
715
- "Default",
716
- "Sizes",
717
- "InIconButton"
718
- ],
719
- "_autoGenerated": true
720
- },
721
- "UiIconButton": {
722
- "description": "A button that displays only an icon without text. Use for compact actions like close, edit, delete, or toolbar buttons. It is being merged into `UiButton` — use `UiButton` with an icon-only child and an `aria-label` instead (e.g. `<UiButton size=\"icon-sm\" aria-label=\"Close\"><UiIcon name=\"x\" /></UiButton>`).",
723
- "category": "Actions",
758
+ "description": "An icon component using Lucide icons. Use for visual indicators, button decorations, or standalone meaningful graphics.",
759
+ "category": "Data Display",
724
760
  "useCases": [
725
- "close button",
726
- "edit action",
727
- "toolbar button",
728
- "compact action"
761
+ "visual indicator",
762
+ "button icon",
763
+ "status icon",
764
+ "decorative graphic"
729
765
  ],
730
766
  "keywords": [
731
- "iconbutton",
732
767
  "icon",
733
- "button",
734
- "action",
735
- "close",
736
- "edit",
737
- "delete"
768
+ "symbol",
769
+ "graphic",
770
+ "lucide",
771
+ "svg"
738
772
  ],
739
773
  "related": [
740
- "UiButton",
741
- "UiIcon"
774
+ "UiButton"
742
775
  ],
743
776
  "props": [
744
- "variant",
777
+ "name",
745
778
  "size",
746
- "asChild",
747
- "type",
748
- "disabled",
749
- "loading",
779
+ "strokeWidth",
750
780
  "ariaLabel"
751
781
  ],
752
782
  "examples": [
753
783
  "Default",
754
- "Secondary",
755
- "Destructive",
756
- "Outline",
757
- "Ghost",
758
- "Link",
759
- "Loading",
760
- "Disabled",
761
- "AllVariants",
762
- "AllSizes",
763
- "AsChild"
784
+ "Sizes",
785
+ "IconLibrary"
764
786
  ],
765
787
  "_autoGenerated": true
766
788
  },
@@ -831,7 +853,6 @@
831
853
  "menu",
832
854
  "settings",
833
855
  "navigation",
834
- "selectable",
835
856
  "card"
836
857
  ],
837
858
  "related": [
@@ -842,6 +863,9 @@
842
863
  "props": [
843
864
  "variant",
844
865
  "size",
866
+ "title",
867
+ "description",
868
+ "desc",
845
869
  "asChild",
846
870
  "withListSemantics"
847
871
  ],
@@ -849,16 +873,14 @@
849
873
  "Basic",
850
874
  "Variants",
851
875
  "Sizes",
876
+ "CustomSlots",
852
877
  "WithIcon",
853
- "WithAvatar",
854
- "WithImage",
878
+ "Actions",
879
+ "ActionTooltip",
880
+ "Media",
855
881
  "Group",
856
- "WithHeaderAndFooter",
857
882
  "AsLink",
858
- "TeamMembersList",
859
- "NotificationList",
860
- "EmptyStateWithInvite",
861
- "AllStates"
883
+ "AllVariants"
862
884
  ],
863
885
  "_autoGenerated": true
864
886
  },
@@ -1166,6 +1188,36 @@
1166
1188
  ],
1167
1189
  "_autoGenerated": true
1168
1190
  },
1191
+ "UiResizable": {
1192
+ "description": "A resizable panel layout for splitting available space into user-adjustable regions. Use for sidebars, inspectors, editors, and other dense layouts where users need control over panel sizes.",
1193
+ "category": "Layout",
1194
+ "useCases": [
1195
+ "split pane",
1196
+ "resizable sidebar",
1197
+ "editor layout",
1198
+ "inspector panel"
1199
+ ],
1200
+ "keywords": [
1201
+ "resizable",
1202
+ "splitter",
1203
+ "panel",
1204
+ "pane",
1205
+ "layout",
1206
+ "sidebar"
1207
+ ],
1208
+ "related": [
1209
+ "UiSheet",
1210
+ "UiSeparator"
1211
+ ],
1212
+ "props": [],
1213
+ "examples": [
1214
+ "Default",
1215
+ "Vertical",
1216
+ "Collapsible",
1217
+ "PixelSizing"
1218
+ ],
1219
+ "_autoGenerated": true
1220
+ },
1169
1221
  "UiSelect": {
1170
1222
  "description": "A dropdown select for choosing from a list of options. Use for form fields where users must pick one value from many.",
1171
1223
  "category": "Form Inputs",
@@ -1193,6 +1245,7 @@
1193
1245
  "Default",
1194
1246
  "WithGroups",
1195
1247
  "WithDisabledItems",
1248
+ "WithIcons",
1196
1249
  "Disabled",
1197
1250
  "Sizes",
1198
1251
  "Multiple",
@@ -1349,6 +1402,48 @@
1349
1402
  ],
1350
1403
  "_autoGenerated": true
1351
1404
  },
1405
+ "UiSnackbar": {
1406
+ "description": "Toast notification container (Sonner). Place once near the app root, then call `toast()` (or `toast.success()`, `toast.error()`, etc.) from anywhere to display a non-blocking notification.",
1407
+ "category": "Feedback",
1408
+ "useCases": [
1409
+ "success confirmation",
1410
+ "error notice",
1411
+ "undo action",
1412
+ "async progress",
1413
+ "background updates"
1414
+ ],
1415
+ "keywords": [
1416
+ "snackbar",
1417
+ "toast",
1418
+ "sonner",
1419
+ "notification",
1420
+ "alert",
1421
+ "message",
1422
+ "banner",
1423
+ "undo"
1424
+ ],
1425
+ "related": [
1426
+ "UiAlert",
1427
+ "UiAlertDialog"
1428
+ ],
1429
+ "props": [],
1430
+ "examples": [
1431
+ "SuccessHigh",
1432
+ "SuccessLow",
1433
+ "InfoHigh",
1434
+ "InfoLow",
1435
+ "WarningHigh",
1436
+ "WarningLow",
1437
+ "ErrorHigh",
1438
+ "ErrorLow",
1439
+ "AllVariants",
1440
+ "Default",
1441
+ "WithDescription",
1442
+ "PromiseSnackbar",
1443
+ "WithDialog"
1444
+ ],
1445
+ "_autoGenerated": true
1446
+ },
1352
1447
  "UiSpinner": {
1353
1448
  "description": "An animated loading indicator. Use while content is loading or an action is processing.",
1354
1449
  "category": "Feedback",
@@ -1593,9 +1688,12 @@
1593
1688
  "props": [
1594
1689
  "modelValue",
1595
1690
  "defaultValue",
1691
+ "active",
1596
1692
  "disabled",
1597
1693
  "variant",
1598
- "size"
1694
+ "size",
1695
+ "leftIcon",
1696
+ "rightIcon"
1599
1697
  ],
1600
1698
  "examples": [
1601
1699
  "Default",
@@ -1681,7 +1779,7 @@
1681
1779
  ],
1682
1780
  "examples": [
1683
1781
  "Default",
1684
- "WithIconButton",
1782
+ "WithIconTrigger",
1685
1783
  "InstantDelay",
1686
1784
  "LongDelay",
1687
1785
  "Disabled",
@@ -1722,8 +1820,11 @@
1722
1820
  "WithSorting",
1723
1821
  "WithPagination",
1724
1822
  "WithToolbar",
1823
+ "WithColumnsFilter",
1725
1824
  "WithGlobalFilter",
1726
1825
  "WithRowSelection",
1826
+ "WithDraggableColumns",
1827
+ "WithExpandableRows",
1727
1828
  "FullExample",
1728
1829
  "WithCustomEmptyText",
1729
1830
  "WithCustomEmptySlot",
@@ -1875,6 +1976,7 @@
1875
1976
  "props": [],
1876
1977
  "examples": [
1877
1978
  "Default",
1979
+ "Resizable",
1878
1980
  "Inset",
1879
1981
  "RightSide"
1880
1982
  ],
package/dist/cli.js CHANGED
@@ -21425,11 +21425,15 @@ function detectCategoryDirs() {
21425
21425
  return [];
21426
21426
  }
21427
21427
  const entries = fs4.readdirSync(srcPath, { withFileTypes: true });
21428
- return entries.filter((entry) => entry.isDirectory()).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules").filter((entry) => {
21428
+ return entries.filter((entry) => entry.isDirectory()).filter(
21429
+ (entry) => !entry.name.startsWith(".") && entry.name !== "node_modules"
21430
+ ).filter((entry) => {
21429
21431
  try {
21430
21432
  const dirPath = path4.join(srcPath, entry.name);
21431
21433
  const subEntries = fs4.readdirSync(dirPath, { withFileTypes: true });
21432
- return subEntries.some((sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX));
21434
+ return subEntries.some(
21435
+ (sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX)
21436
+ );
21433
21437
  } catch {
21434
21438
  return false;
21435
21439
  }
@@ -21970,7 +21974,10 @@ function parseInterface2(content, interfaceName) {
21970
21974
  const propName = propMatch[2];
21971
21975
  const isOptional = !!propMatch[3];
21972
21976
  const typeStartIndex = propMatch.index + propMatch[0].length;
21973
- const { type: propType, endIndex } = extractType2(interfaceBody, typeStartIndex);
21977
+ const { type: propType, endIndex } = extractType2(
21978
+ interfaceBody,
21979
+ typeStartIndex
21980
+ );
21974
21981
  propStartRegex.lastIndex = endIndex + 1;
21975
21982
  const jsdoc = parseJSDoc2(jsdocComment);
21976
21983
  props.push({
@@ -21987,7 +21994,9 @@ function parseInterface2(content, interfaceName) {
21987
21994
  function extractComponentDescription2(content, interfaceName) {
21988
21995
  const lines = content.split("\n");
21989
21996
  const interfaceLineIndex = lines.findIndex(
21990
- (line) => line.match(new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+${interfaceName}`))
21997
+ (line) => line.match(
21998
+ new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+${interfaceName}`)
21999
+ )
21991
22000
  );
21992
22001
  if (interfaceLineIndex === -1) {
21993
22002
  return "";
@@ -22013,7 +22022,9 @@ function extractComponentDescription2(content, interfaceName) {
22013
22022
  return "";
22014
22023
  }
22015
22024
  function resolveUnionOrIntersectionProps2(content, typeName) {
22016
- const typeDefRegex = new RegExp(`export\\s+type\\s+${typeName}\\s*=\\s*([^;]+)`);
22025
+ const typeDefRegex = new RegExp(
22026
+ `export\\s+type\\s+${typeName}\\s*=\\s*([^;]+)`
22027
+ );
22017
22028
  const match = content.match(typeDefRegex);
22018
22029
  if (!match) return [];
22019
22030
  const referencedTypes = match[1].split(/[|&]/).map((t) => t.trim()).filter((t) => t.endsWith("Props"));
@@ -22041,7 +22052,9 @@ function readChatKitTypesFile(filePath, componentName) {
22041
22052
  return null;
22042
22053
  }
22043
22054
  const resolvedName = componentName || path9.basename(path9.dirname(filePath));
22044
- const exactRegex = new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+(${resolvedName}Props)\\b`);
22055
+ const exactRegex = new RegExp(
22056
+ `export\\s+${TYPE_OR_INTERFACE2}\\s+(${resolvedName}Props)\\b`
22057
+ );
22045
22058
  let propsInterfaceMatch = content.match(exactRegex);
22046
22059
  if (!propsInterfaceMatch) {
22047
22060
  const prefixRegex = new RegExp(
@@ -22307,7 +22320,9 @@ async function startServer() {
22307
22320
  server.tool(
22308
22321
  "list_chat_kit_components",
22309
22322
  "List all Chat Kit components. Optionally filter by name, category, or description.",
22310
- { filter: external_exports.string().optional().describe("Filter by name/category/description") },
22323
+ {
22324
+ filter: external_exports.string().optional().describe("Filter by name/category/description")
22325
+ },
22311
22326
  async ({ filter }) => {
22312
22327
  try {
22313
22328
  const components = listChatKitComponents(filter);
@@ -22322,7 +22337,10 @@ async function startServer() {
22322
22337
  },
22323
22338
  {}
22324
22339
  );
22325
- const sections = [`Found ${components.length} chat-kit component(s):`, ""];
22340
+ const sections = [
22341
+ `Found ${components.length} chat-kit component(s):`,
22342
+ ""
22343
+ ];
22326
22344
  Object.entries(grouped).forEach(([category, comps]) => {
22327
22345
  sections.push(`## ${category}`, "");
22328
22346
  comps.forEach((comp) => {
@@ -22339,11 +22357,14 @@ async function startServer() {
22339
22357
  server.tool(
22340
22358
  "get_chat_kit_docs",
22341
22359
  "Get full documentation for a Chat Kit component including props, examples, and usage.",
22342
- { componentName: external_exports.string().describe('Component name (e.g., "CkChatBubble" or "ChatBubble")') },
22360
+ {
22361
+ componentName: external_exports.string().describe('Component name (e.g., "CkChatBubble" or "ChatBubble")')
22362
+ },
22343
22363
  async ({ componentName }) => {
22344
22364
  try {
22345
22365
  const docs = getChatKitComponentDocs(componentName);
22346
- if (!docs) return error2(`Chat Kit component "${componentName}" not found.`);
22366
+ if (!docs)
22367
+ return error2(`Chat Kit component "${componentName}" not found.`);
22347
22368
  return text(formatComponentDocs(docs));
22348
22369
  } catch (e) {
22349
22370
  return handleError(e);
@@ -97,11 +97,15 @@ function detectCategoryDirs() {
97
97
  return [];
98
98
  }
99
99
  const entries = fs3.readdirSync(srcPath, { withFileTypes: true });
100
- return entries.filter((entry) => entry.isDirectory()).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules").filter((entry) => {
100
+ return entries.filter((entry) => entry.isDirectory()).filter(
101
+ (entry) => !entry.name.startsWith(".") && entry.name !== "node_modules"
102
+ ).filter((entry) => {
101
103
  try {
102
104
  const dirPath = path2.join(srcPath, entry.name);
103
105
  const subEntries = fs3.readdirSync(dirPath, { withFileTypes: true });
104
- return subEntries.some((sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX));
106
+ return subEntries.some(
107
+ (sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX)
108
+ );
105
109
  } catch {
106
110
  return false;
107
111
  }
@@ -177,29 +181,11 @@ var __filename2 = fileURLToPath2(import.meta.url);
177
181
  var __dirname2 = path3.dirname(__filename2);
178
182
  var PACKAGE_ROOT = getPackageRoot();
179
183
  var OUTPUT_PATH = path3.join(PACKAGE_ROOT, "components-meta.json");
180
- var CHAT_KIT_OUTPUT_PATH = path3.join(PACKAGE_ROOT, "chat-kit-components-meta.json");
181
- function extractJSDocTags(content, interfaceName) {
182
- const declarationRegex = new RegExp(
183
- `export\\s+(?:interface|type)\\s+${escapeRegex(interfaceName)}\\b`
184
- );
185
- const declarationMatch = content.match(declarationRegex);
186
- if (!declarationMatch || declarationMatch.index === void 0) {
187
- return { description: "", useCases: [], keywords: [], related: [] };
188
- }
189
- const contentBefore = content.substring(0, declarationMatch.index);
190
- const jsdocEndIndex = contentBefore.lastIndexOf("*/");
191
- if (jsdocEndIndex === -1) {
192
- return { description: "", useCases: [], keywords: [], related: [] };
193
- }
194
- const betweenJsdocAndDeclaration = contentBefore.substring(jsdocEndIndex + 2);
195
- if (betweenJsdocAndDeclaration.trim() !== "") {
196
- return { description: "", useCases: [], keywords: [], related: [] };
197
- }
198
- const jsdocStartIndex = contentBefore.lastIndexOf("/**");
199
- if (jsdocStartIndex === -1 || jsdocStartIndex > jsdocEndIndex) {
200
- return { description: "", useCases: [], keywords: [], related: [] };
201
- }
202
- const jsdoc = contentBefore.substring(jsdocStartIndex + 3, jsdocEndIndex);
184
+ var CHAT_KIT_OUTPUT_PATH = path3.join(
185
+ PACKAGE_ROOT,
186
+ "chat-kit-components-meta.json"
187
+ );
188
+ function parseJSDocBlock(jsdoc) {
203
189
  const lines = jsdoc.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim());
204
190
  let description = "";
205
191
  const useCases = [];
@@ -224,6 +210,51 @@ function extractJSDocTags(content, interfaceName) {
224
210
  }
225
211
  return { description, useCases, keywords, related, category };
226
212
  }
213
+ function extractJSDocTags(content, interfaceName) {
214
+ const declarationRegex = new RegExp(
215
+ `export\\s+(?:interface|type)\\s+${escapeRegex(interfaceName)}\\b`
216
+ );
217
+ const declarationMatch = content.match(declarationRegex);
218
+ if (!declarationMatch || declarationMatch.index === void 0) {
219
+ return { description: "", useCases: [], keywords: [], related: [] };
220
+ }
221
+ const contentBefore = content.substring(0, declarationMatch.index);
222
+ const jsdocEndIndex = contentBefore.lastIndexOf("*/");
223
+ if (jsdocEndIndex === -1) {
224
+ return { description: "", useCases: [], keywords: [], related: [] };
225
+ }
226
+ const betweenJsdocAndDeclaration = contentBefore.substring(jsdocEndIndex + 2);
227
+ if (betweenJsdocAndDeclaration.trim() !== "") {
228
+ return { description: "", useCases: [], keywords: [], related: [] };
229
+ }
230
+ const jsdocStartIndex = contentBefore.lastIndexOf("/**");
231
+ if (jsdocStartIndex === -1 || jsdocStartIndex > jsdocEndIndex) {
232
+ return { description: "", useCases: [], keywords: [], related: [] };
233
+ }
234
+ const jsdoc = contentBefore.substring(jsdocStartIndex + 3, jsdocEndIndex);
235
+ return parseJSDocBlock(jsdoc);
236
+ }
237
+ function findBestJSDocSource(content, componentName, primaryInterfaceName) {
238
+ const primary = extractJSDocTags(content, primaryInterfaceName);
239
+ if (primary.category) return primary;
240
+ const propsRegex = new RegExp(
241
+ `export\\s+(?:interface|type)\\s+(${escapeRegex(componentName)}\\w*Props)\\b`,
242
+ "g"
243
+ );
244
+ let propsMatch;
245
+ while ((propsMatch = propsRegex.exec(content)) !== null) {
246
+ if (propsMatch[1] === primaryInterfaceName) continue;
247
+ const tags = extractJSDocTags(content, propsMatch[1]);
248
+ if (tags.category) return tags;
249
+ }
250
+ const blockRegex = /\/\*\*([\s\S]*?)\*\//g;
251
+ let blockMatch;
252
+ while ((blockMatch = blockRegex.exec(content)) !== null) {
253
+ const tags = parseJSDocBlock(blockMatch[1]);
254
+ if (tags.category) return tags;
255
+ }
256
+ return primary;
257
+ }
227
258
  function extractKeywordsFromName(componentName) {
228
259
  const name = componentName.replace(/^(?:Ui|Ck)/, "");
229
260
  return name.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/\s+/).filter((word) => word.length > 2);
@@ -265,15 +296,23 @@ function processComponent(componentDir, componentName) {
265
296
  typesContent = fs4.readFileSync(typesPath, "utf-8");
266
297
  }
267
298
  const escapedName = escapeRegex(componentName);
268
- const exactPropsRegex = new RegExp(`export\\s+(?:interface|type)\\s+(${escapedName}Props)\\b`);
299
+ const exactPropsRegex = new RegExp(
300
+ `export\\s+(?:interface|type)\\s+(${escapedName}Props)\\b`
301
+ );
269
302
  const exactMatch = typesContent.match(exactPropsRegex);
270
303
  const subComponentPropsRegex = new RegExp(
271
304
  `export\\s+(?:interface|type)\\s+(${escapedName}\\w*Props)\\b`
272
305
  );
273
306
  const subComponentMatch = typesContent.match(subComponentPropsRegex);
274
- const anyPropsMatch = typesContent.match(/export\s+(?:interface|type)\s+((?:Ui|Ck)\w+Props)/);
307
+ const anyPropsMatch = typesContent.match(
308
+ /export\s+(?:interface|type)\s+((?:Ui|Ck)\w+Props)/
309
+ );
275
310
  const interfaceName = exactMatch?.[1] || subComponentMatch?.[1] || anyPropsMatch?.[1] || `${componentName}Props`;
276
- const jsdocData = extractJSDocTags(typesContent, interfaceName);
311
+ const jsdocData = findBestJSDocSource(
312
+ typesContent,
313
+ componentName,
314
+ interfaceName
315
+ );
277
316
  const nameKeywords = extractKeywordsFromName(componentName);
278
317
  const keywords = jsdocData.keywords.length > 0 ? jsdocData.keywords : nameKeywords;
279
318
  const category = jsdocData.category || "Uncategorized";
@@ -342,8 +381,12 @@ function scanChatKitComponents() {
342
381
  }
343
382
  function summarize(label, meta) {
344
383
  const total = Object.keys(meta).length;
345
- const withUseCases = Object.values(meta).filter((m) => m.useCases.length > 0).length;
346
- const withKeywords = Object.values(meta).filter((m) => m.keywords.length > 0).length;
384
+ const withUseCases = Object.values(meta).filter(
385
+ (m) => m.useCases.length > 0
386
+ ).length;
387
+ const withKeywords = Object.values(meta).filter(
388
+ (m) => m.keywords.length > 0
389
+ ).length;
347
390
  console.log(`
348
391
  ${label} summary:`);
349
392
  console.log(` Components with @useCases: ${withUseCases}/${total}`);
@@ -371,5 +414,7 @@ if (isMain) {
371
414
  export {
372
415
  extractJSDocTags,
373
416
  extractKeywordsFromName,
374
- extractProps
417
+ extractProps,
418
+ findBestJSDocSource,
419
+ parseJSDocBlock
375
420
  };
package/dist/server.js CHANGED
@@ -21313,11 +21313,15 @@ function detectCategoryDirs() {
21313
21313
  return [];
21314
21314
  }
21315
21315
  const entries = fs3.readdirSync(srcPath, { withFileTypes: true });
21316
- return entries.filter((entry) => entry.isDirectory()).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules").filter((entry) => {
21316
+ return entries.filter((entry) => entry.isDirectory()).filter(
21317
+ (entry) => !entry.name.startsWith(".") && entry.name !== "node_modules"
21318
+ ).filter((entry) => {
21317
21319
  try {
21318
21320
  const dirPath = path3.join(srcPath, entry.name);
21319
21321
  const subEntries = fs3.readdirSync(dirPath, { withFileTypes: true });
21320
- return subEntries.some((sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX));
21322
+ return subEntries.some(
21323
+ (sub) => sub.isDirectory() && sub.name.startsWith(COMPONENT_PREFIX)
21324
+ );
21321
21325
  } catch {
21322
21326
  return false;
21323
21327
  }
@@ -21858,7 +21862,10 @@ function parseInterface2(content, interfaceName) {
21858
21862
  const propName = propMatch[2];
21859
21863
  const isOptional = !!propMatch[3];
21860
21864
  const typeStartIndex = propMatch.index + propMatch[0].length;
21861
- const { type: propType, endIndex } = extractType2(interfaceBody, typeStartIndex);
21865
+ const { type: propType, endIndex } = extractType2(
21866
+ interfaceBody,
21867
+ typeStartIndex
21868
+ );
21862
21869
  propStartRegex.lastIndex = endIndex + 1;
21863
21870
  const jsdoc = parseJSDoc2(jsdocComment);
21864
21871
  props.push({
@@ -21875,7 +21882,9 @@ function parseInterface2(content, interfaceName) {
21875
21882
  function extractComponentDescription2(content, interfaceName) {
21876
21883
  const lines = content.split("\n");
21877
21884
  const interfaceLineIndex = lines.findIndex(
21878
- (line) => line.match(new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+${interfaceName}`))
21885
+ (line) => line.match(
21886
+ new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+${interfaceName}`)
21887
+ )
21879
21888
  );
21880
21889
  if (interfaceLineIndex === -1) {
21881
21890
  return "";
@@ -21901,7 +21910,9 @@ function extractComponentDescription2(content, interfaceName) {
21901
21910
  return "";
21902
21911
  }
21903
21912
  function resolveUnionOrIntersectionProps2(content, typeName) {
21904
- const typeDefRegex = new RegExp(`export\\s+type\\s+${typeName}\\s*=\\s*([^;]+)`);
21913
+ const typeDefRegex = new RegExp(
21914
+ `export\\s+type\\s+${typeName}\\s*=\\s*([^;]+)`
21915
+ );
21905
21916
  const match = content.match(typeDefRegex);
21906
21917
  if (!match) return [];
21907
21918
  const referencedTypes = match[1].split(/[|&]/).map((t) => t.trim()).filter((t) => t.endsWith("Props"));
@@ -21929,7 +21940,9 @@ function readChatKitTypesFile(filePath, componentName) {
21929
21940
  return null;
21930
21941
  }
21931
21942
  const resolvedName = componentName || path8.basename(path8.dirname(filePath));
21932
- const exactRegex = new RegExp(`export\\s+${TYPE_OR_INTERFACE2}\\s+(${resolvedName}Props)\\b`);
21943
+ const exactRegex = new RegExp(
21944
+ `export\\s+${TYPE_OR_INTERFACE2}\\s+(${resolvedName}Props)\\b`
21945
+ );
21933
21946
  let propsInterfaceMatch = content.match(exactRegex);
21934
21947
  if (!propsInterfaceMatch) {
21935
21948
  const prefixRegex = new RegExp(
@@ -22195,7 +22208,9 @@ async function startServer() {
22195
22208
  server.tool(
22196
22209
  "list_chat_kit_components",
22197
22210
  "List all Chat Kit components. Optionally filter by name, category, or description.",
22198
- { filter: external_exports.string().optional().describe("Filter by name/category/description") },
22211
+ {
22212
+ filter: external_exports.string().optional().describe("Filter by name/category/description")
22213
+ },
22199
22214
  async ({ filter }) => {
22200
22215
  try {
22201
22216
  const components = listChatKitComponents(filter);
@@ -22210,7 +22225,10 @@ async function startServer() {
22210
22225
  },
22211
22226
  {}
22212
22227
  );
22213
- const sections = [`Found ${components.length} chat-kit component(s):`, ""];
22228
+ const sections = [
22229
+ `Found ${components.length} chat-kit component(s):`,
22230
+ ""
22231
+ ];
22214
22232
  Object.entries(grouped).forEach(([category, comps]) => {
22215
22233
  sections.push(`## ${category}`, "");
22216
22234
  comps.forEach((comp) => {
@@ -22227,11 +22245,14 @@ async function startServer() {
22227
22245
  server.tool(
22228
22246
  "get_chat_kit_docs",
22229
22247
  "Get full documentation for a Chat Kit component including props, examples, and usage.",
22230
- { componentName: external_exports.string().describe('Component name (e.g., "CkChatBubble" or "ChatBubble")') },
22248
+ {
22249
+ componentName: external_exports.string().describe('Component name (e.g., "CkChatBubble" or "ChatBubble")')
22250
+ },
22231
22251
  async ({ componentName }) => {
22232
22252
  try {
22233
22253
  const docs = getChatKitComponentDocs(componentName);
22234
- if (!docs) return error2(`Chat Kit component "${componentName}" not found.`);
22254
+ if (!docs)
22255
+ return error2(`Chat Kit component "${componentName}" not found.`);
22235
22256
  return text(formatComponentDocs(docs));
22236
22257
  } catch (e) {
22237
22258
  return handleError(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aleph-alpha/lib-mcp",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",
@@ -36,8 +36,8 @@
36
36
  "@aleph-alpha/tsconfig-frontend": "0.6.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@aleph-alpha/chat-kit": "1.0.0",
40
- "@aleph-alpha/ui-library": "2.0.0"
39
+ "@aleph-alpha/ui-library": "2.6.2",
40
+ "@aleph-alpha/chat-kit": "1.11.1"
41
41
  },
42
42
  "homepage": "https://docs.aleph-alpha.com",
43
43
  "scripts": {