Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions packages/worker/src/app/saved-ui-hosted-html.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ test('renderHostedSavedUiHtml emits shared runtime assets for html apps', () =>
user_id: 'user-123',
title: 'Hosted App',
description: 'Hosted generated UI app',
keywords: 'hosted,generated-ui',
code: '<main>Hello</main>',
runtime: 'html',
parameters: null,
search_text: null,
created_at: '2026-03-27T00:00:00.000Z',
updated_at: '2026-03-27T00:00:00.000Z',
},
Expand Down Expand Up @@ -53,11 +51,9 @@ test('renderHostedSavedUiHtml keeps user javascript separate from runtime bootst
user_id: 'user-123',
title: 'Hosted JS App',
description: 'Hosted generated UI javascript app',
keywords: 'hosted,generated-ui,javascript',
code: 'document.querySelector("[data-generated-ui-root]")?.append("hello")',
runtime: 'javascript',
parameters: null,
search_text: null,
created_at: '2026-03-27T00:00:00.000Z',
updated_at: '2026-03-27T00:00:00.000Z',
},
Expand Down
6 changes: 1 addition & 5 deletions packages/worker/src/mcp/capabilities/apps/ui-get-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod'
import { defineDomainCapability } from '#mcp/capabilities/define-domain-capability.ts'
import { capabilityDomainNames } from '#mcp/capabilities/domain-metadata.ts'
import { type CapabilityContext } from '#mcp/capabilities/types.ts'
import { getUiArtifactById, parseStringArray } from '#mcp/ui-artifacts-repo.ts'
import { getUiArtifactById } from '#mcp/ui-artifacts-repo.ts'
import { requireMcpUser } from '#mcp/capabilities/meta/require-user.ts'
import {
parseUiArtifactParameters,
Expand All @@ -13,13 +13,11 @@ const outputSchema = z.object({
app_id: z.string(),
title: z.string(),
description: z.string(),
keywords: z.array(z.string()),
parameters: z.array(uiArtifactParameterSchema).nullable(),
code: z
.string()
.describe('Generated UI source code to render inside the generic shell.'),
runtime: z.string(),
search_text: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
})
Expand Down Expand Up @@ -55,11 +53,9 @@ export const uiGetAppCapability = defineDomainCapability(
app_id: row.id,
title: row.title,
description: row.description,
keywords: parseStringArray(row.keywords),
parameters: parseUiArtifactParameters(row.parameters),
code: row.code,
runtime: row.runtime,
search_text: row.search_text,
created_at: row.created_at,
updated_at: row.updated_at,
}
Expand Down
7 changes: 1 addition & 6 deletions packages/worker/src/mcp/capabilities/apps/ui-list-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import {
parseUiArtifactParameters,
uiArtifactParameterSchema,
} from '#mcp/ui-artifact-parameters.ts'
import {
listUiArtifactsByUserId,
parseStringArray,
} from '#mcp/ui-artifacts-repo.ts'
import { listUiArtifactsByUserId } from '#mcp/ui-artifacts-repo.ts'
import { requireMcpUser } from '#mcp/capabilities/meta/require-user.ts'

const outputSchema = z.object({
Expand All @@ -18,7 +15,6 @@ const outputSchema = z.object({
app_id: z.string(),
title: z.string(),
description: z.string(),
keywords: z.array(z.string()),
runtime: z.string(),
parameters: z.array(uiArtifactParameterSchema).nullable(),
created_at: z.string(),
Expand Down Expand Up @@ -47,7 +43,6 @@ export const uiListAppsCapability = defineDomainCapability(
app_id: row.id,
title: row.title,
description: row.description,
keywords: parseStringArray(row.keywords),
runtime: row.runtime,
parameters: parseUiArtifactParameters(row.parameters),
created_at: row.created_at,
Expand Down
14 changes: 1 addition & 13 deletions packages/worker/src/mcp/capabilities/apps/ui-save-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ const inputSchema = z.object({
.string()
.min(1)
.describe('What the saved app does and when it is useful.'),
keywords: z
.array(z.string())
.describe('Extra search keywords for discovery in the search tool.'),
code: z
.string()
.min(1)
Expand All @@ -33,12 +30,6 @@ const inputSchema = z.object({
.describe(
'Source format accepted by the generic UI shell. Prefer `html`; `javascript` is kept for legacy saved apps.',
),
search_text: z
.string()
.optional()
.describe(
'Optional retrieval-only text that improves search recall without being part of the visible app description.',
),
parameters: z
.array(uiArtifactParameterSchema)
.optional()
Expand Down Expand Up @@ -76,10 +67,8 @@ export const uiSaveAppCapability = defineDomainCapability(
user_id: user.userId,
title: args.title,
description: args.description,
keywords: JSON.stringify(args.keywords),
code: args.code,
runtime: args.runtime,
search_text: args.search_text ?? null,
parameters: parameters ? JSON.stringify(parameters) : null,
created_at: now,
updated_at: now,
Expand All @@ -92,8 +81,7 @@ export const uiSaveAppCapability = defineDomainCapability(
embedText: buildUiArtifactEmbedText({
title: args.title,
description: args.description,
keywords: args.keywords,
searchText: args.search_text ?? null,
code: args.code,
runtime: args.runtime,
parameters,
}),
Expand Down
27 changes: 2 additions & 25 deletions packages/worker/src/mcp/capabilities/apps/ui-update-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { capabilityDomainNames } from '#mcp/capabilities/domain-metadata.ts'
import { type CapabilityContext } from '#mcp/capabilities/types.ts'
import { requireMcpUser } from '#mcp/capabilities/meta/require-user.ts'
import { buildUiArtifactEmbedText } from '#mcp/ui-artifacts-embed.ts'
import {
getUiArtifactById,
parseStringArray,
updateUiArtifact,
} from '#mcp/ui-artifacts-repo.ts'
import { getUiArtifactById, updateUiArtifact } from '#mcp/ui-artifacts-repo.ts'
import { upsertUiArtifactVector } from '#mcp/ui-artifacts-vectorize.ts'
import {
normalizeUiArtifactParameters,
Expand All @@ -33,10 +29,6 @@ const inputSchema = z
.min(1)
.optional()
.describe('What the saved app does and when it is useful.'),
keywords: z
.array(z.string())
.optional()
.describe('Extra search keywords for discovery in the search tool.'),
code: z
.string()
.min(1)
Expand All @@ -50,12 +42,6 @@ const inputSchema = z
.describe(
'Source format accepted by the generic UI shell. Prefer `html`; `javascript` is kept for legacy saved apps.',
),
search_text: z
.string()
.optional()
.describe(
'Optional retrieval-only text that improves search recall without being part of the visible app description.',
),
parameters: z
.array(uiArtifactParameterSchema)
.optional()
Expand All @@ -67,10 +53,8 @@ const inputSchema = z
(value) =>
value.title !== undefined ||
value.description !== undefined ||
value.keywords !== undefined ||
value.code !== undefined ||
value.runtime !== undefined ||
value.search_text !== undefined ||
value.parameters !== undefined,
{
message: 'Provide at least one field to update.',
Expand Down Expand Up @@ -106,18 +90,12 @@ export const uiUpdateAppCapability = defineDomainCapability(
if (args.description !== undefined) {
updates.description = args.description
}
if (args.keywords !== undefined) {
updates.keywords = JSON.stringify(args.keywords)
}
if (args.code !== undefined) {
updates.code = args.code
}
if (args.runtime !== undefined) {
updates.runtime = args.runtime
}
if (args.search_text !== undefined) {
updates.search_text = args.search_text
}
if (args.parameters !== undefined) {
const parameters = normalizeUiArtifactParameters(args.parameters)
updates.parameters = parameters ? JSON.stringify(parameters) : null
Expand Down Expand Up @@ -149,8 +127,7 @@ export const uiUpdateAppCapability = defineDomainCapability(
embedText: buildUiArtifactEmbedText({
title: refreshed.title,
description: refreshed.description,
keywords: parseStringArray(refreshed.keywords),
searchText: refreshed.search_text,
code: refreshed.code,
runtime: refreshed.runtime,
parameters: parseUiArtifactParameters(refreshed.parameters),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ function createUiArtifactRow(appId: string): UiArtifactRow {
user_id: 'user-123',
title: 'Cloudflare deploy app',
description: 'Saved UI for deploying a Worker',
keywords: JSON.stringify(['cloudflare', 'deploy']),
code: '<main>Deploy</main>',
runtime: 'html',
parameters: JSON.stringify([
Expand All @@ -42,7 +41,6 @@ function createUiArtifactRow(appId: string): UiArtifactRow {
required: true,
},
]),
search_text: 'deploy worker ui',
created_at: '2026-03-20T00:00:00.000Z',
updated_at: '2026-03-20T00:00:00.000Z',
}
Expand Down
24 changes: 5 additions & 19 deletions packages/worker/src/mcp/mcp-server.mcp-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,6 @@ test('mcp server searches capabilities', async () => {
expect(saveApp?.requiredInputFields).toEqual([
'title',
'description',
'keywords',
'code',
])
expect(saveApp?.readOnly).toBeUndefined()
Expand Down Expand Up @@ -725,7 +724,6 @@ test('mcp server executes ui_save_app via execute tool', async () => {
await codemode.ui_save_app({
title: 'Execute generated app',
description: 'Saved through execute.',
keywords: ['execute', 'ui'],
code: '<main><h1>Execute App</h1></main>',
})`,
},
Expand Down Expand Up @@ -919,10 +917,8 @@ test('mcp server saves app, search returns app hit, and open_generated_ui suppor
code: `async () =>
await codemode.ui_save_app({
title: 'Saved Searchable App',
description: 'Saved generated UI artifact for search and reopen.',
keywords: ['saved', 'searchable', 'ui'],
code: '<main><h1>Saved Searchable App</h1></main>',
search_text: 'searchable saved ui artifact demo',
description: 'Saved generated UI artifact demo for search and reopen.',
code: '<main><h1>Saved Searchable App Demo</h1></main>',
})`,
},
})
Expand Down Expand Up @@ -993,8 +989,7 @@ test('mcp server supports parameterized saved apps with resolved runtime params'
code: `async () =>
await codemode.ui_save_app({
title: 'Parameterized Greeting App',
description: 'Reusable greeting UI with runtime params.',
keywords: ['greeting', 'parameterized', 'ui'],
description: 'Parameterized greeting UI app with runtime params.',
parameters: [
{
name: 'name',
Expand All @@ -1010,7 +1005,6 @@ test('mcp server supports parameterized saved apps with resolved runtime params'
},
],
code: '<main><h1>Greeting App</h1></main>',
search_text: 'parameterized greeting ui app',
})`,
},
})
Expand Down Expand Up @@ -1214,10 +1208,8 @@ test('generated ui sessions support secret storage, execute-time resolution, and
code: `async () =>
await codemode.ui_save_app({
title: 'Secrets App',
description: 'Generated UI for secret-backed deployments.',
keywords: ['secret', 'deploy'],
code: '<main><h1>Secrets App</h1></main>',
search_text: 'cloudflare deploy secret app',
description: 'Cloudflare deploy secret app for secret-backed deployments.',
code: '<main><h1>Secrets App</h1><p>Cloudflare deploy</p></main>',
})`,
},
})
Expand Down Expand Up @@ -1799,7 +1791,6 @@ test('mcp server deletes saved ui app artifacts', async () => {
await codemode.ui_save_app({
title: 'Delete Me App',
description: 'Saved app to delete.',
keywords: ['delete', 'ui'],
code: '<main><h1>Delete Me</h1></main>',
})`,
},
Expand Down Expand Up @@ -1861,7 +1852,6 @@ test('mcp server updates saved ui app artifacts', async () => {
await codemode.ui_save_app({
title: 'Original App',
description: 'Original app description.',
keywords: ['original', 'ui'],
code: '<main><h1>Original</h1></main>',
})`,
},
Expand All @@ -1883,10 +1873,8 @@ test('mcp server updates saved ui app artifacts', async () => {
app_id: ${JSON.stringify(appId)},
title: 'Updated App',
description: 'Updated description.',
keywords: ['updated', 'ui'],
code: '<main><h1>Updated</h1></main>',
runtime: 'javascript',
search_text: 'updated searchable text',
})`,
},
})
Expand Down Expand Up @@ -1919,8 +1907,6 @@ test('mcp server updates saved ui app artifacts', async () => {
| undefined
expect(getPayload?.title).toBe('Updated App')
expect(getPayload?.description).toBe('Updated description.')
expect(getPayload?.keywords).toEqual(['updated', 'ui'])
expect(getPayload?.code).toBe('<main><h1>Updated</h1></main>')
expect(getPayload?.runtime).toBe('javascript')
expect(getPayload?.search_text).toBe('updated searchable text')
})
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ test('infers static codemode member names', () => {
return await codemode.ui_save_app({
title: 'Demo',
description: 'Demo app',
keywords: ['demo'],
source: '<main>demo</main>',
code: '<main>demo</main>',
})
}`
const { staticNames, inferencePartial } = inferCodemodeCapabilities(src)
Expand Down
3 changes: 1 addition & 2 deletions packages/worker/src/mcp/tools/search.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ test('optional search rows fall back when saved skills lookup fails', async () =
user_id: 'user-123',
title: 'Roku remote',
description: 'Saved remote UI',
keywords: 'roku remote',
code: '<div />',
runtime: 'html',
search_text: 'roku remote',
parameters: null,
created_at: '2026-03-24T00:00:00.000Z',
updated_at: '2026-03-24T00:00:00.000Z',
},
Expand Down
6 changes: 2 additions & 4 deletions packages/worker/src/mcp/ui-artifacts-embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ export function buildUiArtifactEmbedText(
input: {
title: string
description: string
keywords: Array<string>
code: string
runtime: string
searchText: string | null
parameters?: Array<{
name: string
description: string
Expand Down Expand Up @@ -39,12 +38,11 @@ export function buildUiArtifactEmbedText(
const text = [
input.title,
input.description,
input.keywords.join(' '),
input.code,
input.runtime,
'mcp app',
'ui artifact',
...(parameterText ? [parameterText] : []),
...(input.searchText ? [input.searchText] : []),
].join('\n')
return text.slice(0, maxChars)
Comment on lines 38 to 47

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 | 🟠 Major

Keep structured app metadata ahead of raw source in the embed budget.

Line 41 now inserts the full code before runtime and parameterText, then Line 47 truncates the whole document. For larger saved apps, parameter names/descriptions get cut off entirely, so searches for things like a runtime param or default value stop finding the app even though that metadata still exists in the row.

✂️ Suggested fix
-	const text = [
-		input.title,
-		input.description,
-		input.code,
-		input.runtime,
-		'mcp app',
-		'ui artifact',
-		...(parameterText ? [parameterText] : []),
-	].join('\n')
-	return text.slice(0, maxChars)
+	const metadataText = [
+		input.title,
+		input.description,
+		input.runtime,
+		'mcp app',
+		'ui artifact',
+		...(parameterText ? [parameterText] : []),
+	].join('\n')
+	const remainingChars = Math.max(0, maxChars - metadataText.length - 1)
+	const codeText = input.code.slice(0, remainingChars)
+	return [metadataText, codeText].filter(Boolean).join('\n')
📝 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
const text = [
input.title,
input.description,
input.keywords.join(' '),
input.code,
input.runtime,
'mcp app',
'ui artifact',
...(parameterText ? [parameterText] : []),
...(input.searchText ? [input.searchText] : []),
].join('\n')
return text.slice(0, maxChars)
const metadataText = [
input.title,
input.description,
input.runtime,
'mcp app',
'ui artifact',
...(parameterText ? [parameterText] : []),
].join('\n')
const remainingChars = Math.max(0, maxChars - metadataText.length - 1)
const codeText = input.code.slice(0, remainingChars)
return [metadataText, codeText].filter(Boolean).join('\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/ui-artifacts-embed.ts` around lines 38 - 47, The
embed currently places raw source (input.code) before structured metadata so
slice(maxChars) can chop off runtime/parameterText; update the construction in
ui-artifacts-embed.ts so structured metadata (input.title, input.description,
input.runtime, 'mcp app', 'ui artifact', parameterText) appear before the
source, and then append input.code last (or build structuredPart + codePart and,
if truncated to maxChars, only trim the codePart) so that runtime and
parameterText are preserved in the final sliced text; adjust variables
text/structuredPart/codePart and the final return to ensure parameterText and
runtime are kept ahead of the code when enforcing maxChars.

}
Loading
Loading