@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
@@ -28,6 +28,8 @@ import {
28
28
  useMCPUIStrings,
29
29
  } from '../context/MCPUIStringsContext'
30
30
  import { useMCPUIConfig } from '../context/MCPUIConfigContext'
31
+ import { isMissingCellValue, stripUndefinedDebris } from '../utils/missing-value'
32
+ import { MissingValueLegend, MissingValueMark } from './MissingValue'
31
33
  import { shouldSetCredentialless } from '../utils/iframe-coep'
32
34
  import { IframeFallbackLink } from './IframeFallbackLink'
33
35
 
@@ -96,7 +98,7 @@ function CopyButton(props: { getText: () => string; title?: string; position?: '
96
98
  return (
97
99
  <button
98
100
  onClick={handleCopy}
99
- class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm z-10`}
101
+ class={`${positionClasses()} opacity-60 hover:opacity-100 px-2 py-1 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-full 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))] transition-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] z-10`}
100
102
  title={props.title || strings.copy}
101
103
  aria-label={props.title || strings.copy}
102
104
  data-mcp-ui-action="copy"
@@ -105,12 +107,12 @@ function CopyButton(props: { getText: () => string; title?: string; position?: '
105
107
  <Show
106
108
  when={!copied()}
107
109
  fallback={
108
- <svg class="w-3 h-3 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
110
+ <svg class="w-3 h-3 text-[color:var(--mcp-ui-success,var(--color-green-500))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
109
111
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
110
112
  </svg>
111
113
  }
112
114
  >
113
- <svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
115
+ <svg class="w-3 h-3 text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
114
116
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
115
117
  </svg>
116
118
  </Show>
@@ -257,8 +259,8 @@ function ChartRenderer(props: {
257
259
  // Guard: if data or datasets missing, show error instead of crashing Chart.js
258
260
  if (!params()?.data?.datasets) {
259
261
  return (
260
- <div class="p-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
261
- <p class="text-red-500 dark:text-red-400 text-sm">{strings.chartInvalidData}</p>
262
+ <div class="p-4 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
263
+ <p class="text-[color:var(--mcp-ui-danger,var(--color-red-500))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] text-sm">{strings.chartInvalidData}</p>
262
264
  </div>
263
265
  )
264
266
  }
@@ -336,7 +338,7 @@ function ChartRenderer(props: {
336
338
  <Show
337
339
  when={useNative()}
338
340
  fallback={
339
- <div class="relative w-full h-full min-h-[300px] bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
341
+ <div class="relative w-full h-full min-h-[300px] bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden">
340
342
  {/* P1.7 — Chart.js missing and quickchart fallback not allowed:
341
343
  show the chart data as a local table instead of an implicit
342
344
  call to quickchart.io. */}
@@ -347,6 +349,7 @@ function ChartRenderer(props: {
347
349
  caption={strings.chartQuickchartCaption}
348
350
  {...chartToDegradedTable(params() ?? {}, {
349
351
  series: strings.degradedSeries,
352
+ withUnit: strings.chartLabelWithUnit,
350
353
  })}
351
354
  />
352
355
  </div>
@@ -354,15 +357,15 @@ function ChartRenderer(props: {
354
357
 
355
358
  <Show when={isLoading()}>
356
359
  <div class="absolute inset-0 flex items-center justify-center">
357
- <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
360
+ <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-[color:var(--mcp-ui-accent-border,var(--color-blue-600))]" />
358
361
  </div>
359
362
  </Show>
360
363
 
361
364
  <Show when={error()}>
362
365
  <div class="absolute inset-0 flex items-center justify-center p-4">
363
366
  <div class="text-center">
364
- <p class="text-red-600 dark:text-red-400 text-sm font-medium">{strings.chartError}</p>
365
- <p class="text-gray-600 dark:text-gray-400 text-xs mt-1">{error()}</p>
367
+ <p class="text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] text-sm font-medium">{strings.chartError}</p>
368
+ <p class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-xs mt-1">{error()}</p>
366
369
  </div>
367
370
  </div>
368
371
  </Show>
@@ -370,7 +373,7 @@ function ChartRenderer(props: {
370
373
  <Show when={iframeUrl() && !error()}>
371
374
  <div class="w-full h-full p-4">
372
375
  <Show when={params()?.title}>
373
- <h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">
376
+ <h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] mb-3">
374
377
  {params()?.title}
375
378
  </h3>
376
379
  </Show>
@@ -411,7 +414,7 @@ function ChartRenderer(props: {
411
414
  * Smart cell value renderer that handles markdown links and other formats
412
415
  */
413
416
  /** Class list carried by every `<mark>` that `highlightQuery` injects. */
414
- const HIGHLIGHT_MARK_CLASS = 'bg-yellow-200 dark:bg-[#222F49] text-inherit rounded px-0.5'
417
+ const HIGHLIGHT_MARK_CLASS = 'bg-[color:var(--mcp-ui-highlight,var(--color-yellow-200))] dark:bg-[color:var(--mcp-ui-highlight,#222F49)] text-inherit rounded px-0.5'
415
418
 
416
419
  /**
417
420
  * Wrap matches of `query` in `<mark>` tags within an HTML string.
@@ -550,12 +553,12 @@ function defaultCitationChip(
550
553
  const safeDocName = encodeURIComponent(fileName || '')
551
554
  const label = fileName ? `${fileName} - ${pageNum}` : `${pageNum}`
552
555
  if (!verified) {
553
- return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-gray-500 line-through">[${label}]</span></span>`
556
+ return `<span class="citation-ref inline-flex items-center gap-0.5 align-middle opacity-60"><span class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] line-through">[${label}]</span></span>`
554
557
  }
555
558
  return [
556
559
  '<span class="citation-ref inline-flex items-center gap-0.5 align-middle">',
557
- `<span class="text-gray-500">[${label}]</span>`,
558
- '<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-gray-800 hover:bg-gray-700 border border-gray-600 hover:border-teal-500 rounded cursor-pointer transition-colors align-middle"',
560
+ `<span class="text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))]">[${label}]</span>`,
561
+ '<button class="inline-flex items-center ml-0.5 px-1 py-0.5 text-xs bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-700,#374151))] border border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] hover:border-[color:var(--mcp-ui-info-border,var(--color-teal-500))] rounded cursor-pointer transition-colors align-middle"',
559
562
  ` data-citation-page="${pageNum}"`,
560
563
  ` data-citation-doc="${safeDocName}"`,
561
564
  ' data-citation-verified="true"',
@@ -613,7 +616,7 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
613
616
  const label = value.name || value.label || value.title || value.url
614
617
  const sanitizedLabel = sanitizeHtml(String(label))
615
618
  const sanitizedUrl = sanitizeHtml(String(value.url))
616
- const anchor = `<a href="${sanitizedUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline">${sanitizedLabel}</a>`
619
+ const anchor = `<a href="${sanitizedUrl}" target="_blank" rel="noopener noreferrer" class="text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline">${sanitizedLabel}</a>`
617
620
  // Re-sanitize the composed anchor: sanitizing the bare `url` string
618
621
  // leaves quotes and `javascript:` untouched, so a crafted value could
619
622
  // otherwise break out of the attribute or smuggle a scriptable href.
@@ -639,9 +642,10 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
639
642
 
640
643
  // Clean up "undefined" patterns from backend data
641
644
  // Pattern 1: "Text – undefined" or "Text - undefined" → "Text"
642
- strValue = strValue.replace(/\s*[–-]\s*undefined\s*$/gi, '')
643
645
  // Pattern 2: "undefined – Text" or "undefined - Text" → "Text"
644
- strValue = strValue.replace(/^undefined\s*[–-]\s*/gi, '')
646
+ // (shared with `isMissingCellValue`, so the cells shown as `-` and the cells
647
+ // the table marks and explains as missing can never drift apart)
648
+ strValue = stripUndefinedDebris(strValue)
645
649
  // Pattern 3: standalone "undefined" → "-"
646
650
  if (strValue.trim().toLowerCase() === 'undefined') {
647
651
  return '-'
@@ -671,7 +675,7 @@ export function renderCellValue(value: any, citationCtx?: CitationCtx, mathRende
671
675
  // Replace all markdown links with HTML links
672
676
  const htmlValue = strValue.replace(
673
677
  /\[([^\]]+)\]\(([^)]+)\)/g,
674
- '<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:underline">$1</a>'
678
+ '<a href="$2" target="_blank" rel="noopener noreferrer" class="text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] hover:underline">$1</a>'
675
679
  )
676
680
  return sanitizeHtml(htmlValue, SANITIZE_PROFILES.cellLink)
677
681
  }
@@ -766,12 +770,18 @@ function TableRenderer(props: {
766
770
  const dir = sortDir()
767
771
  if (!key || !dir) return r
768
772
  const col = columns().find((c: any) => c.key === key)
769
- const isNum = col?.type === 'number' || (r.length > 0 && typeof r[0]?.[key] === 'number')
773
+ // v6.24.0 — numeric mode is decided from the first NON-missing value, so a
774
+ // missing first row no longer flips a numeric column to string order.
775
+ const firstObserved = r.find((row: any) => !isMissingCellValue(row?.[key]))?.[key]
776
+ const isNum = col?.type === 'number' || typeof firstObserved === 'number'
770
777
  return [...r].sort((a: any, b: any) => {
771
778
  const va = a[key], vb = b[key]
772
- if (va == null && vb == null) return 0
773
- if (va == null) return 1
774
- if (vb == null) return -1
779
+ // Missing values (null, undefined, '', 'undefined', '-') sort last in
780
+ // both directions — never as 0 and never first.
781
+ const missA = isMissingCellValue(va), missB = isMissingCellValue(vb)
782
+ if (missA && missB) return 0
783
+ if (missA) return 1
784
+ if (missB) return -1
775
785
  let cmp: number
776
786
  if (isNum) {
777
787
  cmp = (Number(va) || 0) - (Number(vb) || 0)
@@ -820,7 +830,7 @@ function TableRenderer(props: {
820
830
  return sortedRows().filter((row: any) =>
821
831
  cols.some((col: any) => {
822
832
  const val = row[col.key]
823
- if (val == null) return false
833
+ if (isMissingCellValue(val)) return false
824
834
  return normalize(String(val)).includes(q)
825
835
  })
826
836
  )
@@ -1015,16 +1025,29 @@ function TableRenderer(props: {
1015
1025
 
1016
1026
  const tableId = `table-${Math.random().toString(36).slice(2, 9)}`
1017
1027
 
1028
+ // v6.24.0 — whether ANY declared cell of ANY row is missing. Deliberately
1029
+ // computed on all rows (not the current page / filter) so the legend does not
1030
+ // flicker while paging, sorting or searching.
1031
+ const hasMissingCells = createMemo(() => {
1032
+ const cols = columns()
1033
+ return allRows().some((row: any) => cols.some((col: any) => isMissingCellValue(row?.[col.key])))
1034
+ })
1035
+
1018
1036
  // Standard table body (non-virtualized) — uses client pagination when active
1019
1037
  const StandardTableBody = () => (
1020
- <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
1038
+ <tbody class="bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] divide-y divide-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:divide-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]">
1021
1039
  <For each={clientVisibleRows().slice(0, DEFAULT_RESOURCE_LIMITS.maxTableRows)}>
1022
1040
  {(row: any, i) => (
1023
- <tr class={`hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${i() % 2 === 0 ? 'bg-white dark:bg-gray-800' : 'bg-gray-50/30 dark:bg-gray-800/50'}`}>
1041
+ <tr class={`hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,color-mix(in_oklab,var(--color-gray-700,#374151)_50%,transparent))] transition-colors ${i() % 2 === 0 ? 'bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]' : 'bg-[color:var(--mcp-ui-surface-muted,color-mix(in_oklab,var(--color-gray-50,#f9fafb)_30%,transparent))] dark:bg-[color:var(--mcp-ui-surface,color-mix(in_oklab,var(--color-gray-800,#1f2937)_50%,transparent))]'}`}>
1024
1042
  <For each={tableParams.columns}>
1025
1043
  {(column: any) => (
1026
- <td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
1027
- <SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
1044
+ <td class="px-6 py-4 text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-200,#e5e7eb))] whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
1045
+ <Show
1046
+ when={!isMissingCellValue(row[column.key])}
1047
+ fallback={<MissingValueMark />}
1048
+ >
1049
+ <SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
1050
+ </Show>
1028
1051
  </td>
1029
1052
  )}
1030
1053
  </For>
@@ -1046,7 +1069,7 @@ function TableRenderer(props: {
1046
1069
 
1047
1070
  return (
1048
1071
  <tbody
1049
- class="bg-white dark:bg-gray-800 relative"
1072
+ class="bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] relative"
1050
1073
  style={{ height: `${totalSize}px` }}
1051
1074
  >
1052
1075
  <For each={items}>
@@ -1054,7 +1077,7 @@ function TableRenderer(props: {
1054
1077
  const row = tableParams.rows[virtualRow.index]
1055
1078
  return (
1056
1079
  <tr
1057
- class={`hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors absolute left-0 right-0 ${virtualRow.index % 2 === 0 ? 'bg-white dark:bg-gray-800' : 'bg-gray-50/30 dark:bg-gray-800/50'}`}
1080
+ class={`hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,color-mix(in_oklab,var(--color-gray-700,#374151)_50%,transparent))] transition-colors absolute left-0 right-0 ${virtualRow.index % 2 === 0 ? 'bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]' : 'bg-[color:var(--mcp-ui-surface-muted,color-mix(in_oklab,var(--color-gray-50,#f9fafb)_30%,transparent))] dark:bg-[color:var(--mcp-ui-surface,color-mix(in_oklab,var(--color-gray-800,#1f2937)_50%,transparent))]'}`}
1058
1081
  style={{
1059
1082
  height: `${rowHeight}px`,
1060
1083
  transform: `translateY(${virtualRow.start}px)`
@@ -1062,8 +1085,13 @@ function TableRenderer(props: {
1062
1085
  >
1063
1086
  <For each={tableParams.columns}>
1064
1087
  {(column: any) => (
1065
- <td class="px-6 py-4 text-sm text-gray-700 dark:text-gray-200 whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
1066
- <SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
1088
+ <td class="px-6 py-4 text-sm text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-200,#e5e7eb))] whitespace-normal break-words leading-relaxed first:pl-6 last:pr-6">
1089
+ <Show
1090
+ when={!isMissingCellValue(row[column.key])}
1091
+ fallback={<MissingValueMark />}
1092
+ >
1093
+ <SafeHtml html={() => highlightQuery(renderCellValue(row[column.key], citationCtx, mathRenderer()), debouncedQuery())} />
1094
+ </Show>
1067
1095
  </td>
1068
1096
  )}
1069
1097
  </For>
@@ -1077,7 +1105,7 @@ function TableRenderer(props: {
1077
1105
 
1078
1106
  return (
1079
1107
  <ExpandableWrapper title={tableParams.title || strings.tableTitle} copyData={getTableCSV()} copyLabel={strings.tableCopyCsv} toolbarVariant={props.toolbarVariant} onExpandedChange={setSelfExpanded}>
1080
- <div class={`relative w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden group ${
1108
+ <div class={`relative w-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden group ${
1081
1109
  isExpanded() ? 'flex-1 min-h-0 flex flex-col' : 'h-full'
1082
1110
  } ${tableParams.className || ''}`}>
1083
1111
  <Show when={exportable} fallback={<CopyButton getText={getTableCSV} title={strings.tableCopyCsv} position="top-right" />}>
@@ -1085,13 +1113,13 @@ function TableRenderer(props: {
1085
1113
  <button
1086
1114
  ref={exportTriggerRef}
1087
1115
  onClick={() => setShowExportMenu(!showExportMenu())}
1088
- class="opacity-60 hover:opacity-100 px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"
1116
+ class="opacity-60 hover:opacity-100 px-2 py-1 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-full 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))] transition-all shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]"
1089
1117
  title={strings.tableExport}
1090
1118
  aria-label={strings.tableExport}
1091
1119
  aria-haspopup="menu"
1092
1120
  aria-expanded={showExportMenu()}
1093
1121
  >
1094
- <svg class="w-3 h-3 text-gray-500 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1122
+ <svg class="w-3 h-3 text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1095
1123
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
1096
1124
  </svg>
1097
1125
  </button>
@@ -1102,23 +1130,23 @@ function TableRenderer(props: {
1102
1130
  width={144}
1103
1131
  >
1104
1132
  <Show when={(exportFormats as string[]).includes('tsv')}>
1105
- <button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableCopyTsv}</button>
1133
+ <button onClick={() => handleExport('tsv')} class="w-full text-left px-3 py-1.5 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))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">{strings.tableCopyTsv}</button>
1106
1134
  </Show>
1107
1135
  <Show when={(exportFormats as string[]).includes('csv')}>
1108
- <button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadCsv}</button>
1136
+ <button onClick={() => handleExport('csv')} class="w-full text-left px-3 py-1.5 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))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">{strings.tableDownloadCsv}</button>
1109
1137
  </Show>
1110
1138
  <Show when={(exportFormats as string[]).includes('json')}>
1111
- <button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300">{strings.tableDownloadJson}</button>
1139
+ <button onClick={() => handleExport('json')} class="w-full text-left px-3 py-1.5 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))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]">{strings.tableDownloadJson}</button>
1112
1140
  </Show>
1113
1141
  </PortalDropdownMenu>
1114
1142
  </div>
1115
1143
  </Show>
1116
1144
  <div class={`p-4 ${isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''}`}>
1117
1145
  <Show when={tableParams.title}>
1118
- <h3 id={`${tableId}-title`} class="text-sm font-semibold text-gray-900 dark:text-white mb-3 flex-shrink-0">
1146
+ <h3 id={`${tableId}-title`} class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] mb-3 flex-shrink-0">
1119
1147
  {tableParams.title}
1120
1148
  <Show when={isVirtualizing()}>
1121
- <span class="ml-2 text-xs font-normal text-gray-400">
1149
+ <span class="ml-2 text-xs font-normal text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
1122
1150
  {formatMCPUIString(strings.tableVirtualizedRows, { count: tableParams.rows?.length ?? 0 })}
1123
1151
  </span>
1124
1152
  </Show>
@@ -1128,25 +1156,25 @@ function TableRenderer(props: {
1128
1156
  {/* Search input (v4.3.3) */}
1129
1157
  <Show when={isSearchable()}>
1130
1158
  <div class="relative mb-3">
1131
- <span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-sm">{'\uD83D\uDD0D'}</span>
1159
+ <span class="absolute left-3 top-1/2 -translate-y-1/2 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] pointer-events-none text-sm">{'\uD83D\uDD0D'}</span>
1132
1160
  <input
1133
1161
  type="text"
1134
1162
  value={searchQuery()}
1135
1163
  onInput={(e) => handleSearch(e.currentTarget.value)}
1136
1164
  placeholder={searchPlaceholder()}
1137
- class="w-full max-w-xs min-w-[200px] pl-8 pr-8 py-1.5 text-sm border border-gray-200 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none"
1165
+ class="w-full max-w-xs min-w-[200px] pl-8 pr-8 py-1.5 text-sm border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded-md bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] placeholder-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] focus:border-[color:var(--mcp-ui-focus,var(--color-blue-400))] focus:ring-1 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-400))] outline-none"
1138
1166
  />
1139
1167
  <Show when={searchQuery()}>
1140
1168
  <button
1141
1169
  type="button"
1142
1170
  onClick={() => { handleSearch(''); setSearchQuery(''); setDebouncedQuery('') }}
1143
- class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 text-sm"
1171
+ class="absolute right-2 top-1/2 -translate-y-1/2 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] text-sm"
1144
1172
  aria-label={strings.tableClearSearch}
1145
1173
  >&times;</button>
1146
1174
  </Show>
1147
1175
  </div>
1148
1176
  <Show when={debouncedQuery() && filteredRows().length !== sortedRows().length}>
1149
- <p class="text-xs text-gray-500 dark:text-gray-400 mb-2">
1177
+ <p class="text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] mb-2">
1150
1178
  {formatMCPUIString(
1151
1179
  filteredRows().length === 1 ? strings.tableSearchResultsOne : strings.tableSearchResultsMany,
1152
1180
  { count: filteredRows().length, total: sortedRows().length }
@@ -1185,16 +1213,16 @@ function TableRenderer(props: {
1185
1213
  tabindex="0"
1186
1214
  >
1187
1215
  <table
1188
- class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border-separate border-spacing-0"
1216
+ class="min-w-full divide-y divide-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:divide-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] border-separate border-spacing-0"
1189
1217
  aria-labelledby={tableParams.title ? `${tableId}-title` : undefined}
1190
1218
  >
1191
- <thead class="bg-gray-100 dark:bg-gray-900 sticky top-0 z-10">
1219
+ <thead class="bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] sticky top-0 z-10">
1192
1220
  <tr>
1193
1221
  <For each={tableParams.columns}>
1194
1222
  {(column: any) => (
1195
1223
  <th
1196
1224
  scope="col"
1197
- class="px-6 py-3 text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 first:pl-6 last:pr-6 bg-gray-100 dark:bg-gray-900 cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors"
1225
+ class="px-6 py-3 text-left text-xs font-semibold text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] uppercase tracking-wider border-b border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] first:pl-6 last:pr-6 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] cursor-pointer select-none hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-200,#e5e7eb))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))] transition-colors"
1198
1226
  style={column.width ? { width: column.width } : {}}
1199
1227
  on:click={() => handleSort(column.key)}
1200
1228
  title={formatMCPUIString(strings.sortBy, { column: column.label })}
@@ -1205,7 +1233,7 @@ function TableRenderer(props: {
1205
1233
  class="text-[10px] leading-none"
1206
1234
  classList={{
1207
1235
  'opacity-30': sortKey() !== column.key,
1208
- 'opacity-100 text-blue-600 dark:text-blue-400': sortKey() === column.key,
1236
+ 'opacity-100 text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))]': sortKey() === column.key,
1209
1237
  }}
1210
1238
  >
1211
1239
  {sortIndicator(column.key)}
@@ -1222,9 +1250,14 @@ function TableRenderer(props: {
1222
1250
  </table>
1223
1251
  </div>
1224
1252
 
1253
+ {/* Missing-value legend (v6.24.0) — outside the scroll region, before pagination */}
1254
+ <Show when={hasMissingCells()}>
1255
+ <MissingValueLegend class="mt-2 px-1" />
1256
+ </Show>
1257
+
1225
1258
  {/* Server-side pagination (legacy) */}
1226
1259
  <Show when={tableParams.pagination}>
1227
- <div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
1260
+ <div class="mt-3 flex items-center justify-between text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
1228
1261
  <span>
1229
1262
  {formatMCPUIString(strings.tableServerPageRange, {
1230
1263
  start: tableParams.pagination.currentPage * tableParams.pagination.pageSize + 1,
@@ -1240,14 +1273,14 @@ function TableRenderer(props: {
1240
1273
 
1241
1274
  {/* Client-side pagination (v4.3.7) */}
1242
1275
  <Show when={needsClientPagination()}>
1243
- <div class="mt-3 flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
1276
+ <div class="mt-3 flex items-center justify-between text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
1244
1277
  <span>
1245
1278
  {clientRangeStart()}&ndash;{clientRangeEnd()} / {filteredRows().length.toLocaleString(strings.locale)}
1246
1279
  </span>
1247
1280
  <div class="flex items-center gap-2">
1248
1281
  <button
1249
1282
  type="button"
1250
- class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
1283
+ class="px-2 py-1 rounded 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))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
1251
1284
  disabled={clientPage() === 0}
1252
1285
  onClick={() => setClientPage(p => p - 1)}
1253
1286
  aria-label={strings.paginationPrevious}
@@ -1258,7 +1291,7 @@ function TableRenderer(props: {
1258
1291
  <span aria-live="polite">{clientPage() + 1} / {clientTotalPages()}</span>
1259
1292
  <button
1260
1293
  type="button"
1261
- class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
1294
+ class="px-2 py-1 rounded 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))] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
1262
1295
  disabled={clientPage() >= clientTotalPages() - 1}
1263
1296
  onClick={() => setClientPage(p => p + 1)}
1264
1297
  aria-label={strings.paginationNext}
@@ -1269,7 +1302,7 @@ function TableRenderer(props: {
1269
1302
  {/* Page size selector — fullscreen only */}
1270
1303
  <Show when={isExpanded() && filteredRows().length > 10}>
1271
1304
  <select
1272
- class="ml-2 px-1 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300"
1305
+ class="ml-2 px-1 py-0.5 text-xs border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] rounded bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-700,#374151))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))]"
1273
1306
  value={clientPageSize()}
1274
1307
  onChange={(e) => handlePageSizeChange(Number(e.currentTarget.value))}
1275
1308
  aria-label={strings.paginationPageSize}
@@ -1279,7 +1312,7 @@ function TableRenderer(props: {
1279
1312
  {(opt) => <option value={opt.value}>{opt.label}</option>}
1280
1313
  </For>
1281
1314
  </select>
1282
- <span class="text-gray-400">{strings.perPageSuffix}</span>
1315
+ <span class="text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{strings.perPageSuffix}</span>
1283
1316
  </Show>
1284
1317
  </div>
1285
1318
  </div>
@@ -1306,17 +1339,17 @@ function MetricRenderer(props: { component: UIComponent }) {
1306
1339
  }
1307
1340
 
1308
1341
  return (
1309
- <div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
1342
+ <div class="relative w-full h-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] p-4 group">
1310
1343
  <CopyButton getText={getMetricText} title={strings.copyMetric} position="top-right" />
1311
1344
  <div class="flex flex-col h-full justify-between">
1312
1345
  <div>
1313
- <p class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
1346
+ <p class="text-xs font-medium text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] uppercase tracking-wide">
1314
1347
  {metricParams.title}
1315
1348
  </p>
1316
1349
  <div class="mt-2 flex items-baseline">
1317
- <p class="text-2xl font-semibold text-gray-900 dark:text-white">{metricParams.value}</p>
1350
+ <p class="text-2xl font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{metricParams.value}</p>
1318
1351
  <Show when={metricParams.unit}>
1319
- <span class="ml-2 text-sm font-medium text-gray-500 dark:text-gray-400">
1352
+ <span class="ml-2 text-sm font-medium text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">
1320
1353
  {metricParams.unit}
1321
1354
  </span>
1322
1355
  </Show>
@@ -1327,10 +1360,10 @@ function MetricRenderer(props: { component: UIComponent }) {
1327
1360
  <div class="mt-3 flex items-center">
1328
1361
  <span
1329
1362
  class={`text-sm font-medium ${metricParams.trend.direction === 'up'
1330
- ? 'text-green-600 dark:text-green-400'
1363
+ ? 'text-[color:var(--mcp-ui-success,var(--color-green-600))] dark:text-[color:var(--mcp-ui-success,var(--color-green-400))]'
1331
1364
  : metricParams.trend.direction === 'down'
1332
- ? 'text-red-600 dark:text-red-400'
1333
- : 'text-gray-600 dark:text-gray-400'
1365
+ ? 'text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))]'
1366
+ : 'text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]'
1334
1367
  }`}
1335
1368
  >
1336
1369
  {metricParams.trend.direction === 'up'
@@ -1344,7 +1377,7 @@ function MetricRenderer(props: { component: UIComponent }) {
1344
1377
  </Show>
1345
1378
 
1346
1379
  <Show when={metricParams.subtitle}>
1347
- <p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{metricParams.subtitle}</p>
1380
+ <p class="mt-2 text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))]">{metricParams.subtitle}</p>
1348
1381
  </Show>
1349
1382
  </div>
1350
1383
  </div>
@@ -1435,7 +1468,7 @@ function TextRenderer(props: { component: UIComponent }) {
1435
1468
  <Show
1436
1469
  when={imageData()}
1437
1470
  fallback={
1438
- <div class="relative w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 group">
1471
+ <div class="relative w-full h-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] p-4 group">
1439
1472
  <CopyButton getText={getTextContent} title={strings.copyText} position="top-right" />
1440
1473
  <SafeHtml
1441
1474
  class={`prose prose-sm dark:prose-invert max-w-none ${textParams.className || ''}`}
@@ -1450,13 +1483,13 @@ function TextRenderer(props: { component: UIComponent }) {
1450
1483
  <img
1451
1484
  src={data().imageUrl}
1452
1485
  alt={data().alt}
1453
- class="max-w-full max-h-[400px] object-contain rounded shadow-sm hover:opacity-90 transition-opacity"
1486
+ class="max-w-full max-h-[400px] object-contain rounded shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] hover:opacity-90 transition-opacity"
1454
1487
  loading="lazy"
1455
1488
  />
1456
1489
  )
1457
1490
  return (
1458
- <div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
1459
- <div class="flex-1 flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 min-h-[200px]">
1491
+ <div class="w-full h-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden flex flex-col">
1492
+ <div class="flex-1 flex items-center justify-center p-4 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] min-h-[200px]">
1460
1493
  {/* The anchor only exists when the link URL passed `safeUrl` — a
1461
1494
  `javascript:` link degrades to a plain, unlinked image. */}
1462
1495
  <Show when={data().linkUrl} fallback={image()}>
@@ -1467,8 +1500,8 @@ function TextRenderer(props: { component: UIComponent }) {
1467
1500
  )}
1468
1501
  </Show>
1469
1502
  </div>
1470
- <div class="p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
1471
- <p class="text-sm text-gray-600 dark:text-gray-400 text-center italic">{data().credit}</p>
1503
+ <div class="p-3 border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]">
1504
+ <p class="text-sm text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-center italic">{data().credit}</p>
1472
1505
  </div>
1473
1506
  </div>
1474
1507
  )
@@ -1489,10 +1522,10 @@ function IframeRenderer(props: { component: UIComponent }) {
1489
1522
  // the `credentialless` decision.
1490
1523
  const trustOptions = () => ({ customTrustedDomains: config.customTrustedIframeDomains })
1491
1524
  return (
1492
- <div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
1525
+ <div class="w-full h-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden flex flex-col">
1493
1526
  <Show when={params.title}>
1494
- <div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
1495
- <h3 class="text-sm font-semibold text-gray-900 dark:text-white">{params.title}</h3>
1527
+ <div class="px-4 py-2 border-b border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))]">
1528
+ <h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))]">{params.title}</h3>
1496
1529
  </div>
1497
1530
  </Show>
1498
1531
  <iframe
@@ -1515,7 +1548,7 @@ function IframeRenderer(props: { component: UIComponent }) {
1515
1548
  }
1516
1549
  loading="lazy"
1517
1550
  />
1518
- <IframeFallbackLink url={params.url} class="border-t border-gray-200 dark:border-gray-700" />
1551
+ <IframeFallbackLink url={params.url} class="border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))]" />
1519
1552
  </div>
1520
1553
  )
1521
1554
  }
@@ -1528,13 +1561,13 @@ function ImageRenderer(props: { component: UIComponent }) {
1528
1561
  const params = props.component.params as any
1529
1562
 
1530
1563
  return (
1531
- <figure class={`w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col ${params.className || ''}`}>
1532
- <div class="flex-1 flex items-center justify-center p-4 bg-gray-50 dark:bg-gray-900 min-h-[200px]">
1564
+ <figure class={`w-full h-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden flex flex-col ${params.className || ''}`}>
1565
+ <div class="flex-1 flex items-center justify-center p-4 bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))] min-h-[200px]">
1533
1566
  <a
1534
1567
  href={safeUrl(params.url)}
1535
1568
  target="_blank"
1536
1569
  rel="noopener noreferrer"
1537
- class="cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded"
1570
+ class="cursor-zoom-in focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] rounded"
1538
1571
  aria-label={formatMCPUIString(strings.imageViewFullSize, {
1539
1572
  alt: params.alt || strings.imageAltFallback,
1540
1573
  })}
@@ -1542,14 +1575,14 @@ function ImageRenderer(props: { component: UIComponent }) {
1542
1575
  <img
1543
1576
  src={safeUrl(params.url, { allowDataImage: true })}
1544
1577
  alt={params.alt || strings.imageAlt}
1545
- class="max-w-full max-h-[500px] object-contain rounded shadow-sm hover:opacity-95 transition-opacity"
1578
+ class="max-w-full max-h-[500px] object-contain rounded shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] hover:opacity-95 transition-opacity"
1546
1579
  loading="lazy"
1547
1580
  />
1548
1581
  </a>
1549
1582
  </div>
1550
1583
  <Show when={params.caption}>
1551
- <figcaption class="p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
1552
- <p class="text-sm text-gray-600 dark:text-gray-400 text-center">{params.caption}</p>
1584
+ <figcaption class="p-3 border-t border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))]">
1585
+ <p class="text-sm text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] text-center">{params.caption}</p>
1553
1586
  </figcaption>
1554
1587
  </Show>
1555
1588
  </figure>
@@ -1572,10 +1605,10 @@ function LinkRenderer(props: { component: UIComponent }) {
1572
1605
  label: params.label || strings.linkLabel,
1573
1606
  description: params.description || params.url,
1574
1607
  })}
1575
- class={`flex items-center gap-3 p-4 bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors group h-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${params.className || ''}`}
1608
+ class={`flex items-center gap-3 p-4 bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] 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))] transition-colors group h-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))] ${params.className || ''}`}
1576
1609
  onClick={(e) => e.stopPropagation()}
1577
1610
  >
1578
- <div class="p-2 bg-blue-50 dark:bg-blue-900/30 rounded-full text-blue-600 dark:text-blue-400 group-hover:bg-blue-100 dark:group-hover:bg-blue-900/50 shrink-0 transition-colors" aria-hidden="true">
1611
+ <div class="p-2 bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-50))] dark:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_30%,transparent))] rounded-full text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] group-hover:bg-[color:var(--mcp-ui-accent-soft,var(--color-blue-100))] dark:group-hover:bg-[color:var(--mcp-ui-accent-soft,color-mix(in_oklab,var(--color-blue-900)_50%,transparent))] shrink-0 transition-colors" aria-hidden="true">
1579
1612
  <svg
1580
1613
  xmlns="http://www.w3.org/2000/svg"
1581
1614
  class="w-5 h-5"
@@ -1591,16 +1624,16 @@ function LinkRenderer(props: { component: UIComponent }) {
1591
1624
  </svg>
1592
1625
  </div>
1593
1626
  <div class="flex-1 min-w-0">
1594
- <h4 class="text-sm font-medium text-gray-900 dark:text-white truncate group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
1627
+ <h4 class="text-sm font-medium text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] truncate group-hover:text-[color:var(--mcp-ui-accent,var(--color-blue-600))] dark:group-hover:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] transition-colors">
1595
1628
  {params.label || params.url}
1596
1629
  </h4>
1597
1630
  <Show when={params.description}>
1598
- <p class="text-xs text-gray-500 dark:text-gray-400 truncate">{params.description}</p>
1631
+ <p class="text-xs text-[color:var(--mcp-ui-fg-muted,var(--color-gray-500,#6b7280))] dark:text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] truncate">{params.description}</p>
1599
1632
  </Show>
1600
1633
  </div>
1601
1634
  <svg
1602
1635
  xmlns="http://www.w3.org/2000/svg"
1603
- class="w-4 h-4 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 shrink-0 transition-colors"
1636
+ class="w-4 h-4 text-[color:var(--mcp-ui-fg-subtle,var(--color-gray-400,#9ca3af))] group-hover:text-[color:var(--mcp-ui-fg-muted,var(--color-gray-600,#4b5563))] dark:group-hover:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] shrink-0 transition-colors"
1604
1637
  viewBox="0 0 24 24"
1605
1638
  fill="none"
1606
1639
  stroke="currentColor"
@@ -1725,7 +1758,7 @@ function ComponentRenderer(props: {
1725
1758
  if (mode === 'inline-warn') {
1726
1759
  return (
1727
1760
  <div
1728
- class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 text-xs text-yellow-800 dark:text-yellow-200"
1761
+ class="inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-[color:var(--mcp-ui-warning-soft,var(--color-yellow-50))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-yellow-900)_20%,transparent))] border border-[color:var(--mcp-ui-warning-border,var(--color-yellow-200))] dark:border-[color:var(--mcp-ui-warning-border,var(--color-yellow-800))] text-xs text-[color:var(--mcp-ui-warning,var(--color-yellow-800))] dark:text-[color:var(--mcp-ui-warning,var(--color-yellow-200))]"
1729
1762
  role="alert"
1730
1763
  aria-label={strings.validationWarning}
1731
1764
  title={firstError}
@@ -1752,9 +1785,9 @@ function ComponentRenderer(props: {
1752
1785
 
1753
1786
  // mode === 'block' (default, pre-v5.4.0 behavior)
1754
1787
  return (
1755
- <div class="w-full h-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
1756
- <p class="text-sm font-medium text-red-900 dark:text-red-100">{strings.validationError}</p>
1757
- <p class="text-xs text-red-700 dark:text-red-300 mt-1">
1788
+ <div class="w-full h-full bg-[color:var(--mcp-ui-danger-soft,var(--color-red-50))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_20%,transparent))] border border-[color:var(--mcp-ui-danger-border,var(--color-red-200))] dark:border-[color:var(--mcp-ui-danger-border,var(--color-red-800))] rounded-lg p-4">
1789
+ <p class="text-sm font-medium text-[color:var(--mcp-ui-danger,var(--color-red-900))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-100))]">{strings.validationError}</p>
1790
+ <p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))] mt-1">
1758
1791
  {firstError}
1759
1792
  </p>
1760
1793
  </div>
@@ -1859,7 +1892,7 @@ function UnsupportedComponentFallback(props: { component: UIComponent }) {
1859
1892
  return (
1860
1893
  <div
1861
1894
  role="alert"
1862
- class="w-full rounded-lg border border-amber-300 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-700/50 dark:bg-amber-900/20 dark:text-amber-200"
1895
+ class="w-full rounded-lg border border-[color:var(--mcp-ui-warning-border,var(--color-amber-300))] bg-[color:var(--mcp-ui-warning-soft,var(--color-amber-50))] px-3 py-2 text-sm text-[color:var(--mcp-ui-warning,var(--color-amber-800))] dark:border-[color:var(--mcp-ui-warning-border,color-mix(in_oklab,var(--color-amber-700)_50%,transparent))] dark:bg-[color:var(--mcp-ui-warning-soft,color-mix(in_oklab,var(--color-amber-900)_20%,transparent))] dark:text-[color:var(--mcp-ui-warning,var(--color-amber-200))]"
1863
1896
  >
1864
1897
  {strings.unsupportedComponentType} <code class="font-mono">{props.component.type}</code>
1865
1898
  </div>
@@ -1923,10 +1956,10 @@ function ActionRenderer(props: { component: UIComponent }) {
1923
1956
  target={safeUrl(params.url) ? '_blank' : undefined}
1924
1957
  rel="noopener noreferrer"
1925
1958
  aria-label={params.ariaLabel || params.label}
1926
- class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
1927
- ${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700' :
1928
- params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
1929
- 'text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300'}
1959
+ class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]
1960
+ ${params.variant === 'primary' ? '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))]' :
1961
+ params.variant === 'outline' ? 'border border-[color:var(--mcp-ui-border,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]' :
1962
+ 'text-[color:var(--mcp-ui-accent,var(--color-blue-600))] hover:text-[color:var(--mcp-ui-accent,var(--color-blue-800))] dark:text-[color:var(--mcp-ui-accent,var(--color-blue-400))] dark:hover:text-[color:var(--mcp-ui-accent,var(--color-blue-300))]'}
1930
1963
  ${params.className || ''}`}
1931
1964
  onClick={handleClick}
1932
1965
  >
@@ -1944,12 +1977,12 @@ function ActionRenderer(props: { component: UIComponent }) {
1944
1977
  disabled={isDisabled()}
1945
1978
  aria-busy={isExecuting() && isExecutable()}
1946
1979
  aria-label={params.ariaLabel || params.label}
1947
- class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500
1948
- ${params.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm' :
1949
- params.variant === 'secondary' ? 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600' :
1950
- params.variant === 'outline' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800' :
1951
- params.variant === 'danger' ? 'bg-red-600 text-white hover:bg-red-700' :
1952
- 'bg-transparent text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'}
1980
+ class={`inline-flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--mcp-ui-focus,var(--color-blue-500))]
1981
+ ${params.variant === 'primary' ? '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))] shadow-[var(--mcp-ui-shadow,var(--shadow-sm))]' :
1982
+ params.variant === 'secondary' ? 'bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-100,#f3f4f6))] text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-200,#e5e7eb))] 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))]' :
1983
+ params.variant === 'outline' ? 'border border-[color:var(--mcp-ui-border,var(--color-gray-300,#d1d5db))] text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-50,#f9fafb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-600,#4b5563))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]' :
1984
+ params.variant === 'danger' ? '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))]' :
1985
+ 'bg-transparent text-[color:var(--mcp-ui-fg,var(--color-gray-700,#374151))] hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-100,#f3f4f6))] dark:text-[color:var(--mcp-ui-fg,var(--color-gray-300,#d1d5db))] dark:hover:bg-[color:var(--mcp-ui-surface-hover,var(--color-gray-800,#1f2937))]'}
1953
1986
  ${isDisabled() ? 'opacity-50 cursor-not-allowed' : ''}
1954
1987
  ${params.size === 'sm' ? 'px-3 py-1.5 text-xs' : params.size === 'lg' ? 'px-6 py-3 text-base' : ''}
1955
1988
  ${params.className || ''}`}
@@ -1984,32 +2017,32 @@ function ErrorCardRenderer(props: { error: any }) {
1984
2017
  }
1985
2018
 
1986
2019
  return (
1987
- <div class="relative w-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 group">
2020
+ <div class="relative w-full bg-[color:var(--mcp-ui-danger-soft,var(--color-red-50))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_20%,transparent))] border border-[color:var(--mcp-ui-danger-border,var(--color-red-200))] dark:border-[color:var(--mcp-ui-danger-border,var(--color-red-800))] rounded-lg p-4 group">
1988
2021
  <CopyButton getText={getErrorText} title={strings.copyErrorDetails} position="top-right" />
1989
2022
  <div class="flex items-start gap-3">
1990
- <div class="p-2 bg-red-100 dark:bg-red-900/40 rounded-full shrink-0">
1991
- <svg class="w-5 h-5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
2023
+ <div class="p-2 bg-[color:var(--mcp-ui-danger-soft,var(--color-red-100))] dark:bg-[color:var(--mcp-ui-danger-soft,color-mix(in_oklab,var(--color-red-900)_40%,transparent))] rounded-full shrink-0">
2024
+ <svg class="w-5 h-5 text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
1992
2025
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
1993
2026
  </svg>
1994
2027
  </div>
1995
2028
  <div class="flex-1 min-w-0">
1996
- <h4 class="text-sm font-semibold text-red-800 dark:text-red-200">
2029
+ <h4 class="text-sm font-semibold text-[color:var(--mcp-ui-danger,var(--color-red-800))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-200))]">
1997
2030
  {formatMCPUIString(strings.toolErrorTitle, {
1998
2031
  tool: props.error.tool || strings.errorUnknownToolName,
1999
2032
  })}
2000
2033
  </h4>
2001
- <p class="text-sm text-red-700 dark:text-red-300 mt-1">
2034
+ <p class="text-sm text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))] mt-1">
2002
2035
  {props.error.message || strings.errorToolExecution}
2003
2036
  </p>
2004
2037
  <Show when={props.error.type}>
2005
- <p class="text-xs text-red-600 dark:text-red-400 mt-1">
2038
+ <p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] mt-1">
2006
2039
  {formatMCPUIString(strings.errorTypeLabel, { type: props.error.type })}
2007
2040
  </p>
2008
2041
  </Show>
2009
2042
  <Show when={props.error.suggestions?.length}>
2010
2043
  <div class="mt-3">
2011
- <p class="text-xs font-medium text-red-700 dark:text-red-300">{strings.errorSuggestions}</p>
2012
- <ul class="mt-1 text-xs text-red-600 dark:text-red-400 list-disc list-inside">
2044
+ <p class="text-xs font-medium text-[color:var(--mcp-ui-danger,var(--color-red-700))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-300))]">{strings.errorSuggestions}</p>
2045
+ <ul class="mt-1 text-xs text-[color:var(--mcp-ui-danger,var(--color-red-600))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-400))] list-disc list-inside">
2013
2046
  <For each={props.error.suggestions}>
2014
2047
  {(suggestion: string) => <li>{suggestion}</li>}
2015
2048
  </For>
@@ -2017,7 +2050,7 @@ function ErrorCardRenderer(props: { error: any }) {
2017
2050
  </div>
2018
2051
  </Show>
2019
2052
  <Show when={mounted() && props.error.timestamp}>
2020
- <p class="text-xs text-red-500 dark:text-red-500 mt-2">
2053
+ <p class="text-xs text-[color:var(--mcp-ui-danger,var(--color-red-500))] dark:text-[color:var(--mcp-ui-danger,var(--color-red-500))] mt-2">
2021
2054
  {new Date(props.error.timestamp).toLocaleString(strings.locale)}
2022
2055
  </p>
2023
2056
  </Show>
@@ -2064,10 +2097,10 @@ function UIResourceHtmlRenderer(props: { resource: any }) {
2064
2097
  }
2065
2098
 
2066
2099
  return (
2067
- <div class="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden">
2100
+ <div class="w-full bg-[color:var(--mcp-ui-surface,var(--color-white,#fff))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-800,#1f2937))] rounded-lg shadow-[var(--mcp-ui-shadow,var(--shadow-sm))] border border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] overflow-hidden">
2068
2101
  <Show when={props.resource.metadata?.title || props.resource.uri}>
2069
- <div class="px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
2070
- <h3 class="text-sm font-semibold text-gray-900 dark:text-white capitalize">
2102
+ <div class="px-4 py-2 border-b border-[color:var(--mcp-ui-border,var(--color-gray-200,#e5e7eb))] dark:border-[color:var(--mcp-ui-border,var(--color-gray-700,#374151))] bg-[color:var(--mcp-ui-surface-muted,var(--color-gray-50,#f9fafb))] dark:bg-[color:var(--mcp-ui-surface,var(--color-gray-900,#111827))]">
2103
+ <h3 class="text-sm font-semibold text-[color:var(--mcp-ui-fg,var(--color-gray-900,#111827))] dark:text-[color:var(--mcp-ui-fg,var(--color-white,#fff))] capitalize">
2071
2104
  {resourceTitle()}
2072
2105
  </h3>
2073
2106
  </div>
@@ -2198,7 +2231,7 @@ export const UIResourceRenderer: Component<UIResourceRendererProps> = (props) =>
2198
2231
  return (
2199
2232
  <RenderProvider renderComponent={renderComponent}>
2200
2233
  <div
2201
- class={`w-full ${props.class || ''}`}
2234
+ class={`mcp-ui-root [background-color:var(--mcp-ui-bg)] w-full ${props.class || ''}`}
2202
2235
  {...(isLayoutContent
2203
2236
  ? { 'data-mcp-ui-layout-id': outerKey() }
2204
2237
  : { 'data-mcp-ui-component-id': outerKey() })}