/*
RFC-1211: MCP-UI resource projection for the stateless MCP subset. ui://
resource descriptors derive from the active capability catalog (records
declaring ui.resource === true) — suppressed/disabled capabilities are absent
from the serialized catalog upstream, so the emitted set always mirrors the
manifest. resources/read resolves a ui:// URI to the committed
/.well-known/ui/.html artifact via the knowledge port.
- Do not render UI here — artifacts are committed generated files
emitted by agent.ui.generate; the gate only serves them.
- Do not add ui/notifications/* or interactive bridges — v1 resources
are read-only text/html.
- RFC-1211: initial implementation — ui:// list/read helpers + tool-result resource block.
*/
import type { CapabilityRecord } from "@warpgogol/werkstatt-shared/ontology";
import {
parseUiResourceUri,
UI_RESOURCE_MIME,
uiResourcePath,
uiResourceUri,
} from "@warpgogol/werkstatt-shared/agent/capability-schema";
export interface McpUiResourceDescriptor {
uri: string;
name: string;
mimeType: string;
title: string;
}
/** RFC-1211: capabilities carrying a ui:// projection in the given catalog. */
export function uiCapableCapabilities(catalog: readonly CapabilityRecord[]): CapabilityRecord[] {
return catalog
.filter((cap) => cap.ui?.resource === true)
.sort((a, b) => a.id.localeCompare(b.id));
}
/** resources/list entries for the ui:// emission set. */
export function buildUiResourceList(
site: string,
catalog: readonly CapabilityRecord[],
): McpUiResourceDescriptor[] {
return uiCapableCapabilities(catalog).map((cap) => ({
uri: uiResourceUri(site, cap.id),
name: cap.id,
mimeType: UI_RESOURCE_MIME,
title: Object.values(cap.title)[0] ?? cap.id,
}));
}
/**
* Resolve a ui:// URI to its committed public path when it belongs to this
* site AND names a ui-capable catalog record. Returns null for foreign-site
* or non-ui URIs — the caller answers INVALID_PARAMS (unknown resource).
*/
export function resolveUiResourcePath(
uri: string,
site: string,
catalog: readonly CapabilityRecord[],
): string | null {
const parsed = parseUiResourceUri(uri);
if (!parsed || parsed.site !== site) return null;
const cap = catalog.find((c) => c.id === parsed.capabilityId);
if (!cap || cap.ui?.resource !== true) return null;
return uiResourcePath(parsed.capabilityId);
}
/**
* RFC-1211: the {type:"resource"} content block appended to tool results of
* ui-capable capabilities. `text` carries the committed artifact bytes when
* the knowledge port could read them — otherwise the block degrades to the
* uri+mimeType reference and hosts fetch via resources/read.
*/
export function uiResourceContentBlock(
site: string,
capabilityId: string,
text: string | null,
): { type: "resource"; resource: { uri: string; mimeType: string; text?: string } } {
return {
type: "resource",
resource: {
uri: uiResourceUri(site, capabilityId),
mimeType: UI_RESOURCE_MIME,
...(text !== null ? { text } : {}),
},
};
}