Skip to content

Replace calculator MCP app with generic generated UI shell - #29

Merged
kentcdodds merged 5 commits into
mainfrom
cursor/mcp-app-specification-for-generative-ui-7d68
Mar 24, 2026
Merged

kentcdodds merged 5 commits into
mainfrom
cursor/mcp-app-specification-for-generative-ui-7d68

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Mar 24, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • replace the calculator MCP App example with a single generic generated UI shell
  • add persisted UI artifact capabilities and D1 storage/search support for save/get/list/reopen flows
  • remove calculator and math capabilities, tests, docs, and build references

Testing

  • bun run typecheck
  • bun run test:mcp
  • bun run test:e2e e2e/calculator-widget.spec.ts
  • bun run test:e2e e2e/chat.spec.ts
  • manual browser walkthrough on /dev/generated-ui-shell-test covering default iframe render, sandboxed iframe render, host message, open-link logging, and fullscreen mode text
  • verified bun run inspect remains available for inspector-driven MCP app testing
Open in Web Open in Cursor 

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ability to save reusable UI apps and reopen them by ID
    • Introduced generic UI shell supporting both inline ephemeral code and saved artifacts
    • Added display mode controls (inline/fullscreen) for UI rendering
    • Enabled apps to trigger messaging, open links, and call tools from within generated UIs
  • Chores

    • Replaced calculator widget with generic generated UI framework

@coderabbitai

coderabbitai Bot commented Mar 24, 2026 •

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR replaces the calculator widget with a generic "generated UI shell" architecture. It removes the do_math math domain capability, introduces a new apps domain with artifact persistence, implements database storage for UI artifacts with search, and updates the MCP server, client implementations, and tests accordingly.

Changes

Cohort / File(s) Summary
Documentation
docs/agents/mcp-apps-starter-guide.md, docs/mcp-server-patterns.md
Updated guides to reflect generic UI shell architecture over calculator widget, including artifact persistence, display modes, theming, and tool registration patterns using registerOpenGeneratedUiTool and registerAppTool.
Math Domain Removal
packages/worker/src/mcp/capabilities/math/*, packages/worker/src/mcp/capabilities/domain-metadata.ts
Deleted entire math domain (do_math capability, domain definition, index). Removed math: 'math' from capabilityDomainNames and updated CapabilityDomain type.
Apps Domain & Capabilities
packages/worker/src/mcp/capabilities/apps/*
Added new apps domain with four capabilities: ui_save_app (persist user artifacts with CRUD), ui_get_app (fetch artifact metadata), ui_list_apps (list user artifacts), ui_load_app_source (load artifact source for rendering). Includes domain registry and capability index.
Capability Registry & Search Updates
packages/worker/src/mcp/capabilities/builtin-domains.ts, packages/worker/src/mcp/capabilities/build-capability-registry.test.ts, packages/worker/src/mcp/capabilities/capability-search.test.ts, packages/worker/src/mcp/capabilities/unified-search.ts, packages/worker/src/mcp/capabilities/unified-search.test.ts
Updated builtin domains to include appsDomain instead of mathDomain. Extended unified search to support UI artifact rows, added UiArtifactSearchHit to UnifiedSearchMatch union, and integrated artifact search via reciprocal-rank fusion.
UI Artifacts Repository & Storage
packages/worker/src/mcp/ui-artifacts-types.ts, packages/worker/src/mcp/ui-artifacts-repo.ts, packages/worker/migrations/0006-ui-artifacts.sql
Added D1 table ui_artifacts with user-scoped artifact records. Implemented repository functions for CRUD operations: insertUiArtifact, getUiArtifactById, deleteUiArtifact, listUiArtifactsByUserId. Defined UiArtifactRow and UiArtifactRuntime types.
UI Artifacts Search & Vectorization
packages/worker/src/mcp/ui-artifacts-search.ts, packages/worker/src/mcp/ui-artifacts-vectorize.ts, packages/worker/src/mcp/ui-artifacts-embed.ts
Added search module supporting lexical and vector ranking with RRF fusion. Vectorization upserts artifact embeddings. Embed-text builder generates searchable text from artifact metadata.
Generic UI Shell - Client
packages/worker/client/mcp-apps/generated-ui-shell.ts, packages/worker/client/mcp-apps/widget-host-bridge.ts, packages/worker/client/mcp-apps/widget-host-bridge.test.ts
Implemented generic browser shell supporting inline and saved app rendering. Added callTool, openLink, requestDisplayMode, updateModelContext bridge methods. Extended host bridge to manage render data, handle tool I/O lifecycle notifications, and inject runtime APIs.
Generic UI Shell - Server
packages/worker/src/mcp/apps/generated-ui-shell-entry-point.ts, packages/worker/src/index.ts
Created HTML entry point for generated UI shell at ui://generated-ui-shell/entry-point.html. Replaces calculator UI route /dev/generated-ui.
MCP Tools Registration
packages/worker/src/mcp/tools/open-generated-ui.ts, packages/worker/src/mcp/tools/register-resources.ts, packages/worker/src/mcp/tools/register-tools.ts, packages/worker/src/mcp/tools/search.ts
Added open_generated_ui app tool supporting inline code or saved app references. Updated resource/tool registration to use generated UI shell. Extended search tool to fetch and pass UI artifact rows to unified search.
Calculator Widget Removal
packages/worker/client/mcp-apps/calculator-widget.ts, packages/worker/src/mcp/apps/calculator-ui-entry-point.ts, packages/worker/src/mcp/tools/open-calculator-ui.ts
Deleted calculator widget client, entry point, and tool registration.
MCP Server & Chat Integration
packages/worker/src/mcp/index.ts, packages/worker/src/mcp/mcp-server-e2e.test.ts, packages/worker/src/mcp/observability.ts, packages/worker/src/mcp/observability.test.ts, packages/worker/src/chat-agent.ts
Updated MCP server instructions to describe apps domain and open_generated_ui workflow. Added 'app' to McpToolKind. Replaced do_math mock tool with open_generated_ui mock. Updated skill/capability search tests to reference ui_save_app and ui_get_app.
Skills & Execute Tool
packages/worker/src/mcp/skills/infer-codemode-capabilities.test.ts, packages/worker/src/mcp/skills/skill-embed-and-flags.test.ts, packages/worker/src/mcp/tools/execute.ts
Updated skill inference and embed tests to use ui_save_app instead of do_math. Changed execute tool example from math chaining to single github_rest_api_docs call.
Package Build Configuration
package.json
Updated dev:mcp-apps and build:mcp-apps esbuild scripts to bundle generated-ui-shell.ts instead of calculator-widget.ts.
Mock AI & Tool Parsing
packages/shared/src/mock-ai.ts, packages/shared/src/mock-ai.test.ts, packages/worker/src/ai-runtime.test.ts
Updated mock tool command examples and parsing tests to use tool:open_generated_ui with HTML code payloads instead of tool:do_math arithmetic syntax. Updated tool name references in runtime tests.
Dev Host Page & E2E Tests
packages/worker/public/dev/generated-ui-shell-test.html, e2e/chat.spec.ts, e2e/calculator-widget.spec.ts, e2e/generated-ui-shell.spec.ts
Added new dev host page for generated UI shell with inline/sandboxed iframe support and host messaging handlers. Added comprehensive e2e tests for inline/saved app rendering and host interactions. Removed calculator widget e2e tests. Updated chat e2e test to use open_generated_ui tool.
Database Migrations & Mock Server
packages/worker/migrations/0007-drop-mock-request-tables.sql, packages/mock-servers/ai/src/mock-requests-do.ts, packages/mock-servers/ai/src/worker.ts, packages/mock-servers/resend/src/mock-messages-do.ts
Added migration to drop legacy mock request/message tables. Minor formatting/import cleanup in mock server modules.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Shell as Generated UI Shell
    participant HostBridge as Host Bridge
    participant MCP as MCP Server
    participant DB as Database

    rect rgba(100, 200, 150, 0.5)
    Note over Browser,DB: Inline Code Rendering Flow
    Browser->>Shell: Initialize with render data
    Shell->>HostBridge: initialize()
    HostBridge->>MCP: ui/initialize
    MCP-->>HostBridge: hostContext, initial render data
    HostBridge->>Shell: onRenderData callback
    Shell->>Shell: renderEnvelope(inline_code)
    Shell->>Browser: Generate iframe with inline code
    Browser->>Browser: Execute embedded code in sandbox
    end

    rect rgba(150, 150, 200, 0.5)
    Note over Browser,DB: Saved App Rendering Flow
    Browser->>Shell: renderEnvelope(saved_app, appId)
    Shell->>HostBridge: callTool(ui_load_app_source)
    HostBridge->>MCP: tools/call ui_load_app_source
    MCP->>DB: getUiArtifactById(userId, appId)
    DB-->>MCP: artifact source code
    MCP-->>HostBridge: structured content with code
    HostBridge-->>Shell: artifact source
    Shell->>Browser: Generate iframe with saved source
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Browser,DB: App Saving Flow
    Browser->>HostBridge: sendMessage to host
    HostBridge->>MCP: Send message to agent
    MCP->>MCP: execute.codemode.ui_save_app()
    MCP->>DB: insertUiArtifact(userId, metadata, code)
    MCP->>MCP: upsertUiArtifactVector(appId, embedText)
    DB-->>MCP: artifact persisted with id
    MCP-->>HostBridge: artifact saved response
    HostBridge-->>Browser: UI artifact ready message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #22: Shares the same foundation of moving MCP server sources into packages/worker, reorganizing module structure, and updating build/test/dev wiring; this PR builds on that organizational groundwork.
  • PR #28: Both PRs clean up legacy mock database artifacts; this PR adds the migration to drop mock_resend_messages and mock_ai_requests tables in parallel with removing mock-server D1 state usage.

Poem

🐰 Hop, hop—farewell to calculators old!
A shell appears, both flexible and bold,
Apps save, apps load, inline code runs free,
Search ranks them high—now watch them dance with glee!
From math domain springs apps domain anew,
Generic UI dreams—they all come true! 🌱✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: replacing the calculator MCP app example with a generic generated UI shell implementation.

✏️ 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 cursor/mcp-app-specification-for-generative-ui-7d68

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@kentcdodds
kentcdodds marked this pull request as ready for review March 24, 2026 20:43
@github-actions

github-actions Bot commented Mar 24, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-29.kentcdodds.workers.dev

Worker: kody-pr-29
D1: kody-pr-29-db
KV: kody-pr-29-oauth-kv

Mocks:

Comment thread packages/worker/src/mcp/tools/open-generated-ui.ts
Comment thread packages/worker/src/mcp/ui-artifacts-search.ts Outdated
Comment thread packages/worker/client/mcp-apps/generated-ui-shell.ts Outdated
Comment thread packages/worker/src/mcp/ui-artifacts-search.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/worker/src/mcp/tools/search.ts (1)

43-50: ⚠️ Potential issue | 🟡 Minor

Clarify auth requirement for saved apps in the description.

Line 50 says only skills require authentication, but Lines 106-109 also gate apps on authenticated user context. This can mislead callers about unauthenticated search behavior.

✏️ Suggested wording tweak
-Pass a **query** string describing what you want to do. Results are ranked with semantic (Vectorize) and lexical fusion. **Skills** require an authenticated MCP user.
+Pass a **query** string describing what you want to do. Results are ranked with semantic (Vectorize) and lexical fusion. **Skills and saved apps** require an authenticated MCP user.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/tools/search.ts` around lines 43 - 50, Update the
user-facing description in packages/worker/src/mcp/tools/search.ts so it
accurately reflects that both saved "skills" and saved "apps" require an
authenticated MCP user (not just skills), i.e., change the sentence that
currently reads "Skills require an authenticated MCP user" to mention "Skills
and apps require an authenticated MCP user" and ensure this wording aligns with
the gating logic used elsewhere in this module (e.g., the behavior around
open_generated_ui and meta_run_skill/meta_get_skill) so callers are not misled
about unauthenticated search behavior.
🧹 Nitpick comments (10)
packages/worker/migrations/0006-ui-artifacts.sql (1)

14-14: Consider a composite index for user-scoped listing performance.

If list/reopen queries sort by recency, indexing (user_id, updated_at) will scale better than filtering by user_id then sorting.

Suggested migration adjustment
-CREATE INDEX IF NOT EXISTS idx_ui_artifacts_user_id ON ui_artifacts(user_id);
+CREATE INDEX IF NOT EXISTS idx_ui_artifacts_user_id_updated_at
+ON ui_artifacts(user_id, updated_at DESC);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/migrations/0006-ui-artifacts.sql` at line 14, The current
single-column index idx_ui_artifacts_user_id on table ui_artifacts should be
replaced or supplemented with a composite index on (user_id, updated_at) to
improve user-scoped list/reopen queries that sort by recency; update the
migration to create a composite index (e.g.,
idx_ui_artifacts_user_id_updated_at) on ui_artifacts(user_id, updated_at) and
remove or keep the single-column index as appropriate for other query patterns
so that ORDER BY updated_at FILTER BY user_id can use an index scan.
e2e/chat.spec.ts (1)

126-137: Extract the tool-command literal to avoid drift in this test.

The exact command string is duplicated; a local constant keeps the fill/assertion in sync when syntax changes.

♻️ Suggested refactor
+	const toolCommand =
+		'tool:open_generated_ui;code=<main><h1>Mock App</h1><p>Opened from chat.</p></main>;title=Mock App'
 	await page
 		.getByRole('textbox', { name: 'Message' })
-		.fill(
-			'tool:open_generated_ui;code=<main><h1>Mock App</h1><p>Opened from chat.</p></main>;title=Mock App',
-		)
+		.fill(toolCommand)
@@
 	await expect(
 		messages
-			.getByText(
-				'tool:open_generated_ui;code=<main><h1>Mock App</h1><p>Opened from chat.</p></main>;title=Mock App',
-			)
+			.getByText(toolCommand)
 			.last(),
 	).toBeVisible()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/chat.spec.ts` around lines 126 - 137, Extract the duplicated command
string into a local constant (e.g., toolCommand) and use that constant in the
.fill(...) call and in the messages.getByText(...).last() assertion to prevent
drift; update the occurrences around the .fill(...) call and the
messages.getByText(...) usage in the e2e/chat.spec.ts test so both reference the
single symbol (toolCommand) instead of repeating the literal.
packages/worker/src/mcp/skills/skill-embed-and-flags.test.ts (1)

12-25: Prefer reusing canonical ui_save_app spec instead of hand-maintained fixture fields.

Manually duplicating capability metadata here increases drift risk as the capability contract evolves.

♻️ Suggested approach
+import { capabilityMap } from '#mcp/capabilities/registry.ts'
 import { type CapabilitySpec } from '#mcp/capabilities/types.ts'
@@
 const fakeSpecs: Record<string, CapabilitySpec> = {
-	ui_save_app: {
-		name: 'ui_save_app',
-		domain: capabilityDomainNames.apps,
-		description: 'Save a generated UI artifact.',
-		keywords: ['app', 'ui', 'artifact'],
-		readOnly: true,
-		idempotent: true,
-		destructive: false,
-		inputFields: ['title', 'description', 'keywords', 'source'],
-		requiredInputFields: ['title', 'description', 'keywords', 'source'],
-		outputFields: ['app_id'],
-		inputSchema: {},
-	},
+	ui_save_app: capabilityMap['ui_save_app'],
 	github_rest: {
 		// test-local fixture
 	},
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/skills/skill-embed-and-flags.test.ts` around lines 12
- 25, Replace the hand-maintained fakeSpecs.ui_save_app object with a reference
to the canonical capability spec used by the system (e.g., import the existing
ui_save_app CapabilitySpec or the capabilitySpecs registry and assign
fakeSpecs.ui_save_app = ui_save_appSpec); if the test needs to mutate fields,
clone the imported spec first to avoid mutating global state. Update references
to CapabilitySpec, fakeSpecs, and ui_save_app in the test so the fixture reuses
the authoritative spec instead of duplicating fields.
e2e/calculator-widget.spec.ts (1)

1-2: Consider renaming the test file to match the new functionality.

The file is named calculator-widget.spec.ts but now tests the generated UI shell. Consider renaming to generated-ui-shell.spec.ts for consistency with the PR's refactoring effort.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/calculator-widget.spec.ts` around lines 1 - 2, The test file name no
longer matches its behavior — rename the file from calculator-widget.spec.ts to
generated-ui-shell.spec.ts and update any internal references: change the test
file path in Playwright configs or test globs, update any import or require
paths that reference calculator-widget.spec.ts, and adjust top-level test
titles/describe strings inside the file if they still mention "calculator" so
they reflect "generated UI shell" for consistency with the refactor.
packages/worker/public/dev/generated-ui-shell-test.html (1)

299-319: Consider handling unrecognized tools/call requests.

The tools/call handler only responds to ui_load_app_source. Other tool names will fall through without a response, potentially causing the bridge to hang waiting for a reply. For a more robust test harness, consider returning an error for unrecognized tools.

♻️ Proposed fix to handle unrecognized tools
 					if (message.method === 'tools/call') {
 						if (message.params?.name === 'ui_load_app_source') {
 							event.source.postMessage(
 								{
 									jsonrpc: '2.0',
 									id: message.id,
 									result: {
 										structuredContent: {
 											app_id: 'demo-app',
 											title: 'Loaded app',
 											description: 'Loaded from app-only tool.',
 											runtime: 'javascript',
 											code: demoCode,
 										},
 									},
 								},
 								'*',
 							)
 							return
 						}
+						event.source.postMessage(
+							{
+								jsonrpc: '2.0',
+								id: message.id,
+								error: {
+									code: -32601,
+									message: `Unknown tool: ${message.params?.name}`,
+								},
+							},
+							'*',
+						)
+						return
 					}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/public/dev/generated-ui-shell-test.html` around lines 299 -
319, The tools/call branch currently only handles message.params?.name ===
'ui_load_app_source' and returns a result, leaving other tools unhandled which
can leave the bridge waiting; inside the same if (message.method ===
'tools/call') block add an else (or default) branch that posts back an error
response via event.source.postMessage with the same jsonrpc version and
message.id, including an error object (code and message) indicating
"unrecognized tool" or the tool name (message.params?.name) so callers receive a
proper error instead of hanging.
packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts (1)

6-6: Inconsistent import path style.

This file uses a relative import '../meta/require-user.ts' while the sibling file ui-get-app.ts uses the alias '#mcp/capabilities/meta/require-user.ts'. Consider using the alias consistently across the capability modules.

♻️ Proposed fix
-import { requireMcpUser } from '../meta/require-user.ts'
+import { requireMcpUser } from '#mcp/capabilities/meta/require-user.ts'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts` at line 6,
The import in ui-load-app-source.ts uses a relative path for requireMcpUser;
update the import to use the same project alias as its sibling (e.g.,
'#mcp/capabilities/meta/require-user.ts') so requireMcpUser is imported
consistently across capability modules; locate the requireMcpUser import
statement in ui-load-app-source.ts and replace the relative path with the alias.
packages/worker/src/mcp/tools/open-generated-ui.ts (1)

68-71: Unnecessary void agent statement.

Line 69 uses void agent to suppress unused variable warnings, but agent is actually used on line 71 (agent.server). This statement can be removed.

♻️ Proposed fix
 export async function registerOpenGeneratedUiTool(agent: MCP) {
-	void agent
 	registerAppTool(
 		agent.server,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/tools/open-generated-ui.ts` around lines 68 - 71,
Remove the redundant "void agent" statement in registerOpenGeneratedUiTool — the
parameter agent is used (see agent.server passed to registerAppTool), so simply
delete the standalone void agent line in the registerOpenGeneratedUiTool
function to eliminate the unnecessary no-op. Ensure the function still accepts
the MCP parameter and calls registerAppTool(agent.server, ...) as before.
packages/worker/src/mcp/ui-artifacts-vectorize.ts (1)

24-31: Inconsistent offline mode handling between upsert and delete.

upsertUiArtifactVector checks isCapabilitySearchOffline(env) before proceeding (line 13), but deleteUiArtifactVector does not. If the system is offline, upsert silently skips, but delete would still attempt the operation and could fail.

Consider adding the offline check for consistency:

♻️ Proposed fix
 export async function deleteUiArtifactVector(
 	env: Env,
 	appId: string,
 ): Promise<void> {
 	const index = getCapabilityVectorIndex(env)
-	if (!index) return
+	if (!index || isCapabilitySearchOffline(env)) return
 	await index.deleteByIds([uiArtifactVectorId(appId)])
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-vectorize.ts` around lines 24 - 31, The
deleteUiArtifactVector function is missing the same offline guard used by
upsertUiArtifactVector; add a check using isCapabilitySearchOffline(env) at the
top of deleteUiArtifactVector and return early if true, so deletion only runs
when capability search is online. Keep the existing logic that resolves the
index via getCapabilityVectorIndex(env) and calls
index.deleteByIds([uiArtifactVectorId(appId)]) unchanged; this ensures
consistent offline handling between upsertUiArtifactVector and
deleteUiArtifactVector.
packages/worker/src/mcp/capabilities/apps/ui-get-app.ts (1)

26-26: Inconsistent runtime schema between capabilities.

ui_get_app defines runtime: z.string() (line 26) while ui_load_app_source uses runtime: z.enum(['javascript']) (line 12 in that file). This inconsistency means ui_get_app could return arbitrary runtime strings while ui_load_app_source validates against the enum.

Consider aligning the schemas for consistency:

♻️ Proposed fix
-	runtime: z.string(),
+	runtime: z.enum(['javascript']),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/capabilities/apps/ui-get-app.ts` at line 26, The
ui_get_app capability defines runtime as z.string() which is inconsistent with
ui_load_app_source's runtime: z.enum(['javascript']); update ui_get_app's schema
to match by using the same enum (e.g., replace runtime: z.string() with runtime:
z.enum(['javascript'])) or extract a shared constant like RUNTIME_SCHEMA =
z.enum(['javascript']) and use it in both ui_get_app and ui_load_app_source so
they validate the same runtime values; adjust any imports/exports accordingly.
packages/worker/client/mcp-apps/generated-ui-shell.ts (1)

159-166: toggleFullscreen always returns 'fullscreen' regardless of actual toggle direction.

The kodyWidget.toggleFullscreen() method returns 'fullscreen' unconditionally, even when the user might be toggling from fullscreen back to inline. This could be misleading to consumers relying on the return value.

Consider returning the requested mode or awaiting the actual result from the parent:

♻️ Potential improvement
 	async toggleFullscreen() {
 		if (window.parent === window) return 'inline'
+		const currentMode = document.documentElement.dataset.displayMode || 'inline'
+		const nextMode = currentMode === 'fullscreen' ? 'inline' : 'fullscreen'
 		window.parent.postMessage({
 			type: '${childMessagePrefix}toggle-fullscreen',
 			payload: {},
 		}, '*')
-		return 'fullscreen'
+		return nextMode
 	},

Alternatively, if the actual resolved mode matters, consider making this a proper async request-response pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/client/mcp-apps/generated-ui-shell.ts` around lines 159 -
166, toggleFullscreen currently always returns 'fullscreen' which is misleading;
update the method (toggleFullscreen) to return the actual requested mode or the
parent's response instead of a hardcoded value: either add an input (e.g., mode:
'fullscreen'|'inline') and post that in the payload and return that mode, or
implement a request-response pattern using postMessage with a correlation id
(use childMessagePrefix in the message type) and await the parent's reply to
return the resolved mode; ensure window.parent === window still returns 'inline'
when appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/client/mcp-apps/generated-ui-shell.ts`:
- Line 26: Remove the unused constant inlineScriptMimeType by deleting its
declaration (const inlineScriptMimeType = 'text/javascript') from the
generated-ui-shell.ts file; verify there are no remaining references to
inlineScriptMimeType in functions or templates (e.g., any inline script handling
logic) and run the build/tests to ensure no regressions after removal.

In `@packages/worker/src/mcp/observability.test.ts`:
- Around line 147-152: Update the test mock metadata that currently uses the
obsolete input name "source" to the canonical "code" so it matches the
ui_save_app capability; specifically, in the mock objects where inputFields and
requiredInputFields list "source" (in skill-embed-and-flags.test), replace those
entries with "code" to align with the inputSchema/handler in ui_save_app and the
observability test which already uses code.

In `@packages/worker/src/mcp/ui-artifacts-repo.ts`:
- Around line 130-143: The mapRow function currently force-casts
row['source_type'] to UiArtifactRow['runtime'] which can produce invalid runtime
values; update mapRow to validate row['source_type'] against the allowed runtime
union (e.g., via an isValidRuntime(value) helper or a Set/array of permitted
runtime strings) and only assign it when valid, otherwise use a safe fallback
(null/default) or throw an explicit error; reference the runtime assignment in
mapRow and add the validation helper near that function so unexpected DB values
are handled safely.

---

Outside diff comments:
In `@packages/worker/src/mcp/tools/search.ts`:
- Around line 43-50: Update the user-facing description in
packages/worker/src/mcp/tools/search.ts so it accurately reflects that both
saved "skills" and saved "apps" require an authenticated MCP user (not just
skills), i.e., change the sentence that currently reads "Skills require an
authenticated MCP user" to mention "Skills and apps require an authenticated MCP
user" and ensure this wording aligns with the gating logic used elsewhere in
this module (e.g., the behavior around open_generated_ui and
meta_run_skill/meta_get_skill) so callers are not misled about unauthenticated
search behavior.

---

Nitpick comments:
In `@e2e/calculator-widget.spec.ts`:
- Around line 1-2: The test file name no longer matches its behavior — rename
the file from calculator-widget.spec.ts to generated-ui-shell.spec.ts and update
any internal references: change the test file path in Playwright configs or test
globs, update any import or require paths that reference
calculator-widget.spec.ts, and adjust top-level test titles/describe strings
inside the file if they still mention "calculator" so they reflect "generated UI
shell" for consistency with the refactor.

In `@e2e/chat.spec.ts`:
- Around line 126-137: Extract the duplicated command string into a local
constant (e.g., toolCommand) and use that constant in the .fill(...) call and in
the messages.getByText(...).last() assertion to prevent drift; update the
occurrences around the .fill(...) call and the messages.getByText(...) usage in
the e2e/chat.spec.ts test so both reference the single symbol (toolCommand)
instead of repeating the literal.

In `@packages/worker/client/mcp-apps/generated-ui-shell.ts`:
- Around line 159-166: toggleFullscreen currently always returns 'fullscreen'
which is misleading; update the method (toggleFullscreen) to return the actual
requested mode or the parent's response instead of a hardcoded value: either add
an input (e.g., mode: 'fullscreen'|'inline') and post that in the payload and
return that mode, or implement a request-response pattern using postMessage with
a correlation id (use childMessagePrefix in the message type) and await the
parent's reply to return the resolved mode; ensure window.parent === window
still returns 'inline' when appropriate.

In `@packages/worker/migrations/0006-ui-artifacts.sql`:
- Line 14: The current single-column index idx_ui_artifacts_user_id on table
ui_artifacts should be replaced or supplemented with a composite index on
(user_id, updated_at) to improve user-scoped list/reopen queries that sort by
recency; update the migration to create a composite index (e.g.,
idx_ui_artifacts_user_id_updated_at) on ui_artifacts(user_id, updated_at) and
remove or keep the single-column index as appropriate for other query patterns
so that ORDER BY updated_at FILTER BY user_id can use an index scan.

In `@packages/worker/public/dev/generated-ui-shell-test.html`:
- Around line 299-319: The tools/call branch currently only handles
message.params?.name === 'ui_load_app_source' and returns a result, leaving
other tools unhandled which can leave the bridge waiting; inside the same if
(message.method === 'tools/call') block add an else (or default) branch that
posts back an error response via event.source.postMessage with the same jsonrpc
version and message.id, including an error object (code and message) indicating
"unrecognized tool" or the tool name (message.params?.name) so callers receive a
proper error instead of hanging.

In `@packages/worker/src/mcp/capabilities/apps/ui-get-app.ts`:
- Line 26: The ui_get_app capability defines runtime as z.string() which is
inconsistent with ui_load_app_source's runtime: z.enum(['javascript']); update
ui_get_app's schema to match by using the same enum (e.g., replace runtime:
z.string() with runtime: z.enum(['javascript'])) or extract a shared constant
like RUNTIME_SCHEMA = z.enum(['javascript']) and use it in both ui_get_app and
ui_load_app_source so they validate the same runtime values; adjust any
imports/exports accordingly.

In `@packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts`:
- Line 6: The import in ui-load-app-source.ts uses a relative path for
requireMcpUser; update the import to use the same project alias as its sibling
(e.g., '#mcp/capabilities/meta/require-user.ts') so requireMcpUser is imported
consistently across capability modules; locate the requireMcpUser import
statement in ui-load-app-source.ts and replace the relative path with the alias.

In `@packages/worker/src/mcp/skills/skill-embed-and-flags.test.ts`:
- Around line 12-25: Replace the hand-maintained fakeSpecs.ui_save_app object
with a reference to the canonical capability spec used by the system (e.g.,
import the existing ui_save_app CapabilitySpec or the capabilitySpecs registry
and assign fakeSpecs.ui_save_app = ui_save_appSpec); if the test needs to mutate
fields, clone the imported spec first to avoid mutating global state. Update
references to CapabilitySpec, fakeSpecs, and ui_save_app in the test so the
fixture reuses the authoritative spec instead of duplicating fields.

In `@packages/worker/src/mcp/tools/open-generated-ui.ts`:
- Around line 68-71: Remove the redundant "void agent" statement in
registerOpenGeneratedUiTool — the parameter agent is used (see agent.server
passed to registerAppTool), so simply delete the standalone void agent line in
the registerOpenGeneratedUiTool function to eliminate the unnecessary no-op.
Ensure the function still accepts the MCP parameter and calls
registerAppTool(agent.server, ...) as before.

In `@packages/worker/src/mcp/ui-artifacts-vectorize.ts`:
- Around line 24-31: The deleteUiArtifactVector function is missing the same
offline guard used by upsertUiArtifactVector; add a check using
isCapabilitySearchOffline(env) at the top of deleteUiArtifactVector and return
early if true, so deletion only runs when capability search is online. Keep the
existing logic that resolves the index via getCapabilityVectorIndex(env) and
calls index.deleteByIds([uiArtifactVectorId(appId)]) unchanged; this ensures
consistent offline handling between upsertUiArtifactVector and
deleteUiArtifactVector.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 453fb428-3296-455a-b3f1-a5b06cab1c54

📥 Commits

Reviewing files that changed from the base of the PR and between 8bccb54 and 0d3317c.

📒 Files selected for processing (51)
  • docs/agents/mcp-apps-starter-guide.md
  • docs/mcp-server-patterns.md
  • e2e/calculator-widget.spec.ts
  • e2e/chat.spec.ts
  • package.json
  • packages/shared/src/mock-ai.test.ts
  • packages/shared/src/mock-ai.ts
  • packages/worker/client/mcp-apps/calculator-widget.ts
  • packages/worker/client/mcp-apps/generated-ui-shell.ts
  • packages/worker/client/mcp-apps/widget-host-bridge.test.ts
  • packages/worker/client/mcp-apps/widget-host-bridge.ts
  • packages/worker/migrations/0006-ui-artifacts.sql
  • packages/worker/public/dev/generated-ui-shell-test.html
  • packages/worker/src/ai-runtime.test.ts
  • packages/worker/src/chat-agent.ts
  • packages/worker/src/index.ts
  • packages/worker/src/mcp/apps/calculator-ui-entry-point.ts
  • packages/worker/src/mcp/apps/generated-ui-shell-entry-point.ts
  • packages/worker/src/mcp/capabilities/apps/domain.ts
  • packages/worker/src/mcp/capabilities/apps/index.ts
  • packages/worker/src/mcp/capabilities/apps/ui-get-app.ts
  • packages/worker/src/mcp/capabilities/apps/ui-list-apps.ts
  • packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts
  • packages/worker/src/mcp/capabilities/apps/ui-save-app.ts
  • packages/worker/src/mcp/capabilities/build-capability-registry.test.ts
  • packages/worker/src/mcp/capabilities/builtin-domains.ts
  • packages/worker/src/mcp/capabilities/capability-search.test.ts
  • packages/worker/src/mcp/capabilities/domain-metadata.ts
  • packages/worker/src/mcp/capabilities/math/do-math.ts
  • packages/worker/src/mcp/capabilities/math/domain.ts
  • packages/worker/src/mcp/capabilities/math/index.ts
  • packages/worker/src/mcp/capabilities/unified-search.test.ts
  • packages/worker/src/mcp/capabilities/unified-search.ts
  • packages/worker/src/mcp/index.ts
  • packages/worker/src/mcp/mcp-server-e2e.test.ts
  • packages/worker/src/mcp/observability.test.ts
  • packages/worker/src/mcp/observability.ts
  • packages/worker/src/mcp/register-resources.ts
  • packages/worker/src/mcp/register-tools.ts
  • packages/worker/src/mcp/resources/generated-ui-app-resource.ts
  • packages/worker/src/mcp/skills/infer-codemode-capabilities.test.ts
  • packages/worker/src/mcp/skills/skill-embed-and-flags.test.ts
  • packages/worker/src/mcp/tools/execute.ts
  • packages/worker/src/mcp/tools/open-calculator-ui.ts
  • packages/worker/src/mcp/tools/open-generated-ui.ts
  • packages/worker/src/mcp/tools/search.ts
  • packages/worker/src/mcp/ui-artifacts-embed.ts
  • packages/worker/src/mcp/ui-artifacts-repo.ts
  • packages/worker/src/mcp/ui-artifacts-search.ts
  • packages/worker/src/mcp/ui-artifacts-types.ts
  • packages/worker/src/mcp/ui-artifacts-vectorize.ts
💤 Files with no reviewable changes (6)
  • packages/worker/src/mcp/capabilities/math/domain.ts
  • packages/worker/src/mcp/capabilities/math/index.ts
  • packages/worker/src/mcp/apps/calculator-ui-entry-point.ts
  • packages/worker/src/mcp/tools/open-calculator-ui.ts
  • packages/worker/src/mcp/capabilities/math/do-math.ts
  • packages/worker/client/mcp-apps/calculator-widget.ts

Comment thread packages/worker/client/mcp-apps/generated-ui-shell.ts Outdated
Comment thread packages/worker/src/mcp/observability.test.ts
Comment on lines +130 to +143
function mapRow(row: Record<string, unknown>): UiArtifactRow {
return {
id: String(row['id']),
user_id: String(row['user_id']),
title: String(row['title']),
description: String(row['description']),
keywords: String(row['keywords']),
code: String(row['source_code']),
runtime: String(row['source_type']) as UiArtifactRow['runtime'],
search_text: row['search_text'] == null ? null : String(row['search_text']),
created_at: String(row['created_at']),
updated_at: String(row['updated_at']),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unsafe type cast for runtime field.

Line 138 casts the database value directly to UiArtifactRow['runtime'] without validating that the value is actually a valid member of the union type. If the database contains an unexpected value, this will silently produce an invalid typed object.

🛡️ Proposed fix with validation
+const VALID_RUNTIMES = ['browser', 'node'] as const
+type ValidRuntime = (typeof VALID_RUNTIMES)[number]
+
+function parseRuntime(value: unknown): UiArtifactRow['runtime'] {
+	const str = String(value)
+	return VALID_RUNTIMES.includes(str as ValidRuntime)
+		? (str as ValidRuntime)
+		: 'browser' // or throw if strict validation is preferred
+}
+
 function mapRow(row: Record<string, unknown>): UiArtifactRow {
 	return {
 		id: String(row['id']),
 		user_id: String(row['user_id']),
 		title: String(row['title']),
 		description: String(row['description']),
 		keywords: String(row['keywords']),
 		code: String(row['source_code']),
-		runtime: String(row['source_type']) as UiArtifactRow['runtime'],
+		runtime: parseRuntime(row['source_type']),
 		search_text: row['search_text'] == null ? null : String(row['search_text']),
 		created_at: String(row['created_at']),
 		updated_at: String(row['updated_at']),
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function mapRow(row: Record<string, unknown>): UiArtifactRow {
return {
id: String(row['id']),
user_id: String(row['user_id']),
title: String(row['title']),
description: String(row['description']),
keywords: String(row['keywords']),
code: String(row['source_code']),
runtime: String(row['source_type']) as UiArtifactRow['runtime'],
search_text: row['search_text'] == null ? null : String(row['search_text']),
created_at: String(row['created_at']),
updated_at: String(row['updated_at']),
}
}
const VALID_RUNTIMES = ['browser', 'node'] as const
type ValidRuntime = (typeof VALID_RUNTIMES)[number]
function parseRuntime(value: unknown): UiArtifactRow['runtime'] {
const str = String(value)
return VALID_RUNTIMES.includes(str as ValidRuntime)
? (str as ValidRuntime)
: 'browser' // or throw if strict validation is preferred
}
function mapRow(row: Record<string, unknown>): UiArtifactRow {
return {
id: String(row['id']),
user_id: String(row['user_id']),
title: String(row['title']),
description: String(row['description']),
keywords: String(row['keywords']),
code: String(row['source_code']),
runtime: parseRuntime(row['source_type']),
search_text: row['search_text'] == null ? null : String(row['search_text']),
created_at: String(row['created_at']),
updated_at: String(row['updated_at']),
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-repo.ts` around lines 130 - 143, The
mapRow function currently force-casts row['source_type'] to
UiArtifactRow['runtime'] which can produce invalid runtime values; update mapRow
to validate row['source_type'] against the allowed runtime union (e.g., via an
isValidRuntime(value) helper or a Set/array of permitted runtime strings) and
only assign it when valid, otherwise use a safe fallback (null/default) or throw
an explicit error; reference the runtime assignment in mapRow and add the
validation helper near that function so unexpected DB values are handled safely.

Comment thread packages/worker/migrations/0007-drop-mock-request-tables.sql
Comment thread packages/worker/src/mcp/ui-artifacts-repo.ts Outdated
Comment thread packages/worker/client/mcp-apps/generated-ui-shell.ts
Comment thread packages/worker/client/mcp-apps/generated-ui-shell.ts
})

export async function registerOpenGeneratedUiTool(agent: MCP) {
void agent

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Redundant no-op void agent statement

Low Severity

void agent on line 69 is a no-op expression — agent is already referenced on the very next line via agent.server. This looks like a leftover debugging artifact or an unused-variable suppression that is no longer needed. The deleted open-calculator-ui.ts had no equivalent line.

Fix in Cursor Fix in Web

Comment thread packages/worker/client/mcp-apps/generated-ui-shell.ts
} catch {
return []
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicated parseStringArray across multiple new files

Low Severity

The parseStringArray function is identically defined in both ui-get-app.ts and ui-list-apps.ts. An equivalent function named parseJsonStringArray also exists in ui-artifacts-search.ts and unified-search.ts. All four are new additions with the same logic. A shared utility would reduce duplication and ensure consistent bug fixes.

Additional Locations (1)
Fix in Cursor Fix in Web

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
packages/mock-servers/resend/src/mock-messages-do.ts (1)

81-86: getMessage will return null for older messages after truncation.

Since getMessage searches only recentMessages (capped at 100), any message that gets pushed out due to truncation will become unretrievable, returning null (which the worker surfaces as 404). This differs from the previous D1 behavior where messages persisted indefinitely.

Given the "best-effort" semantics documented in docs/agents/mock-api-servers.md, this may be acceptable for a mock server. However, consider adding a comment or updating the /__mocks/messages/:id endpoint description to clarify this limitation.

📝 Suggested documentation improvement in worker.ts dashboardEndpoints
 {
   method: 'GET',
   path: '/__mocks/messages/:id',
-  description: 'Get a stored message by ID (JSON)',
+  description: 'Get a stored message by ID (JSON, limited to most recent 100)',
   requiresAuth: true,
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/mock-servers/resend/src/mock-messages-do.ts` around lines 81 - 86,
The current 'get' case in mock-messages-do.ts (the logic that implements
getMessage) only searches storage.recentMessages (a 100-item ring) so messages
older than the truncation are returned as null; update the code comments and the
API docs to make this best-effort limitation explicit: add a clear inline
comment beside the case 'get' block referencing storage.recentMessages and
noting that messages are capped at 100 and older messages are unretrievable, and
update the /__mocks/messages/:id entry in docs/agents/mock-api-servers.md (or
the dashboardEndpoints description in worker.ts) to document that getMessage is
best-effort and may return 404 for truncated messages.
packages/worker/src/mcp/capabilities/apps/ui-get-app.ts (1)

56-58: Minor inconsistency in error message.

This capability uses 'Saved UI artifact not found for this user.' while ui-load-app-source.ts uses 'Saved app not found for this user.'. Consider aligning the wording for consistent error responses across similar capabilities.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/capabilities/apps/ui-get-app.ts` around lines 56 -
58, The error message thrown when no saved UI artifact is found ("Saved UI
artifact not found for this user.") should be made consistent with the other
capability; update the thrown Error in the row check inside the handler in
ui-get-app.ts to use the same wording as ui-load-app-source.ts ("Saved app not
found for this user.") so both capabilities return the identical message when a
saved app is missing.
packages/worker/src/mcp/ui-artifacts-search.ts (2)

15-23: Consider extracting shared JSON array parsing utility.

parseJsonStringArray is nearly identical to parseStringArray in ui-get-app.ts. Consider extracting this to a shared utility (e.g., in ui-artifacts-types.ts or a common utils module) to reduce duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-search.ts` around lines 15 - 23, The
parseJsonStringArray function duplicates parseStringArray from ui-get-app.ts;
extract a shared utility (e.g., export a new function parseStringArrayFromJson
or reuse a common parseStringArray) into a shared module such as
ui-artifacts-types.ts or a common utils file, update both
mcp/ui-artifacts-search.ts (replace parseJsonStringArray usage) and
ui-get-app.ts (replace its local parseStringArray) to import and call the shared
utility, and ensure the exported function preserves the current behavior
(JSON.parse with Array.isArray check and string filtering) and retains the same
TypeScript signature (returns Array<string>).

128-131: Non-null assertion assumes vector index availability when not offline.

Line 129 uses ! assuming getCapabilityVectorIndex returns non-null when not offline. If the relationship between isCapabilitySearchOffline and index availability changes, this could cause a runtime error.

🛡️ Proposed defensive fix
 	} else {
 		const index = getCapabilityVectorIndex(input.env)!
+		if (!index) {
+			// Fallback to lexical-only if index unexpectedly unavailable
+			vectorOrder = ids
+		} else {
 		const queryVector = await embedTextForVectorize(input.env, query)
 		const topK = Math.min(Math.max(ids.length, input.limit * 5), 100)
+		// ... rest of vector logic
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-search.ts` around lines 128 - 131, The
code currently uses a non-null assertion on getCapabilityVectorIndex(input.env)
which can crash if the index is absent; update the block around
getCapabilityVectorIndex, embedTextForVectorize and subsequent logic to
defensively handle a null/undefined index: call
getCapabilityVectorIndex(input.env) into a local (e.g., capabilityIndex), check
if it is null/undefined and if so either use the offline fallback path
(respecting isCapabilitySearchOffline) or return/throw a clear error/early-exit
with a logged message; only call embedTextForVectorize and proceed to compute
topK and run the vector search when capabilityIndex is present. Ensure you
reference getCapabilityVectorIndex and embedTextForVectorize when modifying
control flow.
packages/worker/src/mcp/ui-artifacts-repo.ts (1)

54-88: Remove or export unused functions.

updateUiArtifact and listAllUiArtifacts are defined but never referenced anywhere in the codebase. Remove them if no longer needed, or export them if intended for future use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-repo.ts` around lines 54 - 88, The
functions updateUiArtifact and listAllUiArtifacts are defined but unused; either
remove their declarations to clean up dead code or export them so they can be
consumed elsewhere—if exporting, add them to the module's exports (e.g., export
async function updateUiArtifact(...) and export function
listAllUiArtifacts(...)) and update any barrel/index export files as needed so
other modules can import them; if removing, delete both function definitions and
any related types/params (e.g., the fields param and the D1Database usage) and
run a repo-wide search to ensure there are no remaining references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/mock-servers/resend/src/mock-messages-do.ts`:
- Around line 81-86: The current 'get' case in mock-messages-do.ts (the logic
that implements getMessage) only searches storage.recentMessages (a 100-item
ring) so messages older than the truncation are returned as null; update the
code comments and the API docs to make this best-effort limitation explicit: add
a clear inline comment beside the case 'get' block referencing
storage.recentMessages and noting that messages are capped at 100 and older
messages are unretrievable, and update the /__mocks/messages/:id entry in
docs/agents/mock-api-servers.md (or the dashboardEndpoints description in
worker.ts) to document that getMessage is best-effort and may return 404 for
truncated messages.

In `@packages/worker/src/mcp/capabilities/apps/ui-get-app.ts`:
- Around line 56-58: The error message thrown when no saved UI artifact is found
("Saved UI artifact not found for this user.") should be made consistent with
the other capability; update the thrown Error in the row check inside the
handler in ui-get-app.ts to use the same wording as ui-load-app-source.ts
("Saved app not found for this user.") so both capabilities return the identical
message when a saved app is missing.

In `@packages/worker/src/mcp/ui-artifacts-repo.ts`:
- Around line 54-88: The functions updateUiArtifact and listAllUiArtifacts are
defined but unused; either remove their declarations to clean up dead code or
export them so they can be consumed elsewhere—if exporting, add them to the
module's exports (e.g., export async function updateUiArtifact(...) and export
function listAllUiArtifacts(...)) and update any barrel/index export files as
needed so other modules can import them; if removing, delete both function
definitions and any related types/params (e.g., the fields param and the
D1Database usage) and run a repo-wide search to ensure there are no remaining
references.

In `@packages/worker/src/mcp/ui-artifacts-search.ts`:
- Around line 15-23: The parseJsonStringArray function duplicates
parseStringArray from ui-get-app.ts; extract a shared utility (e.g., export a
new function parseStringArrayFromJson or reuse a common parseStringArray) into a
shared module such as ui-artifacts-types.ts or a common utils file, update both
mcp/ui-artifacts-search.ts (replace parseJsonStringArray usage) and
ui-get-app.ts (replace its local parseStringArray) to import and call the shared
utility, and ensure the exported function preserves the current behavior
(JSON.parse with Array.isArray check and string filtering) and retains the same
TypeScript signature (returns Array<string>).
- Around line 128-131: The code currently uses a non-null assertion on
getCapabilityVectorIndex(input.env) which can crash if the index is absent;
update the block around getCapabilityVectorIndex, embedTextForVectorize and
subsequent logic to defensively handle a null/undefined index: call
getCapabilityVectorIndex(input.env) into a local (e.g., capabilityIndex), check
if it is null/undefined and if so either use the offline fallback path
(respecting isCapabilitySearchOffline) or return/throw a clear error/early-exit
with a logged message; only call embedTextForVectorize and proceed to compute
topK and run the vector search when capabilityIndex is present. Ensure you
reference getCapabilityVectorIndex and embedTextForVectorize when modifying
control flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5242948-b166-4a18-a10e-03f8d918c089

📥 Commits

Reviewing files that changed from the base of the PR and between 0d3317c and ab8be92.

📒 Files selected for processing (27)
  • docs/agents/d1-legacy-export.md
  • docs/agents/mcp-apps-starter-guide.md
  • docs/agents/mock-api-servers.md
  • docs/agents/setup.md
  • docs/mcp-server-patterns.md
  • packages/mock-servers/ai/src/mock-requests-do.ts
  • packages/mock-servers/ai/src/worker.ts
  • packages/mock-servers/ai/wrangler.jsonc
  • packages/mock-servers/resend/src/mock-messages-do.ts
  • packages/mock-servers/resend/src/resend-mock.test.ts
  • packages/mock-servers/resend/src/worker.ts
  • packages/mock-servers/resend/wrangler.jsonc
  • packages/worker/client/mcp-apps/generated-ui-shell.ts
  • packages/worker/client/mcp-apps/widget-host-bridge.test.ts
  • packages/worker/migrations/0001-init.sql
  • packages/worker/migrations/0007-drop-mock-request-tables.sql
  • packages/worker/src/db.ts
  • packages/worker/src/index.ts
  • packages/worker/src/mcp/capabilities/apps/ui-get-app.ts
  • packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts
  • packages/worker/src/mcp/capabilities/apps/ui-save-app.ts
  • packages/worker/src/mcp/capabilities/unified-search.ts
  • packages/worker/src/mcp/mcp-server-e2e.test.ts
  • packages/worker/src/mcp/tools/open-generated-ui.ts
  • packages/worker/src/mcp/ui-artifacts-repo.ts
  • packages/worker/src/mcp/ui-artifacts-search.ts
  • packages/worker/src/mcp/ui-artifacts-vectorize.ts
💤 Files with no reviewable changes (2)
  • docs/agents/d1-legacy-export.md
  • packages/worker/src/db.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/worker/migrations/0001-init.sql
  • packages/worker/migrations/0007-drop-mock-request-tables.sql
  • packages/mock-servers/resend/src/resend-mock.test.ts
  • docs/mcp-server-patterns.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/worker/src/index.ts
  • packages/worker/src/mcp/ui-artifacts-vectorize.ts
  • packages/worker/src/mcp/capabilities/apps/ui-load-app-source.ts
  • docs/agents/mcp-apps-starter-guide.md
  • packages/worker/src/mcp/tools/open-generated-ui.ts
  • packages/worker/src/mcp/capabilities/apps/ui-save-app.ts
  • packages/worker/client/mcp-apps/widget-host-bridge.test.ts
  • packages/worker/client/mcp-apps/generated-ui-shell.ts
  • packages/worker/src/mcp/capabilities/unified-search.ts

cursoragent and others added 5 commits March 24, 2026 16:12
Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…ocumentation

- Updated `outputSchema` and related types to accept both 'html' and 'javascript' runtimes.
- Revised documentation to emphasize the preference for self-contained HTML documents in generated apps.
- Added tests for host messaging and tool result notifications to ensure proper integration.
- Refactored inline code examples in the generated UI shell for clarity and consistency.
@kentcdodds
kentcdodds force-pushed the cursor/mcp-app-specification-for-generative-ui-7d68 branch from ab8be92 to d26c8f7 Compare March 24, 2026 22:14
@kentcdodds
kentcdodds merged commit 6bd683c into main Mar 24, 2026
8 of 9 checks passed
@kentcdodds
kentcdodds deleted the cursor/mcp-app-specification-for-generative-ui-7d68 branch March 24, 2026 22:18

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.

await registerSearchTool(agent)
await registerExecuteTool(agent)
await registerOpenCalculatorUiTool(agent)
await registerOpenGeneratedUiTool(agent)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Saved app source tool never registered

High Severity

The shell reopens saved apps by calling tools/call with ui_load_app_source, but that tool is never registered on the MCP server. Only search, execute, and open_generated_ui are registered, so saved-app reopen fails in real hosts.

Additional Locations (1)
Fix in Cursor Fix in Web

}
if (/<html[\s>]/i.test(code)) {
return code.replace(/<html([\s>])/i, `<html$1><head>${injection}</head>`)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

HTML injection regex corrupts document tags

High Severity

injectIntoHtmlDocument uses regexes that capture the closing > (/<head([\s>]*)/ and /<html([\s>])/), then adds another > in replacement. This can emit malformed tags like <head>> and can strip <html> attributes.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants