@velaro/mcp-server 0.6.28 → 0.6.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +308 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.28",
3
+ "version": "0.6.29",
4
4
  "description": "Velaro MCP server — connect Claude and other AI agents directly to your Velaro account: KB, workflows, bots, conversations, contacts, routing, and more.",
5
5
  "type": "module",
6
6
  "bin": {
package/server.js CHANGED
@@ -3834,6 +3834,183 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3834
3834
  },
3835
3835
  },
3836
3836
 
3837
+ // ── Generic Booking Engine (Phase 1b) ─────────────────────────────────────────
3838
+ // Backend: server/Velaro.Messaging/Controllers/{BookableResourcesController,
3839
+ // BookableServicesController,ResourceAvailabilityRulesController}.cs, routed at
3840
+ // /BookableResources, /BookableServices, /ResourceAvailabilityRules. SiteId always
3841
+ // derives server-side from the caller's auth context, never a tool argument (IDOR).
3842
+ // See velaro-admin memory project_2026_09_06_generic_booking_engine_schema_plan.md.
3843
+ {
3844
+ name: 'booking_list_resources',
3845
+ description: 'List bookable resources (staff / room / equipment / virtual) for this site.',
3846
+ inputSchema: { type: 'object', properties: {}, required: [] },
3847
+ },
3848
+ {
3849
+ name: 'booking_get_resource',
3850
+ description: 'Get a single bookable resource by id.',
3851
+ inputSchema: {
3852
+ type: 'object',
3853
+ properties: { resourceId: { type: 'number', description: 'Resource ID. Required.' } },
3854
+ required: ['resourceId'],
3855
+ },
3856
+ },
3857
+ {
3858
+ name: 'booking_create_resource',
3859
+ description: 'Create a bookable resource -- whatever a bookable service may need to be scheduled against (a staff member, a room, a piece of equipment, or a virtual/no-resource slot).',
3860
+ inputSchema: {
3861
+ type: 'object',
3862
+ properties: {
3863
+ name: { type: 'string', description: 'Resource name. Required.' },
3864
+ resourceType: { type: 'string', enum: ['Staff', 'Room', 'Equipment', 'Virtual'], description: 'Resource type. Defaults to Staff.' },
3865
+ isActive: { type: 'boolean', description: 'Whether the resource is active. Defaults to true.' },
3866
+ },
3867
+ required: ['name'],
3868
+ },
3869
+ },
3870
+ {
3871
+ name: 'booking_update_resource',
3872
+ description: 'Update a bookable resource. Only supplied fields change -- omitted fields are left untouched.',
3873
+ inputSchema: {
3874
+ type: 'object',
3875
+ properties: {
3876
+ resourceId: { type: 'number', description: 'Resource ID. Required.' },
3877
+ name: { type: 'string', description: 'New name' },
3878
+ resourceType: { type: 'string', enum: ['Staff', 'Room', 'Equipment', 'Virtual'], description: 'New resource type' },
3879
+ isActive: { type: 'boolean', description: 'New active state' },
3880
+ },
3881
+ required: ['resourceId'],
3882
+ },
3883
+ },
3884
+ {
3885
+ name: 'booking_delete_resource',
3886
+ description: 'Delete a bookable resource. 404 if it isn\'t found on this site.',
3887
+ inputSchema: {
3888
+ type: 'object',
3889
+ properties: { resourceId: { type: 'number', description: 'Resource ID. Required.' } },
3890
+ required: ['resourceId'],
3891
+ },
3892
+ },
3893
+ {
3894
+ name: 'booking_list_services',
3895
+ description: 'List bookable services (the "thing" a customer books -- a fitness class, a salon appointment type, etc.) for this site.',
3896
+ inputSchema: { type: 'object', properties: {}, required: [] },
3897
+ },
3898
+ {
3899
+ name: 'booking_get_service',
3900
+ description: 'Get a single bookable service by id.',
3901
+ inputSchema: {
3902
+ type: 'object',
3903
+ properties: { serviceId: { type: 'number', description: 'Service ID. Required.' } },
3904
+ required: ['serviceId'],
3905
+ },
3906
+ },
3907
+ {
3908
+ name: 'booking_create_service',
3909
+ description: 'Create a bookable service.',
3910
+ inputSchema: {
3911
+ type: 'object',
3912
+ properties: {
3913
+ name: { type: 'string', description: 'Service name. Required.' },
3914
+ durationMinutes: { type: 'number', description: 'Duration in minutes. Defaults to 30.' },
3915
+ defaultCapacity: { type: 'number', description: 'Default seats per generated slot (1 = classic 1:1 appointment, N = a class with N seats). Defaults to 1.' },
3916
+ requiresResource: { type: 'boolean', description: 'Whether a slot for this service must be tied to a specific bookable resource. Defaults to true.' },
3917
+ category: { type: 'string', description: 'Free-text grouping for admin UI/reporting, e.g. "Hair" or "Fitness Classes".' },
3918
+ isActive: { type: 'boolean', description: 'Whether the service is active. Defaults to true.' },
3919
+ },
3920
+ required: ['name'],
3921
+ },
3922
+ },
3923
+ {
3924
+ name: 'booking_update_service',
3925
+ description: 'Update a bookable service. Only supplied fields change -- omitted fields are left untouched.',
3926
+ inputSchema: {
3927
+ type: 'object',
3928
+ properties: {
3929
+ serviceId: { type: 'number', description: 'Service ID. Required.' },
3930
+ name: { type: 'string', description: 'New name' },
3931
+ durationMinutes: { type: 'number', description: 'New duration in minutes' },
3932
+ defaultCapacity: { type: 'number', description: 'New default seats per slot' },
3933
+ requiresResource: { type: 'boolean', description: 'New requires-resource flag' },
3934
+ category: { type: 'string', description: 'New category (empty string clears it)' },
3935
+ isActive: { type: 'boolean', description: 'New active state' },
3936
+ },
3937
+ required: ['serviceId'],
3938
+ },
3939
+ },
3940
+ {
3941
+ name: 'booking_delete_service',
3942
+ description: 'Delete a bookable service. 404 if it isn\'t found on this site.',
3943
+ inputSchema: {
3944
+ type: 'object',
3945
+ properties: { serviceId: { type: 'number', description: 'Service ID. Required.' } },
3946
+ required: ['serviceId'],
3947
+ },
3948
+ },
3949
+ {
3950
+ name: 'booking_list_availability',
3951
+ description: 'List weekly-recurring availability rules for this site, optionally filtered to one resource. A null resourceId on a rule means it is the site-wide default.',
3952
+ inputSchema: {
3953
+ type: 'object',
3954
+ properties: { resourceId: { type: 'number', description: 'Only return rules for this resource ID' } },
3955
+ required: [],
3956
+ },
3957
+ },
3958
+ {
3959
+ name: 'booking_get_availability',
3960
+ description: 'Get a single availability rule by id.',
3961
+ inputSchema: {
3962
+ type: 'object',
3963
+ properties: { ruleId: { type: 'number', description: 'Rule ID. Required.' } },
3964
+ required: ['ruleId'],
3965
+ },
3966
+ },
3967
+ {
3968
+ name: 'booking_create_availability',
3969
+ description: 'Create a weekly-recurring availability rule for a resource (or the whole site if resourceId is omitted).',
3970
+ inputSchema: {
3971
+ type: 'object',
3972
+ properties: {
3973
+ resourceId: { type: 'number', description: 'Resource ID this rule applies to. Omit for a site-wide default -- must belong to this site if supplied.' },
3974
+ dayOfWeek: { type: 'string', enum: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], description: 'Day of week. Required.' },
3975
+ startLocal: { type: 'string', description: 'Local start time, HH:MM:SS. Required.' },
3976
+ endLocal: { type: 'string', description: 'Local end time, HH:MM:SS. Required, must be after startLocal.' },
3977
+ ianaTimeZone: { type: 'string', description: 'IANA timezone id, e.g. America/Los_Angeles (default).' },
3978
+ effectiveFrom: { type: 'string', description: 'ISO 8601 timestamp the rule starts applying. Defaults to now.' },
3979
+ effectiveTo: { type: 'string', description: 'ISO 8601 timestamp the rule stops applying. Omit for open-ended.' },
3980
+ isActive: { type: 'boolean', description: 'Whether the rule is active. Defaults to true.' },
3981
+ },
3982
+ required: ['dayOfWeek', 'startLocal', 'endLocal'],
3983
+ },
3984
+ },
3985
+ {
3986
+ name: 'booking_update_availability',
3987
+ description: 'Update a weekly-recurring availability rule. Only supplied fields change -- omitted fields are left untouched.',
3988
+ inputSchema: {
3989
+ type: 'object',
3990
+ properties: {
3991
+ ruleId: { type: 'number', description: 'Rule ID. Required.' },
3992
+ resourceId: { type: 'number', description: 'New resource ID -- must belong to this site' },
3993
+ dayOfWeek: { type: 'string', enum: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], description: 'New day of week' },
3994
+ startLocal: { type: 'string', description: 'New local start time, HH:MM:SS' },
3995
+ endLocal: { type: 'string', description: 'New local end time, HH:MM:SS' },
3996
+ ianaTimeZone: { type: 'string', description: 'New IANA timezone id' },
3997
+ effectiveFrom: { type: 'string', description: 'New effective-from ISO 8601 timestamp' },
3998
+ effectiveTo: { type: 'string', description: 'New effective-to ISO 8601 timestamp' },
3999
+ isActive: { type: 'boolean', description: 'New active state' },
4000
+ },
4001
+ required: ['ruleId'],
4002
+ },
4003
+ },
4004
+ {
4005
+ name: 'booking_delete_availability',
4006
+ description: 'Delete an availability rule. 404 if it isn\'t found on this site.',
4007
+ inputSchema: {
4008
+ type: 'object',
4009
+ properties: { ruleId: { type: 'number', description: 'Rule ID. Required.' } },
4010
+ required: ['ruleId'],
4011
+ },
4012
+ },
4013
+
3837
4014
  // Focus Inbox -- draft pregeneration usage/cap report ("Always Learning" addon,
3838
4015
  // issue #582 Phase D). Entitlements-gated (EnableFocusDraftPregeneration), not a
3839
4016
  // Subscription.cs flag -- the response's own `entitled` field is the source of
@@ -7784,6 +7961,137 @@ async function handleTool(name, args) {
7784
7961
  return JSON.stringify(result, null, 2);
7785
7962
  }
7786
7963
 
7964
+ // -- Generic Booking Engine (Phase 1b) --
7965
+ case 'booking_list_resources': {
7966
+ const result = await messagingGet('/BookableResources');
7967
+ return JSON.stringify(result, null, 2);
7968
+ }
7969
+
7970
+ case 'booking_get_resource': {
7971
+ if (args.resourceId == null) return 'resourceId is required.';
7972
+ const result = await messagingGet(`/BookableResources/${encodeURIComponent(args.resourceId)}`);
7973
+ return JSON.stringify(result, null, 2);
7974
+ }
7975
+
7976
+ case 'booking_create_resource': {
7977
+ if (!args.name) return 'name is required.';
7978
+ const result = await messagingPost('/BookableResources', {
7979
+ name: args.name,
7980
+ resourceType: args.resourceType,
7981
+ isActive: args.isActive,
7982
+ });
7983
+ return JSON.stringify(result, null, 2);
7984
+ }
7985
+
7986
+ case 'booking_update_resource': {
7987
+ if (args.resourceId == null) return 'resourceId is required.';
7988
+ const result = await messagingPut(`/BookableResources/${encodeURIComponent(args.resourceId)}`, {
7989
+ name: args.name,
7990
+ resourceType: args.resourceType,
7991
+ isActive: args.isActive,
7992
+ });
7993
+ return JSON.stringify(result, null, 2);
7994
+ }
7995
+
7996
+ case 'booking_delete_resource': {
7997
+ if (args.resourceId == null) return 'resourceId is required.';
7998
+ const result = await messagingDel(`/BookableResources/${encodeURIComponent(args.resourceId)}`);
7999
+ return JSON.stringify(result, null, 2);
8000
+ }
8001
+
8002
+ case 'booking_list_services': {
8003
+ const result = await messagingGet('/BookableServices');
8004
+ return JSON.stringify(result, null, 2);
8005
+ }
8006
+
8007
+ case 'booking_get_service': {
8008
+ if (args.serviceId == null) return 'serviceId is required.';
8009
+ const result = await messagingGet(`/BookableServices/${encodeURIComponent(args.serviceId)}`);
8010
+ return JSON.stringify(result, null, 2);
8011
+ }
8012
+
8013
+ case 'booking_create_service': {
8014
+ if (!args.name) return 'name is required.';
8015
+ const result = await messagingPost('/BookableServices', {
8016
+ name: args.name,
8017
+ durationMinutes: args.durationMinutes,
8018
+ defaultCapacity: args.defaultCapacity,
8019
+ requiresResource: args.requiresResource,
8020
+ category: args.category,
8021
+ isActive: args.isActive,
8022
+ });
8023
+ return JSON.stringify(result, null, 2);
8024
+ }
8025
+
8026
+ case 'booking_update_service': {
8027
+ if (args.serviceId == null) return 'serviceId is required.';
8028
+ const result = await messagingPut(`/BookableServices/${encodeURIComponent(args.serviceId)}`, {
8029
+ name: args.name,
8030
+ durationMinutes: args.durationMinutes,
8031
+ defaultCapacity: args.defaultCapacity,
8032
+ requiresResource: args.requiresResource,
8033
+ category: args.category,
8034
+ isActive: args.isActive,
8035
+ });
8036
+ return JSON.stringify(result, null, 2);
8037
+ }
8038
+
8039
+ case 'booking_delete_service': {
8040
+ if (args.serviceId == null) return 'serviceId is required.';
8041
+ const result = await messagingDel(`/BookableServices/${encodeURIComponent(args.serviceId)}`);
8042
+ return JSON.stringify(result, null, 2);
8043
+ }
8044
+
8045
+ case 'booking_list_availability': {
8046
+ const qs = args.resourceId != null ? `?resourceId=${encodeURIComponent(args.resourceId)}` : '';
8047
+ const result = await messagingGet(`/ResourceAvailabilityRules${qs}`);
8048
+ return JSON.stringify(result, null, 2);
8049
+ }
8050
+
8051
+ case 'booking_get_availability': {
8052
+ if (args.ruleId == null) return 'ruleId is required.';
8053
+ const result = await messagingGet(`/ResourceAvailabilityRules/${encodeURIComponent(args.ruleId)}`);
8054
+ return JSON.stringify(result, null, 2);
8055
+ }
8056
+
8057
+ case 'booking_create_availability': {
8058
+ if (!args.dayOfWeek || !args.startLocal || !args.endLocal) {
8059
+ return 'dayOfWeek, startLocal, and endLocal are required.';
8060
+ }
8061
+ const result = await messagingPost('/ResourceAvailabilityRules', {
8062
+ resourceId: args.resourceId,
8063
+ dayOfWeek: args.dayOfWeek,
8064
+ startLocal: args.startLocal,
8065
+ endLocal: args.endLocal,
8066
+ ianaTimeZone: args.ianaTimeZone,
8067
+ effectiveFrom: args.effectiveFrom,
8068
+ effectiveTo: args.effectiveTo,
8069
+ isActive: args.isActive,
8070
+ });
8071
+ return JSON.stringify(result, null, 2);
8072
+ }
8073
+
8074
+ case 'booking_update_availability': {
8075
+ if (args.ruleId == null) return 'ruleId is required.';
8076
+ const result = await messagingPut(`/ResourceAvailabilityRules/${encodeURIComponent(args.ruleId)}`, {
8077
+ resourceId: args.resourceId,
8078
+ dayOfWeek: args.dayOfWeek,
8079
+ startLocal: args.startLocal,
8080
+ endLocal: args.endLocal,
8081
+ ianaTimeZone: args.ianaTimeZone,
8082
+ effectiveFrom: args.effectiveFrom,
8083
+ effectiveTo: args.effectiveTo,
8084
+ isActive: args.isActive,
8085
+ });
8086
+ return JSON.stringify(result, null, 2);
8087
+ }
8088
+
8089
+ case 'booking_delete_availability': {
8090
+ if (args.ruleId == null) return 'ruleId is required.';
8091
+ const result = await messagingDel(`/ResourceAvailabilityRules/${encodeURIComponent(args.ruleId)}`);
8092
+ return JSON.stringify(result, null, 2);
8093
+ }
8094
+
7787
8095
  default:
7788
8096
  throw new Error(`Unknown tool: ${name}`);
7789
8097
  }