feat: publish example servers to npm (#184)
* feat: publish example servers to npm Make example MCP App servers publishable to npm under the @modelcontextprotocol scope: - server-basic-react - server-basic-vanillajs - server-budget-allocator - server-cohort-heatmap - server-customer-segmentation - server-scenario-modeler - server-system-monitor - server-threejs - server-wiki-explorer Changes: - Remove `private: true` from example package.json files - Add proper npm metadata (description, repository, license, files) - Update ext-apps dependency from relative to ^0.2.2 - Copy server-utils.ts into each package for standalone use - Add publish-examples job to npm-publish.yml workflow The examples will be published automatically when a GitHub release is created, after the main SDK package is published. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: publish examples with pkg-pr-new for PR testing * feat: export createServer from example servers * style: format integration-server/server.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: update e2e snapshots for basic-server examples Update golden screenshots for basic-react and basic-vanillajs after the tool output was simplified from JSON to plain text in #182. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * merge * refactor: update sheet-music-server and video-resource-server to new publishing pattern - Move server-utils.ts from src/ to root level - Add npm publishing metadata (scoped name, repository, license, files) - Change ext-apps dependency from local to versioned ^0.2.2 - Update server.ts to handle stdio mode inline * update package-lock * fix: update package-lock.json for renamed server packages * feat: add NASA public domain video to video-resource-server Added 'nasa-earth' option - NASA ISS Earth observations (6.4MB). Source: archive.org/details/NASA-Ultra-High-Definition (public domain) * fix: add default videoId for video-resource-server * fix: use existing bunny-1mb as default video, remove nasa-earth * refactor: update new framework examples to publishing pattern Add publishing metadata for: - @modelcontextprotocol/server-basic-preact - @modelcontextprotocol/server-basic-solid - @modelcontextprotocol/server-basic-svelte - @modelcontextprotocol/server-basic-vue Changes: - Move server-utils.ts from src/ to root - Add npm publishing metadata - Update ext-apps dep to versioned ^0.2.2 - Add stdio mode support in main() * fix: add missing @rollup/rollup-win32-arm64-msvc to optionalDependencies This fixes the Windows ARM64 CI build failure caused by npm's optional dependency bug. The package.json was missing this platform-specific rollup binding. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
This commit is contained in:
parent
8acec0eaad
commit
c25d464d18
70 changed files with 3627 additions and 2776 deletions
44
.github/workflows/npm-publish.yml
vendored
44
.github/workflows/npm-publish.yml
vendored
|
|
@ -83,3 +83,47 @@ jobs:
|
||||||
- run: npm publish --provenance --access public ${{ steps.npm-tag.outputs.tag }}
|
- run: npm publish --provenance --access public ${{ steps.npm-tag.outputs.tag }}
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_SECRET }}
|
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
.github/workflows/publish.yml
vendored
14
.github/workflows/publish.yml
vendored
|
|
@ -20,4 +20,16 @@ jobs:
|
||||||
cache: npm
|
cache: npm
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "basic-server-preact",
|
"name": "@modelcontextprotocol/server-basic-preact",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"description": "Basic MCP App Server example using Preact",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||||
|
"directory": "examples/basic-server-preact"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "server.ts",
|
||||||
|
"files": [
|
||||||
|
"server.ts",
|
||||||
|
"server-utils.ts",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||||
"serve": "bun server.ts",
|
"serve": "bun server.ts",
|
||||||
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
|
"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": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@modelcontextprotocol/ext-apps": "^0.2.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||||
"preact": "^10.0.0",
|
"preact": "^10.0.0",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
|
|
@ -23,7 +36,7 @@
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^10.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
|
|
|
||||||
68
examples/basic-server-preact/server-utils.ts
Normal file
68
examples/basic-server-preact/server-utils.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { startServer } from "./src/server-utils.js";
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
||||||
|
import { startServer } from "./server-utils.js";
|
||||||
|
|
||||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||||
|
|
||||||
|
|
@ -55,4 +56,16 @@ function createServer(): McpServer {
|
||||||
return server;
|
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 (Preact)" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/**
|
|
||||||
* Shared utilities for running MCP servers with various transports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server using the appropriate transport based on command-line arguments.
|
|
||||||
*
|
|
||||||
* If `--stdio` is passed, uses stdio transport. Otherwise, uses Streamable HTTP transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (process.argv.includes("--stdio")) {
|
|
||||||
await startStdioServer(createServer);
|
|
||||||
} else {
|
|
||||||
await startStreamableHttpServer(createServer);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with stdio transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startStdioServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
await createServer().connect(new StdioServerTransport());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with Streamable HTTP transport in stateless mode.
|
|
||||||
*
|
|
||||||
* Each request creates a fresh server and transport instance, which are
|
|
||||||
* closed when the response ends (no session tracking).
|
|
||||||
*
|
|
||||||
* The server listens on the port specified by the PORT environment variable,
|
|
||||||
* defaulting to 3001 if not set.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance per request.
|
|
||||||
*/
|
|
||||||
export async function startStreamableHttpServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
const port = parseInt(process.env.PORT ?? "3001", 10);
|
|
||||||
|
|
||||||
// Express app - bind to all interfaces for development/testing
|
|
||||||
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
|
||||||
expressApp.use(cors());
|
|
||||||
|
|
||||||
expressApp.all("/mcp", async (req: Request, res: Response) => {
|
|
||||||
// Create fresh server and transport for each request (stateless mode)
|
|
||||||
const server = createServer();
|
|
||||||
const transport = new StreamableHTTPServerTransport({
|
|
||||||
sessionIdGenerator: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up when response ends
|
|
||||||
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 { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
||||||
|
|
||||||
const httpServer = expressApp.listen(port, (err?: Error) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
console.log(`Server listening on http://localhost:${port}/mcp`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
const shutdown = () => {
|
|
||||||
console.log("\nShutting down...");
|
|
||||||
httpServer.close(() => process.exit(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on("SIGINT", shutdown);
|
|
||||||
process.on("SIGTERM", shutdown);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "basic-server-react",
|
"name": "@modelcontextprotocol/server-basic-react",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
"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": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||||
"serve": "bun server.ts",
|
"serve": "bun server.ts",
|
||||||
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
|
"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": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@modelcontextprotocol/ext-apps": "^0.2.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
|
@ -26,6 +39,7 @@
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
|
|
|
||||||
68
examples/basic-server-react/server-utils.ts
Normal file
68
examples/basic-server-react/server-utils.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,53 +1,45 @@
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps/server";
|
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");
|
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new MCP server instance with tools and resources registered.
|
* Creates a new MCP server instance with tools and resources registered.
|
||||||
*/
|
*/
|
||||||
function createServer(): McpServer {
|
export function createServer(): McpServer {
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
name: "Basic MCP App Server (React)",
|
name: "Basic MCP App Server (React)",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Two-part registration: tool + resource, tied together by the resource URI.
|
|
||||||
const resourceUri = "ui://get-time/mcp-app.html";
|
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,
|
registerAppTool(server,
|
||||||
"get-time",
|
"get-time",
|
||||||
{
|
{
|
||||||
title: "Get Time",
|
title: "Get Time",
|
||||||
description: "Returns the current server time as an ISO 8601 string.",
|
description: "Returns the current server time.",
|
||||||
inputSchema: {},
|
inputSchema: {},
|
||||||
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
|
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
|
||||||
},
|
},
|
||||||
async (): Promise<CallToolResult> => {
|
async (): Promise<CallToolResult> => {
|
||||||
const time = new Date().toISOString();
|
return { content: [{ type: "text", text: new Date().toISOString() }] };
|
||||||
return { content: [{ type: "text", text: time }] };
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Register the resource, which returns the bundled HTML/JavaScript for the UI.
|
|
||||||
registerAppResource(server,
|
registerAppResource(server,
|
||||||
resourceUri,
|
resourceUri,
|
||||||
resourceUri,
|
resourceUri,
|
||||||
{ mimeType: RESOURCE_MIME_TYPE },
|
{ mimeType: RESOURCE_MIME_TYPE },
|
||||||
async (): Promise<ReadResourceResult> => {
|
async (): Promise<ReadResourceResult> => {
|
||||||
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
|
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contents: [
|
contents: [{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html }],
|
||||||
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -55,4 +47,16 @@ function createServer(): McpServer {
|
||||||
return server;
|
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);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/**
|
|
||||||
* Shared utilities for running MCP servers with various transports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server using the appropriate transport based on command-line arguments.
|
|
||||||
*
|
|
||||||
* If `--stdio` is passed, uses stdio transport. Otherwise, uses Streamable HTTP transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (process.argv.includes("--stdio")) {
|
|
||||||
await startStdioServer(createServer);
|
|
||||||
} else {
|
|
||||||
await startStreamableHttpServer(createServer);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with stdio transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startStdioServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
await createServer().connect(new StdioServerTransport());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with Streamable HTTP transport in stateless mode.
|
|
||||||
*
|
|
||||||
* Each request creates a fresh server and transport instance, which are
|
|
||||||
* closed when the response ends (no session tracking).
|
|
||||||
*
|
|
||||||
* The server listens on the port specified by the PORT environment variable,
|
|
||||||
* defaulting to 3001 if not set.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance per request.
|
|
||||||
*/
|
|
||||||
export async function startStreamableHttpServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
const port = parseInt(process.env.PORT ?? "3001", 10);
|
|
||||||
|
|
||||||
// Express app - bind to all interfaces for development/testing
|
|
||||||
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
|
||||||
expressApp.use(cors());
|
|
||||||
|
|
||||||
expressApp.all("/mcp", async (req: Request, res: Response) => {
|
|
||||||
// Create fresh server and transport for each request (stateless mode)
|
|
||||||
const server = createServer();
|
|
||||||
const transport = new StreamableHTTPServerTransport({
|
|
||||||
sessionIdGenerator: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up when response ends
|
|
||||||
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 { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
||||||
|
|
||||||
const httpServer = expressApp.listen(port, (err?: Error) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
console.log(`Server listening on http://localhost:${port}/mcp`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
const shutdown = () => {
|
|
||||||
console.log("\nShutting down...");
|
|
||||||
httpServer.close(() => process.exit(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on("SIGINT", shutdown);
|
|
||||||
process.on("SIGTERM", shutdown);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "basic-server-solid",
|
"name": "@modelcontextprotocol/server-basic-solid",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"description": "Basic MCP App Server example using Solid",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||||
|
"directory": "examples/basic-server-solid"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "server.ts",
|
||||||
|
"files": [
|
||||||
|
"server.ts",
|
||||||
|
"server-utils.ts",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||||
"serve": "bun server.ts",
|
"serve": "bun server.ts",
|
||||||
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
|
"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": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@modelcontextprotocol/ext-apps": "^0.2.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||||
"solid-js": "^1.9.0",
|
"solid-js": "^1.9.0",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
|
|
@ -22,7 +35,7 @@
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^10.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
|
|
|
||||||
68
examples/basic-server-solid/server-utils.ts
Normal file
68
examples/basic-server-solid/server-utils.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { startServer } from "./src/server-utils.js";
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
||||||
|
import { startServer } from "./server-utils.js";
|
||||||
|
|
||||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||||
|
|
||||||
|
|
@ -55,4 +56,16 @@ function createServer(): McpServer {
|
||||||
return server;
|
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 (Solid)" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/**
|
|
||||||
* Shared utilities for running MCP servers with various transports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server using the appropriate transport based on command-line arguments.
|
|
||||||
*
|
|
||||||
* If `--stdio` is passed, uses stdio transport. Otherwise, uses Streamable HTTP transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (process.argv.includes("--stdio")) {
|
|
||||||
await startStdioServer(createServer);
|
|
||||||
} else {
|
|
||||||
await startStreamableHttpServer(createServer);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with stdio transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startStdioServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
await createServer().connect(new StdioServerTransport());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with Streamable HTTP transport in stateless mode.
|
|
||||||
*
|
|
||||||
* Each request creates a fresh server and transport instance, which are
|
|
||||||
* closed when the response ends (no session tracking).
|
|
||||||
*
|
|
||||||
* The server listens on the port specified by the PORT environment variable,
|
|
||||||
* defaulting to 3001 if not set.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance per request.
|
|
||||||
*/
|
|
||||||
export async function startStreamableHttpServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
const port = parseInt(process.env.PORT ?? "3001", 10);
|
|
||||||
|
|
||||||
// Express app - bind to all interfaces for development/testing
|
|
||||||
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
|
||||||
expressApp.use(cors());
|
|
||||||
|
|
||||||
expressApp.all("/mcp", async (req: Request, res: Response) => {
|
|
||||||
// Create fresh server and transport for each request (stateless mode)
|
|
||||||
const server = createServer();
|
|
||||||
const transport = new StreamableHTTPServerTransport({
|
|
||||||
sessionIdGenerator: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up when response ends
|
|
||||||
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 { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
||||||
|
|
||||||
const httpServer = expressApp.listen(port, (err?: Error) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
console.log(`Server listening on http://localhost:${port}/mcp`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
const shutdown = () => {
|
|
||||||
console.log("\nShutting down...");
|
|
||||||
httpServer.close(() => process.exit(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on("SIGINT", shutdown);
|
|
||||||
process.on("SIGTERM", shutdown);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "basic-server-svelte",
|
"name": "@modelcontextprotocol/server-basic-svelte",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"description": "Basic MCP App Server example using Svelte",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||||
|
"directory": "examples/basic-server-svelte"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "server.ts",
|
||||||
|
"files": [
|
||||||
|
"server.ts",
|
||||||
|
"server-utils.ts",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||||
"serve": "bun server.ts",
|
"serve": "bun server.ts",
|
||||||
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
|
"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": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@modelcontextprotocol/ext-apps": "^0.2.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
|
|
@ -23,6 +36,7 @@
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
|
|
|
||||||
68
examples/basic-server-svelte/server-utils.ts
Normal file
68
examples/basic-server-svelte/server-utils.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
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 type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { startServer } from "./src/server-utils.js";
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
||||||
|
import { startServer } from "./server-utils.js";
|
||||||
|
|
||||||
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
const DIST_DIR = path.join(import.meta.dirname, "dist");
|
||||||
|
|
||||||
|
|
@ -55,4 +56,16 @@ function createServer(): McpServer {
|
||||||
return server;
|
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 (Svelte)" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/**
|
|
||||||
* Shared utilities for running MCP servers with various transports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server using the appropriate transport based on command-line arguments.
|
|
||||||
*
|
|
||||||
* If `--stdio` is passed, uses stdio transport. Otherwise, uses Streamable HTTP transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (process.argv.includes("--stdio")) {
|
|
||||||
await startStdioServer(createServer);
|
|
||||||
} else {
|
|
||||||
await startStreamableHttpServer(createServer);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with stdio transport.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance.
|
|
||||||
*/
|
|
||||||
export async function startStdioServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
await createServer().connect(new StdioServerTransport());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an MCP server with Streamable HTTP transport in stateless mode.
|
|
||||||
*
|
|
||||||
* Each request creates a fresh server and transport instance, which are
|
|
||||||
* closed when the response ends (no session tracking).
|
|
||||||
*
|
|
||||||
* The server listens on the port specified by the PORT environment variable,
|
|
||||||
* defaulting to 3001 if not set.
|
|
||||||
*
|
|
||||||
* @param createServer - Factory function that creates a new McpServer instance per request.
|
|
||||||
*/
|
|
||||||
export async function startStreamableHttpServer(
|
|
||||||
createServer: () => McpServer,
|
|
||||||
): Promise<void> {
|
|
||||||
const port = parseInt(process.env.PORT ?? "3001", 10);
|
|
||||||
|
|
||||||
// Express app - bind to all interfaces for development/testing
|
|
||||||
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
|
||||||
expressApp.use(cors());
|
|
||||||
|
|
||||||
expressApp.all("/mcp", async (req: Request, res: Response) => {
|
|
||||||
// Create fresh server and transport for each request (stateless mode)
|
|
||||||
const server = createServer();
|
|
||||||
const transport = new StreamableHTTPServerTransport({
|
|
||||||
sessionIdGenerator: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up when response ends
|
|
||||||
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 { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
||||||
|
|
||||||
const httpServer = expressApp.listen(port, (err?: Error) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
console.log(`Server listening on http://localhost:${port}/mcp`);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
const shutdown = () => {
|
|
||||||
console.log("\nShutting down...");
|
|
||||||
httpServer.close(() => process.exit(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on("SIGINT", shutdown);
|
|
||||||
process.on("SIGTERM", shutdown);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "basic-server-vanillajs",
|
"name": "@modelcontextprotocol/server-basic-vanillajs",
|
||||||
"version": "1.0.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"description": "Basic MCP App Server example using vanilla JavaScript",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/modelcontextprotocol/ext-apps",
|
||||||
|
"directory": "examples/basic-server-vanillajs"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "server.ts",
|
||||||
|
"files": [
|
||||||
|
"server.ts",
|
||||||
|
"server-utils.ts",
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
"build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
|
||||||
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
"watch": "cross-env INPUT=mcp-app.html vite build --watch",
|
||||||
"serve": "bun server.ts",
|
"serve": "bun server.ts",
|
||||||
"start": "cross-env NODE_ENV=development npm run build && npm run serve",
|
"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": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/ext-apps": "../..",
|
"@modelcontextprotocol/ext-apps": "^0.2.2",
|
||||||
"@modelcontextprotocol/sdk": "^1.24.0",
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
||||||
"zod": "^4.1.13"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
|
|
@ -21,6 +34,7 @@
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^6.0.0",
|
"vite": "^6.0.0",
|
||||||
|
|
|
||||||
68
examples/basic-server-vanillajs/server-utils.ts
Normal file
68
examples/basic-server-vanillajs/server-utils.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue