Skip to content

Commit 4dcb4b0

Browse files
authored
docs: add simple Typescript server example (#44)
1 parent 432ef10 commit 4dcb4b0

10 files changed

Lines changed: 678 additions & 30 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ You can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to
254254

255255
**Server Examples**
256256
* **TypeScript**: A [full-featured server](examples/server) that is deployed to a hosted environment for easy testing.
257-
* **HTTP Streaming**: `https://remote-mcp-server-authless.idosalomon.workers.dev/mcp`
258-
* **SSE**: `https://remote-mcp-server-authless.idosalomon.workers.dev/sse`
257+
* **[`typescript-server-demo`](./examples/typescript-server-demo)**: A simple Typescript server that demonstrates how to generate UI resources.
258+
* **server**: A [full-featured Typescript server](examples/server) that is deployed to a hosted Cloudflare environment for easy testing.
259+
* **HTTP Streaming**: `https://remote-mcp-server-authless.idosalomon.workers.dev/mcp`
260+
* **SSE**: `https://remote-mcp-server-authless.idosalomon.workers.dev/sse`
259261
* **Ruby**: A barebones [demo server](/examples/ruby-server-demo) that shows how to use `mcp_ui_server` and `mcp` gems together.
260262

261263
Drop those URLs into any MCP-compatible host to see `mcp-ui` in action. For a supported local inspector, see the [ui-inspector](https://github.com/idosal/ui-inspector).

docs/src/guide/server/ruby/usage-examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This page provides practical examples for using the `mcp_ui_server` gem.
44

5-
For a complete, runnable server implementation that demonstrates how to use these resources within an MCP tool, see the [Ruby Server Demo](/examples/ruby-server-demo/server.rb).
5+
For a complete, runnable server implementation that demonstrates how to use these resources within an MCP tool, see the Ruby server demo (`/examples/ruby-server-demo/server.rb`).
66

77
## Basic Setup
88

docs/src/guide/server/typescript/overview.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# @mcp-ui/server Overview
22

3-
The `@mcp-ui/server` package provides server-side utilities to help construct `UIResource` objects, which can then be sent to a client as part of an MCP response.
3+
The `@mcp-ui/server` package provides utilities to generate UI resources (`UIResource`) on your MCP server. It allows you to define UI snippets on the server-side, which can then be seamlessly and securely rendered on the client.
4+
5+
For a complete example, see the [`typescript-server-demo`](https://github.com/idosal/mcp-ui/tree/docs/ts-example/examples/typescript-server-demo).
46

57
## Key Exports
68

docs/src/guide/server/typescript/usage-examples.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This page provides practical examples for using the `@mcp-ui/server` package.
44

5+
For a complete example, see the [`typescript-server-demo`](https://github.com/idosal/mcp-ui/tree/docs/ts-example/examples/typescript-server-demo).
6+
57
## Basic Setup
68

79
First, ensure you have `@mcp-ui/server` available in your project:

examples/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
},
1313
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
1414
"dependencies": {
15-
"@mcp-ui/server": "^4.1.1",
15+
"@mcp-ui/server": "^5.2.0",
1616
"@modelcontextprotocol/sdk": "^1.11.1",
1717
"@vis.gl/react-google-maps": "^1.5.2",
1818
"agents": "^0.0.80",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# TypeScript Server Demo
2+
3+
This directory contains a barebones TypeScript Model Context Protocol server demo.
4+
5+
## Installation
6+
7+
To install the dependencies, run the following command from the root of the monorepo:
8+
9+
```bash
10+
pnpm install
11+
```
12+
13+
## Running the Server
14+
15+
To run the server in development mode, run the following command from this directory:
16+
17+
```bash
18+
pnpm dev
19+
```
20+
21+
The server will be available at `http://localhost:3000`.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "typescript-server-demo",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"description": "Barebones TypeScript Model Context Protocol server demo.",
6+
"main": "dist/index.js",
7+
"scripts": {
8+
"build": "tsc",
9+
"start": "node dist/index.js",
10+
"dev": "vite-node src/index.ts"
11+
},
12+
"keywords": [],
13+
"author": "",
14+
"license": "ISC",
15+
"dependencies": {
16+
"@mcp-ui/server": "^5.2.0",
17+
"@modelcontextprotocol/sdk": "^1.16.0",
18+
"cors": "^2.8.5",
19+
"express": "^4.17.1"
20+
},
21+
"devDependencies": {
22+
"@types/cors": "^2.8.12",
23+
"@types/express": "^4.17.13",
24+
"@types/node": "^20.0.0",
25+
"ts-node": "^10.4.0",
26+
"typescript": "^5.2.0",
27+
"vite": "^5.0.0",
28+
"vite-node": "^1.0.0"
29+
}
30+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import express from 'express';
2+
import cors from 'cors';
3+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
6+
import { createUIResource } from '@mcp-ui/server';
7+
import { randomUUID } from 'crypto';
8+
9+
const app = express();
10+
const port = 3000;
11+
12+
app.use(cors({
13+
origin: '*',
14+
exposedHeaders: ['Mcp-Session-Id'],
15+
allowedHeaders: ['Content-Type', 'mcp-session-id'],
16+
}));
17+
app.use(express.json());
18+
19+
// Map to store transports by session ID, as shown in the documentation.
20+
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
21+
22+
// Handle POST requests for client-to-server communication.
23+
app.post('/mcp', async (req, res) => {
24+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
25+
let transport: StreamableHTTPServerTransport;
26+
27+
if (sessionId && transports[sessionId]) {
28+
// A session already exists; reuse the existing transport.
29+
transport = transports[sessionId];
30+
} else if (!sessionId && isInitializeRequest(req.body)) {
31+
// This is a new initialization request. Create a new transport.
32+
transport = new StreamableHTTPServerTransport({
33+
sessionIdGenerator: () => randomUUID(),
34+
onsessioninitialized: (sid) => {
35+
transports[sid] = transport;
36+
console.log(`MCP Session initialized: ${sid}`);
37+
},
38+
});
39+
40+
// Clean up the transport from our map when the session closes.
41+
transport.onclose = () => {
42+
if (transport.sessionId) {
43+
console.log(`MCP Session closed: ${transport.sessionId}`);
44+
delete transports[transport.sessionId];
45+
}
46+
};
47+
48+
// Create a new server instance for this specific session.
49+
const server = new McpServer({
50+
name: "typescript-server-demo",
51+
version: "1.0.0"
52+
});
53+
54+
// Register our tool on the new server instance.
55+
server.registerTool('greet', {
56+
title: 'Greet',
57+
description: 'Creates a UI resource displaying an external URL (example.com).', inputSchema: {},
58+
}, async () => {
59+
// Create the UI resource to be returned to the client
60+
// This is the only MCP-UI specific code in this example
61+
const uiResource = createUIResource({
62+
uri: 'ui://greeting',
63+
content: { type: 'externalUrl', iframeUrl: 'https://example.com' },
64+
encoding: 'text',
65+
});
66+
67+
return {
68+
content: [uiResource],
69+
};
70+
});
71+
72+
// Connect the server instance to the transport for this session.
73+
await server.connect(transport);
74+
} else {
75+
return res.status(400).json({
76+
error: { message: 'Bad Request: No valid session ID provided' },
77+
});
78+
}
79+
80+
// Handle the client's request using the session's transport.
81+
await transport.handleRequest(req, res, req.body);
82+
});
83+
84+
// A separate, reusable handler for GET and DELETE requests.
85+
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
86+
const sessionId = req.headers['mcp-session-id'] as string | undefined;
87+
console.log('sessionId', sessionId);
88+
if (!sessionId || !transports[sessionId]) {
89+
return res.status(404).send('Session not found');
90+
}
91+
92+
const transport = transports[sessionId];
93+
await transport.handleRequest(req, res);
94+
};
95+
96+
// GET handles the long-lived stream for server-to-client messages.
97+
app.get('/mcp', handleSessionRequest);
98+
99+
// DELETE handles explicit session termination from the client.
100+
app.delete('/mcp', handleSessionRequest);
101+
102+
app.listen(port, () => {
103+
console.log(`TypeScript MCP server listening at http://localhost:${port}`);
104+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"module": "NodeNext",
5+
"moduleResolution": "NodeNext",
6+
"strict": true,
7+
"skipLibCheck": true,
8+
"esModuleInterop": true,
9+
"forceConsistentCasingInFileNames": true
10+
},
11+
"include": ["src"]
12+
}

0 commit comments

Comments
 (0)