Skip to content

Commit fd0d507

Browse files
committed
feat: add basic-server-angular example
1 parent a8ff280 commit fd0d507

File tree

21 files changed

+17635
-6562
lines changed

21 files changed

+17635
-6562
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
- run: |
2525
npx pkg-pr-new publish \
2626
. \
27+
./examples/basic-server-angular \
2728
./examples/basic-server-react \
2829
./examples/basic-server-vanillajs \
2930
./examples/budget-allocator-server \

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Standalone docs in `docs/` (listed in `typedoc.config.mjs` `projectDocuments`) c
8888

8989
Uses npm workspaces. Full examples in `examples/` are separate packages:
9090

91-
- `basic-server-*` - Starter templates (vanillajs, react, vue, svelte, preact, solid). Use these as the basis for new examples.
91+
- `basic-server-*` - Starter templates (vanillajs, react, vue, svelte, preact, solid, angular). Use these as the basis for new examples.
9292
- `basic-host` - Reference host implementation
9393
- Other examples showcase specific features (charts, 3D, video, etc.)
9494

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ The [`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/exa
8888

8989
| | |
9090
|:---:|:---|
91-
| [![Basic](examples/basic-server-react/grid-cell.png "Starter template")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) | The same app built with different frameworks — pick your favorite!<br><br>[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) · [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) · [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) · [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) · [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) · [Vanilla JS](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) |
91+
| [![Basic](examples/basic-server-react/grid-cell.png "Starter template")](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) | The same app built with different frameworks — pick your favorite!<br><br>[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) · [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) · [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) · [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) · [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) · [Angular](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-angular) · [Vanilla JS](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) |
9292
<!-- prettier-ignore-end -->
9393

9494
### Running the Examples
@@ -176,6 +176,16 @@ To use these examples with MCP clients that support the stdio transport (such as
176176
"--stdio"
177177
]
178178
},
179+
"basic-angular": {
180+
"command": "npx",
181+
"args": [
182+
"-y",
183+
"--silent",
184+
"--registry=https://registry.npmjs.org/",
185+
"@modelcontextprotocol/server-basic-angular",
186+
"--stdio"
187+
]
188+
},
179189
"budget-allocator": {
180190
"command": "npx",
181191
"args": [

docs/quickstart.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,5 +471,5 @@ You've built your first MCP App!
471471

472472
- **Continue learning**: The [`basic-server-vanillajs`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) example builds on this quickstart with host communication, theming, and lifecycle handlers
473473
- **React version**: Compare with [`basic-server-react`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) for a React-based UI
474-
- **Other frameworks**: See also [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue), [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte), [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact), and [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) examples
474+
- **Other frameworks**: See also [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue), [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte), [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact), [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid), and [Angular](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-angular) examples
475475
- **API reference**: See the full [API documentation](https://modelcontextprotocol.github.io/ext-apps/api/)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Example: Basic Server (Angular)
2+
3+
![Screenshot](screenshot.png)
4+
5+
An MCP App example with an Angular UI.
6+
7+
> [!TIP]
8+
> Looking for a vanilla JavaScript example? See [`basic-server-vanillajs`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs)!
9+
10+
## MCP Client Configuration
11+
12+
Add to your MCP client configuration (stdio transport):
13+
14+
```json
15+
{
16+
"mcpServers": {
17+
"basic-angular": {
18+
"command": "npx",
19+
"args": [
20+
"-y",
21+
"--silent",
22+
"--registry=https://registry.npmjs.org/",
23+
"@modelcontextprotocol/server-basic-angular",
24+
"--stdio"
25+
]
26+
}
27+
}
28+
}
29+
```
30+
31+
### Local Development
32+
33+
To test local modifications, use this configuration (replace `~/code/ext-apps` with your clone path):
34+
35+
```json
36+
{
37+
"mcpServers": {
38+
"basic-angular": {
39+
"command": "bash",
40+
"args": [
41+
"-c",
42+
"cd ~/code/ext-apps/examples/basic-server-angular && npm run build >&2 && node dist/index.js --stdio"
43+
]
44+
}
45+
}
46+
}
47+
```
48+
49+
## Overview
50+
51+
- Tool registration with a linked UI resource
52+
- Angular UI using signals and zoneless change detection
53+
- App communication APIs: [`callServerTool`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#callservertool), [`sendMessage`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendmessage), [`sendLog`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendlog), [`openLink`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#openlink)
54+
55+
## Key Files
56+
57+
- [`server.ts`](server.ts) - MCP server with tool and resource registration
58+
- [`mcp-app.html`](mcp-app.html) / [`src/mcp-app.ts`](src/mcp-app.ts) - Angular standalone component using MCP App SDK
59+
60+
## Getting Started
61+
62+
```bash
63+
npm install
64+
npm run dev
65+
```
66+
67+
## How It Works
68+
69+
1. The server registers a `get-time` tool with metadata linking it to a UI HTML resource (`ui://get-time/mcp-app.html`).
70+
2. When the tool is invoked, the Host renders the UI from the resource.
71+
3. The UI uses the MCP App SDK API to communicate with the host and call server tools.
72+
73+
## Build System
74+
75+
This example bundles into a single HTML file using Vite with `vite-plugin-singlefile` and `@analogjs/vite-plugin-angular` — see [`vite.config.ts`](vite.config.ts). This allows all UI content to be served as a single MCP resource. Alternatively, MCP apps can load external resources by defining [`_meta.ui.csp.resourceDomains`](https://modelcontextprotocol.github.io/ext-apps/api/interfaces/app.McpUiResourceCsp.html#resourcedomains) in the UI resource metadata.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Entry point for running the MCP server.
3+
* Run with: npx @modelcontextprotocol/server-basic-react
4+
* Or: node dist/index.js [--stdio]
5+
*/
6+
7+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
8+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
11+
import cors from "cors";
12+
import type { Request, Response } from "express";
13+
import { createServer } from "./server.js";
14+
15+
/**
16+
* Starts an MCP server with Streamable HTTP transport in stateless mode.
17+
*
18+
* @param createServer - Factory function that creates a new McpServer instance per request.
19+
*/
20+
export async function startStreamableHTTPServer(
21+
createServer: () => McpServer,
22+
): Promise<void> {
23+
const port = parseInt(process.env.PORT ?? "3001", 10);
24+
25+
const app = createMcpExpressApp({ host: "0.0.0.0" });
26+
app.use(cors());
27+
28+
app.all("/mcp", async (req: Request, res: Response) => {
29+
const server = createServer();
30+
const transport = new StreamableHTTPServerTransport({
31+
sessionIdGenerator: undefined,
32+
});
33+
34+
res.on("close", () => {
35+
transport.close().catch(() => {});
36+
server.close().catch(() => {});
37+
});
38+
39+
try {
40+
await server.connect(transport);
41+
await transport.handleRequest(req, res, req.body);
42+
} catch (error) {
43+
console.error("MCP error:", error);
44+
if (!res.headersSent) {
45+
res.status(500).json({
46+
jsonrpc: "2.0",
47+
error: { code: -32603, message: "Internal server error" },
48+
id: null,
49+
});
50+
}
51+
}
52+
});
53+
54+
const httpServer = app.listen(port, (err) => {
55+
if (err) {
56+
console.error("Failed to start server:", err);
57+
process.exit(1);
58+
}
59+
console.log(`MCP server listening on http://localhost:${port}/mcp`);
60+
});
61+
62+
const shutdown = () => {
63+
console.log("\nShutting down...");
64+
httpServer.close(() => process.exit(0));
65+
};
66+
67+
process.on("SIGINT", shutdown);
68+
process.on("SIGTERM", shutdown);
69+
}
70+
71+
/**
72+
* Starts an MCP server with stdio transport.
73+
*
74+
* @param createServer - Factory function that creates a new McpServer instance.
75+
*/
76+
export async function startStdioServer(
77+
createServer: () => McpServer,
78+
): Promise<void> {
79+
await createServer().connect(new StdioServerTransport());
80+
}
81+
82+
async function main() {
83+
if (process.argv.includes("--stdio")) {
84+
await startStdioServer(createServer);
85+
} else {
86+
await startStreamableHTTPServer(createServer);
87+
}
88+
}
89+
90+
main().catch((e) => {
91+
console.error(e);
92+
process.exit(1);
93+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta name="color-scheme" content="light dark">
7+
<title>Get Time App</title>
8+
<link rel="stylesheet" href="/src/global.css">
9+
</head>
10+
<body>
11+
<app-root></app-root>
12+
<script type="module" src="/src/mcp-app.ts"></script>
13+
</body>
14+
</html>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"name": "@modelcontextprotocol/server-basic-angular",
3+
"version": "1.0.1",
4+
"type": "module",
5+
"description": "Basic MCP App Server example using Angular",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/modelcontextprotocol/ext-apps",
9+
"directory": "examples/basic-server-angular"
10+
},
11+
"license": "MIT",
12+
"main": "dist/server.js",
13+
"types": "dist/server.d.ts",
14+
"bin": {
15+
"mcp-server-basic-angular": "dist/index.js"
16+
},
17+
"files": [
18+
"dist"
19+
],
20+
"exports": {
21+
".": {
22+
"types": "./dist/server.d.ts",
23+
"default": "./dist/server.js"
24+
}
25+
},
26+
"scripts": {
27+
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build && tsc -p tsconfig.server.json && bun build server.ts --outdir dist --target node && bun build main.ts --outfile dist/index.js --target node --external \"./server.js\" --banner \"#!/usr/bin/env node\"",
28+
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
29+
"serve": "bun --watch main.ts",
30+
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
31+
"dev": "cross-env NODE_ENV=development concurrently \"npm run watch\" \"npm run serve\"",
32+
"prepublishOnly": "npm run build"
33+
},
34+
"dependencies": {
35+
"@angular/common": "^21.0.0",
36+
"@angular/core": "^21.0.0",
37+
"@angular/forms": "^21.0.0",
38+
"@angular/platform-browser": "^21.0.0",
39+
"@modelcontextprotocol/ext-apps": "^1.0.0",
40+
"@modelcontextprotocol/sdk": "^1.24.0",
41+
"cors": "^2.8.5",
42+
"express": "^5.1.0",
43+
"rxjs": "^7.8.1",
44+
"tslib": "^2.8.0",
45+
"zod": "^4.1.13"
46+
},
47+
"devDependencies": {
48+
"@analogjs/vite-plugin-angular": "^2.2.3",
49+
"@angular/build": "^21.0.0",
50+
"@angular-devkit/build-angular": "^21.0.0",
51+
"@angular/compiler": "^21.0.0",
52+
"@angular/compiler-cli": "^21.0.0",
53+
"@types/cors": "^2.8.19",
54+
"@types/express": "^5.0.0",
55+
"@types/node": "22.10.0",
56+
"concurrently": "^9.2.1",
57+
"cross-env": "^10.1.0",
58+
"typescript": "^5.9.3",
59+
"vite": "^6.0.0",
60+
"vite-plugin-singlefile": "^2.3.0"
61+
}
62+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
2+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
4+
import fs from "node:fs/promises";
5+
import path from "node:path";
6+
7+
// Works both from source (server.ts) and compiled (dist/server.js)
8+
const DIST_DIR = import.meta.filename.endsWith(".ts")
9+
? path.join(import.meta.dirname, "dist")
10+
: import.meta.dirname;
11+
12+
/**
13+
* Creates a new MCP server instance with tools and resources registered.
14+
*/
15+
export function createServer(): McpServer {
16+
const server = new McpServer({
17+
name: "Basic MCP App Server (Angular)",
18+
version: "1.0.0",
19+
});
20+
21+
// Two-part registration: tool + resource, tied together by the resource URI.
22+
const resourceUri = "ui://get-time/mcp-app.html";
23+
24+
// Register a tool with UI metadata. When the host calls this tool, it reads
25+
// `_meta.ui.resourceUri` to know which resource to fetch and render as an
26+
// interactive UI.
27+
registerAppTool(server,
28+
"get-time",
29+
{
30+
title: "Get Time",
31+
description: "Returns the current server time as an ISO 8601 string.",
32+
inputSchema: {},
33+
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
34+
},
35+
async (): Promise<CallToolResult> => {
36+
const time = new Date().toISOString();
37+
return { content: [{ type: "text", text: time }] };
38+
},
39+
);
40+
41+
// Register the resource, which returns the bundled HTML/JavaScript for the UI.
42+
registerAppResource(server,
43+
resourceUri,
44+
resourceUri,
45+
{ mimeType: RESOURCE_MIME_TYPE },
46+
async (): Promise<ReadResourceResult> => {
47+
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
48+
return {
49+
contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
50+
};
51+
},
52+
);
53+
54+
return server;
55+
}

0 commit comments

Comments
 (0)