Skip to content

Commit ebfb179

Browse files
michelle0927Michelle Bergeronclaudeashwins01
authored
mindbody: add AI-optimized MCP actions (#21155)
* mindbody: add AI-optimized MCP actions Adds 10 MCP-ready actions for Mindbody Public API v6. All actions use static schemas (no additionalProps/reloadProps), rich descriptions with cross-references, and proper annotations for the AI tool-use context. New actions: - get-site-info: site identity, timezone, location IDs - list-session-types: bookable service types with IDs - list-staff: staff with role filtering (ClassTeacher, AppointmentInstructor) - search-clients: find members by name, email, or phone - get-client-details: full profile + memberships + service history - get-appointments: appointments by client, staff, and date range - get-classes: upcoming group class schedule (trimmed response for context efficiency) - upsert-client: create (flat body, BirthDate field) or update (Client wrapper + CrossRegionalUpdate: false) - book-appointment: book 1-on-1 service appointments - cancel-appointment: cancel via updateappointment endpoint App file rebuilt from stub: _makeRequest helper with Api-Key, SiteId, and Authorization: Bearer headers using oauth_access_token. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * mindbody: apply code review fixes - filter(Boolean) after CSV split in list-session-types and list-staff to drop empty tokens from malformed input like "a,,b" or trailing commas - upsert-client: fail fast with a clear error when firstName, lastName, or birthDate are missing in the create path, before hitting the API - locationId description: add cross-reference to Get Site Info (consistent with how clientId references Search Clients) - package.json: bump to 0.1.0 (minor bump — 10 new actions added) Inline get-classes limit left as-is: the 20-item default is intentional to prevent context overflow; using the shared propDefinition (default 100) would reintroduce the 215K-token issue seen during evals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * mindbody: remove unnecessary undefined guards Pipedream's axios helper strips undefined values from params and data automatically. Replace pattern: if (this.x !== undefined) obj.Key = this.x; with: obj.Key = this.x; across upsert-client (both update and create paths), book-appointment (Notes field), get-appointments (StartDate/EndDate), get-classes (StartDateTime/EndDateTime), and list-staff (LocationId). Guards that produce wrapped values ([this.x]) or call methods on the value (.split()) are kept unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * mindbody: fix documentation URLs Update all action descriptions to use the current Mindbody developer docs URL format (/ui/documentation/public-api#/http/api-endpoints/...) instead of the old PublicDocumentation/V6 swagger links. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * add missing dependency * updates * pnpm-lock.yaml * updates to book-appointment --------- Co-authored-by: Michelle Bergeron <michelle.bergeron@workday.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ashwin Srinivas <103921345+ashwins01@users.noreply.github.com>
1 parent 837fcb9 commit ebfb179

13 files changed

Lines changed: 812 additions & 6 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-book-appointment",
5+
name: "Book Appointment",
6+
description:
7+
"Books a client into a one-on-one service appointment (personal training, massage, etc.)."
8+
+ " All five core parameters are required: `clientId`, `locationId`, `sessionTypeId`, `startDateTime`, and `staffId`."
9+
+ " Use **Search Clients** to find the client ID, **List Session Types** to find `sessionTypeId`, and **List Staff** to find `staffId`."
10+
+ " `startDateTime` format: ISO 8601 `YYYY-MM-DDTHH:MM:SS` (e.g., `2026-07-15T10:00:00`)."
11+
+ " Location IDs are returned by **Get Site Info**."
12+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/appointment/add-appointment)",
13+
version: "0.0.1",
14+
type: "action",
15+
annotations: {
16+
destructiveHint: false,
17+
openWorldHint: true,
18+
readOnlyHint: false,
19+
},
20+
props: {
21+
app,
22+
clientId: {
23+
propDefinition: [
24+
app,
25+
"clientId",
26+
],
27+
description: "The client to book the appointment for. Use **Search Clients** to find the ID.",
28+
},
29+
locationId: {
30+
type: "integer",
31+
label: "Location ID",
32+
description: "The studio location where the appointment will take place. Location IDs are returned by **Get Site Info**.",
33+
},
34+
sessionTypeId: {
35+
type: "integer",
36+
label: "Session Type ID",
37+
description: "The type of service to book (e.g., personal training, massage). Use **List Session Types** to find valid IDs.",
38+
},
39+
startDateTime: {
40+
type: "string",
41+
label: "Start Date/Time",
42+
description: "When the appointment starts. ISO 8601 format: `YYYY-MM-DDTHH:MM:SS` (e.g., `2026-07-15T10:00:00`).",
43+
},
44+
staffId: {
45+
type: "string",
46+
label: "Staff ID",
47+
description: "The staff member (instructor/therapist) who will deliver the service. Staff member must be an instructor for the requested service. Use **List Staff** to find IDs.",
48+
},
49+
notes: {
50+
type: "string",
51+
label: "Notes",
52+
description: "Optional notes to attach to the appointment.",
53+
optional: true,
54+
},
55+
},
56+
async run({ $ }) {
57+
const body = {
58+
ClientId: this.clientId,
59+
LocationId: this.locationId,
60+
ItineraryEvents: [
61+
{
62+
SessionTypeId: this.sessionTypeId,
63+
StartDateTime: this.startDateTime,
64+
StaffId: this.staffId,
65+
Notes: this.notes,
66+
},
67+
],
68+
};
69+
70+
const response = await this.app.addAppointment({
71+
$,
72+
data: body,
73+
});
74+
const appt = response?.Itinerary?.[0] || {};
75+
$.export("$summary", `Booked appointment ${appt?.Id} for client ${this.clientId} on ${this.startDateTime}`);
76+
return response;
77+
},
78+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-cancel-appointment",
5+
name: "Cancel Appointment",
6+
description:
7+
"Cancels an existing appointment by setting its status to Cancelled."
8+
+ " Requires the appointment ID — use **Get Appointments** to find the ID by client, date, or staff."
9+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/appointment/update-appointment)",
10+
version: "0.0.1",
11+
type: "action",
12+
annotations: {
13+
destructiveHint: false,
14+
openWorldHint: true,
15+
readOnlyHint: false,
16+
},
17+
props: {
18+
app,
19+
appointmentId: {
20+
type: "integer",
21+
label: "Appointment ID",
22+
description: "The ID of the appointment to cancel. Use **Get Appointments** to find the appointment ID.",
23+
},
24+
sendEmail: {
25+
type: "boolean",
26+
label: "Send Cancellation Email",
27+
description: "Whether to send a cancellation confirmation email to the client. Defaults to `false`.",
28+
default: false,
29+
optional: true,
30+
},
31+
},
32+
async run({ $ }) {
33+
const response = await this.app.updateAppointment({
34+
$,
35+
data: {
36+
AppointmentId: this.appointmentId,
37+
Execute: "cancel",
38+
SendEmail: this.sendEmail ?? false,
39+
},
40+
});
41+
const appt = response.Appointment || {};
42+
$.export("$summary", `Cancelled appointment ${this.appointmentId} (status: ${appt.Status || "Cancelled"})`);
43+
return response;
44+
},
45+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-get-appointments",
5+
name: "Get Appointments",
6+
description:
7+
"Returns staff appointments, optionally filtered by date range, client, or staff member."
8+
+ " Use `clientId` to show all appointments for a specific client — first use **Search Clients** to find their ID."
9+
+ " Use `staffId` to filter by instructor."
10+
+ " Date formats: `YYYY-MM-DD` (e.g., `2026-07-01`)."
11+
+ " Without date filters, returns appointments from the current date forward (up to `limit`)."
12+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/appointment/get-staff-appointments)",
13+
version: "0.0.1",
14+
type: "action",
15+
annotations: {
16+
destructiveHint: false,
17+
openWorldHint: true,
18+
readOnlyHint: true,
19+
},
20+
props: {
21+
app,
22+
clientId: {
23+
propDefinition: [
24+
app,
25+
"clientId",
26+
],
27+
description: "Filter appointments for a specific client. Use **Search Clients** to find the client ID.",
28+
optional: true,
29+
},
30+
staffId: {
31+
type: "string",
32+
label: "Staff ID",
33+
description: "Filter appointments for a specific staff member. Use **List Staff** to find staff IDs.",
34+
optional: true,
35+
},
36+
startDate: {
37+
propDefinition: [
38+
app,
39+
"startDate",
40+
],
41+
},
42+
endDate: {
43+
propDefinition: [
44+
app,
45+
"endDate",
46+
],
47+
},
48+
limit: {
49+
propDefinition: [
50+
app,
51+
"limit",
52+
],
53+
},
54+
offset: {
55+
propDefinition: [
56+
app,
57+
"offset",
58+
],
59+
},
60+
},
61+
async run({ $ }) {
62+
const params = {
63+
Limit: this.limit,
64+
Offset: this.offset,
65+
};
66+
if (this.clientId) params.ClientIds = [
67+
this.clientId,
68+
];
69+
if (this.staffId) params.StaffIds = [
70+
this.staffId,
71+
];
72+
params.StartDate = this.startDate;
73+
params.EndDate = this.endDate;
74+
75+
const response = await this.app.getStaffAppointments({
76+
$,
77+
params,
78+
});
79+
const appointments = response.Appointments || [];
80+
$.export("$summary", `Found ${appointments.length} appointment${appointments.length === 1
81+
? ""
82+
: "s"}`);
83+
return response;
84+
},
85+
};
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-get-classes",
5+
name: "Get Classes",
6+
description:
7+
"Returns upcoming group class sessions at the studio, including class name, start/end time, instructor, location, and remaining capacity."
8+
+ " Use `startDateTime` and `endDateTime` to filter by date range (ISO 8601 format, e.g., `2026-07-01T00:00:00`)."
9+
+ " Use `staffId` to find classes taught by a specific instructor — see **List Staff** for IDs."
10+
+ " Use `locationId` to filter by studio location — see **Get Site Info** for location IDs."
11+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/class/get-classes)",
12+
version: "0.0.1",
13+
type: "action",
14+
annotations: {
15+
destructiveHint: false,
16+
openWorldHint: true,
17+
readOnlyHint: true,
18+
},
19+
props: {
20+
app,
21+
startDateTime: {
22+
type: "string",
23+
label: "Start Date/Time",
24+
description: "Return classes starting on or after this datetime. ISO 8601 format: `YYYY-MM-DDTHH:MM:SS` (e.g., `2026-07-01T00:00:00`). Defaults to today.",
25+
optional: true,
26+
},
27+
endDateTime: {
28+
type: "string",
29+
label: "End Date/Time",
30+
description: "Return classes starting on or before this datetime. ISO 8601 format: `YYYY-MM-DDTHH:MM:SS` (e.g., `2026-07-14T23:59:59`).",
31+
optional: true,
32+
},
33+
staffId: {
34+
type: "string",
35+
label: "Staff ID",
36+
description: "Filter classes by instructor. Use **List Staff** to find staff IDs.",
37+
optional: true,
38+
},
39+
locationId: {
40+
propDefinition: [
41+
app,
42+
"locationId",
43+
],
44+
},
45+
limit: {
46+
type: "integer",
47+
label: "Limit",
48+
description: "Maximum number of class sessions to return. Defaults to 20.",
49+
default: 20,
50+
min: 1,
51+
max: 200,
52+
optional: true,
53+
},
54+
offset: {
55+
propDefinition: [
56+
app,
57+
"offset",
58+
],
59+
},
60+
},
61+
async run({ $ }) {
62+
const params = {
63+
Limit: this.limit,
64+
Offset: this.offset,
65+
};
66+
params.StartDateTime = this.startDateTime;
67+
params.EndDateTime = this.endDateTime;
68+
if (this.staffId) params.StaffIds = [
69+
this.staffId,
70+
];
71+
if (this.locationId) params.LocationIds = [
72+
this.locationId,
73+
];
74+
75+
const response = await this.app.getClasses({
76+
$,
77+
params,
78+
});
79+
const classes = (response.Classes || []).map((c) => ({
80+
Id: c.Id,
81+
Name: c.ClassDescription?.Name,
82+
StartDateTime: c.StartDateTime,
83+
EndDateTime: c.EndDateTime,
84+
Staff: c.Staff
85+
? {
86+
Id: c.Staff.Id,
87+
Name: `${c.Staff.FirstName} ${c.Staff.LastName}`,
88+
}
89+
: null,
90+
Location: c.Location
91+
? {
92+
Id: c.Location.Id,
93+
Name: c.Location.Name,
94+
}
95+
: null,
96+
TotalBooked: c.TotalBooked,
97+
MaxCapacity: c.MaxCapacity,
98+
IsCancelled: c.IsCancelled,
99+
IsWaitlistAvailable: c.IsWaitlistAvailable,
100+
}));
101+
$.export("$summary", `Found ${classes.length} class session${classes.length === 1
102+
? ""
103+
: "s"}`);
104+
return {
105+
PaginationResponse: response.PaginationResponse,
106+
Classes: classes,
107+
};
108+
},
109+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-get-client-details",
5+
name: "Get Client Details",
6+
description:
7+
"Returns the complete profile for a client (member), including contact info, active memberships, purchased services, account balance, and visit history."
8+
+ " Requires the client's numeric ID — use **Search Clients** first to look up the ID by name or email."
9+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/client/get-client-complete-info)",
10+
version: "0.0.1",
11+
type: "action",
12+
annotations: {
13+
destructiveHint: false,
14+
openWorldHint: true,
15+
readOnlyHint: true,
16+
},
17+
props: {
18+
app,
19+
clientId: {
20+
propDefinition: [
21+
app,
22+
"clientId",
23+
],
24+
},
25+
},
26+
async run({ $ }) {
27+
const response = await this.app.getClientCompleteInfo({
28+
$,
29+
params: {
30+
ClientId: this.clientId,
31+
},
32+
});
33+
const { Client: client } = response;
34+
const firstName = client.FirstName || "";
35+
const lastName = client.LastName || "";
36+
$.export("$summary", `Retrieved complete profile for ${(firstName + " " + lastName).trim()} (ID: ${this.clientId})`);
37+
return response;
38+
},
39+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import app from "../../mindbody.app.mjs";
2+
3+
export default {
4+
key: "mindbody-get-site-info",
5+
name: "Get Site Info",
6+
description:
7+
"Returns identity and configuration details for the connected Mindbody site, including name, site ID, timezone, phone, and address."
8+
+ " Use this as the identity anchor — call it first to discover the site name, ID, and timezone before querying other resources."
9+
+ " Also provides location IDs needed by **List Staff**, **Get Classes**, and **Book Appointment**."
10+
+ " [See the documentation](https://developers.mindbodyonline.com/ui/documentation/public-api#/http/api-endpoints/site/get-sites)",
11+
version: "0.0.1",
12+
type: "action",
13+
annotations: {
14+
destructiveHint: false,
15+
openWorldHint: true,
16+
readOnlyHint: true,
17+
},
18+
props: {
19+
app,
20+
},
21+
async run({ $ }) {
22+
const response = await this.app.getSiteInfo({
23+
$,
24+
});
25+
const sites = response.Sites || [];
26+
const site = sites[0] || {};
27+
$.export("$summary", `Connected to site: ${site.Name || "Unknown"} (ID: ${site.Id || "N/A"})`);
28+
return response;
29+
},
30+
};

0 commit comments

Comments
 (0)