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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
dist/
node_modules/
yarn.lock
bun.lockb
.vscode/
docs/api/
tmp/
1 change: 1 addition & 0 deletions examples/basic-host/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const SERVERS = [
{ name: "Customer Segmentation", port: 3105 },
{ name: "Scenario Modeler", port: 3106 },
{ name: "System Monitor", port: 3107 },
{ name: "Three.js", port: 3109 },
] as const;

function serverUrl(port: number): string {
Expand Down
137 changes: 137 additions & 0 deletions examples/threejs-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Three.js MCP Server

Interactive 3D scene renderer using Three.js. Demonstrates streaming code preview and full MCP App integration.

![Screenshot](https://modelcontextprotocol.github.io/ext-apps/screenshots/threejs-server/screenshot.png)

## Tools

| Tool | Description |
| -------------------- | ------------------------------------ |
| `show_threejs_scene` | Render 3D scene from JavaScript code |
| `learn_threejs` | Get documentation and examples |

## Quick Start

```bash
# Build
npm run build

# Run (stdio mode for Claude Desktop)
bun server.ts --stdio

# Run (HTTP mode for basic-host)
bun server.ts
```

## Code Structure

```
threejs-server/
├── server.ts # MCP server with tools
├── mcp-app.html # Entry HTML
└── src/
├── mcp-app-wrapper.tsx # Generic MCP App wrapper (reusable)
├── threejs-app.tsx # Three.js widget component
└── global.css # Styles
```

## Key Files

### `src/mcp-app-wrapper.tsx`

Generic wrapper handling MCP connection. Provides `WidgetProps` interface:

```tsx
interface WidgetProps<TToolInput> {
toolInputs: TToolInput | null; // Complete tool input
toolInputsPartial: TToolInput | null; // Streaming partial input
toolResult: CallToolResult | null; // Tool execution result
hostContext: McpUiHostContext | null; // Theme, viewport, locale
callServerTool: App["callServerTool"]; // Call MCP server tools
sendMessage: App["sendMessage"]; // Send chat messages
sendOpenLink: App["sendOpenLink"]; // Open URLs in browser
sendLog: App["sendLog"]; // Debug logging
}
```

### `src/threejs-app.tsx`

Widget component receiving all props. Uses:

- `toolInputs.code` - JavaScript to execute
- `toolInputsPartial.code` - Streaming preview
- `toolInputs.height` - Canvas height

### `server.ts`

MCP server with:

- `show_threejs_scene` tool linked to UI resource
- `learn_threejs` documentation tool
- stdio + HTTP transport support

## Available Three.js Globals

```javascript
THREE; // Three.js library
canvas; // Pre-created canvas
(width, height); // Canvas dimensions
OrbitControls; // Camera controls
EffectComposer; // Post-processing
RenderPass; // Render pass
UnrealBloomPass; // Bloom effect
```

## Test Input

Copy contents of `test-input.json` to test in basic-host (`http://localhost:8080`).

## Example Code

```javascript
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 100);
camera.position.set(2, 2, 2);

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(width, height);
renderer.shadowMap.enabled = true;

const cube = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x00ff88 }),
);
cube.castShadow = true;
cube.position.y = 0.5;
scene.add(cube);

const floor = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
new THREE.MeshStandardMaterial({ color: 0x222233 }),
);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);

const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(3, 5, 3);
light.castShadow = true;
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));

function animate() {
requestAnimationFrame(animate);
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
```

## Creating a New Widget

1. Copy this example
2. Rename `threejs-app.tsx` to your widget name
3. Define your `ToolInput` interface
4. Implement your widget using the `WidgetProps`
5. Update `server.ts` with your tools
13 changes: 13 additions & 0 deletions examples/threejs-server/mcp-app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Widget</title>
<link rel="stylesheet" href="/src/global.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/mcp-app-wrapper.tsx"></script>
</body>
</html>
36 changes: 36 additions & 0 deletions examples/threejs-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "threejs-server",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "INPUT=mcp-app.html vite build",
"watch": "INPUT=mcp-app.html vite build --watch",
"serve": "bun server.ts",
"start": "NODE_ENV=development npm run build && npm run serve",
"dev": "NODE_ENV=development concurrently 'npm run watch' 'npm run serve'"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "../..",
"@modelcontextprotocol/sdk": "^1.22.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"three": "^0.181.0",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@types/three": "^0.181.0",
"@vitejs/plugin-react": "^4.3.4",
"concurrently": "^9.2.1",
"cors": "^2.8.5",
"express": "^5.1.0",
"typescript": "^5.9.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0"
}
}
Loading
Loading