Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,47 @@ jobs:
- run: npm publish --provenance --access public ${{ steps.npm-tag.outputs.tag }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}

publish-examples:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: Release
needs: [publish]

permissions:
contents: read
id-token: write

strategy:
fail-fast: false
matrix:
example:
- basic-server-react
- basic-server-vanillajs
- budget-allocator-server
- cohort-heatmap-server
- customer-segmentation-server
- scenario-modeler-server
- system-monitor-server
- threejs-server
- wiki-explorer-server

steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
registry-url: "https://registry.npmjs.org"
- run: npm ci

- name: Build example
run: npm run build --workspace examples/${{ matrix.example }}

- name: Publish example
run: npm publish --workspace examples/${{ matrix.example }} --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
14 changes: 13 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,16 @@ jobs:
cache: npm
- run: npm ci
- run: npm run build
- run: npx pkg-pr-new publish
- run: npm run examples:build
- run: |
npx pkg-pr-new publish \
. \
./examples/basic-server-react \
./examples/basic-server-vanillajs \
./examples/budget-allocator-server \
./examples/cohort-heatmap-server \
./examples/customer-segmentation-server \
./examples/scenario-modeler-server \
./examples/system-monitor-server \
./examples/threejs-server \
./examples/wiki-explorer-server
24 changes: 19 additions & 5 deletions examples/basic-server-react/package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
{
"name": "basic-server-react",
"version": "1.0.0",
"private": true,
"name": "@modelcontextprotocol/server-basic-react",
"version": "0.1.0",
"type": "module",
"description": "Basic MCP App Server example using React",
"repository": {
"type": "git",
"url": "https://github.com/modelcontextprotocol/ext-apps",
"directory": "examples/basic-server-react"
},
"license": "MIT",
"main": "server.ts",
"files": [
"server.ts",
"server-utils.ts",
"dist"
],
"scripts": {
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
"serve": "bun server.ts",
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
"dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve'"
"dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve'",
"prepublishOnly": "npm run build"
},
"dependencies": {
"@modelcontextprotocol/ext-apps": "../..",
"@modelcontextprotocol/ext-apps": "^0.2.2",
"@modelcontextprotocol/sdk": "^1.24.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
Expand All @@ -26,6 +39,7 @@
"@vitejs/plugin-react": "^4.3.4",
"concurrently": "^9.2.1",
"cors": "^2.8.5",
"cross-env": "^10.1.0",
"express": "^5.1.0",
"typescript": "^5.9.3",
"vite": "^6.0.0",
Expand Down
68 changes: 68 additions & 0 deletions examples/basic-server-react/server-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Shared utilities for running MCP servers with Streamable HTTP transport.
*/

import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import cors from "cors";
import type { Request, Response } from "express";

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(
createServer: () => McpServer,
options: ServerOptions,
): Promise<void> {
const { port, name = "MCP Server" } = options;

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, () => {
console.log(`${name} 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);
}
34 changes: 19 additions & 15 deletions examples/basic-server-react/server.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,62 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
import fs from "node:fs/promises";
import path from "node:path";
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps/server";
import { startServer } from "./src/server-utils.js";
import { startServer } from "./server-utils.js";

const DIST_DIR = path.join(import.meta.dirname, "dist");

/**
* Creates a new MCP server instance with tools and resources registered.
*/
function createServer(): McpServer {
export function createServer(): McpServer {
const server = new McpServer({
name: "Basic MCP App Server (React)",
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[RESOURCE_URI_META_KEY]` 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 as an ISO 8601 string.",
description: "Returns the current server time.",
inputSchema: {},
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async (): Promise<CallToolResult> => {
const time = new Date().toISOString();
return { content: [{ type: "text", text: time }] };
return { content: [{ type: "text", text: new Date().toISOString() }] };
},
);

// Register the resource, which returns the bundled HTML/JavaScript for the UI.
Comment on lines -19 to -39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are educational examples, I would like to preserve these comments.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully restored

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean restored in this PR or elsewhere? They still seem to be removed in the current diff for this PR.

registerAppResource(server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async (): Promise<ReadResourceResult> => {
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");

return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
};
},
);

return server;
}

startServer(createServer);
async function main() {
if (process.argv.includes("--stdio")) {
await createServer().connect(new StdioServerTransport());
} else {
const port = parseInt(process.env.PORT ?? "3001", 10);
await startServer(createServer, { port, name: "Basic MCP App Server (React)" });
}
}

main().catch((e) => {
console.error(e);
process.exit(1);
});
110 changes: 0 additions & 110 deletions examples/basic-server-react/src/server-utils.ts

This file was deleted.

Loading
Loading