/**
* 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);