iToverDose/Software· 25 APRIL 2026 · 04:03

Build Cross-Platform MCP Apps Using Angular for Interactive UIs

Angular streamlines the creation of Model Context Protocol applications with reusable, interactive UIs that render directly inside AI chat interfaces. Learn how to bundle your frontend once and deploy across any MCP-compatible host.

DEV Community5 min read0 Comments

The rise of AI-powered assistants has transformed how users interact with tools, but traditional MCP servers often leave users staring at raw JSON responses. Model Context Protocol (MCP) Apps break this barrier by enabling developers to ship self-contained, interactive user interfaces that render natively within the conversation. Unlike conventional approaches, these UIs are portable across any MCP-compatible host—whether it’s a desktop client like Claude Desktop or a custom AI assistant.

This guide walks through building MCP Apps with Angular, from setting up a server to creating two distinct UIs that share a common codebase. You’ll learn how to bundle your Angular application into a single HTML file, ensuring it works seamlessly across all MCP-supported environments without bloated dependencies.

How MCP Apps Bridge Server and UI

MCP Apps rely on a lightweight communication layer between the MCP server and the Angular frontend. The architecture follows a three-tier model:

  • MCP Server: Registers tools and resources, linking each tool to a specific UI endpoint.
  • Host (AppBridge): Fetches the UI resource and renders it in a sandboxed iframe within the chat interface.
  • Angular View: Runs inside the iframe, using the @modelcontextprotocol/ext-apps library to communicate bidirectionally with the host.

The magic happens during bundling. Using Vite and its vite-plugin-singlefile, your entire Angular application collapses into a single self-contained HTML file. The host doesn’t need to know it’s running Angular—it just loads the HTML, making your UI universally compatible with any MCP-compliant system.

Project Setup: A Minimal Angular MCP App

Start by scaffolding a project with distinct entry points for each UI component. The structure below keeps shared logic centralized while allowing specialized UIs for different tools:

basic-server-angular/
├── mcp-app.html           # UI entry for the "Get Time" tool
├── greeting-app.html      # UI entry for the "Greeting" tool
├── src/
│   ├── main.ts            # Angular bootstrap for the "Get Time" tool
│   ├── app.component.ts   # Core component for time display
│   ├── greeting-main.ts    # Angular bootstrap for the "Greeting" tool
│   ├── greeting.component.ts # Component for personalized greetings
│   ├── shared/
│   │   └── mcp-app-setup.ts # Shared initialization and theming
│   └── global.css         # Host-aware styling variables
├── server.ts              # MCP server registering tools and resources
├── main.ts                # Server entry point (HTTP + stdio)
└── vite.config.ts         # Vite configuration for single-file bundling

This modular approach ensures code reuse while maintaining clear separation between different UI workflows.

Step 1: Configure the MCP Server

The server acts as the bridge between your tools and their corresponding UIs. Begin by initializing a basic MCP server and registering tools with their associated resource URIs:

// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import fs from "node:fs/promises";
import path from "node:path";
import { 
  registerAppTool, 
  registerAppResource, 
  RESOURCE_MIME_TYPE, 
} from "@modelcontextprotocol/ext-apps/server";

const server = new McpServer({
  name: "Angular MCP App Server",
  version: "1.0.0",
});

// Register the "Get Time" tool and link it to its UI
registerAppTool(server, "get-time", {
  title: "Get Current Time",
  description: "Fetches the server's current time in ISO 8601 format.",
  inputSchema: {},
  _meta: {
    ui: {
      resourceUri: "ui://get-time/mcp-app.html" // Maps tool to UI
    }
  }
}, async (): Promise<CallToolResult> => {
  const time = new Date().toISOString();
  return {
    content: [{ type: "text", text: time }]
  };
});

// Register the UI resource for the tool
registerAppResource(
  server,
  "ui://get-time/mcp-app.html",
  "ui://get-time/mcp-app.html",
  { mimeType: RESOURCE_MIME_TYPE },
  async (): Promise<ReadResourceResult> => {
    const html = await fs.readFile(
      path.join(DIST_DIR, "mcp-app.html"),
      "utf-8"
    );
    return {
      contents: [{
        uri: "ui://get-time/mcp-app.html",
        mimeType: RESOURCE_MIME_TYPE,
        text: html
      }]
    };
  }
);

The _meta.ui.resourceUri field is critical—it tells the MCP host which HTML file to load when this tool is invoked, enabling seamless UI integration.

Step 2: Create the Angular Entry Point

Each UI requires a dedicated HTML file at the project root. This file serves as the bundling entry point for Vite and must include Angular’s runtime dependencies:

<!-- mcp-app.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="color-scheme" content="light dark">
  <title>Get Current Time</title>
  <link rel="stylesheet" href="/src/global.css">
</head>
<body>
  <app-root></app-root>
  <script type="module" src="/src/main.ts"></script>
</body>
</html>

This minimal template avoids external dependencies, ensuring the bundled file remains lightweight and portable.

Step 3: Bootstrap the Angular Application

Angular 19+ simplifies this process with zoneless change detection, which improves performance in embedded contexts. Initialize your app with a streamlined setup:

// src/main.ts
import "@angular/compiler";
import { bootstrapApplication } from "@angular/platform-browser";
import { provideZonelessChangeDetection } from "@angular/core";
import { AppComponent } from "./app.component";
import "./global.css";

bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()],
}).catch((err) => console.error("Bootstrap failed:", err));

The component itself integrates with the MCP host using the App class from @modelcontextprotocol/ext-apps. This class provides methods to handle host context, theme adjustments, and secure communication:

// src/app.component.ts
import { Component, OnInit, signal } from "@angular/core";
import {
  App, 
  applyDocumentTheme, 
  applyHostStyleVariables,
  type McpUiHostContext
} from "@modelcontextprotocol/ext-apps";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";

@Component({
  selector: "app-root",
  template: `
    <main [style.padding-top.px]="hostContext()?.safeAreaInsets?.top"
          [style.padding-right.px]="hostContext()?.safeAreaInsets?.right"
          [style.padding-bottom.px]="hostContext()?.safeAreaInsets?.bottom"
          [style.padding-left.px]="hostContext()?.safeAreaInsets?.left">
      <p><strong>Server Time:</strong> <code>{{ serverTime() }}</code></p>
      <button (click)="handleGetTime()">Refresh Time</button>
    </main>
  `
})
export class AppComponent implements OnInit {
  private app = new App();
  hostContext = signal<McpUiHostContext | null>(null);
  serverTime = signal<string>("");

  ngOnInit() {
    this.app.onHostContextChange((context) => {
      this.hostContext.set(context);
      applyHostStyleVariables(context);
      applyDocumentTheme(context);
    });
  }

  async handleGetTime() {
    const result = await this.app.callTool("get-time", {});
    this.serverTime.set(extractText(result));
  }
}

function extractText(result: CallToolResult): string {
  return result.content?.find((c) => c.type === "text")?.text ?? "";
}

This component demonstrates real-time synchronization with the MCP host, including adaptive padding based on the host’s safe area insets and dynamic theme adoption.

Key Takeaways for Scalable MCP Development

Building MCP Apps with Angular offers several advantages:

  • Portability: Your UI works across all MCP-compatible hosts without modification.
  • Performance: Single-file bundling minimizes runtime overhead in embedded environments.
  • Consistency: Shared codebases reduce duplication while allowing tool-specific customizations.
  • User Experience: Interactive UIs replace passive JSON responses with intuitive interactions.

As MCP adoption grows, the ability to deliver rich, embedded experiences will become a differentiator for AI-powered tools. By mastering this pattern now, you position your applications for seamless integration into tomorrow’s AI ecosystems.

The future of human-AI interaction lies in interfaces that feel native, not bolted-on—and MCP Apps are the key to making that vision a reality.

AI summary

MCP standartlarına uyumlu yapay zeka asistanlarında etkileşimli Angular arayüzleri nasıl oluşturabilirsiniz? Tüm bağımlılıkları tek bir HTML dosyasına paketlemenin püf noktalarını keşfedin.

Comments

00
LEAVE A COMMENT
ID #HE9R50

0 / 1200 CHARACTERS

Human check

7 + 9 = ?

Will appear after editor review

Moderation · Spam protection active

No approved comments yet. Be first.