Skip to content

Enhance create-tool-ui documentation and address TypeScript inference#477

Merged
frontegg-david merged 16 commits into
mainfrom
feat/tool-ui-skill-suite
May 28, 2026
Merged

Enhance create-tool-ui documentation and address TypeScript inference#477
frontegg-david merged 16 commits into
mainfrom
feat/tool-ui-skill-suite

Conversation

@frontegg-david
Copy link
Copy Markdown
Contributor

@frontegg-david frontegg-david commented May 28, 2026

Fixes:

Summary by CodeRabbit

  • Documentation
    • Expanded create-tool-ui guides, examples and references; clarified template typing, helpers (formatCurrency, uniqueId, jsonEmbed), ui.displayMode, ui.servingMode, FileSource/serving guidance, platform considerations and troubleshooting.
  • New Features
    • Hosts now surface widget-level CSP/permissions metadata; UI configs gain resourceMode and inlineReact hints to control React bundling/serving behavior.
  • Bug Fixes / UX
    • Friendlier errors for missing widget files and guidance on widget naming/tsconfig excludes.
  • Tests
    • Expanded coverage for widget bundling, resolution, availability probes and CLI tsconfig handling.

Review Change Stack

…rface (#440)

Introduces a comprehensive `create-tool-ui` reference under
`frontmcp-development` documenting `@Tool({ ui })` — the entry point for the
MCP UI / MCP Apps feature (SEP-1865). Adds three worked examples
(basic-html-template, widget-with-csp-and-bridge, file-source-tsx-widget),
wires the reference into the routing table, reading order, manifest, and
related-skills list. Also corrects long-standing API drift in
`docs/frontmcp/guides/building-tool-ui.mdx` (`servingMode`, `displayMode`,
template helpers) and replaces the broken Platform Detection section.

Closes #440
…441)

Adds a "Tool UI (Interactive Widgets)" section to
`frontmcp-development/references/create-tool.md` covering the `ui` option,
the four template formats (with FileSource as the recommended pattern),
common `ToolUIConfig` fields with defaults, and a cross-link to the
dedicated `create-tool-ui` reference. Updates the `'resource'`
output-schema bullet to clarify that media-type resources and the `ui`
widget pipeline are distinct paths. Adds `create-tool-ui` to the
Related skills list.

Closes #441
…442)

Documents the TypeScript inference gap on inline `ui.template: (ctx) => …`:
the `template` field is a union of `TemplateBuilderFn | string | ((props:
any) => any) | FileSource`, which prevents TypeScript from picking a single
contextual type for the arrow parameter and emits TS7006 under `strict` /
`noImplicitAny`. Adds a callout in four places (create-tool-ui.md and
create-tool.md in the skills catalog; tools.mdx and building-tool-ui.mdx
in the docs site) explaining the root cause and the two recommended
escapes: annotate `ctx: TemplateContext<In, Out>` (re-exported from
`@frontmcp/sdk`), or move the widget to its own file and use the
`FileSource` form, which the catalog already recommends.

Closes #442
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 901bb801-4282-4c2f-af1a-c00b43c5b638

📥 Commits

Reviewing files that changed from the base of the PR and between aadd4a8 and e587d91.

📒 Files selected for processing (3)
  • libs/sdk/src/tool/ui/index.ts
  • libs/uipack/src/component/__tests__/ui-availability.spec.ts
  • libs/uipack/src/component/index.ts
✅ Files skipped from review due to trivial changes (2)
  • libs/uipack/src/component/index.ts
  • libs/sdk/src/tool/ui/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/uipack/src/component/tests/ui-availability.spec.ts

📝 Walkthrough

Walkthrough

Adds Tool UI reference/guides/examples; updates skills manifest and CLI tsconfig to exclude widget files; extends uipack with ui-availability and inline-React bundling, FileSource path anchoring and clearer ENOENT errors; and records/emits per-widget UI resource _meta (CSP/permissions).

Changes

Tool UI docs & examples

Layer / File(s) Summary
Reference guide & guide updates
libs/skills/catalog/frontmcp-development/references/create-tool-ui.md, docs/frontmcp/guides/building-tool-ui.mdx, docs/frontmcp/servers/tools.mdx
Adds create-tool-ui reference and guide edits: typed TemplateContext usage, template formats (function/HTML/React/FileSource), explicit helper APIs (formatCurrency, uniqueId, jsonEmbed, escapeHtml), ui.displayMode/ui.servingMode docs, CSP/contentSecurity and Claude host caveats, and troubleshooting.
Examples
libs/skills/catalog/frontmcp-development/examples/create-tool-ui/*
Adds three examples: basic HTML template, interactive CSP+bridge widget, and FileSource .tsx React chart widget demonstrating import.meta.url anchoring, externals/CDN usage, resourceMode: 'inline', and hydrate: false.
Skill manifest & catalog
libs/skills/catalog/frontmcp-development/SKILL.md, libs/skills/catalog/skills-manifest.json
Insert create-tool-ui into scenario routing, recommended reading order, examples list, related skills, and add manifest entry with example metadata.

CLI tsconfig & tests

Layer / File(s) Summary
TSConfig widget exclude handling
libs/cli/src/core/tsconfig.ts, libs/cli/src/core/__tests__/tsconfig.spec.ts
Add WIDGET_FILE_PATTERNS (**/*.widget.tsx, **/*.widget.jsx), include in RECOMMENDED_TSCONFIG.exclude, implement ensureWidgetExcludes() (non-mutating merge + added list), apply during runInit(), and add tests covering add/dedupe/filter behaviors.

uipack runtime, bundling, loader & tests

Layer / File(s) Summary
uipack: ui availability & bundler preflight
libs/uipack/src/component/ui-availability.ts, libs/uipack/src/component/transpiler.ts
Add isFrontmcpUiResolvable(...); bundleFileSource preflight checks resolvability and throws actionable install guidance when @frontmcp/ui missing; introduce BundleFileSourceOptions.bundleReact to control externals and adjust esbuild externals accordingly.
FileSource resolution & loader errors
libs/uipack/src/component/loader.ts, libs/uipack/src/component/__tests__/loader.spec.ts
Resolve relative .tsx/.jsx FileSource paths against process.cwd(); wrap reads to special-case ENOENT with helpful hint and import.meta.url anchoring suggestion; forward inlineReact/bundleReact flags to bundler and add tests.
Adapter rendering/resourceMode
libs/uipack/src/adapters/template-renderer.ts, libs/uipack/src/adapters/__tests__/template-renderer.spec.ts
Add `resourceMode?: 'cdn'
UIConfig types/docs
libs/uipack/src/component/types.ts, libs/uipack/src/types/ui-config.ts
Add UIConfig.inlineReact?: boolean and expand docs for resourceMode/servingMode/CSP with Claude-specific caveats and externals guidance.
Public types
libs/uipack/src/component/index.ts
Re-export BundleFileSourceOptions for consumers.

SDK UI resource metadata

Layer / File(s) Summary
Per-widget _meta emission & registry
libs/sdk/src/tool/ui/ui-shared.ts, libs/sdk/src/tool/ui/ui-resource.handler.ts, libs/sdk/src/tool/ui/__tests__/ui-resource-meta.spec.ts
Add UIResourceMeta and ToolUIRegistry.getResourceMeta(), sync csp/permissions at compile/register time, attach normalized per-widget _meta (nested and slash keys; snake_case CSP fields) to ui://widget/{tool}.html resource responses for cached templates and placeholder fallback; tests validate metadata shapes and clearing on recompile.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #455: This PR implements per-widget UI resource metadata and attaches normalized _meta.ui.csp to widget resources, addressing the behavior in #455.
  • #441: Adds the missing Tool UI documentation/skill entries requested by the issue.

Possibly related PRs

"I’m a rabbit with a keyboard tap—
Docs, widgets, hints, and a tidy map.
CSP guards, bridges, and charts that gleam,
Tsconfig tucked, and clearer errors beam.
Hooray for widgets — hop, test, and team!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 65.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enhance create-tool-ui documentation and address TypeScript inference' clearly and directly describes the primary changes in the PR, which include expanded Tool UI documentation and TypeScript improvements.
Linked Issues check ✅ Passed The PR addresses all coding-related objectives from linked issue #440: it adds comprehensive create-tool-ui skill documentation, routing-table entries, examples for four template formats, runtime API docs, CSP/host behavior guidance, and troubleshooting. It also addresses TypeScript inference improvements (#442), FileSource path resolution (#444), widget file exclusion (#445), and resource-level CSP metadata (#455).
Out of Scope Changes check ✅ Passed All changes are directly within scope: documentation for Tool UI feature, code examples for create-tool-ui skill, TypeScript/build configuration improvements for widget files, uipack/SDK refactoring to support FileSource rendering and CSP metadata, and comprehensive tests covering all new functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tool-ui-skill-suite

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/frontmcp/guides/building-tool-ui.mdx (2)

227-230: 💤 Low value

Add language identifier to fenced code block.

The fenced code block starting at line 227 is missing a language identifier, which triggers a markdownlint warning (MD040). Add a language identifier (e.g., ```text) after the opening fence.

As per coding guidelines: markdownlint-cli2 reported "Fenced code blocks should have a language specified (MD040, fenced-code-language)"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/frontmcp/guides/building-tool-ui.mdx` around lines 227 - 230, The fenced
code block containing the UI template using form, input, and button lacks a
language identifier which causes MD040; edit the block that begins with form(`
and add a language tag immediately after the opening fence (for example ```text
or ```jsx) so the code fence is ```text (or another appropriate language)
followed by the existing template; ensure the opening fence and closing fence
remain intact.

199-209: 💤 Low value

Add language identifier to fenced code block.

The fenced code block starting at line 199 is missing a language identifier, which triggers a markdownlint warning (MD040). Add a language identifier (e.g., ```text or ```uri) after the opening fence.

As per coding guidelines: markdownlint-cli2 reported "Fenced code blocks should have a language specified (MD040, fenced-code-language)"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/frontmcp/guides/building-tool-ui.mdx` around lines 199 - 209, The fenced
code block containing the descriptionList(...) example is missing a language
identifier; update the opening triple-backtick for that block (the one wrapping
the descriptionList call that sets layout:'grid' and className:'mt-4') to
include a language token (e.g., ```text or ```uri) so markdownlint MD040 is
satisfied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/frontmcp/guides/building-tool-ui.mdx`:
- Line 364: The link to the create-tool-ui skill in building-tool-ui.mdx is
broken (points to https://docs.agentfront.dev/frontmcp/skills/create-tool-ui);
fix it by either adding a corresponding MDX page for the create-tool-ui skill
under the frontmcp docs or by updating the link in building-tool-ui.mdx to the
actual skill reference location in the repo (the create-tool-ui skill
reference), ensuring the anchor text remains `create-tool-ui` and the URL
resolves correctly.

---

Nitpick comments:
In `@docs/frontmcp/guides/building-tool-ui.mdx`:
- Around line 227-230: The fenced code block containing the UI template using
form, input, and button lacks a language identifier which causes MD040; edit the
block that begins with form(` and add a language tag immediately after the
opening fence (for example ```text or ```jsx) so the code fence is ```text (or
another appropriate language) followed by the existing template; ensure the
opening fence and closing fence remain intact.
- Around line 199-209: The fenced code block containing the descriptionList(...)
example is missing a language identifier; update the opening triple-backtick for
that block (the one wrapping the descriptionList call that sets layout:'grid'
and className:'mt-4') to include a language token (e.g., ```text or ```uri) so
markdownlint MD040 is satisfied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90e93626-70b8-4120-9ece-d5b61c7a8d83

📥 Commits

Reviewing files that changed from the base of the PR and between be16dff and 10719da.

📒 Files selected for processing (9)
  • docs/frontmcp/guides/building-tool-ui.mdx
  • docs/frontmcp/servers/tools.mdx
  • libs/skills/catalog/frontmcp-development/SKILL.md
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/basic-html-template.md
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/widget-with-csp-and-bridge.md
  • libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
  • libs/skills/catalog/frontmcp-development/references/create-tool.md
  • libs/skills/catalog/skills-manifest.json

Comment thread docs/frontmcp/guides/building-tool-ui.mdx Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-05-28T23:16:39.812Z
Commit: 375e82ec

…r FileSource widgets (#443)

`.tsx` / `.jsx` FileSource widgets are bundled with an auto-generated React
mount that imports `McpBridgeProvider` from `@frontmcp/ui/react`. When
`@frontmcp/ui` isn't installed in the consuming project, esbuild fails with
a cryptic `Could not resolve` wrapped in monorepo-flavored advice
(`Ensure workspace packages are built (e.g. nx build ui)`), which is
meaningless to anyone outside this repo.

Adds a preflight `isFrontmcpUiResolvable(...)` check in
`bundleFileSource` that runs before esbuild and throws a consumer-friendly
error pointing at `npm install @frontmcp/ui` / `yarn add @frontmcp/ui` when
the package isn't reachable from `resolveDir` or `process.cwd()`. Also
reworks the generic fallback error to drop the misleading `nx build ui`
suggestion. Documents the `@frontmcp/ui` prerequisite for FileSource
widgets in both catalog references (create-tool-ui.md, create-tool.md) and
both docs pages (tools.mdx, building-tool-ui.mdx).

Tests: new `ui-availability.spec.ts` covers `isFrontmcpUiResolvable`; the
existing `transpiler.spec.ts` test for the wrapped esbuild error is
re-asserted against the new wording and a new test mocks
`ui-availability` to exercise the missing-package error path.

Closes #443
…isses (#444)

Relative `template: { file: './widget.tsx' }` paths in `FileSource` are
resolved against `process.cwd()`, not the tool source's directory. A
widget at `src/tools/foo.widget.tsx` referenced as `./foo.widget.tsx` from
`src/tools/foo.tool.ts` produces a bare `ENOENT` at tool-call time, and
the documentation never said what `./` was relative to.

Wraps the `fs.readFileSync` in `resolveFileSource` with a try/catch:
when an ENOENT fires on a relative path, the framework now throws a
specific error that names the unresolved path, points at `process.cwd()`
as the resolution base, and shows the
`fileURLToPath(new URL('./widget.tsx', import.meta.url))` workaround.
Non-ENOENT errors and absolute-path ENOENTs are rethrown unchanged so
nothing else regresses.

Tests: new `loader.spec.ts` covers the three branches — relative-ENOENT
(friendly error), absolute-ENOENT (raw passthrough), non-ENOENT (raw
passthrough). Documents the cwd-relative resolution rule and the
`import.meta.url` workaround in both catalog refs and both docs pages.

Closes #444
… don't break frontmcp build (#445)

`.tsx`/`.jsx` widget sources under `src/` are bundled separately by
`@frontmcp/uipack` (esbuild) at render time — they're never compiled by
the project's `tsc --noEmit` pass. But the default scaffolded
`tsconfig.json` had `include: ['src/**/*']` and no `jsx` option, so
`frontmcp build` failed with TS2307 (cannot find React types), TS17004
(no jsx flag), and TS7026 (implicit any on JSX) for any widget the user
added under `src/`.

Adopts the `*.widget.tsx` / `*.widget.jsx` naming convention (already used
by the catalog examples) and excludes both globs from the server typecheck
via tsconfig:

- `RECOMMENDED_TSCONFIG.exclude` now ships with `['**/*.widget.tsx',
  '**/*.widget.jsx']`, so `frontmcp init` on a new project just works.
- New `ensureWidgetExcludes(obj)` helper acts as a safety net: when an
  existing project's tsconfig owns its own `exclude` array (which
  overrides `RECOMMENDED_TSCONFIG.exclude` via deepMerge), `runInit`
  appends the widget patterns and logs which entries it added.

Tests: 5 new `ensureWidgetExcludes` cases (happy path, append-without-dup,
both-present, no-mutation, non-string drop) and 2 new `runInit` cases
(safety-net "added" path with log, inherited-from-RECOMMENDED quiet path).
Documents the convention in both catalog references and both docs pages,
with a sidecar `tsconfig.widget.json` suggestion for users who still want
IDE typecheck on widget sources.

Closes #445
Comment thread libs/cli/src/core/__tests__/tsconfig.spec.ts Dismissed
…idgets (#447)

The uipack JSDoc told contradictory stories about Claude's widget iframe:
`resourceMode` said "use 'inline' for network-blocked environments (Claude
Artifacts)", `externals` said "Claude only allows cdnjs.cloudflare.com",
and `servingMode` said inline works "on all platforms including
network-blocked ones". Empirically (verified in Claude Desktop and
claude.ai), Claude's widget iframe blocks ALL external `<script src>`
execution including cdnjs — regardless of CSP — and the framework
silently fails ("Loading widget…" forever) when a `.tsx` FileSource
widget tries to load React from esm.sh.

Rewrites the JSDoc on `WidgetServingMode`, `resourceMode`, and
`externals` to tell one consistent story:

- Claude blocks all external script execution in widget iframes
  (regardless of origin)
- `_meta.ui.csp` on the tool is ignored by Claude (tracking bug: #455)
- `resourceMode: 'inline'` embeds renderer runtime scripts but does NOT
  inline FileSource component code today (tracking bug: #454)
- For Claude targets, use a self-contained `uiType: 'html'` function
  template that emits a single inline `<script>` block

Updates the catalog `create-tool-ui` reference (Platform Considerations
table, Common Patterns table, Troubleshooting row, new "Claude target
reality check" callout) and the `file-source-tsx-widget` example (frame
it as OpenAI / ChatGPT / Inspector working, Claude broken pending #454)
to match. SKILL.md routing row and skills-manifest.json entry updated to
preserve the docs↔manifest sync invariant.

Closes #447
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cli/src/core/__tests__/tsconfig.spec.ts`:
- Line 82: The test uses "as any" when calling ensureWidgetExcludes; replace the
loose cast with a strictly typed input (e.g., declare the test value as
Record<string, unknown> or an explicit type like { exclude: Array<unknown> } or
(string|unknown)[]) and pass that to ensureWidgetExcludes so the test still
covers non-string excludes without using any; update the call to
ensureWidgetExcludes({ exclude: ['node_modules', 123, true] } as Record<string,
unknown>) or better declare a typed const and pass it to ensureWidgetExcludes to
preserve strict TypeScript typing while exercising non-string values.

In `@libs/cli/src/core/tsconfig.ts`:
- Around line 75-77: The exported function ensureWidgetExcludes currently uses
the unsound type Record<string, any>; change its external signature to avoid any
(e.g., use Record<string, unknown> or a generic <T extends Record<string,
unknown>>) and perform proper runtime narrowing inside ensureWidgetExcludes
(type-guards/casts) before accessing properties, updating the return type to
remain { result: Record<string, unknown>; added: string[] } or a
generic-consistent shape so callers don't receive any-typed values; update all
internal references within ensureWidgetExcludes to use the narrowed types rather
than any.

In `@libs/uipack/src/component/loader.ts`:
- Around line 120-121: Replace the direct Node fs read with the `@frontmcp/utils`
file API: in loader.ts where rawSource is set using fs.readFileSync(filePath,
'utf-8'), remove the direct fs call and call the equivalent function exported by
`@frontmcp/utils` (import it at top), using filePath to obtain the UTF-8 contents
and assign to rawSource; ensure you update the imports and preserve the
surrounding try/catch behavior and variable names (rawSource, filePath) so error
handling remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6eb3324c-fdb3-4f7f-bbda-8f65a622847d

📥 Commits

Reviewing files that changed from the base of the PR and between 10719da and bbf5049.

📒 Files selected for processing (16)
  • docs/frontmcp/guides/building-tool-ui.mdx
  • docs/frontmcp/servers/tools.mdx
  • libs/cli/src/core/__tests__/tsconfig.spec.ts
  • libs/cli/src/core/tsconfig.ts
  • libs/skills/catalog/frontmcp-development/SKILL.md
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
  • libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
  • libs/skills/catalog/frontmcp-development/references/create-tool.md
  • libs/skills/catalog/skills-manifest.json
  • libs/uipack/src/component/__tests__/loader.spec.ts
  • libs/uipack/src/component/__tests__/transpiler.spec.ts
  • libs/uipack/src/component/__tests__/ui-availability.spec.ts
  • libs/uipack/src/component/loader.ts
  • libs/uipack/src/component/transpiler.ts
  • libs/uipack/src/component/ui-availability.ts
  • libs/uipack/src/types/ui-config.ts
✅ Files skipped from review due to trivial changes (5)
  • libs/skills/catalog/frontmcp-development/SKILL.md
  • libs/skills/catalog/frontmcp-development/references/create-tool.md
  • docs/frontmcp/guides/building-tool-ui.mdx
  • libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
  • libs/uipack/src/types/ui-config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/skills/catalog/skills-manifest.json

Comment thread libs/cli/src/core/__tests__/tsconfig.spec.ts Outdated
Comment thread libs/cli/src/core/tsconfig.ts Outdated
Comment thread libs/uipack/src/component/loader.ts
…x FileSource widgets (#454)

React `FileSource` widgets were always emitting an esm.sh import map for
`react` / `react-dom/client` / `react/jsx-runtime`, even when the user
set `resourceMode: 'inline'`. Claude Desktop / claude.ai's widget iframe
blocks all external script execution, so the import map never resolved
and the widget hung on FrontMCP's "Loading widget…" placeholder forever.

Plumbs `resourceMode` end-to-end so `'inline'` truly inlines React:

  UITemplateConfig.resourceMode  (user, on @tool({ ui: { … } }))
    → ToolUIRegistry.compileStaticWidgetAsync / renderAndRegisterAsync
      (extracts from uiConfig)
      → renderToolTemplate.resourceMode  (uipack adapter)
        → renderComponent.config.inlineReact  (uipack renderer)
          → resolveUISource.options.inlineReact
            → resolveFileSource.options.inlineReact
              → bundleFileSource.options.bundleReact
                → esbuild.external = []  (React bundled in)

With React bundled, the widget's `<script type="module">` is fully
self-contained — no import map, no external module load — and renders
in Claude's sandbox. The default `'cdn'` mode is unchanged (smaller
payload for OpenAI Apps SDK / ChatGPT / Cursor / MCP Inspector).

Also hardens `isFrontmcpUiResolvable` (added in #443) for the upcoming
browser build of `@frontmcp/uipack`: `require` / `require.resolve` are
Node-only, so the function now returns `false` cleanly in browser
environments instead of throwing.

Tests: new `bundleFileSource` test confirms `bundleReact: true` empties
the esbuild `external` array; new `resolveUISource` describe block
verifies the `inlineReact` option threads through; new
`renderToolTemplate` end-to-end tests confirm `resourceMode: 'inline'`
reaches esbuild with empty externals, and the default keeps React
external. Existing fallback behavior preserved (all 280 uipack tests
pass).

Updates the catalog `create-tool-ui` reference (Platform Considerations
table, Common Patterns, Troubleshooting row, Claude callout), the
`file-source-tsx-widget` example (no longer flagged as broken in
Claude — `resourceMode: 'inline'` is the fix), the matching manifest
entry, the SKILL.md routing row, the `resourceMode` JSDoc in
`ui-config.ts`, and the docs pages (tools.mdx, building-tool-ui.mdx)
to recommend `resourceMode: 'inline'` for Claude-compatible `.tsx`
widgets.

Closes #454
…eat/tool-ui-skill-suite

# Conflicts:
#	libs/skills/catalog/frontmcp-development/SKILL.md
#	libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
#	libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
#	libs/skills/catalog/skills-manifest.json
#	libs/uipack/src/types/ui-config.ts
…s it (#455)

MCP Apps hosts (Claude in particular) only honor `_meta.ui.csp` declared
on the UI resource — the `resources/read` content item's `_meta`. They
ignore CSP declared on the tool's `_meta.ui.csp`. FrontMCP previously
emitted the CSP on the tool listing only, so any custom origin a user
declared via `ui.csp.connectDomains` / `ui.csp.resourceDomains` was
silently dropped by Claude and external assets / fetches were blocked
by Claude's default `script-src 'self' 'unsafe-inline' 'unsafe-eval'
blob: data: https://assets.claude.ai`.

`ToolUIRegistry` now records per-tool CSP / permissions
(`getResourceMeta(toolName)`), and `handleUIResourceRead` attaches
them as `_meta.ui.csp` (nested) AND `_meta['ui/csp']` (slash) on the
`ui://widget/{toolName}.html` content item. CSP fields are emitted in
the snake_case form the MCP Apps spec uses (`connect_domains` /
`resource_domains`). Empty `_meta` is omitted entirely so clients
don't see noise on widgets that didn't configure CSP.

Tests: new `ui-resource-meta.spec.ts` covers the registry → handler
hand-off (no-meta short-circuit, both nested + slash forms emitted,
snake_case key conversion, permissions-only path, and the placeholder
fallback path).

Updates the `UIContentSecurityPolicy` JSDoc, the `create-tool-ui`
skill (Claude target callout, Platform Considerations row,
Troubleshooting row), and both docs pages (`tools.mdx`,
`building-tool-ui.mdx`) to reflect the fix.

Closes #455

Note: the previous resolve of the diverged origin branch left
`bbf5049d` (an earlier wrong-message version of #447) in the history
alongside the amended `5095ac6d`. Functionally equivalent — both
contain the same #447 content — but the history will need a squash on
the way out.
…laude (#456)

Issue #456 has two parts:

(A) `resourceMode: 'inline'` does not actually inline file/React templates
    — fixed by the #454 plumbing landed earlier in this branch. Setting
    `resourceMode: 'inline'` now genuinely bundles React into the widget.

(B) `servingMode: 'auto'` does not pick the right strategy per host —
    addressed here. `renderToolTemplate` now host-detects when the user
    leaves `resourceMode` unset:

    - `platformType === 'claude'`  → `resourceMode: 'inline'`
      (Claude's widget iframe blocks esm.sh; #447 / #454)
    - any other platform           → `resourceMode: 'cdn'`
      (existing default; smaller payload)

    Explicit `resourceMode` values always win — host detection only
    applies when the field is left unset. The detection runs in
    per-call rendering (`renderAndRegisterAsync` → `renderToolTemplate`)
    so dynamic / hybrid / lean modes pick up the right default
    automatically. Static-mode widgets are pre-compiled at server
    startup with no client context, so they still need an explicit
    `resourceMode: 'inline'` if they target Claude — documented.

Tests: three new `renderToolTemplate` cases verify (claude → inline
auto-selects empty externals), (claude + explicit cdn keeps externals
— user wins), and (openai → cdn default preserved). All 283 uipack
tests pass.

Updates `resourceMode` JSDoc, the `create-tool-ui` Claude callout +
options table, and both docs pages (tools.mdx, building-tool-ui.mdx)
to describe the host-detect behavior and the static-mode caveat.

Closes #456
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
libs/sdk/src/tool/ui/ui-shared.ts (1)

78-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Decouple resource metadata from render success.

resourceMeta is only written after renderToolTemplate() returns. If compileHybridWidgetAsync() falls back after a startup render failure, the widget still gets a manifest but never gets its ui.csp / ui.permissions, so resources/read omits _meta and Claude ignores the widget CSP. The same flow also leaves stale metadata behind when a later compile removes those fields. Persist or clear resourceMeta before the render attempt, or in a finally, so it always matches the current uiConfig.

Suggested fix
-    const result = renderToolTemplate({
-      toolName,
-      input,
-      output,
-      template,
-      resolver: this.resolver,
-      resourceMode,
-    });
+    const csp = uiConfig?.['csp'] as UIResourceMeta['csp'] | undefined;
+    const permissions = uiConfig?.['permissions'] as UIResourceMeta['permissions'] | undefined;
+    if (csp || permissions !== undefined) {
+      this.resourceMeta.set(toolName, { csp, permissions });
+    } else {
+      this.resourceMeta.delete(toolName);
+    }
+
+    const result = renderToolTemplate({
+      toolName,
+      input,
+      output,
+      template,
+      resolver: this.resolver,
+      resourceMode,
+    });
@@
-    const csp = uiConfig?.['csp'] as UIResourceMeta['csp'] | undefined;
-    const permissions = uiConfig?.['permissions'] as UIResourceMeta['permissions'] | undefined;
-    if (csp || permissions !== undefined) {
-      this.resourceMeta.set(toolName, { csp, permissions });
-    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/sdk/src/tool/ui/ui-shared.ts` around lines 78 - 100, The resource
metadata (ui.csp/ui.permissions) is only written after renderToolTemplate()
succeeds, leaving resourceMeta out-of-sync when compileHybridWidgetAsync() falls
back or when uiConfig later removes fields; update the flow in the
compile/registration path (e.g., inside compileHybridWidgetAsync() / the block
that currently calls renderToolTemplate() and then this.widgets/this.manifests)
to persist or clear resource metadata before attempting render and also ensure a
finally/cleanup path updates resourceMeta to match the current uiConfig: use
this.resourceMeta.set(toolName, { csp, permissions }) (or
this.resourceMeta.delete(toolName) when fields are absent) prior to calling
renderToolTemplate(), and ensure the same update runs in a finally block so
manifests, widgets and resourceMeta stay consistent even on fallback or render
failure.
docs/frontmcp/servers/tools.mdx (1)

893-895: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope the React/CDN note to CDN mode only.

This paragraph now overstates the runtime behavior. A few lines below, the docs explain that Claude can host-detect to resourceMode: 'inline' and static widgets may need explicit 'inline', so react / react-dom are not always CDN-loaded at runtime. Please qualify this sentence to CDN mode or mention the inline exception.

Suggested wording
-**`.tsx`/`.jsx` widgets require `@frontmcp/ui` installed.** When `ui.template` points at a `.tsx`/`.jsx` file (the recommended pattern for non-trivial widgets), FrontMCP injects an auto-generated React mount that imports `McpBridgeProvider` from `@frontmcp/ui/react`. Install `@frontmcp/ui` in the consuming project at the same version as `@frontmcp/sdk` — without it, server-side bundling fails. `react` / `react-dom` stay external and load from the CDN at runtime, so only `@frontmcp/ui` needs to be present on disk.
+**`.tsx`/`.jsx` widgets require `@frontmcp/ui` installed.** When `ui.template` points at a `.tsx`/`.jsx` file (the recommended pattern for non-trivial widgets), FrontMCP injects an auto-generated React mount that imports `McpBridgeProvider` from `@frontmcp/ui/react`. Install `@frontmcp/ui` in the consuming project at the same version as `@frontmcp/sdk` — without it, server-side bundling fails. In CDN mode, `react` / `react-dom` stay external and load at runtime; in `resourceMode: 'inline'`, FrontMCP bundles them into the widget instead.

As per coding guidelines, docs/frontmcp/guides/building-tool-ui.mdx requires host-aware resourceMode, including Claude-appropriate inline bundling for .tsx widgets.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/frontmcp/servers/tools.mdx` around lines 893 - 895, Update the Info
paragraph that currently states "react / react-dom stay external and load from
the CDN at runtime" to scope that behavior to CDN mode only: change the sentence
to say that in CDN/resourceMode: 'cdn' mode react and react-dom remain external
and are loaded from the CDN at runtime, but call out the exception that when
host-detection or Claude selects resourceMode: 'inline' (or when the consuming
project sets resourceMode: 'inline'), react/react-dom must be bundled/available
locally; ensure references to ui.template and `@frontmcp/ui` remain intact and
consider adding a short pointer to resourceMode: 'inline' handling described in
building-tool-ui.mdx.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/sdk/src/tool/ui/ui-resource.handler.ts`:
- Around line 112-121: The implementation of normalizeCspForResource currently
only emits connect_domains/resource_domains and drops any other CSP keys despite
the comment; update normalizeCspForResource to iterate over all own properties
of the input csp, map known camelCase keys connectDomains -> connect_domains and
resourceDomains -> resource_domains, and copy any other keys through unchanged
(preserving their original names/values) so unknown/future CSP fields are not
lost; use the existing function name normalizeCspForResource and ensure the
output remains a Record<string, unknown>.

In `@libs/skills/catalog/skills-manifest.json`:
- Around line 1431-1451: Update the "file-source-tsx-widget" skill entry to
include the missing install prerequisite for `@frontmcp/ui` (must match the
`@frontmcp/sdk` version); add a short prerequisite note to the skill's metadata
(e.g., a new "prerequisites" field or a line in the description/features list)
that tells users to install `@frontmcp/ui` at the same version as `@frontmcp/sdk`
before bundling so the bridge mount succeeds during FileSource `.tsx` bundling.

---

Outside diff comments:
In `@docs/frontmcp/servers/tools.mdx`:
- Around line 893-895: Update the Info paragraph that currently states "react /
react-dom stay external and load from the CDN at runtime" to scope that behavior
to CDN mode only: change the sentence to say that in CDN/resourceMode: 'cdn'
mode react and react-dom remain external and are loaded from the CDN at runtime,
but call out the exception that when host-detection or Claude selects
resourceMode: 'inline' (or when the consuming project sets resourceMode:
'inline'), react/react-dom must be bundled/available locally; ensure references
to ui.template and `@frontmcp/ui` remain intact and consider adding a short
pointer to resourceMode: 'inline' handling described in building-tool-ui.mdx.

In `@libs/sdk/src/tool/ui/ui-shared.ts`:
- Around line 78-100: The resource metadata (ui.csp/ui.permissions) is only
written after renderToolTemplate() succeeds, leaving resourceMeta out-of-sync
when compileHybridWidgetAsync() falls back or when uiConfig later removes
fields; update the flow in the compile/registration path (e.g., inside
compileHybridWidgetAsync() / the block that currently calls renderToolTemplate()
and then this.widgets/this.manifests) to persist or clear resource metadata
before attempting render and also ensure a finally/cleanup path updates
resourceMeta to match the current uiConfig: use this.resourceMeta.set(toolName,
{ csp, permissions }) (or this.resourceMeta.delete(toolName) when fields are
absent) prior to calling renderToolTemplate(), and ensure the same update runs
in a finally block so manifests, widgets and resourceMeta stay consistent even
on fallback or render failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b0413ac-e525-4bd0-a238-6009f0104ef5

📥 Commits

Reviewing files that changed from the base of the PR and between bbf5049 and 04a1639.

📒 Files selected for processing (19)
  • docs/frontmcp/guides/building-tool-ui.mdx
  • docs/frontmcp/servers/tools.mdx
  • libs/sdk/src/tool/ui/__tests__/ui-resource-meta.spec.ts
  • libs/sdk/src/tool/ui/ui-resource.handler.ts
  • libs/sdk/src/tool/ui/ui-shared.ts
  • libs/skills/catalog/frontmcp-development/SKILL.md
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
  • libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
  • libs/skills/catalog/skills-manifest.json
  • libs/uipack/src/adapters/__tests__/template-renderer.spec.ts
  • libs/uipack/src/adapters/template-renderer.ts
  • libs/uipack/src/component/__tests__/loader.spec.ts
  • libs/uipack/src/component/__tests__/transpiler.spec.ts
  • libs/uipack/src/component/loader.ts
  • libs/uipack/src/component/renderer.ts
  • libs/uipack/src/component/transpiler.ts
  • libs/uipack/src/component/types.ts
  • libs/uipack/src/component/ui-availability.ts
  • libs/uipack/src/types/ui-config.ts
✅ Files skipped from review due to trivial changes (5)
  • libs/skills/catalog/frontmcp-development/SKILL.md
  • libs/uipack/src/types/ui-config.ts
  • docs/frontmcp/guides/building-tool-ui.mdx
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
  • libs/skills/catalog/frontmcp-development/references/create-tool-ui.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/uipack/src/component/ui-availability.ts
  • libs/uipack/src/component/tests/transpiler.spec.ts

Comment thread libs/sdk/src/tool/ui/ui-resource.handler.ts
Comment thread libs/skills/catalog/skills-manifest.json
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/uipack/src/component/ui-availability.ts`:
- Around line 29-42: In isFrontmcpUiResolvable, remove the require('fs') usage
and replace the nodeFs.existsSync(pkgJson) probe with a synchronous read via
`@frontmcp/utils.readFileSync`: for each candidate/path built with nodePath.join
using ORG and PKG, wrap a call to readFileSync(pkgJson) in a try/catch and
return true if readFileSync succeeds (no throw) and continue/return false when
it throws; keep the surrounding/outer try/catch intact so Node-only errors
(assertNode) still degrade for browser environments, and preserve the function's
synchronous return behavior and use of candidatePaths, ORG, PKG, and
isFrontmcpUiResolvable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 96f12d53-1e7b-417b-8879-87ecb620ddfb

📥 Commits

Reviewing files that changed from the base of the PR and between 04a1639 and aadd4a8.

📒 Files selected for processing (12)
  • docs/frontmcp/guides/building-tool-ui.mdx
  • docs/frontmcp/servers/tools.mdx
  • libs/cli/src/core/__tests__/tsconfig.spec.ts
  • libs/cli/src/core/tsconfig.ts
  • libs/sdk/src/tool/ui/__tests__/ui-resource-meta.spec.ts
  • libs/sdk/src/tool/ui/ui-resource.handler.ts
  • libs/sdk/src/tool/ui/ui-shared.ts
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
  • libs/skills/catalog/skills-manifest.json
  • libs/uipack/src/adapters/__tests__/template-renderer.spec.ts
  • libs/uipack/src/component/__tests__/renderer.spec.ts
  • libs/uipack/src/component/ui-availability.ts
✅ Files skipped from review due to trivial changes (4)
  • docs/frontmcp/servers/tools.mdx
  • docs/frontmcp/guides/building-tool-ui.mdx
  • libs/skills/catalog/skills-manifest.json
  • libs/skills/catalog/frontmcp-development/examples/create-tool-ui/file-source-tsx-widget.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • libs/cli/src/core/tsconfig.ts
  • libs/sdk/src/tool/ui/ui-shared.ts
  • libs/sdk/src/tool/ui/tests/ui-resource-meta.spec.ts
  • libs/sdk/src/tool/ui/ui-resource.handler.ts
  • libs/cli/src/core/tests/tsconfig.spec.ts
  • libs/uipack/src/adapters/tests/template-renderer.spec.ts

Comment thread libs/uipack/src/component/ui-availability.ts
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@frontegg-david frontegg-david merged commit 6eb8b7e into main May 28, 2026
32 checks passed
@frontegg-david frontegg-david deleted the feat/tool-ui-skill-suite branch May 28, 2026 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant