ext-apps/examples/customer-segmentation-server/server.ts
Anton Pidkuiko 8550545ccb
fix: add mimeType to resource declarations in examples (#149)
According to spec, mimeType should be present in both the resource
declaration and the resource content. This adds the missing mimeType
to the registerResource() declaration (third parameter) in all example
servers and the quickstart documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: ochafik <olivier.chafik@gmail.com>
2025-12-15 12:09:47 +00:00

130 lines
3.5 KiB
TypeScript

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, RESOURCE_URI_META_KEY } from "../../dist/src/app";
import { startServer } from "../shared/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");
// Schemas - types are derived from these using z.infer
const GetCustomerDataInputSchema = z.object({
segment: z
.enum(["All", ...SEGMENTS])
.optional()
.describe("Filter by segment (default: All)"),
});
// Cache generated data for session consistency
let cachedCustomers: Customer[] | null = null;
let cachedSegments: SegmentSummary[] | null = null;
function getCustomerData(segmentFilter?: string): {
customers: Customer[];
segments: SegmentSummary[];
} {
// Generate data on first call
if (!cachedCustomers) {
cachedCustomers = generateCustomers(250);
cachedSegments = generateSegmentSummaries(cachedCustomers);
}
// Filter by segment if specified
let customers = cachedCustomers;
if (segmentFilter && segmentFilter !== "All") {
customers = cachedCustomers.filter((c) => c.segment === segmentFilter);
}
return {
customers,
segments: cachedSegments!,
};
}
/**
* Creates a new MCP server instance with tools and resources registered.
* Each HTTP session needs its own server instance because McpServer only supports one transport.
*/
function createServer(): McpServer {
const server = new McpServer({
name: "Customer Segmentation Server",
version: "1.0.0",
});
// Register the get-customer-data tool and its associated UI resource
{
const resourceUri = "ui://customer-segmentation/mcp-app.html";
server.registerTool(
"get-customer-data",
{
title: "Get Customer Data",
description:
"Returns customer data with segment information for visualization. Optionally filter by segment.",
inputSchema: GetCustomerDataInputSchema.shape,
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
},
async ({ segment }): Promise<CallToolResult> => {
const data = getCustomerData(segment);
return {
content: [{ type: "text", text: JSON.stringify(data) }],
};
},
);
server.registerResource(
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);
});