Skip to main content
Glama
server.ts20.2 kB
/** * MCP Apps POC Server - Comprehensive Demo * * Demonstrates SEP-1865 MCP Apps Extension with multiple interactive UIs: * - Quick Tasks: Interactive task manager * - Data Visualization: Charts and graphs * - Form Builder: Dynamic forms with validation * - System Monitor: Real-time metrics dashboard * - Code Playground: JavaScript execution environment * * Run modes: * - HTTP (default): npm start -> http://localhost:3001/mcp * - stdio: npm run start:stdio -> for MCP client integration */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import type { Request, Response } from "express"; import cors from "cors"; import fs from "node:fs/promises"; import path from "node:path"; import { z } from "zod"; // MCP Apps constants const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app"; const RESOURCE_URI_META_KEY = "ui/resourceUri"; const DIST_DIR = path.join(import.meta.dirname, "dist"); // ============================================================================ // Quick Tasks Data // ============================================================================ interface Task { id: string; text: string; completed: boolean; createdAt: string; } const tasks: Task[] = [ { id: "1", text: "Try out MCP Apps Extension", completed: false, createdAt: new Date().toISOString() }, { id: "2", text: "Build something cool with AI", completed: false, createdAt: new Date().toISOString() }, { id: "3", text: "Share on GitHub", completed: false, createdAt: new Date().toISOString() }, ]; function generateId(): string { return Math.random().toString(36).substring(2, 9); } // ============================================================================ // Data Visualization Sample Data // ============================================================================ function generateChartData() { const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const currentMonth = new Date().getMonth(); const labels = months.slice(0, currentMonth + 1); return { title: "Revenue & Expenses 2024", labels, series: [ { name: "Revenue", data: labels.map(() => Math.floor(Math.random() * 50000) + 80000), }, { name: "Expenses", data: labels.map(() => Math.floor(Math.random() * 30000) + 40000), }, ], stats: [ { label: "Total Revenue", value: "$1.2M", change: 12.5 }, { label: "Profit Margin", value: "34%", change: 5.2 }, { label: "Customers", value: "2,847", change: 8.1 }, { label: "Avg Order", value: "$425", change: -2.3 }, ], distribution: [ { label: "Product Sales", value: 450000 }, { label: "Services", value: 280000 }, { label: "Subscriptions", value: 320000 }, { label: "Consulting", value: 150000 }, ], }; } // ============================================================================ // Form Builder Sample Configurations // ============================================================================ const formConfigs: Record<string, object> = { contact: { icon: "📧", title: "Contact Form", description: "Get in touch with our team", sections: [ { title: "Personal Information", icon: "👤", fields: [ { id: "name", type: "text", label: "Full Name", placeholder: "John Doe", required: true }, { id: "email", type: "email", label: "Email Address", placeholder: "john@example.com", required: true }, { id: "phone", type: "tel", label: "Phone Number", placeholder: "+1 (555) 123-4567", hint: "Optional" }, ], }, { title: "Message", icon: "💬", fields: [ { id: "subject", type: "select", label: "Subject", required: true, options: [ { value: "general", label: "General Inquiry" }, { value: "support", label: "Technical Support" }, { value: "sales", label: "Sales Question" }, { value: "feedback", label: "Feedback" }, ], }, { id: "message", type: "textarea", label: "Your Message", placeholder: "How can we help?", required: true, rows: 5 }, ], }, ], }, survey: { icon: "📊", title: "Customer Satisfaction Survey", description: "Help us improve our services", sections: [ { title: "Your Experience", icon: "⭐", fields: [ { id: "satisfaction", type: "radio", label: "Overall Satisfaction", required: true, options: [ { value: "5", label: "Very Satisfied" }, { value: "4", label: "Satisfied" }, { value: "3", label: "Neutral" }, { value: "2", label: "Dissatisfied" }, { value: "1", label: "Very Dissatisfied" }, ], }, { id: "recommend", type: "range", label: "How likely are you to recommend us? (0-10)", min: 0, max: 10, default: 5, }, ], }, { title: "Feedback", icon: "📝", fields: [ { id: "improvements", type: "checkbox", label: "What could we improve?", options: [ { value: "speed", label: "Response Time" }, { value: "quality", label: "Product Quality" }, { value: "support", label: "Customer Support" }, { value: "price", label: "Pricing" }, { value: "features", label: "Features" }, ], }, { id: "comments", type: "textarea", label: "Additional Comments", placeholder: "Share your thoughts...", rows: 4 }, ], }, ], }, registration: { icon: "📝", title: "Event Registration", description: "Register for our upcoming event", sections: [ { title: "Attendee Details", icon: "🎫", fields: [ { id: "firstName", type: "text", label: "First Name", required: true }, { id: "lastName", type: "text", label: "Last Name", required: true }, { id: "email", type: "email", label: "Email", required: true }, { id: "company", type: "text", label: "Company/Organization" }, ], }, { title: "Event Preferences", icon: "📅", fields: [ { id: "sessions", type: "checkbox", label: "Which sessions will you attend?", required: true, options: [ { value: "keynote", label: "Keynote (9:00 AM)" }, { value: "workshop1", label: "Workshop A (11:00 AM)" }, { value: "workshop2", label: "Workshop B (2:00 PM)" }, { value: "networking", label: "Networking Event (5:00 PM)" }, ], }, { id: "dietary", type: "select", label: "Dietary Requirements", options: [ { value: "none", label: "No restrictions" }, { value: "vegetarian", label: "Vegetarian" }, { value: "vegan", label: "Vegan" }, { value: "gluten-free", label: "Gluten-free" }, { value: "other", label: "Other" }, ], }, { id: "topics", type: "tags", label: "Topics of Interest", placeholder: "Type and press Enter" }, ], }, ], }, }; // ============================================================================ // System Monitor Simulated Data // ============================================================================ const processNames = [ "chrome", "node", "code", "docker", "slack", "spotify", "zoom", "firefox", "python", "java", "nginx", "postgres", "redis", "webpack", "vite" ]; function generateSystemMetrics() { // Generate realistic-looking system metrics const cpuBase = 20 + Math.random() * 40; const memBase = 40 + Math.random() * 30; return { cpu: cpuBase + Math.random() * 15, memory: memBase + Math.random() * 10, disk: 45 + Math.random() * 20, network: Math.random() * 5, processes: processNames .sort(() => Math.random() - 0.5) .slice(0, 8) .map((name) => ({ name, cpu: Math.random() * 25, memory: Math.random() * 15, })), }; } // ============================================================================ // Server Creation // ============================================================================ function createServer(): McpServer { const server = new McpServer({ name: "MCP Apps POC Server", version: "1.0.0", }); // Resource URIs const uris = { tasks: "ui://mcp-apps-poc/tasks.html", dataViz: "ui://mcp-apps-poc/data-viz.html", form: "ui://mcp-apps-poc/form.html", monitor: "ui://mcp-apps-poc/monitor.html", playground: "ui://mcp-apps-poc/playground.html", }; // ========================================================================== // Quick Tasks Tools // ========================================================================== server.tool( "get-tasks", "Returns the current task list and opens an interactive task manager UI.", {}, async (): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify({ tasks }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.tasks }, }) ); server.tool( "add-task", "Adds a new task to the list.", { text: z.string().describe("The task text") }, async (args: { text: string }): Promise<CallToolResult> => { const task: Task = { id: generateId(), text: args.text, completed: false, createdAt: new Date().toISOString(), }; tasks.push(task); return { content: [{ type: "text", text: JSON.stringify({ task, tasks }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.tasks }, }; } ); server.tool( "toggle-task", "Toggles a task's completion status.", { id: z.string().describe("The task ID") }, async (args: { id: string }): Promise<CallToolResult> => { const task = tasks.find((t) => t.id === args.id); if (!task) { return { content: [{ type: "text", text: JSON.stringify({ error: "Task not found" }) }], isError: true, }; } task.completed = !task.completed; return { content: [{ type: "text", text: JSON.stringify({ task, tasks }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.tasks }, }; } ); server.tool( "delete-task", "Deletes a task from the list.", { id: z.string().describe("The task ID") }, async (args: { id: string }): Promise<CallToolResult> => { const index = tasks.findIndex((t) => t.id === args.id); if (index === -1) { return { content: [{ type: "text", text: JSON.stringify({ error: "Task not found" }) }], isError: true, }; } const [deleted] = tasks.splice(index, 1); return { content: [{ type: "text", text: JSON.stringify({ deleted, tasks }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.tasks }, }; } ); // ========================================================================== // Data Visualization Tools // ========================================================================== server.tool( "get-chart-data", "Returns sample chart data and opens an interactive data visualization UI with bar charts, line charts, and pie charts.", {}, async (): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify(generateChartData()) }], _meta: { [RESOURCE_URI_META_KEY]: uris.dataViz }, }) ); server.tool( "visualize-data", "Visualizes custom data in interactive charts. Provide your own data series for visualization.", { title: z.string().describe("Chart title"), labels: z.array(z.string()).describe("X-axis labels (e.g., months, categories)"), series: z.array( z.object({ name: z.string().describe("Series name"), data: z.array(z.number()).describe("Data values"), }) ).describe("Data series to plot"), }, async (args): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify({ title: args.title, labels: args.labels, series: args.series, stats: [], distribution: [], }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.dataViz }, }) ); // ========================================================================== // Form Builder Tools // ========================================================================== server.tool( "show-form", "Opens a dynamic form UI. Choose from preset forms: 'contact', 'survey', or 'registration'.", { formType: z.enum(["contact", "survey", "registration"]).describe("The type of form to display"), }, async (args: { formType: string }): Promise<CallToolResult> => { const config = formConfigs[args.formType] || formConfigs.contact; return { content: [{ type: "text", text: JSON.stringify(config) }], _meta: { [RESOURCE_URI_META_KEY]: uris.form }, }; } ); server.tool( "create-custom-form", "Creates a custom form with specified fields. The form will be displayed in an interactive UI.", { title: z.string().describe("Form title"), description: z.string().optional().describe("Form description"), fields: z.array( z.object({ id: z.string().describe("Unique field identifier"), type: z.enum(["text", "email", "number", "tel", "textarea", "select", "radio", "checkbox", "range", "date"]), label: z.string().describe("Field label"), placeholder: z.string().optional(), required: z.boolean().optional(), options: z.array(z.object({ value: z.string(), label: z.string() })).optional(), }) ).describe("Form fields configuration"), }, async (args): Promise<CallToolResult> => { const config = { icon: "📝", title: args.title, description: args.description || "", sections: [{ fields: args.fields }], }; return { content: [{ type: "text", text: JSON.stringify(config) }], _meta: { [RESOURCE_URI_META_KEY]: uris.form }, }; } ); // ========================================================================== // System Monitor Tools // ========================================================================== server.tool( "get-system-metrics", "Returns simulated system metrics (CPU, memory, disk, network) and opens a real-time monitoring dashboard.", {}, async (): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify(generateSystemMetrics()) }], _meta: { [RESOURCE_URI_META_KEY]: uris.monitor }, }) ); server.tool( "monitor-system", "Opens the system monitoring dashboard with live updates.", {}, async (): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify(generateSystemMetrics()) }], _meta: { [RESOURCE_URI_META_KEY]: uris.monitor }, }) ); // ========================================================================== // Code Playground Tools // ========================================================================== server.tool( "open-playground", "Opens an interactive JavaScript code playground where you can write, run, and share code.", { code: z.string().optional().describe("Optional initial code to load into the playground"), }, async (args): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify({ code: args.code || "" }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.playground }, }) ); server.tool( "run-code", "Runs JavaScript code in the playground and shows the result.", { code: z.string().describe("JavaScript code to execute"), }, async (args): Promise<CallToolResult> => ({ content: [{ type: "text", text: JSON.stringify({ code: args.code }) }], _meta: { [RESOURCE_URI_META_KEY]: uris.playground }, }) ); // ========================================================================== // UI Resources // ========================================================================== const registerResource = (uri: string, filename: string, description: string) => { server.resource( uri, uri, { mimeType: RESOURCE_MIME_TYPE, description }, async (): Promise<ReadResourceResult> => { const html = await fs.readFile(path.join(DIST_DIR, filename), "utf-8"); return { contents: [{ uri, mimeType: RESOURCE_MIME_TYPE, text: html }], }; } ); }; registerResource(uris.tasks, "tasks.html", "Quick Tasks Interactive UI"); registerResource(uris.dataViz, "data-viz.html", "Data Visualization Dashboard"); registerResource(uris.form, "form.html", "Dynamic Form Builder"); registerResource(uris.monitor, "monitor.html", "System Monitoring Dashboard"); registerResource(uris.playground, "playground.html", "JavaScript Code Playground"); return server; } // ============================================================================ // Server Startup // ============================================================================ async function startStdioServer(): Promise<void> { const server = createServer(); const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Apps POC Server running on stdio"); } async function startHttpServer(): Promise<void> { const port = parseInt(process.env.PORT ?? "3001", 10); const expressApp = createMcpExpressApp({ host: "0.0.0.0" }); expressApp.use(cors()); expressApp.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, }); } } }); return new Promise((resolve, reject) => { const httpServer = expressApp.listen(port, (err?: Error) => { if (err) return reject(err); console.log(`MCP Apps POC Server running at http://localhost:${port}/mcp`); console.log("\nAvailable tools:"); console.log(" - get-tasks, add-task, toggle-task, delete-task"); console.log(" - get-chart-data, visualize-data"); console.log(" - show-form, create-custom-form"); console.log(" - get-system-metrics, monitor-system"); console.log(" - open-playground, run-code"); resolve(); }); const shutdown = () => { console.log("\nShutting down..."); httpServer.close(() => process.exit(0)); }; process.on("SIGINT", shutdown); process.on("SIGTERM", shutdown); }); } async function main() { if (process.argv.includes("--stdio")) { await startStdioServer(); } else { await startHttpServer(); } } main().catch(console.error);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamesdowzard/mcp-apps-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server