| | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; |
| | import type { |
| | CallToolResult, |
| | ReadResourceResult, |
| | } from "@modelcontextprotocol/sdk/types.js"; |
| | import fs from "node:fs/promises"; |
| | import path from "node:path"; |
| | import { z } from "zod"; |
| | import { |
| | RESOURCE_MIME_TYPE, |
| | registerAppResource, |
| | registerAppTool, |
| | } from "@modelcontextprotocol/ext-apps/server"; |
| | import { startServer } from "./server-utils.js"; |
| | import { |
| | generateCustomers, |
| | generateSegmentSummaries, |
| | } from "./src/data-generator.ts"; |
| | import { SEGMENTS, type Customer, type SegmentSummary } from "./src/types.ts"; |
| |
|
| | const DIST_DIR = path.join(import.meta.dirname, "dist"); |
| |
|
| | |
| | const GetCustomerDataInputSchema = z.object({ |
| | segment: z |
| | .enum(["All", ...SEGMENTS]) |
| | .optional() |
| | .describe("Filter by segment (default: All)"), |
| | }); |
| |
|
| | const CustomerSchema = z.object({ |
| | id: z.string(), |
| | name: z.string(), |
| | segment: z.string(), |
| | annualRevenue: z.number(), |
| | employeeCount: z.number(), |
| | accountAge: z.number(), |
| | engagementScore: z.number(), |
| | supportTickets: z.number(), |
| | nps: z.number(), |
| | }); |
| |
|
| | const SegmentSummarySchema = z.object({ |
| | name: z.string(), |
| | count: z.number(), |
| | color: z.string(), |
| | }); |
| |
|
| | const GetCustomerDataOutputSchema = z.object({ |
| | customers: z.array(CustomerSchema), |
| | segments: z.array(SegmentSummarySchema), |
| | }); |
| |
|
| | |
| | let cachedCustomers: Customer[] | null = null; |
| | let cachedSegments: SegmentSummary[] | null = null; |
| |
|
| | function getCustomerData(segmentFilter?: string): { |
| | customers: Customer[]; |
| | segments: SegmentSummary[]; |
| | } { |
| | |
| | if (!cachedCustomers) { |
| | cachedCustomers = generateCustomers(250); |
| | cachedSegments = generateSegmentSummaries(cachedCustomers); |
| | } |
| |
|
| | |
| | let customers = cachedCustomers; |
| | if (segmentFilter && segmentFilter !== "All") { |
| | customers = cachedCustomers.filter((c) => c.segment === segmentFilter); |
| | } |
| |
|
| | return { |
| | customers, |
| | segments: cachedSegments!, |
| | }; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | export function createServer(): McpServer { |
| | const server = new McpServer({ |
| | name: "Customer Segmentation Server", |
| | version: "1.0.0", |
| | }); |
| |
|
| | |
| | { |
| | const resourceUri = "ui://customer-segmentation/mcp-app.html"; |
| |
|
| | registerAppTool( |
| | server, |
| | "get-customer-data", |
| | { |
| | title: "Get Customer Data", |
| | description: |
| | "Returns customer data with segment information for visualization. Optionally filter by segment.", |
| | inputSchema: GetCustomerDataInputSchema.shape, |
| | outputSchema: GetCustomerDataOutputSchema.shape, |
| | _meta: { ui: { resourceUri } }, |
| | }, |
| | async ({ segment }): Promise<CallToolResult> => { |
| | const data = getCustomerData(segment); |
| |
|
| | return { |
| | content: [{ type: "text", text: JSON.stringify(data) }], |
| | structuredContent: data, |
| | }; |
| | }, |
| | ); |
| |
|
| | registerAppResource( |
| | server, |
| | resourceUri, |
| | resourceUri, |
| | { |
| | mimeType: RESOURCE_MIME_TYPE, |
| | description: "Customer Segmentation Explorer UI", |
| | }, |
| | async (): Promise<ReadResourceResult> => { |
| | const html = await fs.readFile( |
| | path.join(DIST_DIR, "mcp-app.html"), |
| | "utf-8", |
| | ); |
| |
|
| | return { |
| | contents: [ |
| | { |
| | uri: resourceUri, |
| | mimeType: RESOURCE_MIME_TYPE, |
| | text: html, |
| | }, |
| | ], |
| | }; |
| | }, |
| | ); |
| | } |
| |
|
| | return server; |
| | } |
| |
|
| | async function main() { |
| | if (process.argv.includes("--stdio")) { |
| | await createServer().connect(new StdioServerTransport()); |
| | } else { |
| | const port = parseInt(process.env.PORT ?? "3105", 10); |
| | await startServer(createServer, { |
| | port, |
| | name: "Customer Segmentation Server", |
| | }); |
| | } |
| | } |
| |
|
| | main().catch((e) => { |
| | console.error(e); |
| | process.exit(1); |
| | }); |
| |
|