diff --git a/examples/d3-graph-server/README.md b/examples/d3-graph-server/README.md
new file mode 100644
index 00000000..2943d609
--- /dev/null
+++ b/examples/d3-graph-server/README.md
@@ -0,0 +1,123 @@
+# Example: D3 Graph Server
+
+Interactive force-directed graph visualization using D3.js. Explore entity relationships like package dependencies, org charts, or knowledge graphs with zoom, pan, and node interaction.
+
+## Features
+
+- **Force-directed layout**: Physics-based graph simulation with D3.js
+- **Multiple graph datasets**: Package dependencies, org chart, and AI/ML knowledge graph
+- **Interactive nodes**: Drag to reposition, click to recenter view
+- **Zoom and pan**: Scroll to zoom, drag background to pan
+- **Tooltips**: Hover over nodes to see descriptions
+- **Node filtering**: Center on any node with configurable depth
+
+## Running
+
+1. Install dependencies:
+
+ ```bash
+ npm install
+ ```
+
+2. Build and start the server:
+
+ ```bash
+ npm run start:http # for Streamable HTTP transport
+ # OR
+ npm run start:stdio # for stdio transport
+ ```
+
+3. View using the [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) example or another MCP Apps-compatible host.
+
+### Tool Input Examples
+
+**Default (package dependencies graph):**
+
+```json
+{}
+```
+
+**Package dependencies - centered on React:**
+
+```json
+{
+ "graphId": "dependencies",
+ "centerNode": "react",
+ "depth": 2
+}
+```
+
+**Package dependencies - centered on D3:**
+
+```json
+{
+ "graphId": "dependencies",
+ "centerNode": "d3",
+ "depth": 3
+}
+```
+
+**Organization chart:**
+
+```json
+{
+ "graphId": "org-chart"
+}
+```
+
+**Org chart - centered on VP of Engineering:**
+
+```json
+{
+ "graphId": "org-chart",
+ "centerNode": "vp-eng",
+ "depth": 2
+}
+```
+
+**AI/ML knowledge graph:**
+
+```json
+{
+ "graphId": "knowledge"
+}
+```
+
+**Knowledge graph - centered on transformers:**
+
+```json
+{
+ "graphId": "knowledge",
+ "centerNode": "transformers",
+ "depth": 2
+}
+```
+
+**Knowledge graph - centered on PyTorch:**
+
+```json
+{
+ "graphId": "knowledge",
+ "centerNode": "pytorch",
+ "depth": 3
+}
+```
+
+## Architecture
+
+### Server (`server.ts`)
+
+MCP server with sample graph datasets representing different relationship types.
+
+Exposes one tool:
+
+- `get-graph-data` - Returns nodes and links for force-directed visualization
+
+### App (`src/mcp-app.ts`)
+
+Vanilla TypeScript app using D3.js that:
+
+- Receives graph data via the MCP App SDK
+- Renders an interactive force-directed graph with `d3.forceSimulation()`
+- Supports zoom/pan via `d3.zoom()`
+- Enables node dragging and click-to-recenter
diff --git a/examples/d3-graph-server/mcp-app.html b/examples/d3-graph-server/mcp-app.html
new file mode 100644
index 00000000..378eb1d9
--- /dev/null
+++ b/examples/d3-graph-server/mcp-app.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ D3 Graph Explorer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/d3-graph-server/package.json b/examples/d3-graph-server/package.json
new file mode 100644
index 00000000..5dddec32
--- /dev/null
+++ b/examples/d3-graph-server/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "d3-graph-server",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
+ "watch": "cross-env INPUT=mcp-app.html vite build --watch",
+ "serve": "bun server.ts",
+ "start": "cross-env NODE_ENV=development npm run build && npm run serve",
+ "dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve'"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/ext-apps": "../..",
+ "@modelcontextprotocol/sdk": "^1.24.0",
+ "d3": "^7.9.0",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.19",
+ "@types/d3": "^7.4.3",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.0.0",
+ "concurrently": "^9.2.1",
+ "cors": "^2.8.5",
+ "cross-env": "^7.0.3",
+ "express": "^5.1.0",
+ "typescript": "^5.9.3",
+ "vite": "^6.0.0",
+ "vite-plugin-singlefile": "^2.3.0"
+ }
+}
diff --git a/examples/d3-graph-server/server.ts b/examples/d3-graph-server/server.ts
new file mode 100644
index 00000000..7fd8853b
--- /dev/null
+++ b/examples/d3-graph-server/server.ts
@@ -0,0 +1,506 @@
+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import type {
+ CallToolResult,
+ ReadResourceResult,
+} from "@modelcontextprotocol/sdk/types.js";
+import fs from "node:fs/promises";
+import path from "node:path";
+import { z } from "zod";
+import {
+ RESOURCE_MIME_TYPE,
+ RESOURCE_URI_META_KEY,
+ registerAppResource,
+ registerAppTool,
+} from "@modelcontextprotocol/ext-apps/server";
+import { startServer } from "./src/server-utils.js";
+
+const DIST_DIR = path.join(import.meta.dirname, "dist");
+
+// ============================================================================
+// Schemas
+// ============================================================================
+
+const GraphNodeSchema = z.object({
+ id: z.string(),
+ group: z.string(),
+ size: z.number(),
+ description: z.string().optional(),
+});
+
+const GraphLinkSchema = z.object({
+ source: z.string(),
+ target: z.string(),
+ strength: z.number().min(0).max(1),
+});
+
+const GraphDataSchema = z.object({
+ nodes: z.array(GraphNodeSchema),
+ links: z.array(GraphLinkSchema),
+ metadata: z.object({
+ title: z.string(),
+ description: z.string(),
+ }),
+});
+
+const GetGraphDataInputSchema = z.object({
+ graphId: z
+ .enum(["dependencies", "org-chart", "knowledge"])
+ .optional()
+ .describe("Which graph dataset to load"),
+ centerNode: z.string().optional().describe("Node ID to center the view on"),
+ depth: z
+ .number()
+ .min(1)
+ .max(5)
+ .optional()
+ .describe("Depth of nodes to include from center (1-5)"),
+});
+
+// Types
+type GraphData = z.infer;
+
+// ============================================================================
+// Sample Graph Data
+// ============================================================================
+
+const SAMPLE_GRAPHS: Record = {
+ dependencies: {
+ nodes: [
+ {
+ id: "react",
+ group: "core",
+ size: 40,
+ description: "React library for building UIs",
+ },
+ {
+ id: "react-dom",
+ group: "core",
+ size: 30,
+ description: "React DOM renderer",
+ },
+ {
+ id: "recharts",
+ group: "viz",
+ size: 25,
+ description: "Charting library for React",
+ },
+ {
+ id: "d3",
+ group: "viz",
+ size: 35,
+ description: "Data visualization library",
+ },
+ {
+ id: "typescript",
+ group: "tooling",
+ size: 30,
+ description: "TypeScript compiler",
+ },
+ {
+ id: "vite",
+ group: "tooling",
+ size: 28,
+ description: "Next-generation frontend build tool",
+ },
+ {
+ id: "eslint",
+ group: "tooling",
+ size: 22,
+ description: "JavaScript linter",
+ },
+ {
+ id: "prettier",
+ group: "tooling",
+ size: 20,
+ description: "Code formatter",
+ },
+ {
+ id: "zod",
+ group: "runtime",
+ size: 18,
+ description: "TypeScript-first schema validation",
+ },
+ {
+ id: "express",
+ group: "runtime",
+ size: 26,
+ description: "Web framework for Node.js",
+ },
+ { id: "lodash", group: "util", size: 24, description: "Utility library" },
+ { id: "axios", group: "util", size: 20, description: "HTTP client" },
+ {
+ id: "dayjs",
+ group: "util",
+ size: 16,
+ description: "Date utility library",
+ },
+ {
+ id: "tailwindcss",
+ group: "styling",
+ size: 28,
+ description: "Utility-first CSS framework",
+ },
+ {
+ id: "postcss",
+ group: "styling",
+ size: 18,
+ description: "CSS transformer",
+ },
+ ],
+ links: [
+ { source: "react-dom", target: "react", strength: 1.0 },
+ { source: "recharts", target: "react", strength: 0.9 },
+ { source: "recharts", target: "d3", strength: 0.7 },
+ { source: "vite", target: "typescript", strength: 0.6 },
+ { source: "eslint", target: "typescript", strength: 0.5 },
+ { source: "prettier", target: "eslint", strength: 0.4 },
+ { source: "axios", target: "lodash", strength: 0.3 },
+ { source: "tailwindcss", target: "postcss", strength: 0.8 },
+ { source: "zod", target: "typescript", strength: 0.6 },
+ { source: "express", target: "zod", strength: 0.4 },
+ { source: "vite", target: "postcss", strength: 0.5 },
+ { source: "recharts", target: "lodash", strength: 0.3 },
+ ],
+ metadata: {
+ title: "Package Dependencies",
+ description: "Common npm package relationships in a modern web project",
+ },
+ },
+ "org-chart": {
+ nodes: [
+ {
+ id: "ceo",
+ group: "executive",
+ size: 45,
+ description: "Chief Executive Officer",
+ },
+ {
+ id: "cto",
+ group: "executive",
+ size: 38,
+ description: "Chief Technology Officer",
+ },
+ {
+ id: "cfo",
+ group: "executive",
+ size: 38,
+ description: "Chief Financial Officer",
+ },
+ {
+ id: "vp-eng",
+ group: "management",
+ size: 32,
+ description: "VP of Engineering",
+ },
+ {
+ id: "vp-product",
+ group: "management",
+ size: 32,
+ description: "VP of Product",
+ },
+ {
+ id: "vp-sales",
+ group: "management",
+ size: 32,
+ description: "VP of Sales",
+ },
+ {
+ id: "eng-lead-1",
+ group: "lead",
+ size: 26,
+ description: "Engineering Lead - Platform",
+ },
+ {
+ id: "eng-lead-2",
+ group: "lead",
+ size: 26,
+ description: "Engineering Lead - Frontend",
+ },
+ {
+ id: "pm-1",
+ group: "lead",
+ size: 24,
+ description: "Product Manager - Core",
+ },
+ {
+ id: "pm-2",
+ group: "lead",
+ size: 24,
+ description: "Product Manager - Growth",
+ },
+ { id: "dev-1", group: "ic", size: 18, description: "Senior Developer" },
+ { id: "dev-2", group: "ic", size: 18, description: "Developer" },
+ { id: "dev-3", group: "ic", size: 18, description: "Developer" },
+ { id: "designer", group: "ic", size: 20, description: "UX Designer" },
+ ],
+ links: [
+ { source: "cto", target: "ceo", strength: 1.0 },
+ { source: "cfo", target: "ceo", strength: 1.0 },
+ { source: "vp-eng", target: "cto", strength: 0.9 },
+ { source: "vp-product", target: "cto", strength: 0.9 },
+ { source: "vp-sales", target: "cfo", strength: 0.8 },
+ { source: "eng-lead-1", target: "vp-eng", strength: 0.8 },
+ { source: "eng-lead-2", target: "vp-eng", strength: 0.8 },
+ { source: "pm-1", target: "vp-product", strength: 0.8 },
+ { source: "pm-2", target: "vp-product", strength: 0.8 },
+ { source: "dev-1", target: "eng-lead-1", strength: 0.7 },
+ { source: "dev-2", target: "eng-lead-2", strength: 0.7 },
+ { source: "dev-3", target: "eng-lead-2", strength: 0.7 },
+ { source: "designer", target: "pm-1", strength: 0.6 },
+ { source: "designer", target: "eng-lead-2", strength: 0.4 },
+ ],
+ metadata: {
+ title: "Organization Chart",
+ description:
+ "Company organizational structure and reporting relationships",
+ },
+ },
+ knowledge: {
+ nodes: [
+ { id: "ml", group: "ai", size: 40, description: "Machine Learning" },
+ {
+ id: "deep-learning",
+ group: "ai",
+ size: 35,
+ description: "Deep Learning",
+ },
+ {
+ id: "nlp",
+ group: "ai",
+ size: 30,
+ description: "Natural Language Processing",
+ },
+ { id: "cv", group: "ai", size: 28, description: "Computer Vision" },
+ {
+ id: "transformers",
+ group: "architecture",
+ size: 32,
+ description: "Transformer architecture",
+ },
+ {
+ id: "cnn",
+ group: "architecture",
+ size: 26,
+ description: "Convolutional Neural Networks",
+ },
+ {
+ id: "rnn",
+ group: "architecture",
+ size: 22,
+ description: "Recurrent Neural Networks",
+ },
+ {
+ id: "python",
+ group: "tools",
+ size: 38,
+ description: "Python programming language",
+ },
+ {
+ id: "pytorch",
+ group: "tools",
+ size: 30,
+ description: "PyTorch framework",
+ },
+ {
+ id: "tensorflow",
+ group: "tools",
+ size: 28,
+ description: "TensorFlow framework",
+ },
+ {
+ id: "huggingface",
+ group: "tools",
+ size: 26,
+ description: "Hugging Face library",
+ },
+ {
+ id: "llm",
+ group: "application",
+ size: 35,
+ description: "Large Language Models",
+ },
+ { id: "chatgpt", group: "application", size: 30, description: "ChatGPT" },
+ {
+ id: "stable-diffusion",
+ group: "application",
+ size: 28,
+ description: "Stable Diffusion",
+ },
+ ],
+ links: [
+ { source: "deep-learning", target: "ml", strength: 1.0 },
+ { source: "nlp", target: "ml", strength: 0.9 },
+ { source: "cv", target: "ml", strength: 0.9 },
+ { source: "transformers", target: "deep-learning", strength: 0.8 },
+ { source: "cnn", target: "deep-learning", strength: 0.8 },
+ { source: "rnn", target: "deep-learning", strength: 0.7 },
+ { source: "transformers", target: "nlp", strength: 0.9 },
+ { source: "cnn", target: "cv", strength: 0.9 },
+ { source: "pytorch", target: "python", strength: 0.8 },
+ { source: "tensorflow", target: "python", strength: 0.8 },
+ { source: "huggingface", target: "transformers", strength: 0.9 },
+ { source: "huggingface", target: "pytorch", strength: 0.7 },
+ { source: "llm", target: "transformers", strength: 1.0 },
+ { source: "llm", target: "nlp", strength: 0.9 },
+ { source: "chatgpt", target: "llm", strength: 1.0 },
+ { source: "stable-diffusion", target: "cv", strength: 0.8 },
+ { source: "stable-diffusion", target: "deep-learning", strength: 0.7 },
+ ],
+ metadata: {
+ title: "AI/ML Knowledge Graph",
+ description:
+ "Concepts and relationships in artificial intelligence and machine learning",
+ },
+ },
+};
+
+// ============================================================================
+// Graph Filtering
+// ============================================================================
+
+function filterGraphByCenter(
+ graph: GraphData,
+ centerNode: string,
+ depth: number,
+): GraphData {
+ // Find all nodes within `depth` hops from centerNode
+ const nodeSet = new Set([centerNode]);
+ const linkSet = new Map>();
+
+ // Build adjacency map
+ for (const link of graph.links) {
+ if (!linkSet.has(link.source)) linkSet.set(link.source, new Set());
+ if (!linkSet.has(link.target)) linkSet.set(link.target, new Set());
+ linkSet.get(link.source)!.add(link.target);
+ linkSet.get(link.target)!.add(link.source);
+ }
+
+ // BFS to find nodes within depth
+ let frontier = [centerNode];
+ for (let d = 0; d < depth && frontier.length > 0; d++) {
+ const next: string[] = [];
+ for (const node of frontier) {
+ const neighbors = linkSet.get(node) ?? [];
+ for (const neighbor of neighbors) {
+ if (!nodeSet.has(neighbor)) {
+ nodeSet.add(neighbor);
+ next.push(neighbor);
+ }
+ }
+ }
+ frontier = next;
+ }
+
+ // Filter nodes and links
+ const filteredNodes = graph.nodes.filter((n) => nodeSet.has(n.id));
+ const filteredLinks = graph.links.filter(
+ (l) => nodeSet.has(l.source) && nodeSet.has(l.target),
+ );
+
+ return {
+ nodes: filteredNodes,
+ links: filteredLinks,
+ metadata: {
+ ...graph.metadata,
+ title: `${graph.metadata.title} (centered on ${centerNode})`,
+ },
+ };
+}
+
+// ============================================================================
+// MCP Server
+// ============================================================================
+
+function createServer(): McpServer {
+ const server = new McpServer({
+ name: "D3 Graph Server",
+ version: "1.0.0",
+ });
+
+ const resourceUri = "ui://d3-graph/mcp-app.html";
+
+ registerAppTool(
+ server,
+ "get-graph-data",
+ {
+ title: "Get Graph Data",
+ description:
+ "Returns graph data (nodes and links) for force-directed visualization. Supports multiple graph datasets and optional filtering by center node.",
+ inputSchema: GetGraphDataInputSchema.shape,
+ _meta: { [RESOURCE_URI_META_KEY]: resourceUri },
+ },
+ async (args: {
+ graphId?: "dependencies" | "org-chart" | "knowledge";
+ centerNode?: string;
+ depth?: number;
+ }): Promise => {
+ const graphId = args.graphId ?? "dependencies";
+ let graphData = SAMPLE_GRAPHS[graphId];
+
+ if (!graphData) {
+ return {
+ isError: true,
+ content: [{ type: "text", text: `Unknown graph: ${graphId}` }],
+ };
+ }
+
+ // Filter if centerNode is specified
+ if (args.centerNode) {
+ const nodeExists = graphData.nodes.some(
+ (n) => n.id === args.centerNode,
+ );
+ if (!nodeExists) {
+ return {
+ isError: true,
+ content: [
+ { type: "text", text: `Node not found: ${args.centerNode}` },
+ ],
+ };
+ }
+ graphData = filterGraphByCenter(
+ graphData,
+ args.centerNode,
+ args.depth ?? 2,
+ );
+ }
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ graphId,
+ ...graphData,
+ }),
+ },
+ ],
+ };
+ },
+ );
+
+ registerAppResource(
+ server,
+ resourceUri,
+ resourceUri,
+ { mimeType: RESOURCE_MIME_TYPE, description: "D3 Graph Explorer UI" },
+ async (): Promise => {
+ const html = await fs.readFile(
+ path.join(DIST_DIR, "mcp-app.html"),
+ "utf-8",
+ );
+ return {
+ contents: [
+ {
+ uri: resourceUri,
+ mimeType: RESOURCE_MIME_TYPE,
+ text: html,
+ },
+ ],
+ };
+ },
+ );
+
+ return server;
+}
+
+startServer(createServer);
diff --git a/examples/d3-graph-server/src/global.css b/examples/d3-graph-server/src/global.css
new file mode 100644
index 00000000..59c501f5
--- /dev/null
+++ b/examples/d3-graph-server/src/global.css
@@ -0,0 +1,12 @@
+* {
+ box-sizing: border-box;
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ font-family: system-ui, -apple-system, sans-serif;
+ font-size: 1rem;
+ height: 100%;
+ overflow: hidden;
+}
diff --git a/examples/d3-graph-server/src/mcp-app.css b/examples/d3-graph-server/src/mcp-app.css
new file mode 100644
index 00000000..02a0b735
--- /dev/null
+++ b/examples/d3-graph-server/src/mcp-app.css
@@ -0,0 +1,126 @@
+#main {
+ width: 100%;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ background: #f8fafc;
+}
+
+@media (prefers-color-scheme: dark) {
+ #main {
+ background: #0f172a;
+ }
+}
+
+#controls {
+ display: flex;
+ gap: 0.5rem;
+ padding: 0.75rem;
+ background: white;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+@media (prefers-color-scheme: dark) {
+ #controls {
+ background: #1e293b;
+ border-bottom-color: #334155;
+ }
+}
+
+#controls select,
+#controls button {
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+#controls select {
+ border: 1px solid #cbd5e1;
+ background: white;
+ flex: 1;
+ max-width: 250px;
+}
+
+@media (prefers-color-scheme: dark) {
+ #controls select {
+ background: #334155;
+ border-color: #475569;
+ color: #f1f5f9;
+ }
+}
+
+#controls button {
+ border: none;
+ background: #3b82f6;
+ color: white;
+ font-weight: 500;
+}
+
+#controls button:hover {
+ background: #2563eb;
+}
+
+#graph-container {
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+#graph {
+ width: 100%;
+ height: 100%;
+}
+
+.node {
+ cursor: pointer;
+ stroke: #fff;
+ stroke-width: 2px;
+}
+
+.node:hover {
+ stroke-width: 3px;
+}
+
+.link {
+ stroke: #94a3b8;
+ stroke-opacity: 0.6;
+}
+
+@media (prefers-color-scheme: dark) {
+ .link {
+ stroke: #64748b;
+ }
+}
+
+.node-label {
+ font-size: 10px;
+ pointer-events: none;
+ fill: #1e293b;
+ text-anchor: middle;
+ dominant-baseline: central;
+}
+
+@media (prefers-color-scheme: dark) {
+ .node-label {
+ fill: #f1f5f9;
+ }
+}
+
+#tooltip {
+ position: absolute;
+ padding: 8px 12px;
+ background: rgba(15, 23, 42, 0.9);
+ color: white;
+ border-radius: 6px;
+ font-size: 12px;
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 0.15s;
+ z-index: 100;
+ max-width: 200px;
+}
+
+#tooltip.visible {
+ opacity: 1;
+}
diff --git a/examples/d3-graph-server/src/mcp-app.ts b/examples/d3-graph-server/src/mcp-app.ts
new file mode 100644
index 00000000..080276db
--- /dev/null
+++ b/examples/d3-graph-server/src/mcp-app.ts
@@ -0,0 +1,301 @@
+/**
+ * D3 Force-Directed Graph Visualization for MCP Apps
+ */
+import { App } from "@modelcontextprotocol/ext-apps";
+import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
+import * as d3 from "d3";
+import "./global.css";
+import "./mcp-app.css";
+
+// Types
+interface GraphNode {
+ id: string;
+ group: string;
+ size: number;
+ description?: string;
+ x?: number;
+ y?: number;
+ fx?: number | null;
+ fy?: number | null;
+}
+
+interface GraphLink {
+ source: string | GraphNode;
+ target: string | GraphNode;
+ strength: number;
+}
+
+interface GraphData {
+ graphId: string;
+ nodes: GraphNode[];
+ links: GraphLink[];
+ metadata: {
+ title: string;
+ description: string;
+ };
+}
+
+// Color scheme for node groups
+const GROUP_COLORS: Record = {
+ // dependencies graph
+ core: "#ef4444",
+ viz: "#8b5cf6",
+ tooling: "#3b82f6",
+ runtime: "#10b981",
+ util: "#f59e0b",
+ styling: "#ec4899",
+ // org-chart graph
+ executive: "#dc2626",
+ management: "#2563eb",
+ lead: "#7c3aed",
+ ic: "#059669",
+ // knowledge graph
+ ai: "#6366f1",
+ architecture: "#0891b2",
+ tools: "#65a30d",
+ application: "#ea580c",
+};
+
+// Logging
+const log = {
+ info: console.log.bind(console, "[APP]"),
+ error: console.error.bind(console, "[APP]"),
+};
+
+// DOM Elements
+const graphSelect = document.getElementById(
+ "graph-select",
+) as HTMLSelectElement;
+const resetBtn = document.getElementById("reset-btn") as HTMLButtonElement;
+const graphContainer = document.getElementById("graph-container")!;
+const svg = d3.select("#graph");
+const tooltip = document.getElementById("tooltip")!;
+
+// State
+let currentGraphData: GraphData | null = null;
+let simulation: d3.Simulation | null = null;
+
+// Create main group for zoom/pan
+const g = svg.append("g");
+
+// Create groups for links and nodes (in order)
+const linksGroup = g.append("g").attr("class", "links");
+const nodesGroup = g.append("g").attr("class", "nodes");
+
+// Zoom behavior
+const zoom = d3
+ .zoom()
+ .scaleExtent([0.1, 4])
+ .on("zoom", (event) => {
+ g.attr("transform", event.transform.toString());
+ });
+
+svg.call(zoom);
+
+// Extract data from tool result
+function extractGraphData(result: CallToolResult): GraphData | null {
+ const textContent = result.content?.find((c) => c.type === "text");
+ if (!textContent || textContent.type !== "text") return null;
+ try {
+ return JSON.parse(textContent.text) as GraphData;
+ } catch {
+ log.error("Failed to parse graph data");
+ return null;
+ }
+}
+
+// Get node color by group
+function getNodeColor(group: string): string {
+ return GROUP_COLORS[group] ?? "#64748b";
+}
+
+// Update tooltip content safely (no innerHTML)
+function updateTooltip(node: GraphNode): void {
+ // Clear existing content
+ tooltip.textContent = "";
+
+ // Create title element
+ const title = document.createElement("strong");
+ title.textContent = node.id;
+ tooltip.appendChild(title);
+
+ // Add line break
+ tooltip.appendChild(document.createElement("br"));
+
+ // Add description
+ const desc = document.createTextNode(node.description ?? node.group);
+ tooltip.appendChild(desc);
+}
+
+// Render the graph
+function renderGraph(data: GraphData): void {
+ currentGraphData = data;
+
+ // Clear existing
+ linksGroup.selectAll("*").remove();
+ nodesGroup.selectAll("*").remove();
+
+ // Stop previous simulation
+ if (simulation) {
+ simulation.stop();
+ }
+
+ const width = graphContainer.clientWidth;
+ const height = graphContainer.clientHeight;
+
+ // Create simulation
+ simulation = d3
+ .forceSimulation(data.nodes)
+ .force(
+ "link",
+ d3
+ .forceLink(data.links)
+ .id((d) => d.id)
+ .strength((d) => (d.strength as number) * 0.5),
+ )
+ .force("charge", d3.forceManyBody().strength(-300))
+ .force("center", d3.forceCenter(width / 2, height / 2))
+ .force(
+ "collision",
+ d3.forceCollide().radius((d) => d.size + 5),
+ );
+
+ // Draw links
+ const links = linksGroup
+ .selectAll("line")
+ .data(data.links)
+ .join("line")
+ .attr("class", "link")
+ .attr("stroke-width", (d) => Math.max(1, (d.strength as number) * 3));
+
+ // Draw nodes
+ const nodeGroups = nodesGroup
+ .selectAll("g")
+ .data(data.nodes)
+ .join("g")
+ .attr("cursor", "pointer");
+
+ // Node circles
+ nodeGroups
+ .append("circle")
+ .attr("class", "node")
+ .attr("r", (d) => d.size / 2)
+ .attr("fill", (d) => getNodeColor(d.group))
+ .on("mouseover", (_event, d) => {
+ updateTooltip(d);
+ tooltip.classList.add("visible");
+ })
+ .on("mousemove", (event) => {
+ tooltip.style.left = `${event.pageX + 12}px`;
+ tooltip.style.top = `${event.pageY - 12}px`;
+ })
+ .on("mouseout", () => {
+ tooltip.classList.remove("visible");
+ })
+ .on("click", async (_event, d) => {
+ // Re-center graph on clicked node
+ log.info(`Clicked node: ${d.id}, loading centered view...`);
+ try {
+ await app.callServerTool({
+ name: "get-graph-data",
+ arguments: {
+ graphId: currentGraphData?.graphId,
+ centerNode: d.id,
+ depth: 2,
+ },
+ });
+ } catch (e) {
+ log.error("Failed to load centered graph:", e);
+ }
+ });
+
+ // Node labels (for larger nodes)
+ nodeGroups
+ .filter((d) => d.size >= 24)
+ .append("text")
+ .attr("class", "node-label")
+ .attr("dy", (d) => d.size / 2 + 14)
+ .text((d) => d.id);
+
+ // Drag behavior
+ const drag = d3
+ .drag()
+ .on("start", (event, d) => {
+ if (!event.active) simulation!.alphaTarget(0.3).restart();
+ d.fx = d.x;
+ d.fy = d.y;
+ })
+ .on("drag", (event, d) => {
+ d.fx = event.x;
+ d.fy = event.y;
+ })
+ .on("end", (event, d) => {
+ if (!event.active) simulation!.alphaTarget(0);
+ d.fx = null;
+ d.fy = null;
+ });
+
+ nodeGroups.call(drag);
+
+ // Update positions on tick
+ simulation.on("tick", () => {
+ links
+ .attr("x1", (d) => (d.source as GraphNode).x!)
+ .attr("y1", (d) => (d.source as GraphNode).y!)
+ .attr("x2", (d) => (d.target as GraphNode).x!)
+ .attr("y2", (d) => (d.target as GraphNode).y!);
+
+ nodeGroups.attr("transform", (d) => `translate(${d.x},${d.y})`);
+ });
+}
+
+// Reset view to initial zoom
+function resetView(): void {
+ svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity);
+}
+
+// Event listeners
+graphSelect.addEventListener("change", async () => {
+ const graphId = graphSelect.value;
+ log.info(`Loading graph: ${graphId}`);
+ try {
+ await app.callServerTool({
+ name: "get-graph-data",
+ arguments: { graphId },
+ });
+ } catch (e) {
+ log.error("Failed to load graph:", e);
+ }
+});
+
+resetBtn.addEventListener("click", resetView);
+
+// Handle window resize
+window.addEventListener("resize", () => {
+ if (currentGraphData && simulation) {
+ const width = graphContainer.clientWidth;
+ const height = graphContainer.clientHeight;
+ simulation.force("center", d3.forceCenter(width / 2, height / 2));
+ simulation.alpha(0.3).restart();
+ }
+});
+
+// Create MCP App
+const app = new App({ name: "D3 Graph Explorer", version: "1.0.0" });
+
+app.ontoolresult = (result) => {
+ log.info("Received graph data");
+ const data = extractGraphData(result);
+ if (data) {
+ renderGraph(data);
+ // Update select to match current graph
+ if (data.graphId && graphSelect.value !== data.graphId) {
+ graphSelect.value = data.graphId;
+ }
+ }
+};
+
+app.onerror = log.error;
+
+// Connect to host
+app.connect();
diff --git a/examples/d3-graph-server/src/server-utils.ts b/examples/d3-graph-server/src/server-utils.ts
new file mode 100644
index 00000000..40524237
--- /dev/null
+++ b/examples/d3-graph-server/src/server-utils.ts
@@ -0,0 +1,110 @@
+/**
+ * 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 {
+ 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 {
+ 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 {
+ 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();
+
+ 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;
+}
diff --git a/examples/d3-graph-server/tsconfig.json b/examples/d3-graph-server/tsconfig.json
new file mode 100644
index 00000000..535267b2
--- /dev/null
+++ b/examples/d3-graph-server/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "server.ts"]
+}
diff --git a/examples/d3-graph-server/vite.config.ts b/examples/d3-graph-server/vite.config.ts
new file mode 100644
index 00000000..6ff6d997
--- /dev/null
+++ b/examples/d3-graph-server/vite.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig } from "vite";
+import { viteSingleFile } from "vite-plugin-singlefile";
+
+const INPUT = process.env.INPUT;
+if (!INPUT) {
+ throw new Error("INPUT environment variable is not set");
+}
+
+const isDevelopment = process.env.NODE_ENV === "development";
+
+export default defineConfig({
+ plugins: [viteSingleFile()],
+ build: {
+ sourcemap: isDevelopment ? "inline" : undefined,
+ cssMinify: !isDevelopment,
+ minify: !isDevelopment,
+
+ rollupOptions: {
+ input: INPUT,
+ },
+ outDir: "dist",
+ emptyOutDir: false,
+ },
+});
diff --git a/examples/recharts-chart-server/README.md b/examples/recharts-chart-server/README.md
new file mode 100644
index 00000000..6154ec37
--- /dev/null
+++ b/examples/recharts-chart-server/README.md
@@ -0,0 +1,116 @@
+# Example: Recharts Dashboard Server
+
+A React-based business metrics dashboard with switchable chart types. Visualize revenue, sales, and product data with bar, line, area, and pie charts.
+
+## Features
+
+- **Multiple chart types**: Bar, line, area, and pie charts
+- **Dataset switching**: Toggle between monthly revenue, quarterly sales, and product mix
+- **Responsive design**: Charts adapt to container size
+- **Custom tooltips**: Formatted values with dark theme styling
+- **Color-coded series**: Each data series has a distinct color
+- **Theme support**: Adapts to light/dark mode preferences
+
+## Running
+
+1. Install dependencies:
+
+ ```bash
+ npm install
+ ```
+
+2. Build and start the server:
+
+ ```bash
+ npm run start:http # for Streamable HTTP transport
+ # OR
+ npm run start:stdio # for stdio transport
+ ```
+
+3. View using the [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) example or another MCP Apps-compatible host.
+
+### Tool Input Examples
+
+**Default (monthly revenue as bar chart):**
+
+```json
+{}
+```
+
+**Monthly revenue as line chart:**
+
+```json
+{
+ "datasetId": "monthly-revenue",
+ "chartType": "line"
+}
+```
+
+**Monthly revenue as area chart:**
+
+```json
+{
+ "datasetId": "monthly-revenue",
+ "chartType": "area"
+}
+```
+
+**Quarterly sales by region (bar chart):**
+
+```json
+{
+ "datasetId": "quarterly-sales"
+}
+```
+
+**Quarterly sales as line chart:**
+
+```json
+{
+ "datasetId": "quarterly-sales",
+ "chartType": "line"
+}
+```
+
+**Quarterly sales as area chart:**
+
+```json
+{
+ "datasetId": "quarterly-sales",
+ "chartType": "area"
+}
+```
+
+**Product mix as pie chart:**
+
+```json
+{
+ "datasetId": "product-mix"
+}
+```
+
+**Product mix as bar chart:**
+
+```json
+{
+ "datasetId": "product-mix",
+ "chartType": "bar"
+}
+```
+
+## Architecture
+
+### Server (`server.ts`)
+
+MCP server with sample business datasets for different visualization scenarios.
+
+Exposes one tool:
+
+- `get-chart-data` - Returns chart data with metadata for rendering
+
+### App (`src/`)
+
+- Built with React for reactive state management
+- Uses Recharts for chart visualization
+- Components: `ChartRenderer` (renders bar/line/area/pie based on type)
+- Chart type and dataset selection triggers tool calls for new data
diff --git a/examples/recharts-chart-server/mcp-app.html b/examples/recharts-chart-server/mcp-app.html
new file mode 100644
index 00000000..6b895ed8
--- /dev/null
+++ b/examples/recharts-chart-server/mcp-app.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Recharts Dashboard
+
+
+
+
+
+
diff --git a/examples/recharts-chart-server/package.json b/examples/recharts-chart-server/package.json
new file mode 100644
index 00000000..bc01040f
--- /dev/null
+++ b/examples/recharts-chart-server/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "recharts-chart-server",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "tsc --noEmit && cross-env INPUT=mcp-app.html vite build",
+ "watch": "cross-env INPUT=mcp-app.html vite build --watch",
+ "serve": "bun server.ts",
+ "start": "cross-env NODE_ENV=development npm run build && npm run serve",
+ "dev": "cross-env NODE_ENV=development concurrently 'npm run watch' 'npm run serve'"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/ext-apps": "../..",
+ "@modelcontextprotocol/sdk": "^1.24.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "recharts": "^2.15.0",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.0.0",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@vitejs/plugin-react": "^4.3.4",
+ "concurrently": "^9.2.1",
+ "cors": "^2.8.5",
+ "cross-env": "^7.0.3",
+ "express": "^5.1.0",
+ "typescript": "^5.9.3",
+ "vite": "^6.0.0",
+ "vite-plugin-singlefile": "^2.3.0"
+ }
+}
diff --git a/examples/recharts-chart-server/server.ts b/examples/recharts-chart-server/server.ts
new file mode 100644
index 00000000..0d7ac19a
--- /dev/null
+++ b/examples/recharts-chart-server/server.ts
@@ -0,0 +1,268 @@
+import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
+import type {
+ CallToolResult,
+ ReadResourceResult,
+} from "@modelcontextprotocol/sdk/types.js";
+import fs from "node:fs/promises";
+import path from "node:path";
+import { z } from "zod";
+import {
+ RESOURCE_MIME_TYPE,
+ RESOURCE_URI_META_KEY,
+ registerAppResource,
+ registerAppTool,
+} from "@modelcontextprotocol/ext-apps/server";
+import { startServer } from "./src/server-utils.js";
+
+const DIST_DIR = path.join(import.meta.dirname, "dist");
+
+// ============================================================================
+// Schemas
+// ============================================================================
+
+const ChartTypeSchema = z.enum(["bar", "line", "area", "pie"]);
+
+const GetChartDataInputSchema = z.object({
+ datasetId: z
+ .enum(["monthly-revenue", "quarterly-sales", "product-mix"])
+ .describe("Which dataset to load"),
+ chartType: ChartTypeSchema.optional().describe(
+ "Preferred chart type (bar, line, area, pie)",
+ ),
+});
+
+// Types
+type ChartType = z.infer;
+
+interface DataPoint {
+ [key: string]: string | number;
+}
+
+interface ChartMetadata {
+ title: string;
+ description: string;
+ xKey: string;
+ series: string[];
+ colors: Record;
+ defaultChartType: ChartType;
+ supportedChartTypes: ChartType[];
+}
+
+interface ChartDataset {
+ data: DataPoint[];
+ metadata: ChartMetadata;
+}
+
+// ============================================================================
+// Sample Datasets
+// ============================================================================
+
+const DATASETS: Record = {
+ "monthly-revenue": {
+ data: [
+ { month: "Jan", revenue: 42000, costs: 28000, profit: 14000 },
+ { month: "Feb", revenue: 38000, costs: 25000, profit: 13000 },
+ { month: "Mar", revenue: 45000, costs: 29000, profit: 16000 },
+ { month: "Apr", revenue: 52000, costs: 32000, profit: 20000 },
+ { month: "May", revenue: 48000, costs: 30000, profit: 18000 },
+ { month: "Jun", revenue: 56000, costs: 35000, profit: 21000 },
+ { month: "Jul", revenue: 61000, costs: 38000, profit: 23000 },
+ { month: "Aug", revenue: 58000, costs: 36000, profit: 22000 },
+ { month: "Sep", revenue: 64000, costs: 40000, profit: 24000 },
+ { month: "Oct", revenue: 70000, costs: 43000, profit: 27000 },
+ { month: "Nov", revenue: 75000, costs: 46000, profit: 29000 },
+ { month: "Dec", revenue: 82000, costs: 50000, profit: 32000 },
+ ],
+ metadata: {
+ title: "Monthly Revenue",
+ description: "Revenue, costs, and profit over the past 12 months",
+ xKey: "month",
+ series: ["revenue", "costs", "profit"],
+ colors: {
+ revenue: "#3b82f6",
+ costs: "#ef4444",
+ profit: "#10b981",
+ },
+ defaultChartType: "bar",
+ supportedChartTypes: ["bar", "line", "area"],
+ },
+ },
+ "quarterly-sales": {
+ data: [
+ {
+ quarter: "Q1 2023",
+ north: 120000,
+ south: 85000,
+ east: 95000,
+ west: 110000,
+ },
+ {
+ quarter: "Q2 2023",
+ north: 135000,
+ south: 92000,
+ east: 105000,
+ west: 125000,
+ },
+ {
+ quarter: "Q3 2023",
+ north: 142000,
+ south: 98000,
+ east: 112000,
+ west: 138000,
+ },
+ {
+ quarter: "Q4 2023",
+ north: 168000,
+ south: 115000,
+ east: 128000,
+ west: 155000,
+ },
+ {
+ quarter: "Q1 2024",
+ north: 155000,
+ south: 108000,
+ east: 118000,
+ west: 145000,
+ },
+ {
+ quarter: "Q2 2024",
+ north: 178000,
+ south: 125000,
+ east: 135000,
+ west: 165000,
+ },
+ ],
+ metadata: {
+ title: "Quarterly Sales by Region",
+ description: "Sales performance across different regions",
+ xKey: "quarter",
+ series: ["north", "south", "east", "west"],
+ colors: {
+ north: "#6366f1",
+ south: "#f59e0b",
+ east: "#14b8a6",
+ west: "#ec4899",
+ },
+ defaultChartType: "bar",
+ supportedChartTypes: ["bar", "line", "area"],
+ },
+ },
+ "product-mix": {
+ data: [
+ { name: "Enterprise", value: 42, color: "#3b82f6" },
+ { name: "Professional", value: 28, color: "#8b5cf6" },
+ { name: "Starter", value: 18, color: "#10b981" },
+ { name: "Free", value: 8, color: "#f59e0b" },
+ { name: "Legacy", value: 4, color: "#64748b" },
+ ],
+ metadata: {
+ title: "Product Mix",
+ description: "Revenue distribution across product tiers",
+ xKey: "name",
+ series: ["value"],
+ colors: {
+ Enterprise: "#3b82f6",
+ Professional: "#8b5cf6",
+ Starter: "#10b981",
+ Free: "#f59e0b",
+ Legacy: "#64748b",
+ },
+ defaultChartType: "pie",
+ supportedChartTypes: ["pie", "bar"],
+ },
+ },
+};
+
+// ============================================================================
+// MCP Server
+// ============================================================================
+
+function createServer(): McpServer {
+ const server = new McpServer({
+ name: "Recharts Dashboard Server",
+ version: "1.0.0",
+ });
+
+ const resourceUri = "ui://recharts-dashboard/mcp-app.html";
+
+ registerAppTool(
+ server,
+ "get-chart-data",
+ {
+ title: "Get Chart Data",
+ description:
+ "Returns chart data for visualization. Supports multiple datasets and chart types.",
+ inputSchema: GetChartDataInputSchema.shape,
+ _meta: { [RESOURCE_URI_META_KEY]: resourceUri },
+ },
+ async (args: {
+ datasetId: "monthly-revenue" | "quarterly-sales" | "product-mix";
+ chartType?: ChartType;
+ }): Promise => {
+ const dataset = DATASETS[args.datasetId];
+
+ if (!dataset) {
+ return {
+ isError: true,
+ content: [
+ { type: "text", text: `Unknown dataset: ${args.datasetId}` },
+ ],
+ };
+ }
+
+ // Validate chart type if provided
+ const chartType = args.chartType ?? dataset.metadata.defaultChartType;
+ if (!dataset.metadata.supportedChartTypes.includes(chartType)) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: "text",
+ text: `Chart type "${chartType}" not supported for this dataset. Supported: ${dataset.metadata.supportedChartTypes.join(", ")}`,
+ },
+ ],
+ };
+ }
+
+ return {
+ content: [
+ {
+ type: "text",
+ text: JSON.stringify({
+ datasetId: args.datasetId,
+ chartType,
+ data: dataset.data,
+ metadata: dataset.metadata,
+ }),
+ },
+ ],
+ };
+ },
+ );
+
+ registerAppResource(
+ server,
+ resourceUri,
+ resourceUri,
+ { mimeType: RESOURCE_MIME_TYPE, description: "Recharts Dashboard UI" },
+ async (): Promise => {
+ const html = await fs.readFile(
+ path.join(DIST_DIR, "mcp-app.html"),
+ "utf-8",
+ );
+ return {
+ contents: [
+ {
+ uri: resourceUri,
+ mimeType: RESOURCE_MIME_TYPE,
+ text: html,
+ },
+ ],
+ };
+ },
+ );
+
+ return server;
+}
+
+startServer(createServer);
diff --git a/examples/recharts-chart-server/src/components/ChartRenderer.tsx b/examples/recharts-chart-server/src/components/ChartRenderer.tsx
new file mode 100644
index 00000000..66521868
--- /dev/null
+++ b/examples/recharts-chart-server/src/components/ChartRenderer.tsx
@@ -0,0 +1,203 @@
+import {
+ ResponsiveContainer,
+ BarChart,
+ Bar,
+ LineChart,
+ Line,
+ AreaChart,
+ Area,
+ PieChart,
+ Pie,
+ Cell,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+} from "recharts";
+
+type ChartType = "bar" | "line" | "area" | "pie";
+
+interface DataPoint {
+ [key: string]: string | number;
+}
+
+interface ChartMetadata {
+ title: string;
+ description: string;
+ xKey: string;
+ series: string[];
+ colors: Record;
+ defaultChartType: ChartType;
+ supportedChartTypes: ChartType[];
+}
+
+interface ChartRendererProps {
+ data: DataPoint[];
+ metadata: ChartMetadata;
+ chartType: ChartType;
+}
+
+// Format currency values
+function formatValue(value: number): string {
+ if (value >= 1000000) {
+ return `$${(value / 1000000).toFixed(1)}M`;
+ }
+ if (value >= 1000) {
+ return `$${(value / 1000).toFixed(0)}K`;
+ }
+ return `$${value}`;
+}
+
+// Format percentage values
+function formatPercent(value: number): string {
+ return `${value}%`;
+}
+
+export function ChartRenderer({
+ data,
+ metadata,
+ chartType,
+}: ChartRendererProps) {
+ const { xKey, series, colors } = metadata;
+ const isPie = chartType === "pie";
+
+ // For pie charts, use the name field and add colors from data if available
+ const pieColors =
+ data[0]?.color !== undefined
+ ? data.map((d) => d.color as string)
+ : series.map((s) => colors[s] ?? "#64748b");
+
+ if (isPie) {
+ return (
+
+
+
+ `${name}: ${(percent * 100).toFixed(0)}%`
+ }
+ labelLine={true}
+ >
+ {data.map((entry, index) => (
+ |
+ ))}
+
+ formatPercent(value)}
+ contentStyle={{
+ backgroundColor: "#1e293b",
+ border: "none",
+ borderRadius: "6px",
+ color: "#f1f5f9",
+ }}
+ />
+
+
+
+ );
+ }
+
+ // Common props for cartesian charts
+ const chartContent = (
+ <>
+
+
+
+ formatValue(value)}
+ contentStyle={{
+ backgroundColor: "#1e293b",
+ border: "none",
+ borderRadius: "6px",
+ color: "#f1f5f9",
+ }}
+ labelStyle={{ color: "#94a3b8" }}
+ />
+
+ >
+ );
+
+ if (chartType === "bar") {
+ return (
+
+
+ {chartContent}
+ {series.map((key) => (
+
+ ))}
+
+
+ );
+ }
+
+ if (chartType === "line") {
+ return (
+
+
+ {chartContent}
+ {series.map((key) => (
+
+ ))}
+
+
+ );
+ }
+
+ // Area chart
+ return (
+
+
+ {chartContent}
+ {series.map((key) => (
+
+ ))}
+
+
+ );
+}
diff --git a/examples/recharts-chart-server/src/global.css b/examples/recharts-chart-server/src/global.css
new file mode 100644
index 00000000..0102ed9a
--- /dev/null
+++ b/examples/recharts-chart-server/src/global.css
@@ -0,0 +1,15 @@
+* {
+ box-sizing: border-box;
+}
+
+html, body {
+ margin: 0;
+ padding: 0;
+ font-family: system-ui, -apple-system, sans-serif;
+ font-size: 1rem;
+ min-height: 100%;
+}
+
+#root {
+ min-height: 100vh;
+}
diff --git a/examples/recharts-chart-server/src/mcp-app.css b/examples/recharts-chart-server/src/mcp-app.css
new file mode 100644
index 00000000..0dc3dfc0
--- /dev/null
+++ b/examples/recharts-chart-server/src/mcp-app.css
@@ -0,0 +1,132 @@
+.main {
+ min-height: 100vh;
+ padding: 1rem;
+ background: #f8fafc;
+}
+
+@media (prefers-color-scheme: dark) {
+ .main {
+ background: #0f172a;
+ color: #f1f5f9;
+ }
+}
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.header-title {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+.controls {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+.controls select,
+.controls button {
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ cursor: pointer;
+}
+
+.controls select {
+ border: 1px solid #cbd5e1;
+ background: white;
+ min-width: 180px;
+}
+
+@media (prefers-color-scheme: dark) {
+ .controls select {
+ background: #334155;
+ border-color: #475569;
+ color: #f1f5f9;
+ }
+}
+
+.controls button {
+ border: none;
+ font-weight: 500;
+ transition: background-color 0.15s;
+}
+
+.controls button.active {
+ background: #3b82f6;
+ color: white;
+}
+
+.controls button:not(.active) {
+ background: #e2e8f0;
+ color: #475569;
+}
+
+@media (prefers-color-scheme: dark) {
+ .controls button:not(.active) {
+ background: #334155;
+ color: #94a3b8;
+ }
+}
+
+.controls button:hover {
+ opacity: 0.9;
+}
+
+.chart-container {
+ background: white;
+ border-radius: 12px;
+ padding: 1.5rem;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin-bottom: 1rem;
+}
+
+@media (prefers-color-scheme: dark) {
+ .chart-container {
+ background: #1e293b;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+ }
+}
+
+.chart-title {
+ margin: 0 0 1rem 0;
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+@media (prefers-color-scheme: dark) {
+ .chart-title {
+ color: #f1f5f9;
+ }
+}
+
+.chart-description {
+ margin: 0 0 1rem 0;
+ font-size: 0.875rem;
+ color: #64748b;
+}
+
+.loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 300px;
+ color: #64748b;
+}
+
+.error {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 300px;
+ color: #ef4444;
+}
diff --git a/examples/recharts-chart-server/src/mcp-app.tsx b/examples/recharts-chart-server/src/mcp-app.tsx
new file mode 100644
index 00000000..786c04c5
--- /dev/null
+++ b/examples/recharts-chart-server/src/mcp-app.tsx
@@ -0,0 +1,196 @@
+import { useApp } from "@modelcontextprotocol/ext-apps/react";
+import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
+import { StrictMode, useState, useCallback } from "react";
+import { createRoot } from "react-dom/client";
+import { ChartRenderer } from "./components/ChartRenderer.tsx";
+import "./global.css";
+import "./mcp-app.css";
+
+// Types
+type ChartType = "bar" | "line" | "area" | "pie";
+type DatasetId = "monthly-revenue" | "quarterly-sales" | "product-mix";
+
+interface DataPoint {
+ [key: string]: string | number;
+}
+
+interface ChartMetadata {
+ title: string;
+ description: string;
+ xKey: string;
+ series: string[];
+ colors: Record;
+ defaultChartType: ChartType;
+ supportedChartTypes: ChartType[];
+}
+
+interface ChartData {
+ datasetId: DatasetId;
+ chartType: ChartType;
+ data: DataPoint[];
+ metadata: ChartMetadata;
+}
+
+const APP_INFO = { name: "Recharts Dashboard", version: "1.0.0" };
+
+// Dataset options for the selector
+const DATASET_OPTIONS: { id: DatasetId; label: string }[] = [
+ { id: "monthly-revenue", label: "Monthly Revenue" },
+ { id: "quarterly-sales", label: "Quarterly Sales by Region" },
+ { id: "product-mix", label: "Product Mix" },
+];
+
+// Extract chart data from tool result
+function extractChartData(result: CallToolResult): ChartData | null {
+ const textContent = result.content?.find((c) => c.type === "text");
+ if (!textContent || textContent.type !== "text") return null;
+ try {
+ return JSON.parse(textContent.text) as ChartData;
+ } catch {
+ console.error("[APP] Failed to parse chart data");
+ return null;
+ }
+}
+
+function RechartsDashboard() {
+ const [chartData, setChartData] = useState(null);
+ const [selectedDataset, setSelectedDataset] =
+ useState("monthly-revenue");
+ const [selectedChartType, setSelectedChartType] = useState("bar");
+
+ const { app, error } = useApp({
+ appInfo: APP_INFO,
+ capabilities: {},
+ onAppCreated: (app) => {
+ app.ontoolresult = async (result) => {
+ const data = extractChartData(result);
+ if (data) {
+ setChartData(data);
+ setSelectedDataset(data.datasetId);
+ setSelectedChartType(data.chartType);
+ }
+ };
+ },
+ });
+
+ const handleDatasetChange = useCallback(
+ async (e: React.ChangeEvent) => {
+ const datasetId = e.target.value as DatasetId;
+ setSelectedDataset(datasetId);
+ if (app) {
+ try {
+ await app.callServerTool({
+ name: "get-chart-data",
+ arguments: { datasetId },
+ });
+ } catch (err) {
+ console.error("[APP] Failed to load dataset:", err);
+ }
+ }
+ },
+ [app],
+ );
+
+ const handleChartTypeChange = useCallback(
+ async (chartType: ChartType) => {
+ // Check if chart type is supported
+ if (
+ chartData &&
+ !chartData.metadata.supportedChartTypes.includes(chartType)
+ ) {
+ return;
+ }
+ setSelectedChartType(chartType);
+ if (app) {
+ try {
+ await app.callServerTool({
+ name: "get-chart-data",
+ arguments: { datasetId: selectedDataset, chartType },
+ });
+ } catch (err) {
+ console.error("[APP] Failed to change chart type:", err);
+ }
+ }
+ },
+ [app, selectedDataset, chartData],
+ );
+
+ if (error) {
+ return (
+
+ Error: {error.message}
+
+ );
+ }
+
+ if (!app) {
+ return (
+
+ Connecting...
+
+ );
+ }
+
+ const supportedTypes = chartData?.metadata.supportedChartTypes ?? [
+ "bar",
+ "line",
+ "area",
+ "pie",
+ ];
+
+ return (
+
+
+ Recharts Dashboard
+
+
+ {(["bar", "line", "area", "pie"] as ChartType[]).map((type) => (
+
+ ))}
+
+
+
+
+ {chartData ? (
+ <>
+
{chartData.metadata.title}
+
+ {chartData.metadata.description}
+
+
+ >
+ ) : (
+
Loading chart data...
+ )}
+
+
+ );
+}
+
+createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/examples/recharts-chart-server/src/server-utils.ts b/examples/recharts-chart-server/src/server-utils.ts
new file mode 100644
index 00000000..40524237
--- /dev/null
+++ b/examples/recharts-chart-server/src/server-utils.ts
@@ -0,0 +1,110 @@
+/**
+ * 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 {
+ 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 {
+ 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 {
+ 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();
+
+ 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;
+}
diff --git a/examples/recharts-chart-server/tsconfig.json b/examples/recharts-chart-server/tsconfig.json
new file mode 100644
index 00000000..b9fb7fac
--- /dev/null
+++ b/examples/recharts-chart-server/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src", "server.ts"]
+}
diff --git a/examples/recharts-chart-server/vite.config.ts b/examples/recharts-chart-server/vite.config.ts
new file mode 100644
index 00000000..da0af84e
--- /dev/null
+++ b/examples/recharts-chart-server/vite.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import { viteSingleFile } from "vite-plugin-singlefile";
+
+const INPUT = process.env.INPUT;
+if (!INPUT) {
+ throw new Error("INPUT environment variable is not set");
+}
+
+const isDevelopment = process.env.NODE_ENV === "development";
+
+export default defineConfig({
+ plugins: [react(), viteSingleFile()],
+ build: {
+ sourcemap: isDevelopment ? "inline" : undefined,
+ cssMinify: !isDevelopment,
+ minify: !isDevelopment,
+
+ rollupOptions: {
+ input: INPUT,
+ },
+ outDir: "dist",
+ emptyOutDir: false,
+ },
+});
diff --git a/package-lock.json b/package-lock.json
index 780bf796..e6e832ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -442,6 +442,64 @@
"dev": true,
"license": "MIT"
},
+ "examples/d3-graph-server": {
+ "version": "1.0.0",
+ "dependencies": {
+ "@modelcontextprotocol/ext-apps": "../..",
+ "@modelcontextprotocol/sdk": "^1.24.0",
+ "d3": "^7.9.0",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.19",
+ "@types/d3": "^7.4.3",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.0.0",
+ "concurrently": "^9.2.1",
+ "cors": "^2.8.5",
+ "cross-env": "^7.0.3",
+ "express": "^5.1.0",
+ "typescript": "^5.9.3",
+ "vite": "^6.0.0",
+ "vite-plugin-singlefile": "^2.3.0"
+ }
+ },
+ "examples/d3-graph-server/node_modules/@types/node": {
+ "version": "22.19.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
+ "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "examples/d3-graph-server/node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "examples/d3-graph-server/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"examples/integration-server": {
"version": "1.0.0",
"dependencies": {
@@ -479,6 +537,68 @@
"dev": true,
"license": "MIT"
},
+ "examples/recharts-chart-server": {
+ "version": "1.0.0",
+ "dependencies": {
+ "@modelcontextprotocol/ext-apps": "../..",
+ "@modelcontextprotocol/sdk": "^1.24.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "recharts": "^2.15.0",
+ "zod": "^4.1.13"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.19",
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.0.0",
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.2",
+ "@vitejs/plugin-react": "^4.3.4",
+ "concurrently": "^9.2.1",
+ "cors": "^2.8.5",
+ "cross-env": "^7.0.3",
+ "express": "^5.1.0",
+ "typescript": "^5.9.3",
+ "vite": "^6.0.0",
+ "vite-plugin-singlefile": "^2.3.0"
+ }
+ },
+ "examples/recharts-chart-server/node_modules/@types/node": {
+ "version": "22.19.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
+ "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "examples/recharts-chart-server/node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "examples/recharts-chart-server/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"examples/scenario-modeler-server": {
"name": "@modelcontextprotocol/server-scenario-modeler",
"version": "0.1.0",
@@ -1023,6 +1143,15 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
@@ -2578,6 +2707,281 @@
"@types/node": "*"
}
},
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -2616,6 +3020,13 @@
"@types/send": "*"
}
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -3758,6 +4169,10 @@
"node": ">=6"
}
},
+ "node_modules/cohort-heatmap-server": {
+ "resolved": "examples/cohort-heatmap-server",
+ "link": true
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3785,6 +4200,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3943,11 +4367,55 @@
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
+ "node_modules/customer-segmentation-server": {
+ "resolved": "examples/customer-segmentation-server",
+ "link": true
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
- "dev": true,
"license": "ISC",
"dependencies": {
"internmap": "1 - 2"
@@ -3956,6 +4424,15 @@
"node": ">=12"
}
},
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-binarytree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz",
@@ -3963,21 +4440,71 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-dispatch": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -3987,7 +4514,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -3997,16 +4523,78 @@
"node": ">=12"
}
},
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=12"
}
},
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-force-3d": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz",
@@ -4028,7 +4616,31 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
- "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-graph-server": {
+ "resolved": "examples/d3-graph-server",
+ "link": true
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -4038,7 +4650,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3"
@@ -4054,11 +4665,37 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-quadtree": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
"integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
- "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
"license": "ISC",
"engines": {
"node": ">=12"
@@ -4068,7 +4705,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-array": "2.10.0 - 3",
@@ -4085,7 +4721,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
"integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
@@ -4099,17 +4734,27 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-array": "2 - 3"
@@ -4122,7 +4767,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-time": "1 - 3"
@@ -4135,7 +4779,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -4145,7 +4788,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-color": "1 - 3",
@@ -4165,7 +4807,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
- "dev": true,
"license": "ISC",
"dependencies": {
"d3-dispatch": "1 - 3",
@@ -4195,6 +4836,12 @@
}
}
},
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
"node_modules/deep-eql": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
@@ -4205,6 +4852,13 @@
"node": ">=6"
}
},
+ "node_modules/delaunator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+ "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -4224,6 +4878,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
"node_modules/devalue": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz",
@@ -4634,6 +5297,15 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
+ "node_modules/fast-equals": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+ "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
@@ -5111,7 +5783,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
@@ -5282,7 +5953,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/jsesc": {
@@ -5450,6 +6120,10 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@@ -5555,6 +6229,18 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
"node_modules/loupe": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
@@ -6137,6 +6823,23 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6227,6 +6930,12 @@
"react": "^19.2.3"
}
},
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -6237,6 +6946,37 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -6250,6 +6990,48 @@
"node": ">=8.10.0"
}
},
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-chart-server": {
+ "resolved": "examples/recharts-chart-server",
+ "link": true
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/recharts/node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -6303,6 +7085,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/robust-predicates": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+ "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+ "license": "Unlicense"
+ },
"node_modules/rollup": {
"version": "4.55.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
@@ -6364,6 +7152,12 @@
"node": ">= 18"
}
},
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@@ -7118,6 +7912,16 @@
"integrity": "sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==",
"license": "MIT"
},
+ "node_modules/threejs-server": {
+ "resolved": "examples/threejs-server",
+ "link": true
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -8016,6 +8820,32 @@
"node": ">= 0.8"
}
},
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/video-resource-server": {
+ "resolved": "examples/video-resource-server",
+ "link": true
+ },
"node_modules/vite": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
diff --git a/tests/e2e/servers.spec.ts b/tests/e2e/servers.spec.ts
index e554bc3b..fe3cf2c3 100644
--- a/tests/e2e/servers.spec.ts
+++ b/tests/e2e/servers.spec.ts
@@ -23,6 +23,8 @@ const DYNAMIC_MASKS: Record = {
],
threejs: ["#threejs-canvas", ".threejs-container"], // 3D render canvas (dynamic animation)
"wiki-explorer": ["#graph"], // Force-directed graph (dynamic layout)
+ "d3-graph": ["#graph-container", "svg"], // Force simulation is dynamic
+ "recharts-chart": [".chart-container"], // Charts with dynamic rendering
};
// Server configurations (key is used for screenshot filenames, name is the MCP server name)
@@ -42,6 +44,8 @@ const SERVERS = [
{ key: "system-monitor", name: "System Monitor Server" },
{ key: "threejs", name: "Three.js Server" },
{ key: "wiki-explorer", name: "Wiki Explorer" },
+ { key: "d3-graph", name: "D3 Graph Server" },
+ { key: "recharts-chart", name: "Recharts Dashboard Server" },
];
/**
diff --git a/tests/e2e/servers.spec.ts-snapshots/d3-graph.png b/tests/e2e/servers.spec.ts-snapshots/d3-graph.png
new file mode 100644
index 00000000..20f8eaac
Binary files /dev/null and b/tests/e2e/servers.spec.ts-snapshots/d3-graph.png differ
diff --git a/tests/e2e/servers.spec.ts-snapshots/recharts-chart.png b/tests/e2e/servers.spec.ts-snapshots/recharts-chart.png
new file mode 100644
index 00000000..4fac0ff2
Binary files /dev/null and b/tests/e2e/servers.spec.ts-snapshots/recharts-chart.png differ