diff --git a/mcpapps/app.html b/mcpapps/app.html
new file mode 100644
index 0000000..7548859
--- /dev/null
+++ b/mcpapps/app.html
@@ -0,0 +1,749 @@
+
+
+
+
+
+
+{{TITLE}}
+
+
+
+Waiting for tool output…
+
+
+
\ No newline at end of file
diff --git a/mcpapps/apps.go b/mcpapps/apps.go
new file mode 100644
index 0000000..4a571d6
--- /dev/null
+++ b/mcpapps/apps.go
@@ -0,0 +1,15 @@
+package mcpapps
+
+import (
+ _ "embed"
+ "strings"
+)
+
+//go:embed app.html
+var appHTMLTemplate string
+
+// HTML renders one shared MCP App document for a known AgentDock view.
+// View and title are internal constants owned by AgentDock/NexusDock, not user input.
+func HTML(view, title string) string {
+ return strings.NewReplacer("{{VIEW}}", view, "{{TITLE}}", title).Replace(appHTMLTemplate)
+}
diff --git a/mcpapps/apps_test.go b/mcpapps/apps_test.go
new file mode 100644
index 0000000..6cb893f
--- /dev/null
+++ b/mcpapps/apps_test.go
@@ -0,0 +1,51 @@
+package mcpapps
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestHTMLRendersSharedMCPAppTemplate(t *testing.T) {
+ html := HTML("recall", "Recall")
+ for _, marker := range []string{
+ `Recall`,
+ `expectedView="recall"`,
+ `rpcRequest("ui/initialize"`,
+ `protocolVersion:"2026-01-26"`,
+ `rpcNotify("ui/notifications/initialized"`,
+ `message.method==="ui/notifications/tool-result"`,
+ } {
+ if !strings.Contains(html, marker) {
+ t.Fatalf("shared MCP App missing marker %q", marker)
+ }
+ }
+ for _, forbidden := range []string{"{{VIEW}}", "{{TITLE}}", "window.openai", "openai/widget", "openai/outputTemplate"} {
+ if strings.Contains(html, forbidden) {
+ t.Fatalf("shared MCP App contains forbidden marker %q", forbidden)
+ }
+ }
+}
+
+func TestHTMLUsesStandardHostContextThemeAndSemanticTokens(t *testing.T) {
+ html := HTML("workflow", "Workflow")
+ for _, marker := range []string{
+ `:root{color-scheme:light dark;`,
+ `:root[data-theme="dark"]{color-scheme:dark;`,
+ `@media(prefers-color-scheme:dark)`,
+ `--ad-text-primary:`,
+ `var(--ad-text-primary)`,
+ `const initialized=await rpcRequest("ui/initialize"`,
+ `applyHostContext(initialized&&initialized.hostContext)`,
+ `message.method==="ui/notifications/host-context-changed"`,
+ `applyHostContext(message.params)`,
+ `context.styles.variables`,
+ `window.matchMedia("(prefers-color-scheme: dark)")`,
+ } {
+ if !strings.Contains(html, marker) {
+ t.Fatalf("shared MCP App missing theme marker %q", marker)
+ }
+ }
+ if strings.Contains(html, `:root{color-scheme:light;font:`) {
+ t.Fatal("shared MCP App still forces a light-only color scheme")
+ }
+}