@seed-ship/mcp-ui-solid 6.23.0 → 6.25.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.
Files changed (172) hide show
  1. package/CHANGELOG.md +167 -0
  2. package/README.md +154 -2
  3. package/dist/components/ActionGroupRenderer.cjs +6 -6
  4. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  5. package/dist/components/ActionGroupRenderer.js +6 -6
  6. package/dist/components/ActionGroupRenderer.js.map +1 -1
  7. package/dist/components/AutocompleteDropdown.cjs +6 -6
  8. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  9. package/dist/components/AutocompleteDropdown.js +6 -6
  10. package/dist/components/AutocompleteDropdown.js.map +1 -1
  11. package/dist/components/AutocompleteFormField.cjs +6 -5
  12. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  13. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  14. package/dist/components/AutocompleteFormField.js +6 -5
  15. package/dist/components/AutocompleteFormField.js.map +1 -1
  16. package/dist/components/ChartJSRenderer.cjs +406 -70
  17. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  18. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  19. package/dist/components/ChartJSRenderer.js +407 -71
  20. package/dist/components/ChartJSRenderer.js.map +1 -1
  21. package/dist/components/ChatPrompt.cjs +5 -5
  22. package/dist/components/ChatPrompt.cjs.map +1 -1
  23. package/dist/components/ChatPrompt.js +5 -5
  24. package/dist/components/ChatPrompt.js.map +1 -1
  25. package/dist/components/ComponentToolbar.cjs +5 -5
  26. package/dist/components/ComponentToolbar.cjs.map +1 -1
  27. package/dist/components/ComponentToolbar.js +5 -5
  28. package/dist/components/ComponentToolbar.js.map +1 -1
  29. package/dist/components/DegradedFallback.cjs +35 -3
  30. package/dist/components/DegradedFallback.cjs.map +1 -1
  31. package/dist/components/DegradedFallback.d.ts +7 -0
  32. package/dist/components/DegradedFallback.d.ts.map +1 -1
  33. package/dist/components/DegradedFallback.js +35 -3
  34. package/dist/components/DegradedFallback.js.map +1 -1
  35. package/dist/components/ExpandableWrapper.cjs +10 -7
  36. package/dist/components/ExpandableWrapper.cjs.map +1 -1
  37. package/dist/components/ExpandableWrapper.d.ts.map +1 -1
  38. package/dist/components/ExpandableWrapper.js +10 -7
  39. package/dist/components/ExpandableWrapper.js.map +1 -1
  40. package/dist/components/FormFieldRenderer.cjs +13 -12
  41. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  42. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  43. package/dist/components/FormFieldRenderer.js +13 -12
  44. package/dist/components/FormFieldRenderer.js.map +1 -1
  45. package/dist/components/FormRenderer.cjs +1 -1
  46. package/dist/components/FormRenderer.cjs.map +1 -1
  47. package/dist/components/FormRenderer.js +1 -1
  48. package/dist/components/FormRenderer.js.map +1 -1
  49. package/dist/components/GenerativeUIErrorBoundary.cjs +1 -1
  50. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  51. package/dist/components/GenerativeUIErrorBoundary.js +1 -1
  52. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  53. package/dist/components/GhostText.cjs +2 -2
  54. package/dist/components/GhostText.cjs.map +1 -1
  55. package/dist/components/GhostText.js +2 -2
  56. package/dist/components/GhostText.js.map +1 -1
  57. package/dist/components/GraphRenderer.cjs +100 -29
  58. package/dist/components/GraphRenderer.cjs.map +1 -1
  59. package/dist/components/GraphRenderer.d.ts +1 -1
  60. package/dist/components/GraphRenderer.d.ts.map +1 -1
  61. package/dist/components/GraphRenderer.js +100 -29
  62. package/dist/components/GraphRenderer.js.map +1 -1
  63. package/dist/components/MissingValue.cjs +43 -0
  64. package/dist/components/MissingValue.cjs.map +1 -0
  65. package/dist/components/MissingValue.d.ts +27 -0
  66. package/dist/components/MissingValue.d.ts.map +1 -0
  67. package/dist/components/MissingValue.js +43 -0
  68. package/dist/components/MissingValue.js.map +1 -0
  69. package/dist/components/ModalRenderer.cjs +39 -31
  70. package/dist/components/ModalRenderer.cjs.map +1 -1
  71. package/dist/components/ModalRenderer.d.ts.map +1 -1
  72. package/dist/components/ModalRenderer.js +40 -32
  73. package/dist/components/ModalRenderer.js.map +1 -1
  74. package/dist/components/PortalDropdownMenu.cjs +6 -3
  75. package/dist/components/PortalDropdownMenu.cjs.map +1 -1
  76. package/dist/components/PortalDropdownMenu.d.ts.map +1 -1
  77. package/dist/components/PortalDropdownMenu.js +7 -4
  78. package/dist/components/PortalDropdownMenu.js.map +1 -1
  79. package/dist/components/ScratchpadPanel.cjs +38 -44
  80. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  81. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  82. package/dist/components/ScratchpadPanel.js +38 -44
  83. package/dist/components/ScratchpadPanel.js.map +1 -1
  84. package/dist/components/UIResourceRenderer.cjs +255 -217
  85. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  86. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  87. package/dist/components/UIResourceRenderer.js +255 -217
  88. package/dist/components/UIResourceRenderer.js.map +1 -1
  89. package/dist/components/chart-data-table.cjs +30 -5
  90. package/dist/components/chart-data-table.cjs.map +1 -1
  91. package/dist/components/chart-data-table.d.ts +18 -1
  92. package/dist/components/chart-data-table.d.ts.map +1 -1
  93. package/dist/components/chart-data-table.js +30 -5
  94. package/dist/components/chart-data-table.js.map +1 -1
  95. package/dist/components/chart-missing-plugin.cjs +194 -0
  96. package/dist/components/chart-missing-plugin.cjs.map +1 -0
  97. package/dist/components/chart-missing-plugin.d.ts +129 -0
  98. package/dist/components/chart-missing-plugin.d.ts.map +1 -0
  99. package/dist/components/chart-missing-plugin.js +194 -0
  100. package/dist/components/chart-missing-plugin.js.map +1 -0
  101. package/dist/context/MCPUIStringsContext.cjs +12 -1
  102. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  103. package/dist/context/MCPUIStringsContext.d.ts +62 -0
  104. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  105. package/dist/context/MCPUIStringsContext.js +12 -1
  106. package/dist/context/MCPUIStringsContext.js.map +1 -1
  107. package/dist/services/component-registry.cjs +9 -2
  108. package/dist/services/component-registry.cjs.map +1 -1
  109. package/dist/services/component-registry.d.ts.map +1 -1
  110. package/dist/services/component-registry.js +9 -2
  111. package/dist/services/component-registry.js.map +1 -1
  112. package/dist/services/validation.cjs +31 -8
  113. package/dist/services/validation.cjs.map +1 -1
  114. package/dist/services/validation.d.ts.map +1 -1
  115. package/dist/services/validation.js +31 -8
  116. package/dist/services/validation.js.map +1 -1
  117. package/dist/types/index.d.ts +14 -2
  118. package/dist/types/index.d.ts.map +1 -1
  119. package/dist/utils/degraded-projections.cjs +19 -3
  120. package/dist/utils/degraded-projections.cjs.map +1 -1
  121. package/dist/utils/degraded-projections.d.ts +22 -2
  122. package/dist/utils/degraded-projections.d.ts.map +1 -1
  123. package/dist/utils/degraded-projections.js +19 -3
  124. package/dist/utils/degraded-projections.js.map +1 -1
  125. package/dist/utils/math-markdown.cjs +1 -1
  126. package/dist/utils/math-markdown.cjs.map +1 -1
  127. package/dist/utils/math-markdown.js +1 -1
  128. package/dist/utils/math-markdown.js.map +1 -1
  129. package/dist/utils/missing-value.cjs +60 -0
  130. package/dist/utils/missing-value.cjs.map +1 -0
  131. package/dist/utils/missing-value.d.ts +79 -0
  132. package/dist/utils/missing-value.d.ts.map +1 -0
  133. package/dist/utils/missing-value.js +60 -0
  134. package/dist/utils/missing-value.js.map +1 -0
  135. package/dist/utils/theme.cjs +91 -0
  136. package/dist/utils/theme.cjs.map +1 -0
  137. package/dist/utils/theme.d.ts +11 -0
  138. package/dist/utils/theme.d.ts.map +1 -0
  139. package/dist/utils/theme.js +91 -0
  140. package/dist/utils/theme.js.map +1 -0
  141. package/docs/SECURITY-DEPENDENCIES.md +81 -0
  142. package/docs/THEMING.md +205 -0
  143. package/package.json +7 -5
  144. package/src/components/ActionGroupRenderer.tsx +6 -6
  145. package/src/components/AutocompleteDropdown.tsx +17 -17
  146. package/src/components/AutocompleteFormField.tsx +10 -9
  147. package/src/components/ChartJSRenderer.tsx +609 -212
  148. package/src/components/ChatPrompt.tsx +16 -16
  149. package/src/components/ComponentToolbar.tsx +5 -5
  150. package/src/components/DegradedFallback.tsx +33 -11
  151. package/src/components/ExpandableWrapper.tsx +15 -11
  152. package/src/components/FormFieldRenderer.tsx +52 -51
  153. package/src/components/FormRenderer.tsx +12 -12
  154. package/src/components/GenerativeUIErrorBoundary.tsx +6 -6
  155. package/src/components/GhostText.tsx +7 -7
  156. package/src/components/GraphRenderer.tsx +83 -67
  157. package/src/components/MissingValue.tsx +61 -0
  158. package/src/components/ModalRenderer.tsx +11 -5
  159. package/src/components/PortalDropdownMenu.tsx +6 -2
  160. package/src/components/ScratchpadPanel.tsx +144 -141
  161. package/src/components/UIResourceRenderer.tsx +143 -110
  162. package/src/components/chart-data-table.ts +50 -6
  163. package/src/components/chart-missing-plugin.ts +336 -0
  164. package/src/context/MCPUIStringsContext.tsx +76 -0
  165. package/src/services/component-registry.ts +10 -2
  166. package/src/services/validation.ts +44 -9
  167. package/src/styles/autocomplete.css +53 -53
  168. package/src/types/index.ts +13 -1
  169. package/src/utils/degraded-projections.ts +44 -4
  170. package/src/utils/math-markdown.ts +1 -1
  171. package/src/utils/missing-value.ts +127 -0
  172. package/src/utils/theme.ts +101 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.cjs","sources":["../../src/utils/theme.ts"],"sourcesContent":["import { createEffect, onCleanup } from 'solid-js'\n\nconst THEME_PREFIX = '--mcp-ui-'\n\n/** Read only explicitly supplied host tokens; blank values retain engine defaults. */\nexport function readThemeTokens(\n element: Element | undefined,\n names: readonly string[],\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {}\n const view = element?.ownerDocument.defaultView\n const style = element && view ? view.getComputedStyle(element) : undefined\n for (const name of names) result[name] = style?.getPropertyValue(name).trim() || undefined\n return result\n}\n\n/** Observe inherited theme changes, including moving a mounted renderer into a portal. */\nexport function observeTheme(element: HTMLElement, callback: () => void): () => void {\n const view = element.ownerDocument.defaultView\n if (!view?.MutationObserver) return () => {}\n let stopped = false\n let pending = false\n const schedule = () => {\n if (pending || stopped) return\n pending = true\n queueMicrotask(() => {\n pending = false\n if (!stopped) callback()\n })\n }\n const observer = new view.MutationObserver((records) => {\n if (records.some((record) => {\n if (record.type === 'attributes') {\n return record.target instanceof view.Element && record.target.contains(element)\n }\n return [...record.addedNodes, ...record.removedNodes].some(\n (node) => node === element || node.contains(element),\n )\n })) schedule()\n })\n // Attribute names are deliberately not filtered: hosts may use any data-* selector.\n observer.observe(element.ownerDocument.documentElement, {\n attributes: true,\n childList: true,\n subtree: true,\n })\n return () => {\n stopped = true\n observer.disconnect()\n }\n}\n\n/**\n * A body portal cannot inherit a local host theme. Copy only our effective\n * custom properties and dark state from its stationary anchor, and keep them\n * synchronized for the lifetime of the Solid owner. Geometry stays unchanged.\n */\nexport function bindPortalTheme(portal: HTMLElement, source: () => HTMLElement | undefined): void {\n createEffect(() => {\n const anchor = source()\n const view = anchor?.ownerDocument.defaultView\n if (!anchor || !view) return\n const copied = new Set<string>()\n const sync = () => {\n const style = view.getComputedStyle(anchor)\n const inherited = portal.parentElement ? view.getComputedStyle(portal.parentElement) : undefined\n const names = new Set(copied)\n for (const computed of [style, inherited]) {\n if (!computed) continue\n for (let index = 0; index < computed.length; index++) {\n const name = computed.item(index)\n if (name.startsWith(THEME_PREFIX)) names.add(name)\n }\n }\n const values = new Map<string, string>()\n for (const name of names) {\n const value = style.getPropertyValue(name).trim()\n if (value) values.set(name, value)\n // Empty setProperty removes a declaration. Explicitly block a body\n // token when the local scope reset it to the guaranteed-invalid value.\n else if (inherited?.getPropertyValue(name).trim()) values.set(name, 'initial')\n }\n for (const name of copied) {\n if (!values.has(name)) portal.style.removeProperty(name)\n }\n copied.clear()\n for (const [name, value] of values) {\n if (portal.style.getPropertyValue(name) !== value) portal.style.setProperty(name, value)\n copied.add(name)\n }\n portal.classList.toggle('dark', anchor.closest('.dark') !== null)\n }\n sync()\n const stop = observeTheme(anchor, sync)\n onCleanup(() => {\n stop()\n for (const name of copied) portal.style.removeProperty(name)\n portal.classList.remove('dark')\n })\n })\n}\n"],"names":["createEffect","onCleanup"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Read only explicitly supplied host tokens; blank values retain engine defaults. */
2
+ export declare function readThemeTokens(element: Element | undefined, names: readonly string[]): Record<string, string | undefined>;
3
+ /** Observe inherited theme changes, including moving a mounted renderer into a portal. */
4
+ export declare function observeTheme(element: HTMLElement, callback: () => void): () => void;
5
+ /**
6
+ * A body portal cannot inherit a local host theme. Copy only our effective
7
+ * custom properties and dark state from its stationary anchor, and keep them
8
+ * synchronized for the lifetime of the Solid owner. Geometry stays unchanged.
9
+ */
10
+ export declare function bindPortalTheme(portal: HTMLElement, source: () => HTMLElement | undefined): void;
11
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/utils/theme.ts"],"names":[],"mappings":"AAIA,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAMpC;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAiCnF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,GAAG,SAAS,GAAG,IAAI,CA2ChG"}
@@ -0,0 +1,91 @@
1
+ import { createEffect, onCleanup } from "solid-js";
2
+ const THEME_PREFIX = "--mcp-ui-";
3
+ function readThemeTokens(element, names) {
4
+ const result = {};
5
+ const view = element == null ? void 0 : element.ownerDocument.defaultView;
6
+ const style = element && view ? view.getComputedStyle(element) : void 0;
7
+ for (const name of names) result[name] = (style == null ? void 0 : style.getPropertyValue(name).trim()) || void 0;
8
+ return result;
9
+ }
10
+ function observeTheme(element, callback) {
11
+ const view = element.ownerDocument.defaultView;
12
+ if (!(view == null ? void 0 : view.MutationObserver)) return () => {
13
+ };
14
+ let stopped = false;
15
+ let pending = false;
16
+ const schedule = () => {
17
+ if (pending || stopped) return;
18
+ pending = true;
19
+ queueMicrotask(() => {
20
+ pending = false;
21
+ if (!stopped) callback();
22
+ });
23
+ };
24
+ const observer = new view.MutationObserver((records) => {
25
+ if (records.some((record) => {
26
+ if (record.type === "attributes") {
27
+ return record.target instanceof view.Element && record.target.contains(element);
28
+ }
29
+ return [...record.addedNodes, ...record.removedNodes].some(
30
+ (node) => node === element || node.contains(element)
31
+ );
32
+ })) schedule();
33
+ });
34
+ observer.observe(element.ownerDocument.documentElement, {
35
+ attributes: true,
36
+ childList: true,
37
+ subtree: true
38
+ });
39
+ return () => {
40
+ stopped = true;
41
+ observer.disconnect();
42
+ };
43
+ }
44
+ function bindPortalTheme(portal, source) {
45
+ createEffect(() => {
46
+ const anchor = source();
47
+ const view = anchor == null ? void 0 : anchor.ownerDocument.defaultView;
48
+ if (!anchor || !view) return;
49
+ const copied = /* @__PURE__ */ new Set();
50
+ const sync = () => {
51
+ const style = view.getComputedStyle(anchor);
52
+ const inherited = portal.parentElement ? view.getComputedStyle(portal.parentElement) : void 0;
53
+ const names = new Set(copied);
54
+ for (const computed of [style, inherited]) {
55
+ if (!computed) continue;
56
+ for (let index = 0; index < computed.length; index++) {
57
+ const name = computed.item(index);
58
+ if (name.startsWith(THEME_PREFIX)) names.add(name);
59
+ }
60
+ }
61
+ const values = /* @__PURE__ */ new Map();
62
+ for (const name of names) {
63
+ const value = style.getPropertyValue(name).trim();
64
+ if (value) values.set(name, value);
65
+ else if (inherited == null ? void 0 : inherited.getPropertyValue(name).trim()) values.set(name, "initial");
66
+ }
67
+ for (const name of copied) {
68
+ if (!values.has(name)) portal.style.removeProperty(name);
69
+ }
70
+ copied.clear();
71
+ for (const [name, value] of values) {
72
+ if (portal.style.getPropertyValue(name) !== value) portal.style.setProperty(name, value);
73
+ copied.add(name);
74
+ }
75
+ portal.classList.toggle("dark", anchor.closest(".dark") !== null);
76
+ };
77
+ sync();
78
+ const stop = observeTheme(anchor, sync);
79
+ onCleanup(() => {
80
+ stop();
81
+ for (const name of copied) portal.style.removeProperty(name);
82
+ portal.classList.remove("dark");
83
+ });
84
+ });
85
+ }
86
+ export {
87
+ bindPortalTheme,
88
+ observeTheme,
89
+ readThemeTokens
90
+ };
91
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sources":["../../src/utils/theme.ts"],"sourcesContent":["import { createEffect, onCleanup } from 'solid-js'\n\nconst THEME_PREFIX = '--mcp-ui-'\n\n/** Read only explicitly supplied host tokens; blank values retain engine defaults. */\nexport function readThemeTokens(\n element: Element | undefined,\n names: readonly string[],\n): Record<string, string | undefined> {\n const result: Record<string, string | undefined> = {}\n const view = element?.ownerDocument.defaultView\n const style = element && view ? view.getComputedStyle(element) : undefined\n for (const name of names) result[name] = style?.getPropertyValue(name).trim() || undefined\n return result\n}\n\n/** Observe inherited theme changes, including moving a mounted renderer into a portal. */\nexport function observeTheme(element: HTMLElement, callback: () => void): () => void {\n const view = element.ownerDocument.defaultView\n if (!view?.MutationObserver) return () => {}\n let stopped = false\n let pending = false\n const schedule = () => {\n if (pending || stopped) return\n pending = true\n queueMicrotask(() => {\n pending = false\n if (!stopped) callback()\n })\n }\n const observer = new view.MutationObserver((records) => {\n if (records.some((record) => {\n if (record.type === 'attributes') {\n return record.target instanceof view.Element && record.target.contains(element)\n }\n return [...record.addedNodes, ...record.removedNodes].some(\n (node) => node === element || node.contains(element),\n )\n })) schedule()\n })\n // Attribute names are deliberately not filtered: hosts may use any data-* selector.\n observer.observe(element.ownerDocument.documentElement, {\n attributes: true,\n childList: true,\n subtree: true,\n })\n return () => {\n stopped = true\n observer.disconnect()\n }\n}\n\n/**\n * A body portal cannot inherit a local host theme. Copy only our effective\n * custom properties and dark state from its stationary anchor, and keep them\n * synchronized for the lifetime of the Solid owner. Geometry stays unchanged.\n */\nexport function bindPortalTheme(portal: HTMLElement, source: () => HTMLElement | undefined): void {\n createEffect(() => {\n const anchor = source()\n const view = anchor?.ownerDocument.defaultView\n if (!anchor || !view) return\n const copied = new Set<string>()\n const sync = () => {\n const style = view.getComputedStyle(anchor)\n const inherited = portal.parentElement ? view.getComputedStyle(portal.parentElement) : undefined\n const names = new Set(copied)\n for (const computed of [style, inherited]) {\n if (!computed) continue\n for (let index = 0; index < computed.length; index++) {\n const name = computed.item(index)\n if (name.startsWith(THEME_PREFIX)) names.add(name)\n }\n }\n const values = new Map<string, string>()\n for (const name of names) {\n const value = style.getPropertyValue(name).trim()\n if (value) values.set(name, value)\n // Empty setProperty removes a declaration. Explicitly block a body\n // token when the local scope reset it to the guaranteed-invalid value.\n else if (inherited?.getPropertyValue(name).trim()) values.set(name, 'initial')\n }\n for (const name of copied) {\n if (!values.has(name)) portal.style.removeProperty(name)\n }\n copied.clear()\n for (const [name, value] of values) {\n if (portal.style.getPropertyValue(name) !== value) portal.style.setProperty(name, value)\n copied.add(name)\n }\n portal.classList.toggle('dark', anchor.closest('.dark') !== null)\n }\n sync()\n const stop = observeTheme(anchor, sync)\n onCleanup(() => {\n stop()\n for (const name of copied) portal.style.removeProperty(name)\n portal.classList.remove('dark')\n })\n })\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ # Security dependency notes (6.25.1)
2
+
3
+ Verified on 2026-10-18. This is a dependency-remediation record, not a claim
4
+ that every renderer or consuming application has been security-audited.
5
+
6
+ ## Updates
7
+
8
+ | Dependency | Previous lock | Updated lock | Scope and advisories |
9
+ | --- | --- | --- | --- |
10
+ | DOMPurify | 3.4.13 | 3.4.17 | Required runtime dependency; published floor is `^3.4.17`. [Rawtext root](https://github.com/cure53/DOMPurify/security/advisories/GHSA-6688-9rhm-gjv2), [hook removal](https://github.com/cure53/DOMPurify/security/advisories/GHSA-p98j-92pf-mc4p) and [subsequent hardening](https://github.com/cure53/DOMPurify/releases/tag/3.4.17). |
11
+ | Solid / Seroval / Seroval plugins | 1.9.11 / 1.5.6 / 1.5.0 | 1.9.17 / 1.6.11 / 1.6.11 | Workspace development resolution; Solid remains a required host peer. [Callable invocation](https://github.com/lxsmnsyc/seroval/security/advisories/GHSA-p6vx-979v-rg4c), [TypedArray allocation](https://github.com/lxsmnsyc/seroval/security/advisories/GHSA-jp82-f5mq-hwhp). |
12
+ | source-map-js | 1.2.1 | 1.2.2 | Build/test tooling via PostCSS and css-tree. [Indexed-map denial of service](https://github.com/advisories/GHSA-68fv-2mgg-jv7q). |
13
+ | brace-expansion | 5.0.9 | 5.0.12 | Lint tooling via minimatch. [Rewrite complexity](https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-q2hr-2g5m-vwhr), [nested groups](https://github.com/advisories/GHSA-qhr7-859c-m2p7), [comma recursion](https://github.com/advisories/GHSA-6j4f-fj2g-mc7p). |
14
+
15
+ The sanitizer accepts strings, not live DOM nodes, and the library's profiles
16
+ do not use `IN_PLACE` or node-removing hooks. The two reported DOMPurify
17
+ preconditions were not found in these library paths; this is not a finding
18
+ that every possible host configuration is safe. No renderer policy is relaxed.
19
+
20
+ Solid 1.9.17 requests Seroval and its plugins in the supported `~1.6.8` range.
21
+ The workspace override follows that range instead of forcing Solid's old
22
+ `~1.5.3` resolution. Seroval 1.6.2 alone would not cover the additional
23
+ TypedArray advisory, which was fixed in 1.6.3.
24
+
25
+ ## Host applications
26
+
27
+ This package externalizes dependencies. Its DOMPurify manifest floor affects
28
+ the version installed by consumers, but this repository's root pnpm overrides
29
+ and development lockfile do **not** propagate to an application's lockfile.
30
+ The public `solid-js` peer range remains `^1.9.0` for compatibility; it is not
31
+ a guarantee that the host has a corrected Seroval resolution.
32
+
33
+ Update the application lockfile separately. An upstream-aligned option is
34
+ Solid 1.9.17 with its supported Seroval/plugin 1.6.x dependencies; verify the
35
+ resolved versions and audit the application itself. The library's new test
36
+ performs a real asynchronous Solid server render with hydration serialization,
37
+ plus a legitimate Map/Date/typed-array/Promise JSON transport roundtrip through
38
+ Solid's own Seroval. It is a compatibility smoke test, not browser hydration
39
+ proof, an exploitability assessment of the host, or an end-to-end SolidStart test.
40
+
41
+ ## Remaining advisory: braces 3.0.3
42
+
43
+ [`GHSA-vfj7-8cjw-p6xm`](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm)
44
+ is HIGH severity and has no patched version listed as of the verification
45
+ date. It concerns stack exhaustion when deeply nested, attacker-controlled
46
+ brace patterns reach recursive glob parsing. `braces` and `brace-expansion`
47
+ are different packages: updating the latter does not fix this advisory.
48
+
49
+ The retained development-only chain is:
50
+
51
+ ```text
52
+ ESLint / typescript-eslint
53
+ -> typescript-estree -> globby -> fast-glob -> micromatch -> braces 3.0.3
54
+ ```
55
+
56
+ No compatible published parent update that removes this chain was identified.
57
+ Even the checked latest globby release still uses fast-glob/micromatch and
58
+ braces. The [upstream issue](https://github.com/micromatch/braces/issues/70)
59
+ remains the follow-up point.
60
+
61
+ Current lint scripts use `eslint src`; repository lint configuration has
62
+ simple static file patterns and no `parserOptions.project` glob. No production
63
+ renderer path to braces was identified. Keep project/glob patterns static and
64
+ reviewed, and do not expose this tooling as a service accepting untrusted
65
+ patterns without a depth guard and isolation. These observations bound the
66
+ known exposure; they do not prove complete unreachability, including through
67
+ every future lint configuration or pull request.
68
+
69
+ The advisory stays visible: no audit suppression, alias, downgrade, vendored
70
+ patch or GitHub dismissal is included. Recheck upstream before claiming it
71
+ resolved. The verified full-workspace audit moves from 9 advisories
72
+ (1 critical, 5 high, 1 moderate, 2 low) to **1 high**; a nonzero full audit
73
+ result is therefore expected until this residual is fixed.
74
+
75
+ ## Release boundary
76
+
77
+ The package version bump does not itself start npm staging. The current
78
+ GitHub workflow triggers on an approved `v*.*.*` tag (or manual dispatch),
79
+ then uses `npm stage publish`. A successful upload still requires maintainer
80
+ approval in npm Staged Packages with 2FA before it is public. Do not label a
81
+ merge, a green CI run or an uploaded stage as a public npm release.
@@ -0,0 +1,205 @@
1
+ # Theming (6.25.0)
2
+
3
+ Set `--mcp-ui-*` custom properties on any ancestor of the library's components.
4
+ They inherit through nested components and can be scoped differently for two
5
+ instances on the same page. No provider, global theme or new dependency is
6
+ required. Continue scanning `src/**/*.{ts,tsx}` with Tailwind v4 and use the
7
+ host's class-based `.dark` variant. Import the existing autocomplete CSS when
8
+ using those components, as before.
9
+
10
+ ## Defaults and inheritance
11
+
12
+ The migrated component roots carry `mcp-ui-root`, but **do not assign public
13
+ token defaults** there. Assigning `--mcp-ui-surface: white` on a nested root
14
+ would mask an inherited host value. Instead, each static utility consumes a
15
+ token with the exact old color as its fallback, for example:
16
+
17
+ ```tsx
18
+ class="bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]"
19
+ ```
20
+
21
+ The host's existing Tailwind palette remains authoritative. Fallbacks retain
22
+ the original per-component shades, dark variants and alpha paints; there is
23
+ deliberately no single replacement palette that collapses those distinctions.
24
+ Removing a token restores that original fallback. A custom color replaces the
25
+ whole paint, including opacity, rather than applying the old opacity to it.
26
+ Host tokens apply without a `.dark` class too; that class only selects the
27
+ original dark fallbacks. Normal form text falls back to its inherited color
28
+ when `--mcp-ui-fg` is absent, while muted fields keep their secondary role.
29
+
30
+ ## Tokens
31
+
32
+ Values below describe the original fallbacks, not declarations on the root.
33
+ `gray-*`, `blue-*`, etc. refer to the host's Tailwind color variables. Exact
34
+ per-use defaults remain visible in the static source classes. The frozen
35
+ 6.24.0 browser fixture records representative resolved light/dark values.
36
+
37
+ | Token | Role | Original default / behavior |
38
+ | --- | --- | --- |
39
+ | `--mcp-ui-bg` | Outer resource-renderer background | Transparent |
40
+ | `--mcp-ui-surface` | Cards, panels, menus, input surfaces | White; dark gray-800, with original local exceptions |
41
+ | `--mcp-ui-surface-muted` | Headers, secondary/disabled surfaces | Original gray-50/100; dark gray-700/900 variants |
42
+ | `--mcp-ui-surface-hover` | Hovered neutral elements | Original gray-50/100; dark gray-700 variants |
43
+ | `--mcp-ui-fg` | Primary text | Original gray-700/900; dark gray-100/300/white |
44
+ | `--mcp-ui-fg-muted` | Descriptions and secondary text | Original gray-500/600; dark gray-400 variants |
45
+ | `--mcp-ui-fg-subtle` | Hints, placeholders, disabled text | Original gray-400 and existing autocomplete RGBA values |
46
+ | `--mcp-ui-border` | Decorative borders and dividers | Original gray-100/200; dark gray-600/700 |
47
+ | `--mcp-ui-border-control` | Form-field outlines | Original gray-300; dark gray-600, with local variants |
48
+ | `--mcp-ui-focus` | Focus ring/border | Original blue-400/500 |
49
+ | `--mcp-ui-accent` | Primary action, link, selected text | Original blue/indigo shades |
50
+ | `--mcp-ui-accent-fg` | Text on a strong action fill | White |
51
+ | `--mcp-ui-accent-hover` | Strong action hover fill | Original blue-700 |
52
+ | `--mcp-ui-accent-soft` | Selection and preview backgrounds | Original blue-50/100; dark blue-900 with original alpha |
53
+ | `--mcp-ui-accent-border` | Accent borders | Original blue-200/300; dark blue-600/800 |
54
+ | `--mcp-ui-danger` | Destructive/error text or strong fill | Original red/rose shades |
55
+ | `--mcp-ui-danger-hover` | Destructive hover fill | Original red-700 |
56
+ | `--mcp-ui-danger-soft` | Error backgrounds | Original red-50/100; dark red-900 with original alpha |
57
+ | `--mcp-ui-danger-border` | Error borders | Original red-200/300; dark red-600/800 |
58
+ | `--mcp-ui-warning` | Warning/loading text or strong fill | Original amber/yellow/orange shades |
59
+ | `--mcp-ui-warning-hover` | Warning action hover | Original amber-700 |
60
+ | `--mcp-ui-warning-soft` | Warning backgrounds | Original amber/yellow/orange soft paints |
61
+ | `--mcp-ui-warning-border` | Warning borders | Original amber/yellow/orange border shades |
62
+ | `--mcp-ui-success` | Successful/completed states | Original green/emerald shades |
63
+ | `--mcp-ui-success-soft` | Success backgrounds | Original green-50/100; dark green-900 with original alpha |
64
+ | `--mcp-ui-info` | Ready/information states | Original cyan/sky/teal shades |
65
+ | `--mcp-ui-info-soft` | Information backgrounds | Original cyan/sky/teal soft paints |
66
+ | `--mcp-ui-info-border` | Information borders | Original cyan/sky/teal border shades |
67
+ | `--mcp-ui-secondary` | Processing/comparison states | Original purple/violet/pink shades |
68
+ | `--mcp-ui-secondary-soft` | Secondary-state backgrounds | Original purple/violet/pink soft paints |
69
+ | `--mcp-ui-highlight` | Table search match | Original yellow-200 and existing dark search paint |
70
+ | `--mcp-ui-overlay` | Dialog backdrop | Black at 50% |
71
+ | `--mcp-ui-shadow` | Full shadow value, not a color | Existing Tailwind shadow or autocomplete box-shadow |
72
+ | `--mcp-ui-scrollbar-thumb` / `--mcp-ui-scrollbar-track` | Scratchpad native scrollbars | Original automatic rendering when unset |
73
+ | `--mcp-ui-chart-1` … `--mcp-ui-chart-8` | Series / graph node / pie-point colors | Engine-managed defaults when absent |
74
+ | `--mcp-ui-chart-grid` | Chart grid / graph edges | Existing engine defaults |
75
+ | `--mcp-ui-chart-axis` | Chart axis border / graph node stroke | Existing engine defaults |
76
+ | `--mcp-ui-chart-label` | Axes, legend, graph labels, no-data text | Existing engine defaults |
77
+
78
+ Typography, spacing and radii are not changed by this release. Native
79
+ scrollbars retain their automatic appearance unless the host supplies tokens.
80
+ Existing non-color `!important` utilities and the pre-existing
81
+ autocomplete selected-state/reduced-motion rules remain; the theme migration
82
+ adds no `!important` rule and no global palette selector.
83
+
84
+ ## Host example
85
+
86
+ ```css
87
+ .app-theme {
88
+ --mcp-ui-surface: #fff;
89
+ --mcp-ui-surface-muted: #f1f5f9;
90
+ --mcp-ui-surface-hover: #e2e8f0;
91
+ --mcp-ui-fg: #0f172a;
92
+ --mcp-ui-fg-muted: #475569;
93
+ --mcp-ui-fg-subtle: #64748b;
94
+ --mcp-ui-border: #cbd5e1;
95
+ --mcp-ui-border-control: #64748b;
96
+ --mcp-ui-accent: #1d4ed8;
97
+ --mcp-ui-accent-hover: #1e40af;
98
+ --mcp-ui-accent-fg: #fff;
99
+ --mcp-ui-accent-soft: #dbeafe;
100
+ --mcp-ui-focus: #1d4ed8;
101
+ --mcp-ui-chart-1: #1d4ed8;
102
+ --mcp-ui-chart-grid: #cbd5e1;
103
+ --mcp-ui-chart-axis: #64748b;
104
+ --mcp-ui-chart-label: #334155;
105
+ }
106
+ .dark .app-theme {
107
+ --mcp-ui-surface: #0f172a;
108
+ --mcp-ui-surface-muted: #1e293b;
109
+ --mcp-ui-surface-hover: #334155;
110
+ --mcp-ui-fg: #f8fafc;
111
+ --mcp-ui-fg-muted: #cbd5e1;
112
+ --mcp-ui-fg-subtle: #94a3b8;
113
+ --mcp-ui-border: #475569;
114
+ --mcp-ui-border-control: #94a3b8;
115
+ --mcp-ui-accent: #93c5fd;
116
+ --mcp-ui-accent-hover: #60a5fa;
117
+ --mcp-ui-accent-fg: #0f172a;
118
+ --mcp-ui-accent-soft: #1e3a8a;
119
+ --mcp-ui-focus: #93c5fd;
120
+ --mcp-ui-chart-1: #93c5fd;
121
+ --mcp-ui-chart-grid: #475569;
122
+ --mcp-ui-chart-axis: #94a3b8;
123
+ --mcp-ui-chart-label: #e2e8f0;
124
+ }
125
+ ```
126
+
127
+ Choose semantic status colors and all series colors appropriate for the host.
128
+ Recheck contrast against the actual backgrounds, including hover/disabled
129
+ states and chart series. The example is a starting point, not a full theme
130
+ or an accessibility certification.
131
+
132
+ ## Portals and mounted graphics
133
+
134
+ Export menus and dialogs stay mounted under `document.body`. Moving them into
135
+ the host container would restore clipping and transformed-parent positioning
136
+ bugs. The library copies only effective `--mcp-ui-*` values and local dark
137
+ state from the menu trigger or the stationary inline expansion slot. It
138
+ updates them while the portal is open and clears stale values when its
139
+ anchor changes. `ModalRenderer` uses a hidden inline anchor. Fullscreen
140
+ content is still moved, not duplicated.
141
+
142
+ Canvas renderers read computed tokens from their own roots. Ancestor
143
+ class/style/data-attribute changes, token removal and DOM reparenting trigger
144
+ an in-place Chart.js `update('none')` or G6 `setOptions`/`draw`, preserving
145
+ renderer identity and layout/zoom. Explicit dataset, scale, element, tooltip,
146
+ node and edge styles keep precedence. Chart payload objects are shallow-cloned
147
+ before the engine adds its generated colors, allowing one payload in multiple
148
+ independently themed instances.
149
+ Point paint overrides are separate from the line/radar stroke: an explicit
150
+ point outline does not block a host series color on the line itself. Live
151
+ updates also refresh Chart.js's shared point/bar paints without replacing
152
+ their shared option objects or changing non-color settings.
153
+
154
+ Chart.js tooltips use `surface`, `fg` and `border`; G6 tooltip content reads
155
+ those current tokens when shown. An omitted series token leaves its original
156
+ engine color. Chart.js's default cycle is seven colors, while configured host
157
+ tokens address eight repeating slots. No default palette is installed globally.
158
+
159
+ Pure stylesheet/CSSOM or media-query changes without a DOM attribute change
160
+ are not observed. When switching such a host theme, also update the host's
161
+ class or a `data-theme` attribute. The supported light/dark strategy remains
162
+ the `.dark` ancestor class.
163
+
164
+ ## Coverage and known accessibility gaps
165
+
166
+ Migrated: `ChatPrompt`, `UIResourceRenderer` (including table chrome and virtual
167
+ rows), `ScratchpadPanel`, `FormFieldRenderer`, `AutocompleteFormField`,
168
+ `AutocompleteDropdown`, `GhostText`, `ComponentToolbar`, `ExpandableWrapper`,
169
+ `PortalDropdownMenu`, `FormRenderer`, `ActionGroupRenderer`, `ModalRenderer`,
170
+ missing-value/degraded/error surfaces, and Chart.js/G6 chrome and canvas colors.
171
+
172
+ Not fully migrated: map/Leaflet chrome and external tiles, code highlighting
173
+ themes, `AgentCard`/`AgentHandoff`/`AgentStatusBadge`, standalone artifact,
174
+ carousel, footer, grid/editor, gallery/video and presentation-feedback chrome,
175
+ and other renderers outside the list above. Native OS select popups are outside
176
+ the library's DOM styling. Hosts should remove old overrides only for migrated
177
+ surfaces and retain targeted overrides for these remaining components.
178
+
179
+ Default colors are preserved even where they miss accessibility targets.
180
+ The frozen Chromium fixture measured these existing gaps:
181
+
182
+ | Measurement | Light | Dark | Target |
183
+ | --- | ---: | ---: | ---: |
184
+ | Required input / form select / scratchpad select border | 1.47:1 | 1.94:1 | 3:1 |
185
+ | Search / choice control border | 1.24:1 | 1.94:1 | 3:1 |
186
+ | Required input visible 2px focus ring | 3.76:1 | 3.90:1 | 3:1 |
187
+
188
+ The represented text measurements reached at least 4.52:1; this is not a full
189
+ audit of every field, state or renderer. Supplementary Chart.js 4.5.1 palette
190
+ measurements on white put six of seven opaque default colors, and all seven
191
+ 50%-alpha fills, below 3:1. These are palette measurements, not a rendered
192
+ canvas or adjacent-series audit. Changing those defaults would alter existing
193
+ appearance, so hosts must supply their own accessible theme where needed.
194
+
195
+ See [`tests/theme/README.md`](../tests/theme/README.md) for the exact baseline
196
+ commit, tool versions, measured deficits and limits. The fixture checks actual
197
+ computed styles, not screenshot equality; raster canvas appearance and native
198
+ popup pixels are outside that comparison.
199
+
200
+ ## Release handoff
201
+
202
+ This is a Solid-only 6.25.0 release. The MCP schema and Spec/CLI versions are
203
+ unchanged. Hosts can upgrade, bind their design tokens, remove the corresponding
204
+ overrides and run their own browser contrast checks. The public release follows
205
+ review/CI, the `v6.25.0` tag, then maintainer approval of the staged npm package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-solid",
3
- "version": "6.23.0",
3
+ "version": "6.25.1",
4
4
  "description": "SolidJS components for rendering MCP-generated UI resources",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,6 +11,8 @@
11
11
  "!src/test-setup.ts",
12
12
  "README.md",
13
13
  "CHANGELOG.md",
14
+ "docs/THEMING.md",
15
+ "docs/SECURITY-DEPENDENCIES.md",
14
16
  "LICENSE"
15
17
  ],
16
18
  "sideEffects": [
@@ -229,9 +231,9 @@
229
231
  }
230
232
  },
231
233
  "dependencies": {
232
- "@seed-ship/mcp-ui-spec": "^5.6.0",
234
+ "@seed-ship/mcp-ui-spec": "^5.7.0",
233
235
  "@types/dompurify": "^3.0.5",
234
- "dompurify": "^3.4.12",
236
+ "dompurify": "^3.4.17",
235
237
  "marked": "^16.3.0",
236
238
  "zod": "^3.22.4"
237
239
  },
@@ -253,7 +255,7 @@
253
255
  "katex": "0.18.4",
254
256
  "leaflet.markercluster": "^1.5.3",
255
257
  "size-limit": "^12.0.0",
256
- "solid-js": "^1.9.11",
258
+ "solid-js": "^1.9.17",
257
259
  "typescript": "^5.3.3",
258
260
  "vite": "^6.4.1",
259
261
  "vite-plugin-solid": "^2.11.8",
@@ -289,7 +291,7 @@
289
291
  "test:watch": "vitest",
290
292
  "test:browser": "vitest --mode browser",
291
293
  "lint": "eslint src",
292
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.contracts.json",
294
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.contracts.json && tsc --noEmit -p tsconfig.examples.json",
293
295
  "clean": "rm -rf dist",
294
296
  "size": "size-limit",
295
297
  "size:analyze": "size-limit --why"
@@ -62,17 +62,17 @@ const ActionButton: Component<{
62
62
  const variantClass = () => {
63
63
  switch (props.action.variant) {
64
64
  case 'primary':
65
- return 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500'
65
+ return 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))] focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]'
66
66
  case 'secondary':
67
- return 'bg-gray-200 text-gray-800 hover:bg-gray-300 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:ring-gray-500'
67
+ return 'bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-200,#e5e7eb))] text-[color:var(--mcp-ui-fg,var(--color-gray-800,#1f2937))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-300,#d1d5db))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-600,#4b5563))] focus:ring-[color:var(--mcp-ui-focus,var(--color-gray-500,#6b7280))]'
68
68
  case 'outline':
69
- return 'border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 focus:ring-gray-500'
69
+ return 'border border-[color:var(--mcp-ui-border,var(--color-gray-300,#d1d5db))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] focus:ring-[color:var(--mcp-ui-focus,var(--color-gray-500,#6b7280))]'
70
70
  case 'ghost':
71
- return 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500'
71
+ return 'text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] focus:ring-[color:var(--mcp-ui-focus,var(--color-gray-500,#6b7280))]'
72
72
  case 'danger':
73
- return 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500'
73
+ return 'bg-[color:var(--mcp-ui-danger,var(--color-red-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-danger-hover,var(--color-red-700))] focus:ring-[color:var(--mcp-ui-focus,var(--color-red-500))]'
74
74
  default:
75
- return 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500'
75
+ return 'bg-[color:var(--mcp-ui-accent,var(--color-blue-600))] text-[color:var(--mcp-ui-accent-fg,var(--color-white,#fff))] hover:bg-[color:var(--mcp-ui-accent-hover,var(--color-blue-700))] focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]'
76
76
  }
77
77
  }
78
78
 
@@ -162,10 +162,10 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
162
162
  left: '0',
163
163
  right: '0',
164
164
  'z-index': '50',
165
- 'background-color': '#ffffff',
166
- border: '1px solid #e5e7eb',
165
+ 'background-color': 'var(--mcp-ui-surface, #ffffff)',
166
+ border: '1px solid var(--mcp-ui-border, #e5e7eb)',
167
167
  'border-radius': '6px',
168
- 'box-shadow': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
168
+ 'box-shadow': 'var(--mcp-ui-shadow, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06))',
169
169
  'max-height': props.maxHeight || '240px',
170
170
  overflow: 'auto',
171
171
  ...positionStyles()
@@ -174,7 +174,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
174
174
  return (
175
175
  <Show when={props.isOpen}>
176
176
  <div
177
- class={`mcp-autocomplete-dropdown ${props.class || ''}`}
177
+ class={`mcp-ui-root mcp-autocomplete-dropdown ${props.class || ''}`}
178
178
  style={containerStyles()}
179
179
  role="listbox"
180
180
  aria-label={strings.autocompleteSuggestions}
@@ -185,7 +185,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
185
185
  class="mcp-autocomplete-loading"
186
186
  style={{
187
187
  padding: '12px 16px',
188
- color: '#6b7280',
188
+ color: 'var(--mcp-ui-fg-muted, #6b7280)',
189
189
  'font-size': '0.875rem',
190
190
  display: 'flex',
191
191
  'align-items': 'center',
@@ -197,8 +197,8 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
197
197
  display: 'inline-block',
198
198
  width: '14px',
199
199
  height: '14px',
200
- border: '2px solid #e5e7eb',
201
- 'border-top-color': '#3b82f6',
200
+ border: '2px solid var(--mcp-ui-border, #e5e7eb)',
201
+ 'border-top-color': 'var(--mcp-ui-accent, #3b82f6)',
202
202
  'border-radius': '50%',
203
203
  animation: 'mcp-spin 0.6s linear infinite'
204
204
  }}
@@ -213,7 +213,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
213
213
  class="mcp-autocomplete-empty"
214
214
  style={{
215
215
  padding: '12px 16px',
216
- color: '#9ca3af',
216
+ color: 'var(--mcp-ui-fg-subtle, #9ca3af)',
217
217
  'font-size': '0.875rem',
218
218
  'text-align': 'center'
219
219
  }}
@@ -242,8 +242,8 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
242
242
  style={{
243
243
  padding: '8px 16px',
244
244
  cursor: isDisabled() ? 'not-allowed' : 'pointer',
245
- 'background-color': isSelected() ? '#eff6ff' : 'transparent',
246
- color: isDisabled() ? '#9ca3af' : '#374151',
245
+ 'background-color': isSelected() ? 'var(--mcp-ui-accent-soft, #eff6ff)' : 'transparent',
246
+ color: isDisabled() ? 'var(--mcp-ui-fg-subtle, #9ca3af)' : 'var(--mcp-ui-fg, #374151)',
247
247
  'font-size': '0.875rem',
248
248
  transition: 'background-color 150ms ease'
249
249
  }}
@@ -283,14 +283,14 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
283
283
  class="mcp-autocomplete-footer"
284
284
  style={{
285
285
  padding: '6px 12px',
286
- 'border-top': '1px solid #e5e7eb',
287
- 'background-color': '#f9fafb',
286
+ 'border-top': '1px solid var(--mcp-ui-border, #e5e7eb)',
287
+ 'background-color': 'var(--mcp-ui-surface-muted, #f9fafb)',
288
288
  'font-size': '0.75rem',
289
- color: '#6b7280'
289
+ color: 'var(--mcp-ui-fg-muted, #6b7280)'
290
290
  }}
291
291
  >
292
292
  <kbd style={{
293
- 'background-color': '#e5e7eb',
293
+ 'background-color': 'var(--mcp-ui-surface-hover, #e5e7eb)',
294
294
  padding: '1px 4px',
295
295
  'border-radius': '2px',
296
296
  'font-family': 'inherit',
@@ -298,7 +298,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
298
298
  }}>↑</kbd>
299
299
  {' '}
300
300
  <kbd style={{
301
- 'background-color': '#e5e7eb',
301
+ 'background-color': 'var(--mcp-ui-surface-hover, #e5e7eb)',
302
302
  padding: '1px 4px',
303
303
  'border-radius': '2px',
304
304
  'font-family': 'inherit',
@@ -306,7 +306,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
306
306
  }}>↓</kbd>
307
307
  {strings.autocompleteHintNavigate}
308
308
  <kbd style={{
309
- 'background-color': '#e5e7eb',
309
+ 'background-color': 'var(--mcp-ui-surface-hover, #e5e7eb)',
310
310
  padding: '1px 4px',
311
311
  'border-radius': '2px',
312
312
  'font-family': 'inherit',
@@ -314,7 +314,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
314
314
  }}>Enter</kbd>
315
315
  {strings.autocompleteHintSelect}
316
316
  <kbd style={{
317
- 'background-color': '#e5e7eb',
317
+ 'background-color': 'var(--mcp-ui-surface-hover, #e5e7eb)',
318
318
  padding: '1px 4px',
319
319
  'border-radius': '2px',
320
320
  'font-family': 'inherit',