Skip to main content
Glama
nickytonline

Pimp My Ride MCP Server

by nickytonline
index.ts18.4 kB
import express from "express"; import { randomUUID } from "node:crypto"; import { mkdir } from "node:fs/promises"; import { dirname } from "node:path"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { createTextResult } from "./lib/utils.ts"; import { createErrorResult } from "./lib/errors.ts"; import { logger } from "./logger.ts"; import { getConfig } from "./config.ts"; import { createKV, type KV } from "./storage/index.ts"; import { resolveIdentity } from "./auth/pomerium.ts"; import { getCurrentBuild, updateCarConfig, updateDriverProfile, saveBuild, loadBuild, listBuilds, deleteBuild, getBuildDetails, } from "./tools/builds.ts"; import { PERSONA_PERKS, ColorSchema, WheelTypeSchema, BodyKitSchema, DecalSchema, SpoilerSchema, ExhaustSchema, UnderglowSchema, DriverPersonaSchema, } from "./domain/models.ts"; // Initialize KV storage let kv: KV; const getServer = (req: express.Request) => { const config = getConfig(); const server = new McpServer({ name: config.SERVER_NAME, version: config.SERVER_VERSION, }); // Resolve user identity from Pomerium headers const identity = resolveIdentity(req); // Register tool: Get current build server.registerTool( "get_current_build", { title: "Get Current Car Build", description: "Retrieve or create the user's active car build with all customizations", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: {}, }, async () => { try { logger.info("Tool executed: getCurrentBuild", { userId: identity.userId, }); const build = await getCurrentBuild(kv, identity); return createTextResult(build); } catch (error) { logger.error("Error in getCurrentBuild", { error, userId: identity.userId, }); return createErrorResult(error); } }, ); // Register tool: Update car configuration server.registerTool( "update_car_config", { title: "Update Car Configuration", description: "Update car attributes like color, wheels, bodyKit, etc.", inputSchema: { color: z .enum([ "red", "blue", "green", "yellow", "orange", "purple", "pink", "black", "white", "silver", "gold", "cyan", "magenta", "lime", ]) .optional() .describe("Primary color of the car"), secondaryColor: z .enum([ "red", "blue", "green", "yellow", "orange", "purple", "pink", "black", "white", "silver", "gold", "cyan", "magenta", "lime", ]) .optional() .describe("Secondary/accent color"), wheels: z .enum([ "stock", "sport", "racing", "offroad", "chrome", "neon", "spinner", ]) .optional() .describe("Wheel type"), bodyKit: z .enum([ "stock", "sport", "racing", "drift", "luxury", "rally", "muscle", ]) .optional() .describe("Body kit style"), decal: z .enum([ "none", "racing_stripes", "flames", "tribal", "camo", "carbon_fiber", "checkered", "sponsor", "custom", ]) .optional() .describe("Decal/livery style"), spoiler: z .enum(["none", "stock", "sport", "racing", "gt_wing", "ducktail"]) .optional() .describe("Spoiler type"), exhaust: z .enum(["stock", "sport", "racing", "dual", "quad", "side_exit"]) .optional() .describe("Exhaust system"), underglow: z .enum(["none", "red", "blue", "green", "purple", "rainbow", "white"]) .optional() .describe("Underglow lighting"), performance: z .object({ power: z .number() .min(0) .max(100) .optional() .describe("Engine power (0-100)"), grip: z .number() .min(0) .max(100) .optional() .describe("Tire grip (0-100)"), aero: z .number() .min(0) .max(100) .optional() .describe("Aerodynamics (0-100)"), weight: z .number() .min(0) .max(100) .optional() .describe("Weight reduction (0-100, higher = lighter)"), }) .optional() .describe("Performance characteristics"), }, }, async (args) => { try { logger.info("Tool executed: updateCarConfig", { userId: identity.userId, updates: args, }); const build = await updateCarConfig(kv, identity, args); return createTextResult(build); } catch (error) { logger.error("Error in updateCarConfig", { error, userId: identity.userId, }); return createErrorResult(error); } }, ); // Register tool: Update driver profile server.registerTool( "update_driver_profile", { title: "Update Driver Profile", description: "Set driver persona and nickname", inputSchema: { persona: z .enum([ "CoolCalmCollected", "RoadRage", "SpeedDemon", "Cautious", "ShowOff", "Tactical", "Wildcard", ]) .optional() .describe("Driver personality and racing style"), nickname: z .string() .min(1) .max(50) .optional() .describe("Driver nickname"), }, }, async (args) => { try { logger.info("Tool executed: updateDriverProfile", { userId: identity.userId, updates: args, }); const build = await updateDriverProfile(kv, identity, args); return createTextResult(build); } catch (error) { logger.error("Error in updateDriverProfile", { error, userId: identity.userId, }); return createErrorResult(error); } }, ); // Register tool: Save build server.registerTool( "save_build", { title: "Save Car Build", description: "Save the current car build configuration under a specific name", inputSchema: { name: z.string().min(1).max(100).describe("Name for the saved build"), }, }, async (args) => { try { logger.info("Tool executed: saveBuild", { userId: identity.userId, name: args.name, }); const build = await saveBuild(kv, identity, args.name); return createTextResult(build); } catch (error) { logger.error("Error in saveBuild", { error, userId: identity.userId }); return createErrorResult(error); } }, ); // Register tool: Load build server.registerTool( "load_build", { title: "Load Car Build", description: "Load a saved car build and make it the active build", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: { buildId: z.string().describe("ID of the build to load"), }, }, async (args) => { try { logger.info("Tool executed: loadBuild", { userId: identity.userId, buildId: args.buildId, }); const build = await loadBuild(kv, identity, args.buildId); return createTextResult(build); } catch (error) { logger.error("Error in loadBuild", { error, userId: identity.userId }); return createErrorResult(error); } }, ); // Register tool: List builds server.registerTool( "list_builds", { title: "List Car Builds", description: "List all saved car builds for the user", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: { limit: z .number() .min(1) .max(100) .optional() .describe("Maximum number of builds to return (default: 50)"), cursor: z .string() .optional() .describe("Pagination cursor from previous response"), }, }, async (args) => { try { logger.info("Tool executed: listBuilds", { userId: identity.userId }); const result = await listBuilds(kv, identity, args); return createTextResult(result); } catch (error) { logger.error("Error in listBuilds", { error, userId: identity.userId }); return createErrorResult(error); } }, ); // Register tool: Delete build server.registerTool( "delete_build", { title: "Delete Car Build", description: "Delete a saved car build (cannot delete active build)", inputSchema: { buildId: z.string().describe("ID of the build to delete"), }, }, async (args) => { try { logger.info("Tool executed: deleteBuild", { userId: identity.userId, buildId: args.buildId, }); const deleted = await deleteBuild(kv, identity, args.buildId); return createTextResult({ deleted, buildId: args.buildId }); } catch (error) { logger.error("Error in deleteBuild", { error, userId: identity.userId, }); return createErrorResult(error); } }, ); // Register tool: Get build details server.registerTool( "get_build_details", { title: "Get Car Build Details", description: "Get detailed information about a car build including performance score", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: { buildId: z .string() .optional() .describe("ID of the build (defaults to active build)"), }, }, async (args) => { try { logger.info("Tool executed: getBuildDetails", { userId: identity.userId, buildId: args.buildId, }); const details = await getBuildDetails(kv, identity, args.buildId); return createTextResult(details); } catch (error) { logger.error("Error in getBuildDetails", { error, userId: identity.userId, }); return createErrorResult(error); } }, ); // Register tool: Get customization options server.registerTool( "get_customization_options", { title: "Get Customization Options", description: "Get all available car customization options (colors, wheels, body kits, etc.)", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: {}, }, async () => { try { logger.info("Tool executed: getCustomizationOptions"); const options = { colors: ColorSchema.options, wheels: WheelTypeSchema.options, bodyKits: BodyKitSchema.options, decals: DecalSchema.options, spoilers: SpoilerSchema.options, exhausts: ExhaustSchema.options, underglows: UnderglowSchema.options, driverPersonas: DriverPersonaSchema.options, }; return createTextResult(options); } catch (error) { logger.error("Error in getCustomizationOptions", { error }); return createErrorResult(error); } }, ); // Register tool: Get persona info server.registerTool( "get_persona_info", { title: "Get Driver Persona Info", description: "Get information about driver personas including racing style, strengths and weaknesses", annotations: { readOnlyHint: true, openWorldHint: true, }, inputSchema: { persona: z .enum([ "CoolCalmCollected", "RoadRage", "SpeedDemon", "Cautious", "ShowOff", "Tactical", "Wildcard", ]) .optional() .describe( "Specific persona to get info for (returns all if not specified)", ), }, }, async (args) => { try { logger.info("Tool executed: getPersonaInfo", { persona: args.persona }); if (args.persona) { return createTextResult({ persona: args.persona, ...PERSONA_PERKS[args.persona], }); } return createTextResult(PERSONA_PERKS); } catch (error) { logger.error("Error in getPersonaInfo", { error }); return createErrorResult(error); } }, ); return server; }; const app = express(); app.use(express.json()); const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; const mcpHandler = async (req: express.Request, res: express.Response) => { const sessionId = req.headers["mcp-session-id"] as string | undefined; try { // Handle initialization requests (usually POST without session ID) if (req.method === "POST" && !sessionId && isInitializeRequest(req.body)) { logger.info("Initializing new MCP session"); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (sessionId) => { transports[sessionId] = transport; logger.info("MCP session initialized", { sessionId }); }, }); const server = getServer(req); await server.connect(transport); await transport.handleRequest(req, res, req.body); return; } // Handle existing session requests if (sessionId && transports[sessionId]) { const transport = transports[sessionId]; await transport.handleRequest(req, res, req.body); return; } // Handle case where no session ID is provided for non-init requests if (req.method === "POST" && !sessionId) { logger.warn( "POST request without session ID for non-initialization request", ); res .status(400) .json({ error: "Session ID required for non-initialization requests" }); return; } // Handle unknown session if (sessionId && !transports[sessionId]) { logger.warn("Request for unknown session", { sessionId }); res.status(404).json({ error: "Session not found" }); return; } // For GET requests without session, return server info if (req.method === "GET") { const config = getConfig(); res.json({ name: config.SERVER_NAME, version: config.SERVER_VERSION, description: "Pimp My Ride MCP Server - Car customization and racing", capabilities: ["tools"], }); } } catch (error) { logger.error("Error handling MCP request", { error: error instanceof Error ? error.message : error, }); res.status(500).json({ error: "Internal server error" }); } }; // Handle MCP requests on /mcp endpoint app.post("/mcp", mcpHandler); app.get("/mcp", mcpHandler); // Healthcheck endpoint app.get("/health", async (req, res) => { try { const healthy = await kv.healthcheck(); if (healthy) { res.json({ status: "healthy", storage: "connected" }); } else { res.status(503).json({ status: "unhealthy", storage: "disconnected" }); } } catch (error) { logger.error("Healthcheck failed", { error: error instanceof Error ? error.message : error, }); res.status(503).json({ status: "unhealthy", error: "healthcheck failed" }); } }); async function main() { const config = getConfig(); // Ensure data directory exists for SQLite if (config.STORAGE_BACKEND === "sqlite") { const dataDir = dirname(config.SQLITE_DB_PATH); try { await mkdir(dataDir, { recursive: true }); logger.info("Data directory ready", { path: dataDir }); } catch (error) { logger.error("Failed to create data directory", { path: dataDir, error: error instanceof Error ? error.message : error, }); process.exit(1); } } // Initialize KV storage logger.info("Initializing storage", { backend: config.STORAGE_BACKEND }); kv = createKV({ backend: config.STORAGE_BACKEND, sqlite: { filename: config.SQLITE_DB_PATH, verbose: config.SQLITE_VERBOSE, }, }); // Verify storage is healthy const healthy = await kv.healthcheck(); if (!healthy) { logger.error("Storage healthcheck failed"); process.exit(1); } logger.info("Storage initialized successfully"); // Graceful shutdown handling const shutdown = async () => { logger.info("Shutting down gracefully"); try { await kv.close(); logger.info("Storage closed"); } catch (error) { logger.error("Error closing storage", { error: error instanceof Error ? error.message : error, }); } process.exit(0); }; process.on("SIGTERM", shutdown); process.on("SIGINT", shutdown); app.listen(config.PORT, config.HOST, () => { logger.info("Pimp My Ride MCP Server running", { host: config.HOST, port: config.PORT, environment: config.NODE_ENV, serverName: config.SERVER_NAME, version: config.SERVER_VERSION, storage: config.STORAGE_BACKEND, }); }); } main().catch((error) => { logger.error("Server startup error", { error: error instanceof Error ? error.message : error, }); process.exit(1); });

Latest Blog Posts

MCP directory API

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

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nickytonline/pimp-my-ride-mcp'

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