Skip to content

Commit 2df8bb1

Browse files
Add integration-server example for E2E testing
Add dedicated `integration-server` example (duplicated from `basic-server-react`) to preserve E2E testing capabilities while allowing `basic-server-react` to be simplified as a minimal example. Changes: - Add `examples/integration-server` exercising SDK communication APIs - Update E2E tests to use `integration-server` instead of `basic-server-react` and `basic-server-vanillajs` - Use server name labels instead of indices for more robust test selection - Remove `basic-react.png` and `basic-vanillajs.png` golden snapshots 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 9fa2c2a commit 2df8bb1

File tree

16 files changed

+710
-95
lines changed

16 files changed

+710
-95
lines changed

examples/basic-server-react/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const RESOURCE_URI = "ui://get-time/mcp-app.html";
1313
*/
1414
function createServer(): McpServer {
1515
const server = new McpServer({
16-
name: "Basic MCP App Server (React-based)",
16+
name: "Basic MCP App Server (React)",
1717
version: "1.0.0",
1818
});
1919

examples/basic-server-react/src/mcp-app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ function GetTimeAppInner({ app, toolResult }: GetTimeAppInnerProps) {
120120

121121
<div className={styles.action}>
122122
<p>
123-
<strong>Server Time:</strong> <code>{serverTime}</code>
123+
<strong>Server Time:</strong> <code id="server-time">{serverTime}</code>
124124
</p>
125125
<button onClick={handleGetTime}>Get Server Time</button>
126126
</div>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Example: Integration Test Server
2+
3+
An MCP App example used for E2E integration testing.
4+
5+
## Overview
6+
7+
This example demonstrates all App SDK communication APIs and is used by the E2E test suite to verify host-app interactions:
8+
9+
- Tool registration with a linked UI resource
10+
- React UI using the [`useApp()`](https://modelcontextprotocol.github.io/ext-apps/api/functions/_modelcontextprotocol_ext-apps_react.useApp.html) hook
11+
- 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)
12+
13+
## Key Files
14+
15+
- [`server.ts`](server.ts) - MCP server with tool and resource registration
16+
- [`mcp-app.html`](mcp-app.html) / [`src/mcp-app.tsx`](src/mcp-app.tsx) - React UI using `useApp()` hook
17+
18+
## Getting Started
19+
20+
```bash
21+
npm install
22+
npm run dev
23+
```
24+
25+
## How It Works
26+
27+
1. The server registers a `get-time` tool with metadata linking it to a UI HTML resource (`ui://get-time/mcp-app.html`).
28+
2. When the tool is invoked, the Host renders the UI from the resource.
29+
3. The UI uses the MCP App SDK API to communicate with the host and call server tools.
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+
<div id="root"></div>
12+
<script type="module" src="/src/mcp-app.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "integration-server",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
8+
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
9+
"serve:http": "bun server.ts",
10+
"serve:stdio": "bun server.ts --stdio",
11+
"start": "npm run start:http",
12+
"start:http": "cross-env NODE_ENV=development npm run build && npm run serve:http",
13+
"start:stdio": "cross-env NODE_ENV=development npm run build && npm run serve:stdio",
14+
"dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve:http'"
15+
},
16+
"dependencies": {
17+
"@modelcontextprotocol/ext-apps": "../..",
18+
"@modelcontextprotocol/sdk": "^1.24.0",
19+
"react": "^19.2.0",
20+
"react-dom": "^19.2.0",
21+
"zod": "^4.1.13"
22+
},
23+
"devDependencies": {
24+
"@types/cors": "^2.8.19",
25+
"@types/express": "^5.0.0",
26+
"@types/node": "^22.0.0",
27+
"@types/react": "^19.2.2",
28+
"@types/react-dom": "^19.2.2",
29+
"@vitejs/plugin-react": "^4.3.4",
30+
"concurrently": "^9.2.1",
31+
"cors": "^2.8.5",
32+
"express": "^5.1.0",
33+
"typescript": "^5.9.3",
34+
"vite": "^6.0.0",
35+
"vite-plugin-singlefile": "^2.3.0"
36+
}
37+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
registerAppResource,
3+
registerAppTool,
4+
RESOURCE_MIME_TYPE,
5+
RESOURCE_URI_META_KEY,
6+
} from "@modelcontextprotocol/ext-apps/server";
7+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8+
import type {
9+
CallToolResult,
10+
ReadResourceResult,
11+
} from "@modelcontextprotocol/sdk/types.js";
12+
import fs from "node:fs/promises";
13+
import path from "node:path";
14+
import { startServer } from "./src/server-utils.js";
15+
16+
const DIST_DIR = path.join(import.meta.dirname, "dist");
17+
const RESOURCE_URI = "ui://get-time/mcp-app.html";
18+
19+
/**
20+
* Creates a new MCP server instance with tools and resources registered.
21+
*/
22+
function createServer(): McpServer {
23+
const server = new McpServer({
24+
name: "Integration Test Server",
25+
version: "1.0.0",
26+
});
27+
28+
registerAppTool(
29+
server,
30+
"get-time",
31+
{
32+
title: "Get Time",
33+
description: "Returns the current server time as an ISO 8601 string.",
34+
inputSchema: {},
35+
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
36+
},
37+
async (): Promise<CallToolResult> => {
38+
const time = new Date().toISOString();
39+
return {
40+
content: [{ type: "text", text: JSON.stringify({ time }) }],
41+
};
42+
},
43+
);
44+
45+
registerAppResource(
46+
server,
47+
RESOURCE_URI,
48+
RESOURCE_URI,
49+
{ mimeType: RESOURCE_MIME_TYPE },
50+
async (): Promise<ReadResourceResult> => {
51+
const html = await fs.readFile(
52+
path.join(DIST_DIR, "mcp-app.html"),
53+
"utf-8",
54+
);
55+
56+
return {
57+
contents: [
58+
{ uri: RESOURCE_URI, mimeType: RESOURCE_MIME_TYPE, text: html },
59+
],
60+
};
61+
},
62+
);
63+
64+
return server;
65+
}
66+
67+
startServer(createServer);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
html, body {
6+
font-family: system-ui, -apple-system, sans-serif;
7+
font-size: 1rem;
8+
}
9+
10+
code {
11+
font-size: 1em;
12+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.main {
2+
--color-primary: #2563eb;
3+
--color-primary-hover: #1d4ed8;
4+
--color-notice-bg: #eff6ff;
5+
6+
width: 100%;
7+
max-width: 425px;
8+
box-sizing: border-box;
9+
10+
> * {
11+
margin-top: 0;
12+
margin-bottom: 0;
13+
}
14+
15+
> * + * {
16+
margin-top: 1.5rem;
17+
}
18+
}
19+
20+
.action {
21+
> * {
22+
margin-top: 0;
23+
margin-bottom: 0;
24+
width: 100%;
25+
}
26+
27+
> * + * {
28+
margin-top: 0.5rem;
29+
}
30+
31+
/* Consistent font for form inputs (inherits from global.css) */
32+
textarea,
33+
input {
34+
font-family: inherit;
35+
font-size: inherit;
36+
}
37+
38+
button {
39+
padding: 0.5rem 1rem;
40+
border: none;
41+
border-radius: 6px;
42+
color: white;
43+
font-weight: bold;
44+
background-color: var(--color-primary);
45+
cursor: pointer;
46+
47+
&:hover,
48+
&:focus-visible {
49+
background-color: var(--color-primary-hover);
50+
}
51+
}
52+
}
53+
54+
.notice {
55+
padding: 0.5rem 0.75rem;
56+
color: var(--color-primary);
57+
text-align: center;
58+
font-style: italic;
59+
background-color: var(--color-notice-bg);
60+
61+
&::before {
62+
content: "ℹ️ ";
63+
font-style: normal;
64+
}
65+
}

0 commit comments

Comments
 (0)