#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import dotenv from 'dotenv';
import http from 'http';
import https from 'https';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// Load environment variables
dotenv.config();
// Import all tools
import { getTeacherScheduleTool } from './tools/teacherSchedule';
import {
getClassesTool,
getClassDescriptionsTool,
addClientToClassTool,
removeClientFromClassTool,
getWaitlistEntriesTool,
substituteClassTeacherTool,
getClassSchedulesTool,
getClassVisitsTool,
} from './tools/classManagement';
import {
getClientsTool,
addClientTool,
updateClientTool,
getClientVisitsTool,
getClientMembershipsTool,
addClientArrivalTool,
getClientAccountBalancesTool,
getClientContractsTool,
} from './tools/clientManagement';
import {
getServicesTool,
getPackagesTool,
getProductsTool,
checkoutShoppingCartTool,
purchaseContractTool,
getContractsTool,
} from './tools/salesManagement';
import {
getSitesTool,
getLocationsTool,
getProgramsTool,
getResourcesTool,
getSessionTypesTool,
getStaffTool,
getActivationCodeTool,
} from './tools/siteManagement';
import {
getStaffAppointmentsTool,
addAppointmentTool,
updateAppointmentTool,
getBookableItemsTool,
getActiveSessionTimesTool,
getScheduleItemsTool,
} from './tools/appointmentManagement';
import {
getEnrollmentsTool,
addClientToEnrollmentTool,
getClientEnrollmentsTool,
} from './tools/enrollmentManagement';
// Validate required environment variables
const requiredEnvVars = [
'MINDBODY_API_KEY',
'MINDBODY_SITE_ID',
'MINDBODY_SOURCE_NAME',
'MINDBODY_SOURCE_PASSWORD'
];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Missing required environment variable: ${envVar}`);
console.error('Please copy .env.example to .env and fill in your credentials');
process.exit(1);
}
}
// Create MCP server
const server = new Server(
{
name: process.env.MCP_SERVER_NAME || 'mindbody-mcp',
version: process.env.MCP_SERVER_VERSION || '2.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Register tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// Teacher/Staff Tools
{
name: 'getTeacherSchedule',
description: 'Get a teacher\'s class schedule for a specified date range',
inputSchema: {
type: 'object',
properties: {
teacherName: { type: 'string', description: 'The name of the teacher' },
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
},
required: ['teacherName'],
},
},
{
name: 'getStaff',
description: 'Get all staff members with optional filters',
inputSchema: {
type: 'object',
properties: {
staffIds: { type: 'array', items: { type: 'number' }, description: 'Specific staff IDs to retrieve' },
filters: { type: 'array', items: { type: 'string' }, description: 'Filters to apply' },
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
startDateTime: { type: 'string', description: 'Start date/time in ISO format' },
},
},
},
// Class Management Tools
{
name: 'getClasses',
description: 'Get all classes with filtering options',
inputSchema: {
type: 'object',
properties: {
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs to filter by' },
classDescriptionIds: { type: 'array', items: { type: 'number' }, description: 'Class description IDs' },
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs to filter by' },
},
},
},
{
name: 'getClassDescriptions',
description: 'Get all class types/descriptions offered',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'getClassSchedules',
description: 'Get class schedules (recurring class templates)',
inputSchema: {
type: 'object',
properties: {
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
classDescriptionIds: { type: 'array', items: { type: 'number' }, description: 'Class description IDs' },
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs' },
programIds: { type: 'array', items: { type: 'number' }, description: 'Program IDs' },
},
},
},
{
name: 'addClientToClass',
description: 'Book a client into a class',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
classId: { type: 'number', description: 'Class ID to book' },
requirePayment: { type: 'boolean', description: 'Require payment (default true)' },
waitlist: { type: 'boolean', description: 'Add to waitlist if full (default false)' },
},
required: ['clientId', 'classId'],
},
},
{
name: 'removeClientFromClass',
description: 'Cancel a client\'s class booking',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
classId: { type: 'number', description: 'Class ID' },
lateCancel: { type: 'boolean', description: 'Mark as late cancel (default false)' },
},
required: ['clientId', 'classId'],
},
},
{
name: 'getWaitlistEntries',
description: 'Get waitlist entries for classes',
inputSchema: {
type: 'object',
properties: {
classScheduleIds: { type: 'array', items: { type: 'number' }, description: 'Class schedule IDs' },
classIds: { type: 'array', items: { type: 'number' }, description: 'Class IDs' },
clientIds: { type: 'array', items: { type: 'string' }, description: 'Client IDs' },
},
},
},
{
name: 'substituteClassTeacher',
description: 'Substitute a teacher for a class',
inputSchema: {
type: 'object',
properties: {
classId: { type: 'number', description: 'Class ID' },
originalTeacherId: { type: 'number', description: 'Original teacher ID' },
substituteTeacherId: { type: 'number', description: 'Substitute teacher ID' },
substituteTeacherName: { type: 'string', description: 'Substitute teacher name (optional)' },
},
required: ['classId', 'originalTeacherId', 'substituteTeacherId'],
},
},
{
name: 'getClassVisits',
description: 'Get client visits/attendance for a specific class. Returns all clients who booked or attended the class, including sign-in status, late cancellations, and service information.',
inputSchema: {
type: 'object',
properties: {
classId: { type: 'number', description: 'The ID of the class to get visits for' },
lastModifiedDate: { type: 'string', description: 'Only return visits modified after this date (YYYY-MM-DD format)' },
},
required: ['classId'],
},
},
// Client Management Tools
{
name: 'getClients',
description: 'Search and retrieve clients',
inputSchema: {
type: 'object',
properties: {
searchText: { type: 'string', description: 'Search text for client name/email/phone' },
clientIds: { type: 'array', items: { type: 'string' }, description: 'Specific client IDs' },
lastModifiedDate: { type: 'string', description: 'Get clients modified after this date' },
isProspect: { type: 'boolean', description: 'Filter for prospects only' },
},
},
},
{
name: 'addClient',
description: 'Add a new client',
inputSchema: {
type: 'object',
properties: {
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
email: { type: 'string', description: 'Email address' },
mobilePhone: { type: 'string', description: 'Mobile phone' },
birthDate: { type: 'string', description: 'Birth date in YYYY-MM-DD format' },
addressLine1: { type: 'string', description: 'Street address' },
city: { type: 'string', description: 'City' },
state: { type: 'string', description: 'State/Province' },
postalCode: { type: 'string', description: 'Postal code' },
country: { type: 'string', description: 'Country' },
emergencyContactName: { type: 'string', description: 'Emergency contact name' },
emergencyContactPhone: { type: 'string', description: 'Emergency contact phone' },
emergencyContactRelationship: { type: 'string', description: 'Emergency contact relationship' },
sendAccountEmails: { type: 'boolean', description: 'Send account emails (default true)' },
referredBy: { type: 'string', description: 'Referral source' },
},
required: ['firstName', 'lastName'],
},
},
{
name: 'updateClient',
description: 'Update client information',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID to update' },
updates: {
type: 'object',
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
email: { type: 'string' },
mobilePhone: { type: 'string' },
birthDate: { type: 'string' },
addressLine1: { type: 'string' },
city: { type: 'string' },
state: { type: 'string' },
postalCode: { type: 'string' },
emergencyContactName: { type: 'string' },
emergencyContactPhone: { type: 'string' },
sendAccountEmails: { type: 'boolean' },
},
},
},
required: ['clientId', 'updates'],
},
},
{
name: 'getClientVisits',
description: 'Get client\'s visit/attendance history',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
},
required: ['clientId'],
},
},
{
name: 'getClientMemberships',
description: 'Get client\'s active memberships',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
locationId: { type: 'number', description: 'Location ID (optional)' },
},
required: ['clientId'],
},
},
{
name: 'addClientArrival',
description: 'Check in a client (mark arrival)',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
locationId: { type: 'number', description: 'Location ID' },
},
required: ['clientId', 'locationId'],
},
},
{
name: 'getClientAccountBalances',
description: 'Get client\'s account balances',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
},
required: ['clientId'],
},
},
{
name: 'getClientContracts',
description: 'Get client\'s contracts/memberships',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
},
required: ['clientId'],
},
},
// Sales & Commerce Tools
{
name: 'getServices',
description: 'Get available services (class packages, memberships)',
inputSchema: {
type: 'object',
properties: {
programIds: { type: 'array', items: { type: 'number' }, description: 'Program IDs' },
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
locationId: { type: 'number', description: 'Location ID' },
classId: { type: 'number', description: 'Class ID' },
hideRelatedPrograms: { type: 'boolean', description: 'Hide related programs' },
},
},
},
{
name: 'getPackages',
description: 'Get class packages',
inputSchema: {
type: 'object',
properties: {
locationId: { type: 'number', description: 'Location ID' },
classScheduleId: { type: 'number', description: 'Class schedule ID' },
},
},
},
{
name: 'getProducts',
description: 'Get retail products',
inputSchema: {
type: 'object',
properties: {
productIds: { type: 'array', items: { type: 'number' }, description: 'Product IDs' },
searchText: { type: 'string', description: 'Search text' },
categoryIds: { type: 'array', items: { type: 'string' }, description: 'Category IDs' },
subCategoryIds: { type: 'array', items: { type: 'string' }, description: 'Subcategory IDs' },
sellOnline: { type: 'boolean', description: 'Filter for online products' },
},
},
},
{
name: 'getContracts',
description: 'Get available contracts/memberships',
inputSchema: {
type: 'object',
properties: {
contractIds: { type: 'array', items: { type: 'number' }, description: 'Contract IDs' },
soldOnline: { type: 'boolean', description: 'Filter for online contracts' },
locationId: { type: 'number', description: 'Location ID' },
},
},
},
{
name: 'checkoutShoppingCart',
description: 'Process a shopping cart checkout',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
items: {
type: 'array',
description: 'Cart items',
items: {
type: 'object',
properties: {
item: {
type: 'object',
properties: {
type: { type: 'string', enum: ['Service', 'Product', 'Package', 'Tip'] },
metadata: { type: 'object' },
},
},
quantity: { type: 'number' },
},
},
},
payments: {
type: 'array',
description: 'Payment methods',
items: {
type: 'object',
properties: {
type: { type: 'string', enum: ['Cash', 'Check', 'CreditCard', 'Comp', 'Custom', 'StoredCard'] },
metadata: { type: 'object' },
},
},
},
inStore: { type: 'boolean', description: 'In-store purchase' },
promotionCode: { type: 'string', description: 'Promotion code' },
sendEmail: { type: 'boolean', description: 'Send email receipt' },
locationId: { type: 'number', description: 'Location ID' },
},
required: ['clientId', 'items', 'payments'],
},
},
{
name: 'purchaseContract',
description: 'Purchase a contract/membership',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
contractId: { type: 'number', description: 'Contract ID' },
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
firstPaymentOccurs: { type: 'string', enum: ['StartDate', 'UponSale', 'BillingDate'] },
clientSignature: { type: 'string', description: 'Client signature' },
promotionCode: { type: 'string', description: 'Promotion code' },
locationId: { type: 'number', description: 'Location ID' },
},
required: ['clientId', 'contractId', 'startDate'],
},
},
// Site & Location Tools
{
name: 'getSites',
description: 'Get site/business information',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'getLocations',
description: 'Get all studio locations',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'getPrograms',
description: 'Get programs (yoga, pilates, etc.)',
inputSchema: {
type: 'object',
properties: {
scheduleType: { type: 'string', enum: ['All', 'Class', 'Enrollment', 'Appointment'] },
onlineOnly: { type: 'boolean', description: 'Online programs only' },
},
},
},
{
name: 'getResources',
description: 'Get resources (rooms, equipment)',
inputSchema: {
type: 'object',
properties: {
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
locationId: { type: 'number', description: 'Location ID' },
startDateTime: { type: 'string', description: 'Start date/time' },
endDateTime: { type: 'string', description: 'End date/time' },
},
},
},
{
name: 'getSessionTypes',
description: 'Get session types (class types, appointment types)',
inputSchema: {
type: 'object',
properties: {
programIds: { type: 'array', items: { type: 'number' }, description: 'Program IDs' },
onlineOnly: { type: 'boolean', description: 'Online sessions only' },
},
},
},
{
name: 'getActivationCode',
description: 'Get site activation code',
inputSchema: { type: 'object', properties: {} },
},
// Appointment Tools
{
name: 'getStaffAppointments',
description: 'Get staff appointments',
inputSchema: {
type: 'object',
properties: {
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs' },
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
startDate: { type: 'string', description: 'Start date in YYYY-MM-DD format' },
endDate: { type: 'string', description: 'End date in YYYY-MM-DD format' },
appointmentIds: { type: 'array', items: { type: 'number' }, description: 'Appointment IDs' },
clientIds: { type: 'array', items: { type: 'string' }, description: 'Client IDs' },
},
required: ['staffIds'],
},
},
{
name: 'addAppointment',
description: 'Book an appointment',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
staffId: { type: 'number', description: 'Staff ID' },
locationId: { type: 'number', description: 'Location ID' },
sessionTypeId: { type: 'number', description: 'Session type ID' },
startDateTime: { type: 'string', description: 'Start date/time in ISO format' },
resourceIds: { type: 'array', items: { type: 'number' }, description: 'Resource IDs' },
notes: { type: 'string', description: 'Appointment notes' },
staffRequested: { type: 'boolean', description: 'Staff requested' },
executePayment: { type: 'boolean', description: 'Execute payment' },
sendEmail: { type: 'boolean', description: 'Send confirmation email' },
applyPayment: { type: 'boolean', description: 'Apply payment' },
},
required: ['clientId', 'staffId', 'locationId', 'sessionTypeId', 'startDateTime'],
},
},
{
name: 'updateAppointment',
description: 'Update an appointment',
inputSchema: {
type: 'object',
properties: {
appointmentId: { type: 'number', description: 'Appointment ID' },
staffId: { type: 'number', description: 'Staff ID' },
startDateTime: { type: 'string', description: 'Start date/time' },
endDateTime: { type: 'string', description: 'End date/time' },
resourceIds: { type: 'array', items: { type: 'number' }, description: 'Resource IDs' },
notes: { type: 'string', description: 'Notes' },
executePayment: { type: 'boolean', description: 'Execute payment' },
sendEmail: { type: 'boolean', description: 'Send email' },
applyPayment: { type: 'boolean', description: 'Apply payment' },
},
required: ['appointmentId'],
},
},
{
name: 'getBookableItems',
description: 'Get available appointment slots',
inputSchema: {
type: 'object',
properties: {
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs' },
startDate: { type: 'string', description: 'Start date' },
endDate: { type: 'string', description: 'End date' },
appointmentId: { type: 'number', description: 'Appointment ID for rescheduling' },
},
required: ['sessionTypeIds'],
},
},
{
name: 'getActiveSessionTimes',
description: 'Get active session availability times',
inputSchema: {
type: 'object',
properties: {
scheduleType: { type: 'string', enum: ['All', 'Class', 'Enrollment', 'Appointment'] },
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
startTime: { type: 'string', description: 'Start time' },
endTime: { type: 'string', description: 'End time' },
days: { type: 'array', items: { type: 'string' }, description: 'Days of week' },
},
},
},
{
name: 'getScheduleItems',
description: 'Get schedule items/availability',
inputSchema: {
type: 'object',
properties: {
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs' },
startDate: { type: 'string', description: 'Start date' },
endDate: { type: 'string', description: 'End date' },
ignorePrepFinishBuffer: { type: 'boolean', description: 'Ignore prep/finish buffer' },
},
},
},
// Enrollment Tools
{
name: 'getEnrollments',
description: 'Get enrollments (courses, workshops, series)',
inputSchema: {
type: 'object',
properties: {
locationIds: { type: 'array', items: { type: 'number' }, description: 'Location IDs' },
classScheduleIds: { type: 'array', items: { type: 'number' }, description: 'Class schedule IDs' },
staffIds: { type: 'array', items: { type: 'number' }, description: 'Staff IDs' },
programIds: { type: 'array', items: { type: 'number' }, description: 'Program IDs' },
sessionTypeIds: { type: 'array', items: { type: 'number' }, description: 'Session type IDs' },
semesterIds: { type: 'array', items: { type: 'number' }, description: 'Semester IDs' },
courseIds: { type: 'array', items: { type: 'number' }, description: 'Course IDs' },
startDate: { type: 'string', description: 'Start date' },
endDate: { type: 'string', description: 'End date' },
},
},
},
{
name: 'addClientToEnrollment',
description: 'Register client for course/workshop',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
classScheduleIds: { type: 'array', items: { type: 'number' }, description: 'Class schedule IDs' },
enrollmentDateForward: { type: 'string', description: 'Enrollment date forward' },
enrollmentDates: { type: 'array', items: { type: 'string' }, description: 'Specific enrollment dates' },
enroll: { type: 'boolean', description: 'Enroll (default true)' },
waitlist: { type: 'boolean', description: 'Add to waitlist' },
sendEmail: { type: 'boolean', description: 'Send confirmation email' },
testMode: { type: 'boolean', description: 'Test mode' },
},
required: ['clientId', 'classScheduleIds'],
},
},
{
name: 'getClientEnrollments',
description: 'Get client\'s enrollments',
inputSchema: {
type: 'object',
properties: {
clientId: { type: 'string', description: 'Client ID' },
},
required: ['clientId'],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
// Teacher/Staff Tools
case 'getTeacherSchedule':
const teacherResult = await getTeacherScheduleTool(
args.teacherName as string,
args.startDate as string | undefined,
args.endDate as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(teacherResult, null, 2) }] };
case 'getStaff':
const staffResult = await getStaffTool(
args.staffIds as number[] | undefined,
args.filters as string[] | undefined,
args.sessionTypeIds as number[] | undefined,
args.locationIds as number[] | undefined,
args.startDateTime as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(staffResult, null, 2) }] };
// Class Management Tools
case 'getClasses':
const classesResult = await getClassesTool(
args.startDate as string | undefined,
args.endDate as string | undefined,
args.locationIds as number[] | undefined,
args.classDescriptionIds as number[] | undefined,
args.staffIds as number[] | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(classesResult, null, 2) }] };
case 'getClassDescriptions':
const descriptionsResult = await getClassDescriptionsTool();
return { content: [{ type: 'text', text: JSON.stringify(descriptionsResult, null, 2) }] };
case 'getClassSchedules':
const schedulesResult = await getClassSchedulesTool(
args.locationIds as number[] | undefined,
args.classDescriptionIds as number[] | undefined,
args.staffIds as number[] | undefined,
args.programIds as number[] | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(schedulesResult, null, 2) }] };
case 'addClientToClass':
const addClassResult = await addClientToClassTool(
args.clientId as string,
args.classId as number,
args.requirePayment as boolean | undefined,
args.waitlist as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(addClassResult, null, 2) }] };
case 'removeClientFromClass':
const removeClassResult = await removeClientFromClassTool(
args.clientId as string,
args.classId as number,
args.lateCancel as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(removeClassResult, null, 2) }] };
case 'getWaitlistEntries':
const waitlistResult = await getWaitlistEntriesTool(
args.classScheduleIds as number[] | undefined,
args.classIds as number[] | undefined,
args.clientIds as string[] | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(waitlistResult, null, 2) }] };
case 'substituteClassTeacher':
const subResult = await substituteClassTeacherTool(
args.classId as number,
args.originalTeacherId as number,
args.substituteTeacherId as number,
args.substituteTeacherName as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(subResult, null, 2) }] };
case 'getClassVisits':
const classVisitsResult = await getClassVisitsTool(
args.classId as number,
args.lastModifiedDate as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(classVisitsResult, null, 2) }] };
// Client Management Tools
case 'getClients':
const clientsResult = await getClientsTool(
args.searchText as string | undefined,
args.clientIds as string[] | undefined,
args.lastModifiedDate as string | undefined,
args.isProspect as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(clientsResult, null, 2) }] };
case 'addClient':
const addClientResult = await addClientTool(
args.firstName as string,
args.lastName as string,
args.email as string | undefined,
args.mobilePhone as string | undefined,
args.birthDate as string | undefined,
args.addressLine1 as string | undefined,
args.city as string | undefined,
args.state as string | undefined,
args.postalCode as string | undefined,
args.country as string | undefined,
args.emergencyContactName as string | undefined,
args.emergencyContactPhone as string | undefined,
args.emergencyContactRelationship as string | undefined,
args.sendAccountEmails as boolean | undefined,
args.referredBy as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(addClientResult, null, 2) }] };
case 'updateClient':
const updateClientResult = await updateClientTool(
args.clientId as string,
args.updates as any
);
return { content: [{ type: 'text', text: JSON.stringify(updateClientResult, null, 2) }] };
case 'getClientVisits':
const visitsResult = await getClientVisitsTool(
args.clientId as string,
args.startDate as string | undefined,
args.endDate as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(visitsResult, null, 2) }] };
case 'getClientMemberships':
const membershipsResult = await getClientMembershipsTool(
args.clientId as string,
args.locationId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(membershipsResult, null, 2) }] };
case 'addClientArrival':
const arrivalResult = await addClientArrivalTool(
args.clientId as string,
args.locationId as number
);
return { content: [{ type: 'text', text: JSON.stringify(arrivalResult, null, 2) }] };
case 'getClientAccountBalances':
const balancesResult = await getClientAccountBalancesTool(args.clientId as string);
return { content: [{ type: 'text', text: JSON.stringify(balancesResult, null, 2) }] };
case 'getClientContracts':
const contractsResult = await getClientContractsTool(args.clientId as string);
return { content: [{ type: 'text', text: JSON.stringify(contractsResult, null, 2) }] };
// Sales & Commerce Tools
case 'getServices':
const servicesResult = await getServicesTool(
args.programIds as number[] | undefined,
args.sessionTypeIds as number[] | undefined,
args.locationId as number | undefined,
args.classId as number | undefined,
args.hideRelatedPrograms as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(servicesResult, null, 2) }] };
case 'getPackages':
const packagesResult = await getPackagesTool(
args.locationId as number | undefined,
args.classScheduleId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(packagesResult, null, 2) }] };
case 'getProducts':
const productsResult = await getProductsTool(
args.productIds as number[] | undefined,
args.searchText as string | undefined,
args.categoryIds as string[] | undefined,
args.subCategoryIds as string[] | undefined,
args.sellOnline as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(productsResult, null, 2) }] };
case 'getContracts':
const getContractsResult = await getContractsTool(
args.contractIds as number[] | undefined,
args.soldOnline as boolean | undefined,
args.locationId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(getContractsResult, null, 2) }] };
case 'checkoutShoppingCart':
const checkoutResult = await checkoutShoppingCartTool(
args.clientId as string,
args.items as any,
args.payments as any,
args.inStore as boolean | undefined,
args.promotionCode as string | undefined,
args.sendEmail as boolean | undefined,
args.locationId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(checkoutResult, null, 2) }] };
case 'purchaseContract':
const purchaseResult = await purchaseContractTool(
args.clientId as string,
args.contractId as number,
args.startDate as string,
args.firstPaymentOccurs as any,
args.clientSignature as string | undefined,
args.promotionCode as string | undefined,
args.locationId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(purchaseResult, null, 2) }] };
// Site & Location Tools
case 'getSites':
const sitesResult = await getSitesTool();
return { content: [{ type: 'text', text: JSON.stringify(sitesResult, null, 2) }] };
case 'getLocations':
const locationsResult = await getLocationsTool();
return { content: [{ type: 'text', text: JSON.stringify(locationsResult, null, 2) }] };
case 'getPrograms':
const programsResult = await getProgramsTool(
args.scheduleType as any,
args.onlineOnly as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(programsResult, null, 2) }] };
case 'getResources':
const resourcesResult = await getResourcesTool(
args.sessionTypeIds as number[] | undefined,
args.locationId as number | undefined,
args.startDateTime as string | undefined,
args.endDateTime as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(resourcesResult, null, 2) }] };
case 'getSessionTypes':
const sessionTypesResult = await getSessionTypesTool(
args.programIds as number[] | undefined,
args.onlineOnly as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(sessionTypesResult, null, 2) }] };
case 'getActivationCode':
const activationResult = await getActivationCodeTool();
return { content: [{ type: 'text', text: JSON.stringify(activationResult, null, 2) }] };
// Appointment Tools
case 'getStaffAppointments':
const appointmentsResult = await getStaffAppointmentsTool(
args.staffIds as number[],
args.locationIds as number[] | undefined,
args.startDate as string | undefined,
args.endDate as string | undefined,
args.appointmentIds as number[] | undefined,
args.clientIds as string[] | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(appointmentsResult, null, 2) }] };
case 'addAppointment':
const addAppointmentResult = await addAppointmentTool(
args.clientId as string,
args.staffId as number,
args.locationId as number,
args.sessionTypeId as number,
args.startDateTime as string,
args.resourceIds as number[] | undefined,
args.notes as string | undefined,
args.staffRequested as boolean | undefined,
args.executePayment as boolean | undefined,
args.sendEmail as boolean | undefined,
args.applyPayment as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(addAppointmentResult, null, 2) }] };
case 'updateAppointment':
const updateAppointmentResult = await updateAppointmentTool(
args.appointmentId as number,
args.staffId as number | undefined,
args.startDateTime as string | undefined,
args.endDateTime as string | undefined,
args.resourceIds as number[] | undefined,
args.notes as string | undefined,
args.executePayment as boolean | undefined,
args.sendEmail as boolean | undefined,
args.applyPayment as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(updateAppointmentResult, null, 2) }] };
case 'getBookableItems':
const bookableResult = await getBookableItemsTool(
args.sessionTypeIds as number[],
args.locationIds as number[] | undefined,
args.staffIds as number[] | undefined,
args.startDate as string | undefined,
args.endDate as string | undefined,
args.appointmentId as number | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(bookableResult, null, 2) }] };
case 'getActiveSessionTimes':
const activeTimesResult = await getActiveSessionTimesTool(
args.scheduleType as any,
args.sessionTypeIds as number[] | undefined,
args.startTime as string | undefined,
args.endTime as string | undefined,
args.days as string[] | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(activeTimesResult, null, 2) }] };
case 'getScheduleItems':
const scheduleItemsResult = await getScheduleItemsTool(
args.locationIds as number[] | undefined,
args.staffIds as number[] | undefined,
args.startDate as string | undefined,
args.endDate as string | undefined,
args.ignorePrepFinishBuffer as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(scheduleItemsResult, null, 2) }] };
// Enrollment Tools
case 'getEnrollments':
const enrollmentsResult = await getEnrollmentsTool(
args.locationIds as number[] | undefined,
args.classScheduleIds as number[] | undefined,
args.staffIds as number[] | undefined,
args.programIds as number[] | undefined,
args.sessionTypeIds as number[] | undefined,
args.semesterIds as number[] | undefined,
args.courseIds as number[] | undefined,
args.startDate as string | undefined,
args.endDate as string | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(enrollmentsResult, null, 2) }] };
case 'addClientToEnrollment':
const addEnrollmentResult = await addClientToEnrollmentTool(
args.clientId as string,
args.classScheduleIds as number[],
args.enrollmentDateForward as string | undefined,
args.enrollmentDates as string[] | undefined,
args.enroll as boolean | undefined,
args.waitlist as boolean | undefined,
args.sendEmail as boolean | undefined,
args.testMode as boolean | undefined
);
return { content: [{ type: 'text', text: JSON.stringify(addEnrollmentResult, null, 2) }] };
case 'getClientEnrollments':
const clientEnrollmentsResult = await getClientEnrollmentsTool(args.clientId as string);
return { content: [{ type: 'text', text: JSON.stringify(clientEnrollmentsResult, null, 2) }] };
default:
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true,
};
}
} catch (error: any) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true,
};
}
});
// Parse command-line arguments
function parseArgs(): { transport: 'stdio' | 'sse', port?: number, host?: string, sslCert?: string, sslKey?: string } {
const args = process.argv.slice(2);
let transport: 'stdio' | 'sse' = 'stdio';
let port: number | undefined;
let host: string | undefined;
let sslCert: string | undefined;
let sslKey: string | undefined;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--transport' || arg === '-t') {
const value = args[++i];
if (value === 'sse' || value === 'stdio') {
transport = value;
}
} else if (arg === '--port' || arg === '-p') {
port = parseInt(args[++i], 10);
} else if (arg === '--host' || arg === '-h') {
host = args[++i];
} else if (arg === '--ssl-cert') {
sslCert = args[++i];
} else if (arg === '--ssl-key') {
sslKey = args[++i];
}
}
// Check environment variables as fallback
if (!transport && process.env.MCP_TRANSPORT) {
transport = process.env.MCP_TRANSPORT.toLowerCase() as 'stdio' | 'sse';
}
if (!port && process.env.MCP_PORT) {
port = parseInt(process.env.MCP_PORT, 10);
}
if (!host && process.env.MCP_HOST) {
host = process.env.MCP_HOST;
}
if (!sslCert && process.env.MCP_SSL_CERT) {
sslCert = process.env.MCP_SSL_CERT;
}
if (!sslKey && process.env.MCP_SSL_KEY) {
sslKey = process.env.MCP_SSL_KEY;
}
return { transport, port, host, sslCert, sslKey };
}
// Start SSE server
async function startSSEServer(config: { port?: number, host?: string, sslCert?: string, sslKey?: string }) {
const port = config.port || 3000;
const host = config.host || '0.0.0.0';
// Create HTTP/HTTPS server based on SSL configuration
let httpServer: http.Server | https.Server;
if (config.sslCert && config.sslKey) {
// HTTPS server for production
try {
const sslOptions = {
cert: fs.readFileSync(config.sslCert),
key: fs.readFileSync(config.sslKey),
};
httpServer = https.createServer(sslOptions);
console.error(`Starting HTTPS SSE server on ${host}:${port}`);
} catch (error) {
console.error('Failed to load SSL certificates:', error);
console.error('Falling back to HTTP server');
httpServer = http.createServer();
}
} else {
// HTTP server for development
httpServer = http.createServer();
console.error(`Starting HTTP SSE server on ${host}:${port}`);
}
// Handle HTTP requests
httpServer.on('request', async (req, res) => {
// Set CORS headers for all requests
const corsOrigin = process.env.MCP_CORS_ORIGIN || '*';
// Handle OPTIONS preflight requests
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Request-ID',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400'
});
res.end();
return;
}
// Health check endpoint
if (req.url === '/health' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin
});
res.end(JSON.stringify({
status: 'healthy',
server: 'mindbody-mcp',
version: process.env.MCP_SERVER_VERSION || '2.0.0',
transport: 'sse',
timestamp: new Date().toISOString()
}));
return;
}
// Server info endpoint
if (req.url === '/info' && req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': corsOrigin
});
res.end(JSON.stringify({
name: process.env.MCP_SERVER_NAME || 'mindbody-mcp',
version: process.env.MCP_SERVER_VERSION || '2.0.0',
description: 'Comprehensive MCP server for Mindbody API',
capabilities: {
tools: true,
resources: false,
prompts: false
},
transport: {
type: 'sse',
endpoint: '/sse',
cors: true
}
}));
return;
}
// SSE endpoint - create transport for each connection
if (req.url === '/sse' && req.method === 'GET') {
// Set SSE headers
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': corsOrigin,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Request-ID',
'Access-Control-Allow-Credentials': 'true',
'X-Accel-Buffering': 'no' // Disable Nginx buffering
});
// Create SSE transport for this connection
const transport = new SSEServerTransport('/sse', res as any, {
// DNS rebinding protection options
enableDnsRebindingProtection: false, // Set to true in production with proper allowed hosts
allowedHosts: process.env.MCP_ALLOWED_HOSTS?.split(','),
allowedOrigins: process.env.MCP_ALLOWED_ORIGINS?.split(',')
});
// Connect the server to this transport
await server.connect(transport);
// Handle connection close
req.on('close', () => {
transport.close();
});
return;
}
// Handle POST requests to SSE endpoint (for sending messages back)
if (req.url === '/sse' && req.method === 'POST') {
// This will be handled by the SSE transport
// Just pass through with CORS headers
res.setHeader('Access-Control-Allow-Origin', corsOrigin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Request-ID');
return;
}
// 404 for other paths
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
});
// Start the HTTP server
await new Promise<void>((resolve, reject) => {
httpServer.listen(port, host, () => {
console.error(`Mindbody MCP Server v2.0 (SSE) listening on ${host}:${port}`);
console.error(`Health check: http${config.sslCert ? 's' : ''}://${host}:${port}/health`);
console.error(`SSE endpoint: http${config.sslCert ? 's' : ''}://${host}:${port}/sse`);
resolve();
});
httpServer.on('error', reject);
});
// Graceful shutdown handling
process.on('SIGINT', async () => {
console.error('\nShutting down SSE server gracefully...');
await server.close();
httpServer.close(() => {
console.error('Server closed');
process.exit(0);
});
});
process.on('SIGTERM', async () => {
console.error('\nShutting down SSE server gracefully...');
await server.close();
httpServer.close(() => {
console.error('Server closed');
process.exit(0);
});
});
}
// Start STDIO server
async function startStdioServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Mindbody MCP Server v2.0 started (STDIO) - Complete yoga studio management');
}
// Main entry point
async function main() {
const config = parseArgs();
console.error('Starting Mindbody MCP Server v2.0');
console.error(`Transport: ${config.transport.toUpperCase()}`);
if (config.transport === 'sse') {
await startSSEServer(config);
} else {
await startStdioServer();
}
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});