diff --git a/.prettierignore b/.prettierignore index dd3c59ba..f5edcf9d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,5 +2,6 @@ examples/basic-host/**/*.ts examples/basic-host/**/*.tsx examples/basic-server-*/**/*.ts examples/basic-server-*/**/*.tsx +examples/quickstart/**/*.ts **/vendor/** SKILL.md diff --git a/AGENTS.md b/AGENTS.md index ae8903a4..9bbd2f05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Serv ## Documentation -JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `app.ts` → `app.examples.ts`). Use ` ```ts source="./file.examples.ts#regionName" ` fences referencing `//#region regionName` blocks, then run `npm run sync:snippets`. Region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`). +JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `app.ts` → `app.examples.ts`). Use ` ```ts source="./file.examples.ts#regionName" ` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`). For whole-file inclusion (any file type), omit the `#regionName`. Run `npm run sync:snippets` to sync. Standalone docs in `docs/` (listed in `typedoc.config.mjs` `projectDocuments`) can also have type-checked companion `.ts`/`.tsx` files using the same pattern. diff --git a/docs/quickstart-success.png b/docs/quickstart-success.png new file mode 100644 index 00000000..058fb3f2 Binary files /dev/null and b/docs/quickstart-success.png differ diff --git a/docs/quickstart.md b/docs/quickstart.md index 9481df31..63e93713 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -4,200 +4,351 @@ title: Quickstart # Build Your First MCP App -This tutorial walks you through building an MCP App—a tool with an interactive UI that renders inside MCP hosts like Claude Desktop. +This tutorial walks you through building an MCP App—a tool with an interactive **View** (a UI that renders inside an iframe) that displays in MCP hosts like Claude Desktop. ## What You'll Build -A simple app that fetches the current server time and displays it in a clickable UI. You'll learn the core pattern: **MCP Apps = Tool + UI Resource**. +A simple app that fetches the current server time and displays it in an interactive View. You'll learn the core pattern: **MCP Apps = Tool + UI Resource**. > [!NOTE] -> The complete example is available at [`examples/basic-server-vanillajs`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs). +> The complete example is available at [`examples/quickstart`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/quickstart). ## Prerequisites -- Familiarity with MCP concepts, especially [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) and [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) -- Familiarity with the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) -- Node.js 18+ +This tutorial assumes you've built an MCP server before and are comfortable with [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) and [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources). If not, the [official MCP quickstart](https://modelcontextprotocol.io/docs/develop/build-server) is a good place to start. -> [!TIP] -> New to building MCP servers? Start with the [official MCP quickstart guide](https://modelcontextprotocol.io/docs/develop/build-server) to learn the core concepts first. +We'll use the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) to build the server. -## 1. Project Setup +You'll also need Node.js 18+. -Create a new directory and initialize: +## 1. Set up the project + +We'll set up a minimal TypeScript project with Vite for bundling. + +Start by creating a project directory: ```bash mkdir my-mcp-app && cd my-mcp-app +``` + +Install the dependencies you'll need: + +```bash npm init -y +npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors +npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env ``` -Install dependencies: +Configure your [`package.json`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/quickstart/package.json): ```bash -npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk -npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx +npm pkg set type=module +npm pkg set scripts.build="tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build" +npm pkg set scripts.start="concurrently 'cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch' 'tsx watch main.ts'" ``` -Create `tsconfig.json`: +
+Create tsconfig.json: -```json + +```json source="../examples/quickstart/tsconfig.json" { "compilerOptions": { - "target": "ES2022", + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "server.ts", "main.ts"] +} +``` + +
+ +
+Create tsconfig.server.json — for compiling server-side code: + + +```json source="../examples/quickstart/tsconfig.server.json" +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist", + "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "outDir": "dist" + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true }, - "include": ["*.ts", "src/**/*.ts"] + "include": ["server.ts", "main.ts"] } ``` -Create `vite.config.ts` — this bundles your UI into a single HTML file: +
-```typescript +
+Create vite.config.ts — bundles UI into a single HTML file: + + +```ts source="../examples/quickstart/vite.config.ts" import { defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; +const INPUT = process.env.INPUT; +if (!INPUT) { + throw new Error("INPUT environment variable is not set"); +} + +const isDevelopment = process.env.NODE_ENV === "development"; + export default defineConfig({ plugins: [viteSingleFile()], build: { - outDir: "dist", + sourcemap: isDevelopment ? "inline" : undefined, + cssMinify: !isDevelopment, + minify: !isDevelopment, + rollupOptions: { - input: process.env.INPUT, + input: INPUT, }, + outDir: "dist", + emptyOutDir: false, }, }); ``` -Add to your `package.json`: +
-```json -{ - "type": "module", - "scripts": { - "build": "INPUT=mcp-app.html vite build", - "serve": "npx tsx server.ts" - } -} +Your `my-mcp-app` directory should now contain: + +``` +my-mcp-app/ +├── package.json +├── tsconfig.json +├── tsconfig.server.json +└── vite.config.ts ``` -> [!NOTE] -> **Full files:** [`package.json`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/package.json), [`tsconfig.json`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/tsconfig.json), [`vite.config.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/vite.config.ts) +With the project scaffolded, let's write the server code. -## 2. Create the Server +## 2. Register the tool and UI resource MCP Apps use a **two-part registration**: 1. A **tool** that the LLM/host calls -2. A **resource** that serves the UI HTML +2. A **resource** that contains the View HTML -The tool's `_meta` field links them together. +The tool's `_meta` field links them together via the resource's URI. When an MCP Apps-capable host calls the tool, it will also read the resource and render the View. -Create `server.ts`: +Create [`server.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/quickstart/server.ts), which registers the tool and its UI resource: -```typescript -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; + +```ts source="../examples/quickstart/server.ts" import { - registerAppTool, registerAppResource, + registerAppTool, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; -import cors from "cors"; -import express from "express"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import fs from "node:fs/promises"; import path from "node:path"; -const server = new McpServer({ - name: "My MCP App Server", - version: "1.0.0", -}); +const DIST_DIR = path.join(import.meta.dirname, "dist"); -// Two-part registration: tool + resource, tied together by the resource URI. -const resourceUri = "ui://get-time/mcp-app.html"; - -// Register a tool with UI metadata. When the host calls this tool, it reads -// `_meta.ui.resourceUri` to know which resource to fetch and render as an -// interactive UI. -registerAppTool( - server, - "get-time", - { - title: "Get Time", - description: "Returns the current server time.", - inputSchema: {}, - _meta: { ui: { resourceUri } }, - }, - async () => { - const time = new Date().toISOString(); - return { - content: [{ type: "text", text: time }], - }; - }, -); - -// Register the resource, which returns the bundled HTML/JavaScript for the UI. -registerAppResource( - server, - resourceUri, - resourceUri, - { mimeType: RESOURCE_MIME_TYPE }, - async () => { - const html = await fs.readFile( - path.join(import.meta.dirname, "dist", "mcp-app.html"), - "utf-8", - ); - return { - contents: [ - { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }, - ], - }; - }, -); +/** + * Creates a new MCP server instance with tools and resources registered. + */ +export function createServer(): McpServer { + const server = new McpServer({ + name: "Quickstart MCP App Server", + version: "1.0.0", + }); + + // Two-part registration: tool + resource, tied together by the resource URI. + const resourceUri = "ui://get-time/mcp-app.html"; + + // Register a tool with UI metadata. When the host calls this tool, it reads + // `_meta.ui.resourceUri` to know which resource to fetch and render as an + // interactive UI. + registerAppTool( + server, + "get-time", + { + title: "Get Time", + description: "Returns the current server time.", + inputSchema: {}, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource + }, + async () => { + const time = new Date().toISOString(); + return { content: [{ type: "text", text: time }] }; + }, + ); + + // Register the resource, which returns the bundled HTML/JavaScript for the UI. + registerAppResource( + server, + resourceUri, + resourceUri, + { mimeType: RESOURCE_MIME_TYPE }, + async () => { + const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8"); + + return { + contents: [ + { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }, + ], + }; + }, + ); + + return server; +} +``` + +
+Create main.ts — the entry point that starts the server: -// Start an Express server that exposes the MCP endpoint. -const expressApp = express(); -expressApp.use(cors()); -expressApp.use(express.json()); + +```ts source="../examples/quickstart/main.ts" +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import cors from "cors"; +import type { Request, Response } from "express"; +import { createServer } from "./server.js"; + +/** + * Starts an MCP server with Streamable HTTP transport in stateless mode. + * + * @param createServer - Factory function that creates a new McpServer instance per request. + */ +export async function startStreamableHTTPServer( + createServer: () => McpServer, +): Promise { + const port = parseInt(process.env.PORT ?? "3001", 10); + + const app = createMcpExpressApp({ host: "0.0.0.0" }); + app.use(cors()); + + app.all("/mcp", async (req: Request, res: Response) => { + const server = createServer(); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + + res.on("close", () => { + transport.close().catch(() => {}); + server.close().catch(() => {}); + }); + + try { + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("MCP error:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); -expressApp.post("/mcp", async (req, res) => { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, - enableJsonResponse: true, + const httpServer = app.listen(port, (err) => { + if (err) { + console.error("Failed to start server:", err); + process.exit(1); + } + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); - res.on("close", () => transport.close()); - await server.connect(transport); - await transport.handleRequest(req, res, req.body); -}); -expressApp.listen(3001, (err) => { - if (err) { - console.error("Error starting server:", err); - process.exit(1); + const shutdown = () => { + console.log("\nShutting down..."); + httpServer.close(() => process.exit(0)); + }; + + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); +} + +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + +async function main() { + if (process.argv.includes("--stdio")) { + await startStdioServer(createServer); + } else { + await startStreamableHTTPServer(createServer); } - console.log("Server listening on http://localhost:3001/mcp"); +} + +main().catch((e) => { + console.error(e); + process.exit(1); }); ``` -> [!NOTE] -> **Full file:** [`server.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/server.ts) +
+ +Your `my-mcp-app` directory should now contain: + +``` +my-mcp-app/ +├── main.ts +├── package.json +├── server.ts +├── tsconfig.json +├── tsconfig.server.json +└── vite.config.ts +``` -Then, verify your server compiles: +Let's verify everything compiles: ```bash npx tsc --noEmit ``` -No output means success. If you see errors, check for typos in `server.ts`. +No output means success! If you see errors, check for typos in `server.ts` or `main.ts`. + +The server can return the current time when the tool is called. Now let's build the UI to display it. + +## 3. Build the View -## 3. Build the UI +The View consists of an HTML page and a script that connects to the host. -Create `mcp-app.html`: +Create [`mcp-app.html`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/quickstart/mcp-app.html), the HTML for your View: -```html + +```html source="../examples/quickstart/mcp-app.html" @@ -214,9 +365,10 @@ Create `mcp-app.html`: ``` -Create `src/mcp-app.ts`: +Create [`src/mcp-app.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/quickstart/src/mcp-app.ts), which connects to the host and handles user interactions: -```typescript + +```ts source="../examples/quickstart/src/mcp-app.ts" import { App } from "@modelcontextprotocol/ext-apps"; // Get element references @@ -226,7 +378,8 @@ const getTimeBtn = document.getElementById("get-time-btn")!; // Create app instance const app = new App({ name: "Get Time App", version: "1.0.0" }); -// Register handlers BEFORE connecting +// Handle tool results from the server. Set before `app.connect()` to avoid +// missing the initial tool result. app.ontoolresult = (result) => { const time = result.content?.find((c) => c.type === "text")?.text; serverTimeEl.textContent = time ?? "[ERROR]"; @@ -234,6 +387,7 @@ app.ontoolresult = (result) => { // Wire up button click getTimeBtn.addEventListener("click", async () => { + // `app.callServerTool()` lets the UI request fresh data from the server const result = await app.callServerTool({ name: "get-time", arguments: {} }); const time = result.content?.find((c) => c.type === "text")?.text; serverTimeEl.textContent = time ?? "[ERROR]"; @@ -243,30 +397,50 @@ getTimeBtn.addEventListener("click", async () => { app.connect(); ``` -> [!NOTE] -> **Full files:** [`mcp-app.html`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/mcp-app.html), [`src/mcp-app.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/src/mcp-app.ts) +Your `my-mcp-app` directory should now contain: -Build the UI: +``` +my-mcp-app/ +├── main.ts +├── mcp-app.html +├── package.json +├── server.ts +├── src/ +│ └── mcp-app.ts +├── tsconfig.json +├── tsconfig.server.json +└── vite.config.ts +``` + +Now let's build the bundled View: ```bash npm run build ``` -This produces `dist/mcp-app.html` which contains your bundled app: +This produces `dist/mcp-app.html`: ```console -$ ls dist/mcp-app.html -dist/mcp-app.html +$ ls dist/ +mcp-app.html ``` -## 4. Test It +The View will connect to the host, receive the tool result, and display it. Let's see it in action! + +## 4. See it in action -You'll need two terminals. +You'll need two terminal windows. -**Terminal 1** — Build and start your server: +**Terminal 1** — Start your server (with watch mode): ```bash -npm run build && npm run serve +npm start +``` + +You should see: + +```console +MCP server listening on http://localhost:3001/mcp ``` **Terminal 2** — Run the test host (from the [ext-apps repo](https://github.com/modelcontextprotocol/ext-apps)): @@ -275,18 +449,23 @@ npm run build && npm run serve git clone https://github.com/modelcontextprotocol/ext-apps.git cd ext-apps/examples/basic-host npm install -npm run start +npm start ``` Open http://localhost:8080 in your browser: 1. Select **get-time** from the "Tool Name" dropdown 2. Click **Call Tool** -3. Your UI renders in the sandbox below +3. Your View renders in the sandbox below 4. Click **Get Server Time** — the current time appears! +![Your first MCP App](./quickstart-success.png) + +You've built your first MCP App! + ## Next Steps -- **Host communication**: Add [`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), and [`sendOpenLink()`](https://modelcontextprotocol.github.io/ext-apps/api/classes/app.App.html#sendopenlink) to interact with the host — see [`src/mcp-app.ts`](https://github.com/modelcontextprotocol/ext-apps/blob/main/examples/basic-server-vanillajs/src/mcp-app.ts) +- **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 - **React version**: Compare with [`basic-server-react`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) for a React-based UI +- **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 - **API reference**: See the full [API documentation](https://modelcontextprotocol.github.io/ext-apps/api/) diff --git a/examples/basic-host/src/index.module.css b/examples/basic-host/src/index.module.css index eb4d6795..ec18e2bf 100644 --- a/examples/basic-host/src/index.module.css +++ b/examples/basic-host/src/index.module.css @@ -145,11 +145,11 @@ } .appIframePanel { - min-height: 200px; + min-height: 100px; iframe { width: 100%; - height: 600px; + height: 400px; box-sizing: border-box; border: 3px dashed var(--color-border); border-radius: 4px; diff --git a/examples/basic-server-preact/main.ts b/examples/basic-server-preact/main.ts index d9a51a2e..76426326 100644 --- a/examples/basic-server-preact/main.ts +++ b/examples/basic-server-preact/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (Preact)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-preact/server.ts b/examples/basic-server-preact/server.ts index 2d999645..bf14ea59 100644 --- a/examples/basic-server-preact/server.ts +++ b/examples/basic-server-preact/server.ts @@ -29,7 +29,7 @@ export function createServer(): McpServer { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", inputSchema: {}, - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/basic-server-react/main.ts b/examples/basic-server-react/main.ts index 39840a13..ec187b68 100644 --- a/examples/basic-server-react/main.ts +++ b/examples/basic-server-react/main.ts @@ -12,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -63,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -75,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (React)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-react/server.ts b/examples/basic-server-react/server.ts index 416b217f..23f6dda5 100644 --- a/examples/basic-server-react/server.ts +++ b/examples/basic-server-react/server.ts @@ -30,7 +30,7 @@ export function createServer(): McpServer { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", inputSchema: {}, - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/basic-server-solid/main.ts b/examples/basic-server-solid/main.ts index 23353486..c8d9de22 100644 --- a/examples/basic-server-solid/main.ts +++ b/examples/basic-server-solid/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (Solid)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-solid/server.ts b/examples/basic-server-solid/server.ts index 33f76a13..2ed2d935 100644 --- a/examples/basic-server-solid/server.ts +++ b/examples/basic-server-solid/server.ts @@ -29,7 +29,7 @@ export function createServer(): McpServer { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", inputSchema: {}, - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/basic-server-svelte/main.ts b/examples/basic-server-svelte/main.ts index 762a40eb..6c50a254 100644 --- a/examples/basic-server-svelte/main.ts +++ b/examples/basic-server-svelte/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (Svelte)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-svelte/server.ts b/examples/basic-server-svelte/server.ts index dd2dba66..da603ca8 100644 --- a/examples/basic-server-svelte/server.ts +++ b/examples/basic-server-svelte/server.ts @@ -29,7 +29,7 @@ export function createServer(): McpServer { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", inputSchema: {}, - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/basic-server-vanillajs/main.ts b/examples/basic-server-vanillajs/main.ts index d53d53b5..286fa34f 100644 --- a/examples/basic-server-vanillajs/main.ts +++ b/examples/basic-server-vanillajs/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3102", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (Vanilla JS)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-vanillajs/server.ts b/examples/basic-server-vanillajs/server.ts index 07e82c20..5b2daf70 100644 --- a/examples/basic-server-vanillajs/server.ts +++ b/examples/basic-server-vanillajs/server.ts @@ -33,7 +33,7 @@ export function createServer(): McpServer { outputSchema: z.object({ time: z.string(), }), - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/basic-server-vue/main.ts b/examples/basic-server-vue/main.ts index 0304600e..669a0718 100644 --- a/examples/basic-server-vue/main.ts +++ b/examples/basic-server-vue/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Basic MCP App Server (Vue)" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/basic-server-vue/server.ts b/examples/basic-server-vue/server.ts index 73cb54d4..5aa72de0 100644 --- a/examples/basic-server-vue/server.ts +++ b/examples/basic-server-vue/server.ts @@ -29,7 +29,7 @@ export function createServer(): McpServer { title: "Get Time", description: "Returns the current server time as an ISO 8601 string.", inputSchema: {}, - _meta: { ui: { resourceUri } }, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource }, async (): Promise => { const time = new Date().toISOString(); diff --git a/examples/budget-allocator-server/main.ts b/examples/budget-allocator-server/main.ts index 534e876d..cc488f0b 100644 --- a/examples/budget-allocator-server/main.ts +++ b/examples/budget-allocator-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3103", 10); - await startServer(createServer, { port, name: "Marketing" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/cohort-heatmap-server/main.ts b/examples/cohort-heatmap-server/main.ts index 0b716d1d..d59903b3 100644 --- a/examples/cohort-heatmap-server/main.ts +++ b/examples/cohort-heatmap-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3104", 10); - await startServer(createServer, { port, name: "Cohort Heatmap Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/customer-segmentation-server/main.ts b/examples/customer-segmentation-server/main.ts index 7be715a4..130076c5 100644 --- a/examples/customer-segmentation-server/main.ts +++ b/examples/customer-segmentation-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,15 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3105", 10); - await startServer(createServer, { - port, - name: "Customer Segmentation Server", - }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/debug-server/main.ts b/examples/debug-server/main.ts index 5aa8ddbc..3c89a975 100644 --- a/examples/debug-server/main.ts +++ b/examples/debug-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3110", 10); - await startServer(createServer, { port, name: "Debug Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/integration-server/main.ts b/examples/integration-server/main.ts index 5340e8a8..c45787e2 100644 --- a/examples/integration-server/main.ts +++ b/examples/integration-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Integration Test Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/map-server/main.ts b/examples/map-server/main.ts index 44956598..f91f57b6 100644 --- a/examples/map-server/main.ts +++ b/examples/map-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -63,7 +52,7 @@ export async function startServer( }); const httpServer = app.listen(port, () => { - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -75,12 +64,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "CesiumJS Map Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/pdf-server/main.ts b/examples/pdf-server/main.ts index 2b0e3ff9..310a3127 100644 --- a/examples/pdf-server/main.ts +++ b/examples/pdf-server/main.ts @@ -23,19 +23,13 @@ import { DEFAULT_PDF, } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -71,7 +65,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -83,6 +77,17 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + function parseArgs(): { urls: string[]; stdio: boolean } { const args = process.argv.slice(2); const urls: string[] = []; @@ -132,10 +137,9 @@ async function main() { ); if (stdio) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3120", 10); - await startServer(createServer, { port, name: "PDF Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/qr-server/server.py b/examples/qr-server/server.py index 5595ef3d..02384c7b 100755 --- a/examples/qr-server/server.py +++ b/examples/qr-server/server.py @@ -25,7 +25,7 @@ VIEW_URI = "ui://qr-server/view.html" HOST = os.environ.get("HOST", "0.0.0.0") # 0.0.0.0 for Docker compatibility -PORT = int(os.environ.get("PORT", "3108")) +PORT = int(os.environ.get("PORT", "3001")) mcp = FastMCP("QR Code Server") diff --git a/examples/quickstart/.gitignore b/examples/quickstart/.gitignore new file mode 100644 index 00000000..b9470778 --- /dev/null +++ b/examples/quickstart/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/examples/quickstart/README.md b/examples/quickstart/README.md new file mode 100644 index 00000000..74e070db --- /dev/null +++ b/examples/quickstart/README.md @@ -0,0 +1,3 @@ +# Quickstart Server + +This is the example code for the [Quickstart guide](../../docs/quickstart.md). diff --git a/examples/quickstart/main.ts b/examples/quickstart/main.ts new file mode 100644 index 00000000..06895a49 --- /dev/null +++ b/examples/quickstart/main.ts @@ -0,0 +1,87 @@ +import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import cors from "cors"; +import type { Request, Response } from "express"; +import { createServer } from "./server.js"; + +/** + * Starts an MCP server with Streamable HTTP transport in stateless mode. + * + * @param createServer - Factory function that creates a new McpServer instance per request. + */ +export async function startStreamableHTTPServer( + createServer: () => McpServer, +): Promise { + const port = parseInt(process.env.PORT ?? "3001", 10); + + const app = createMcpExpressApp({ host: "0.0.0.0" }); + app.use(cors()); + + app.all("/mcp", async (req: Request, res: Response) => { + const server = createServer(); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + + res.on("close", () => { + transport.close().catch(() => {}); + server.close().catch(() => {}); + }); + + try { + await server.connect(transport); + await transport.handleRequest(req, res, req.body); + } catch (error) { + console.error("MCP error:", error); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: "2.0", + error: { code: -32603, message: "Internal server error" }, + id: null, + }); + } + } + }); + + const httpServer = app.listen(port, (err) => { + if (err) { + console.error("Failed to start server:", err); + process.exit(1); + } + console.log(`MCP server listening on http://localhost:${port}/mcp`); + }); + + const shutdown = () => { + console.log("\nShutting down..."); + httpServer.close(() => process.exit(0)); + }; + + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); +} + +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + +async function main() { + if (process.argv.includes("--stdio")) { + await startStdioServer(createServer); + } else { + await startStreamableHTTPServer(createServer); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/quickstart/mcp-app.html b/examples/quickstart/mcp-app.html new file mode 100644 index 00000000..bf276aa5 --- /dev/null +++ b/examples/quickstart/mcp-app.html @@ -0,0 +1,14 @@ + + + + + Get Time App + + +

+ Server Time: Loading... +

+ + + + diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json new file mode 100644 index 00000000..86933d40 --- /dev/null +++ b/examples/quickstart/package.json @@ -0,0 +1,34 @@ +{ + "name": "@modelcontextprotocol/quickstart", + "version": "0.4.1", + "type": "module", + "private": true, + "description": "Quickstart MCP App Server example", + "repository": { + "type": "git", + "url": "https://github.com/modelcontextprotocol/ext-apps", + "directory": "examples/quickstart" + }, + "license": "MIT", + "scripts": { + "build": "tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build", + "start": "concurrently 'cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch' 'tsx watch main.ts'" + }, + "dependencies": { + "@modelcontextprotocol/ext-apps": "^0.4.1", + "@modelcontextprotocol/sdk": "^1.24.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "concurrently": "^9.2.1", + "@types/express": "^5.0.0", + "@types/node": "^22.0.0", + "cross-env": "^10.1.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^6.0.0", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/examples/quickstart/server.ts b/examples/quickstart/server.ts new file mode 100644 index 00000000..eb3c3b1a --- /dev/null +++ b/examples/quickstart/server.ts @@ -0,0 +1,60 @@ +import { + registerAppResource, + registerAppTool, + RESOURCE_MIME_TYPE, +} from "@modelcontextprotocol/ext-apps/server"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import fs from "node:fs/promises"; +import path from "node:path"; + +const DIST_DIR = path.join(import.meta.dirname, "dist"); + +/** + * Creates a new MCP server instance with tools and resources registered. + */ +export function createServer(): McpServer { + const server = new McpServer({ + name: "Quickstart MCP App Server", + version: "1.0.0", + }); + + // Two-part registration: tool + resource, tied together by the resource URI. + const resourceUri = "ui://get-time/mcp-app.html"; + + // Register a tool with UI metadata. When the host calls this tool, it reads + // `_meta.ui.resourceUri` to know which resource to fetch and render as an + // interactive UI. + registerAppTool( + server, + "get-time", + { + title: "Get Time", + description: "Returns the current server time.", + inputSchema: {}, + _meta: { ui: { resourceUri } }, // Links this tool to its UI resource + }, + async () => { + const time = new Date().toISOString(); + return { content: [{ type: "text", text: time }] }; + }, + ); + + // Register the resource, which returns the bundled HTML/JavaScript for the UI. + registerAppResource( + server, + resourceUri, + resourceUri, + { mimeType: RESOURCE_MIME_TYPE }, + async () => { + const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8"); + + return { + contents: [ + { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }, + ], + }; + }, + ); + + return server; +} diff --git a/examples/quickstart/src/mcp-app.ts b/examples/quickstart/src/mcp-app.ts new file mode 100644 index 00000000..5780bb70 --- /dev/null +++ b/examples/quickstart/src/mcp-app.ts @@ -0,0 +1,26 @@ +import { App } from "@modelcontextprotocol/ext-apps"; + +// Get element references +const serverTimeEl = document.getElementById("server-time")!; +const getTimeBtn = document.getElementById("get-time-btn")!; + +// Create app instance +const app = new App({ name: "Get Time App", version: "1.0.0" }); + +// Handle tool results from the server. Set before `app.connect()` to avoid +// missing the initial tool result. +app.ontoolresult = (result) => { + const time = result.content?.find((c) => c.type === "text")?.text; + serverTimeEl.textContent = time ?? "[ERROR]"; +}; + +// Wire up button click +getTimeBtn.addEventListener("click", async () => { + // `app.callServerTool()` lets the UI request fresh data from the server + const result = await app.callServerTool({ name: "get-time", arguments: {} }); + const time = result.content?.find((c) => c.type === "text")?.text; + serverTimeEl.textContent = time ?? "[ERROR]"; +}); + +// Connect to host +app.connect(); diff --git a/examples/quickstart/tsconfig.json b/examples/quickstart/tsconfig.json new file mode 100644 index 00000000..6c553b5d --- /dev/null +++ b/examples/quickstart/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src", "server.ts", "main.ts"] +} diff --git a/examples/quickstart/tsconfig.server.json b/examples/quickstart/tsconfig.server.json new file mode 100644 index 00000000..7e65f5f7 --- /dev/null +++ b/examples/quickstart/tsconfig.server.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["server.ts", "main.ts"] +} diff --git a/examples/quickstart/vite.config.ts b/examples/quickstart/vite.config.ts new file mode 100644 index 00000000..6ff6d997 --- /dev/null +++ b/examples/quickstart/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +const INPUT = process.env.INPUT; +if (!INPUT) { + throw new Error("INPUT environment variable is not set"); +} + +const isDevelopment = process.env.NODE_ENV === "development"; + +export default defineConfig({ + plugins: [viteSingleFile()], + build: { + sourcemap: isDevelopment ? "inline" : undefined, + cssMinify: !isDevelopment, + minify: !isDevelopment, + + rollupOptions: { + input: INPUT, + }, + outDir: "dist", + emptyOutDir: false, + }, +}); diff --git a/examples/say-server/server.py b/examples/say-server/server.py index bc1313e0..64b7417a 100755 --- a/examples/say-server/server.py +++ b/examples/say-server/server.py @@ -58,7 +58,7 @@ VIEW_URI = "ui://say-demo/view.html" HOST = os.environ.get("HOST", "0.0.0.0") -PORT = int(os.environ.get("PORT", "3109")) +PORT = int(os.environ.get("PORT", "3001")) # Speaker icon as SVG data URI SPEAKER_ICON = Icon( diff --git a/examples/scenario-modeler-server/main.ts b/examples/scenario-modeler-server/main.ts index b7cc7d00..dbd3ab6a 100644 --- a/examples/scenario-modeler-server/main.ts +++ b/examples/scenario-modeler-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3106", 10); - await startServer(createServer, { port, name: "SaaS Scenario Modeler" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/shadertoy-server/main.ts b/examples/shadertoy-server/main.ts index 717f5e64..ac8f6ad8 100644 --- a/examples/shadertoy-server/main.ts +++ b/examples/shadertoy-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "ShaderToy Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/sheet-music-server/main.ts b/examples/sheet-music-server/main.ts index 4b206e72..8b143a7a 100644 --- a/examples/sheet-music-server/main.ts +++ b/examples/sheet-music-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Sheet Music Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/system-monitor-server/main.ts b/examples/system-monitor-server/main.ts index 19624809..365a5604 100644 --- a/examples/system-monitor-server/main.ts +++ b/examples/system-monitor-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3107", 10); - await startServer(createServer, { port, name: "System Monitor Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/threejs-server/main.ts b/examples/threejs-server/main.ts index 1f779a97..13a2084a 100644 --- a/examples/threejs-server/main.ts +++ b/examples/threejs-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3108", 10); - await startServer(createServer, { port, name: "Three.js Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/transcript-server/main.ts b/examples/transcript-server/main.ts index 2a927041..68db8dc7 100644 --- a/examples/transcript-server/main.ts +++ b/examples/transcript-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,19 +12,13 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -64,7 +54,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -76,12 +66,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3109", 10); - await startServer(createServer, { port, name: "Transcript Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/video-resource-server/main.ts b/examples/video-resource-server/main.ts index ff304639..082fa5ab 100644 --- a/examples/video-resource-server/main.ts +++ b/examples/video-resource-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3001", 10); - await startServer(createServer, { port, name: "Video Resource Server" }); + await startStreamableHTTPServer(createServer); } } diff --git a/examples/wiki-explorer-server/main.ts b/examples/wiki-explorer-server/main.ts index d83ac427..cc7b3a22 100644 --- a/examples/wiki-explorer-server/main.ts +++ b/examples/wiki-explorer-server/main.ts @@ -4,10 +4,6 @@ * Or: node dist/index.js [--stdio] */ -/** - * Shared utilities for running MCP servers with Streamable HTTP transport. - */ - import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; @@ -16,22 +12,15 @@ import cors from "cors"; import type { Request, Response } from "express"; import { createServer } from "./server.js"; -export interface ServerOptions { - port: number; - name?: string; -} - /** * Starts an MCP server with Streamable HTTP transport in stateless mode. * * @param createServer - Factory function that creates a new McpServer instance per request. - * @param options - Server configuration options. */ -export async function startServer( +export async function startStreamableHTTPServer( createServer: () => McpServer, - options: ServerOptions, ): Promise { - const { port, name = "MCP Server" } = options; + const port = parseInt(process.env.PORT ?? "3001", 10); const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); @@ -67,7 +56,7 @@ export async function startServer( console.error("Failed to start server:", err); process.exit(1); } - console.log(`${name} listening on http://localhost:${port}/mcp`); + console.log(`MCP server listening on http://localhost:${port}/mcp`); }); const shutdown = () => { @@ -79,12 +68,22 @@ export async function startServer( process.on("SIGTERM", shutdown); } +/** + * Starts an MCP server with stdio transport. + * + * @param createServer - Factory function that creates a new McpServer instance. + */ +export async function startStdioServer( + createServer: () => McpServer, +): Promise { + await createServer().connect(new StdioServerTransport()); +} + async function main() { if (process.argv.includes("--stdio")) { - await createServer().connect(new StdioServerTransport()); + await startStdioServer(createServer); } else { - const port = parseInt(process.env.PORT ?? "3109", 10); - await startServer(createServer, { port, name: "Wiki Explorer" }); + await startStreamableHTTPServer(createServer); } } diff --git a/package-lock.json b/package-lock.json index b3d94905..9907c20f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -568,6 +568,45 @@ "@modelcontextprotocol/ext-apps": "^0.4.1" } }, + "examples/quickstart": { + "name": "@modelcontextprotocol/quickstart", + "version": "0.4.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/ext-apps": "^0.4.1", + "@modelcontextprotocol/sdk": "^1.24.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.0", + "@types/node": "^22.0.0", + "concurrently": "^9.2.1", + "cross-env": "^10.1.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^6.0.0", + "vite-plugin-singlefile": "^2.3.0" + } + }, + "examples/quickstart/node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "examples/quickstart/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "examples/say-server": { "name": "@modelcontextprotocol/server-say", "version": "0.4.1", @@ -897,6 +936,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -2333,11 +2373,16 @@ "resolved": "examples/basic-host", "link": true }, + "node_modules/@modelcontextprotocol/quickstart": { + "resolved": "examples/quickstart", + "link": true + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.25.3", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", "license": "MIT", + "peer": true, "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -3417,6 +3462,7 @@ "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", @@ -3623,6 +3669,7 @@ "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3647,6 +3694,7 @@ "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4049,6 +4097,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4390,6 +4439,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5105,6 +5155,7 @@ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -5577,6 +5628,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -7126,6 +7178,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -7335,6 +7388,7 @@ "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -7456,6 +7510,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.0.tgz", "integrity": "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -7746,6 +7801,7 @@ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.11.tgz", "integrity": "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", @@ -7924,6 +7980,7 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.0.tgz", "integrity": "sha512-+NUe82VoFP1RQViZI/esojx70eazGF4u0O/9ucqZ4rPcOZD+n5EVp17uYsqwdzjUjZyTpGKunHbDziW6AIAVkQ==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -9013,6 +9070,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9106,6 +9164,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -9400,6 +9459,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.27", "@vue/compiler-sfc": "3.5.27", @@ -9560,6 +9620,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -9610,6 +9671,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/scripts/sync-snippets.ts b/scripts/sync-snippets.ts index 3933727a..4e9cfa8d 100644 --- a/scripts/sync-snippets.ts +++ b/scripts/sync-snippets.ts @@ -1,17 +1,29 @@ /** * Code Snippet Sync Script * - * This script syncs code snippets from `.examples.ts/.examples.tsx` files - * into JSDoc comments containing labeled code fences. + * This script syncs code snippets into JSDoc comments and markdown files + * containing labeled code fences. * - * The script replaces the content inside code fences that have a path#region - * reference in their info string. + * ## Supported Source Files + * + * - **Full-file inclusion**: Any file type (e.g., `.json`, `.yaml`, `.sh`, `.ts`) + * - **Region extraction**: Only `.ts` and `.tsx` files (using `//#region` markers) * * ## Code Fence Format * + * Full-file inclusion (any file type): + * + * ``````typescript + * ```json source="./config.json" + * // entire file content is synced here + * ``` + * `````` + * + * Region extraction (.ts/.tsx only): + * * ``````typescript * ```ts source="./path.examples.ts#regionName" - * // code is synced here + * // region content is synced here * ``` * `````` * @@ -55,10 +67,10 @@ interface LabeledCodeFence { displayName?: string; /** Relative path to the example file (e.g., "./app.examples.ts") */ examplePath: string; - /** Region name (e.g., "App_basicUsage") */ - regionName: string; - /** Language from the code fence (ts or tsx) */ - language: "ts" | "tsx"; + /** Region name (e.g., "App_basicUsage"), or undefined for whole file */ + regionName?: string; + /** Language from the code fence (e.g., "ts", "json", "yaml") */ + language: string; /** Character index of the opening fence line start */ openingFenceStart: number; /** Character index after the opening fence line (after newline) */ @@ -69,22 +81,12 @@ interface LabeledCodeFence { linePrefix: string; } -/** - * Represents extracted region content from an example file. - */ -interface RegionContent { - /** The dedented code content */ - code: string; - /** Language for code fence (ts or tsx) */ - language: "ts" | "tsx"; -} - /** * Cache for example file regions to avoid re-reading files. - * Key: absolute example file path - * Value: Map + * Key: `${absoluteExamplePath}#${regionName}` (empty regionName for whole file) + * Value: extracted code string */ -type RegionCache = Map>; +type RegionCache = Map; /** * Processing result for a source file. @@ -97,18 +99,20 @@ interface FileProcessingResult { } // JSDoc patterns - for code fences inside JSDoc comments with " * " prefix -// Matches: ``` [displayName] source="#" +// Matches: ``` [displayName] source="" or source="#" // Example: " * ```ts my-app.ts source="./app.examples.ts#App_basicUsage"" // Example: " * ```ts source="./app.examples.ts#App_basicUsage"" +// Example: " * ```ts source="./complete-example.ts"" (whole file) const JSDOC_LABELED_FENCE_PATTERN = - /^(\s*\*\s*)```(ts|tsx)(?:\s+(\S+))?\s+source="([^"#]+)#([^"]+)"/; + /^(\s*\*\s*)```(\w+)(?:\s+(\S+))?\s+source="([^"#]+)(?:#([^"]+))?"/; const JSDOC_CLOSING_FENCE_PATTERN = /^(\s*\*\s*)```\s*$/; // Markdown patterns - for plain code fences in markdown files (no prefix) -// Matches: ``` [displayName] source="#" +// Matches: ``` [displayName] source="" or source="#" // Example: ```tsx source="./patterns.tsx#chunkedDataServer" +// Example: ```tsx source="./complete-example.tsx" (whole file) const MARKDOWN_LABELED_FENCE_PATTERN = - /^```(ts|tsx)(?:\s+(\S+))?\s+source="([^"#]+)#([^"]+)"/; + /^```(\w+)(?:\s+(\S+))?\s+source="([^"#]+)(?:#([^"]+))?"/; const MARKDOWN_CLOSING_FENCE_PATTERN = /^```\s*$/; /** @@ -184,7 +188,7 @@ function findLabeledCodeFences( displayName, examplePath, regionName, - language: language as "ts" | "tsx", + language, openingFenceStart, openingFenceEnd, closingFenceStart, @@ -242,6 +246,14 @@ function extractRegion( regionName: string, examplePath: string, ): string { + // Region extraction only supported for .ts/.tsx files (uses //#region syntax) + if (!examplePath.endsWith(".ts") && !examplePath.endsWith(".tsx")) { + throw new Error( + `Region extraction (#${regionName}) is only supported for .ts/.tsx files. ` + + `Use full-file inclusion (without #regionName) for: ${examplePath}`, + ); + } + const regionStart = `//#region ${regionName}`; const regionEnd = `//#endregion ${regionName}`; @@ -281,53 +293,46 @@ function extractRegion( * Get or load a region from the cache. * @param sourceFilePath The source file requesting the region * @param examplePath The relative path to the example file - * @param regionName The region name to extract + * @param regionName The region name to extract, or undefined for whole file * @param cache The region cache - * @returns The region content + * @returns The extracted code string */ function getOrLoadRegion( sourceFilePath: string, examplePath: string, - regionName: string, + regionName: string | undefined, cache: RegionCache, -): RegionContent { +): string { // Resolve the example path relative to the source file const sourceDir = dirname(sourceFilePath); const absoluteExamplePath = resolve(sourceDir, examplePath); - // Check cache first - let fileCache = cache.get(absoluteExamplePath); - if (fileCache) { - const cached = fileCache.get(regionName); - if (cached) { - return cached; - } - } + // File content is always cached with key ending in "#" (empty region) + const fileKey = `${absoluteExamplePath}#`; + let fileContent = cache.get(fileKey); - // Load the example file - let exampleContent: string; - try { - exampleContent = readFileSync(absoluteExamplePath, "utf-8"); - } catch { - throw new Error(`Example file not found: ${absoluteExamplePath}`); + if (fileContent === undefined) { + try { + fileContent = readFileSync(absoluteExamplePath, "utf-8").trim(); + } catch { + throw new Error(`Example file not found: ${absoluteExamplePath}`); + } + cache.set(fileKey, fileContent); } - // Initialize file cache if needed - if (!fileCache) { - fileCache = new Map(); - cache.set(absoluteExamplePath, fileCache); + // If no region name, return whole file + if (!regionName) { + return fileContent; } - // Determine language from file extension - const language: "ts" | "tsx" = absoluteExamplePath.endsWith(".tsx") - ? "tsx" - : "ts"; + // Extract region from cached file content, cache the result + const regionKey = `${absoluteExamplePath}#${regionName}`; + let regionContent = cache.get(regionKey); - // Extract the region - const code = extractRegion(exampleContent, regionName, examplePath); - - const regionContent: RegionContent = { code, language }; - fileCache.set(regionName, regionContent); + if (regionContent === undefined) { + regionContent = extractRegion(fileContent, regionName, examplePath); + cache.set(regionKey, regionContent); + } return regionContent; } @@ -393,17 +398,14 @@ function processFile( const fence = fences[i]; try { - const regionContent = getOrLoadRegion( + const code = getOrLoadRegion( filePath, fence.examplePath, fence.regionName, cache, ); - const formattedCode = formatCodeLines( - regionContent.code, - fence.linePrefix, - ); + const formattedCode = formatCodeLines(code, fence.linePrefix); // Replace content between opening fence end and closing fence start content = diff --git a/tests/e2e/servers.spec.ts b/tests/e2e/servers.spec.ts index 0f2d8ec1..420da936 100644 --- a/tests/e2e/servers.spec.ts +++ b/tests/e2e/servers.spec.ts @@ -17,6 +17,7 @@ const DYNAMIC_MASKS: Record = { "cohort-heatmap": ['[class*="heatmapWrapper"]'], // Heatmap grid (random data) "customer-segmentation": [".chart-container"], // Scatter plot (random data) "debug-server": ["#event-log", "#callback-table-body"], // Event log and callback counts (dynamic) + quickstart: ["#server-time"], // Server time display "say-server": [".playBtn", ".playOverlayBtn"], // Play buttons may have different states shadertoy: ["#canvas"], // WebGL shader canvas (animated) "system-monitor": [ @@ -101,6 +102,11 @@ const ALL_SERVERS = [ { key: "map-server", name: "CesiumJS Map Server", dir: "map-server" }, { key: "pdf-server", name: "PDF Server", dir: "pdf-server" }, { key: "qr-server", name: "QR Code Server", dir: "qr-server" }, + { + key: "quickstart", + name: "Quickstart MCP App Server", + dir: "quickstart", + }, { key: "say-server", name: "Say Demo", dir: "say-server" }, { key: "scenario-modeler", diff --git a/tests/e2e/servers.spec.ts-snapshots/quickstart.png b/tests/e2e/servers.spec.ts-snapshots/quickstart.png new file mode 100644 index 00000000..18ad40e8 Binary files /dev/null and b/tests/e2e/servers.spec.ts-snapshots/quickstart.png differ