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
16 changes: 8 additions & 8 deletions .trinity/seals/t27mcp_T27mcpVisualOutput.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:076a0683dcc4e8487922bd6ff561422a1dfa707069e639fffaafea9d70ad8e2d",
"gen_hash_rust": "sha256:6267998500edaecfe3378c6ff7ed7482d1b0c239e1b8480fa697a8fd05b0d7b8",
"gen_hash_verilog": "sha256:241e62e3030919b4f540ff38480f8d726a4cc8301fbc30443369e7978adbbb1b",
"gen_hash_zig": "sha256:e7446390463513c4496f484f375ef59ae2f260172c8e46326f21008d84860411",
"gen_hash_c": "sha256:42e25ac4e6d33d72e11f45318478ef154bfc23b77daaa9a63e92d482718381c7",
"gen_hash_rust": "sha256:51c33ae8d38c5eebeaf38905c7577519df66fc057adee87a3d65c4eacf78818a",
"gen_hash_verilog": "sha256:a8af847801b01cfc44a3a88761714a6bf699fc91553ed72b5eaae7b40f60d5df",
"gen_hash_zig": "sha256:eb037cb00adb5f970d714f5c6a4d182289f326d3f8e890c124ebc5c3ef571b19",
"module": "T27mcpVisualOutput",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T15:31:37Z",
"sealed_at": "2026-10-18T17:13:49Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:06c1c5e73142e2f79fa5d7be037eace63c1f20597a6e33274ebf8e1b428b7584",
"spec_hash": "sha256:2c44da44a1f662108a7b3558e763ff86e89db4bc9f49fe749dd7617051343960",
"spec_path": "specs/t27mcp/visual_output.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 9,
"total": 9,
"passed": 12,
"total": 12,
"vacuous": 0
},
"toolchain": {
Expand Down
72 changes: 71 additions & 1 deletion specs/t27mcp/visual_output.t27
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
; readable when F * INLINE_W >= MIN_FONT_PX * W for its smallest text. A tool SVG is embedded as it is only
; when readable and at most INLINE_MAX_BYTES (the model re-emits it, so its bytes are output); otherwise it
; is redrawn at INLINE_W from t27f_outline rows.
; UI resource (MCP Apps, the extension UI_EXTENSION): a client says it renders MCP Apps by naming
; UI_EXTENSION in its initialize capabilities with UI_MIME among its mimeTypes. Only that client is
; HOST_MCP_APPS (client_host); a client that says nothing is served the file path, the floor every host
; reads, since the server cannot tell the desktop app from the CLI. On HOST_MCP_APPS t27f_view and
; t27f_graph add a ui:// resource to their tools/call result: the SVG between UI_PAGE[0..2], a page with
; no script and nothing fetched. Each tool names its URI (UI_URI_*) in its tools/list _meta.ui.resourceUri,
; and resources/read of that URI returns the tool's latest drawing, UI_EMPTY before the first. Other hosts
; get no resource: its bytes would reach the model as text. With no drawing there is nothing to carry, so
; HOST_MCP_APPS falls back to the markdown table.
; phi^2 + 1/phi^2 = 3 | TRINITY

module T27mcpVisualOutput;
Expand Down Expand Up @@ -68,6 +77,20 @@ pub const INLINE_W : u32 = 680;
pub const MIN_FONT_PX : u32 = 11;
pub const INLINE_MAX_BYTES : u32 = 16384;

/// The MCP Apps resource: the capability a client names, the page's mime type, one URI per t27f tool,
/// the page around the SVG (the SVG goes between [1] and [2], the title between [0] and [1]), and the
/// page served before a tool has drawn.
pub const UI_EXTENSION : str = "io.modelcontextprotocol/ui";
pub const UI_MIME : str = "text/html;profile=mcp-app";
pub const UI_URI_VIEW : str = "ui://t27f/view";
pub const UI_URI_GRAPH : str = "ui://t27f/graph";
pub const UI_PAGE : [3]str = [
"<!doctype html><html><head><meta charset=utf-8><title>",
"</title><style>body{margin:0;background:#000}svg{display:block;width:100%;height:auto}</style></head><body>",
"</body></html>"
];
pub const UI_EMPTY : str = "<!doctype html><html><head><meta charset=utf-8><title>t27f</title></head><body>No drawing yet: call the tool first.</body></html>";

/// Where a tool SVG's drawing comes from.
pub const SRC_EMBED : u32 = 0;
pub const SRC_REDRAW : u32 = 1;
Expand Down Expand Up @@ -127,7 +150,10 @@ pub fn answer_channel(host: u32, inline_tool: bool, svg: bool) -> u32 {
if (svg) { return CH_FILE_AND_PANE; }
return CH_MARKDOWN_TABLE;
}
if (host == HOST_MCP_APPS) { return CH_UI_RESOURCE; }
if (host == HOST_MCP_APPS) {
if (svg) { return CH_UI_RESOURCE; }
return CH_MARKDOWN_TABLE;
}
if (host == HOST_SITE) { return CH_LIVE_PLAYER; }
if (host == HOST_TRI_CLAW) {
if (svg) { return CH_IMAGE_URL; }
Expand All @@ -137,6 +163,20 @@ pub fn answer_channel(host: u32, inline_tool: bool, svg: bool) -> u32 {
return CH_MARKDOWN_TABLE;
}

/// The host an MCP client is, from its initialize capabilities: `ui_extension`, it names UI_EXTENSION;
/// `ui_mime`, UI_MIME is among that extension's mimeTypes. Anything less is served the file path.
pub fn client_host(ui_extension: bool, ui_mime: bool) -> u32 {
if (ui_extension) {
if (ui_mime) { return HOST_MCP_APPS; }
}
return HOST_CLAUDE_CODE_CLI;
}

/// Whether a t27f tool's tools/call result carries its drawing as a ui:// resource on `host`.
pub fn carries_ui_resource(host: u32, svg: bool) -> bool {
return answer_channel(host, false, svg) == CH_UI_RESOURCE;
}

/// Whether text of `font_px` in a drawing `view_w` wide is at least MIN_FONT_PX when shown INLINE_W wide.
pub fn inline_readable(view_w: u32, font_px: u32) -> bool {
if (view_w == 0) { return false; }
Expand Down Expand Up @@ -204,6 +244,36 @@ test "answer_channel: with no inline tool or no drawing, a path or a markdown ta
assert answer_channel(HOST_MCP_APPS, false, true) == CH_UI_RESOURCE;
assert answer_channel(HOST_SITE, false, true) == CH_LIVE_PLAYER;
assert answer_channel(HOST_COUNT, false, true) == CH_FILE_PATH;
assert answer_channel(HOST_MCP_APPS, false, false) == CH_MARKDOWN_TABLE;
assert answer_channel(HOST_MCP_APPS, true, false) == CH_MARKDOWN_TABLE;
}

test "client_host: only a client naming the extension and its mime is an MCP Apps host" {
assert client_host(true, true) == HOST_MCP_APPS;
assert client_host(true, false) == HOST_CLAUDE_CODE_CLI;
assert client_host(false, true) == HOST_CLAUDE_CODE_CLI;
assert client_host(false, false) == HOST_CLAUDE_CODE_CLI;
}

test "carries_ui_resource: an MCP Apps host gets the drawing as a ui:// resource" {
assert carries_ui_resource(HOST_MCP_APPS, true);
assert carries_ui_resource(client_host(true, true), true);
assert answer_channel(HOST_MCP_APPS, true, true) == CH_UI_RESOURCE;
assert UI_EXTENSION == "io.modelcontextprotocol/ui";
assert UI_MIME == "text/html;profile=mcp-app";
assert UI_URI_VIEW == "ui://t27f/view";
assert UI_URI_GRAPH == "ui://t27f/graph";
assert UI_PAGE[2] == "</body></html>";
}

test "negative control: no resource without a drawing, or on a host that did not ask for one" {
assert carries_ui_resource(HOST_MCP_APPS, false) == false;
assert carries_ui_resource(client_host(true, false), true) == false;
assert carries_ui_resource(client_host(false, false), true) == false;
assert carries_ui_resource(HOST_CLAUDE_CODE_DESKTOP, true) == false;
assert carries_ui_resource(HOST_TRI_CLAW, true) == false;
assert carries_ui_resource(HOST_SITE, true) == false;
assert carries_ui_resource(HOST_COUNT, true) == false;
}

test "inline_readable: the measured drawings" {
Expand Down
Loading