@awesomate/hosting-mcp 0.68.0 → 0.70.0

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.
package/dist/index.js CHANGED
@@ -39861,6 +39861,7 @@ var TOOL_FEATURE = {
39861
39861
  awesomate_crm_queries: "contacts",
39862
39862
  awesomate_crm_recipes: "contacts",
39863
39863
  awesomate_crm_email: "email",
39864
+ awesomate_bookings: "bookings",
39864
39865
  awesomate_knowledge_status: "knowledge",
39865
39866
  awesomate_knowledge_provision: "knowledge",
39866
39867
  awesomate_knowledge_sources: "knowledge",
@@ -40320,7 +40321,7 @@ async function filesAction(config3, args) {
40320
40321
  const out = await hubPost(config3, "/api/files/move", { from, to: swapTopFolder(from, "private") });
40321
40322
  return {
40322
40323
  ...out,
40323
- note: `Moved from ${from}. Its public address no longer serves it, but Cloudflare can keep showing the last copy for up to 4 hours, and a copy someone already downloaded is not recalled. Any workflow, page or app that used it needs changing.`
40324
+ note: `Moved from ${from}. Its public address stops serving it within seconds; a browser that already opened it may show its own copy for a while, and a copy someone downloaded is not recalled. Any workflow, page or app that used it needs changing.`
40324
40325
  };
40325
40326
  }
40326
40327
  case "delete": {
@@ -41578,6 +41579,7 @@ var TITLES = {
41578
41579
  awesomate_crm_assistant: "Set up the AI in your app's conversations",
41579
41580
  awesomate_crm_notifications: "Email people when someone replies in your app",
41580
41581
  awesomate_crm_hooks: "Send record changes to your n8n",
41582
+ awesomate_bookings: "Your booking diary and booking box",
41581
41583
  awesomate_crm_email: "Draft an email to a contact list"
41582
41584
  };
41583
41585
  function annotate(name, kind) {
@@ -42988,7 +42990,7 @@ server.registerTool(
42988
42990
  "awesomate_files",
42989
42991
  {
42990
42992
  annotations: annotate("awesomate_files", DESTRUCTIVE),
42991
- description: `The account's Files: the folders on their automation account (n8n) that workflows read and write, and that the hub shows at Knowledge, Files. Three top folders: public/ (every file has a public_url anyone can open with no login: images for a site or email, downloads, media a workflow made), private/ (only the account, its workflows and the hub) and temp/ (scratch space). Support Plus, Pro and Embedded; the owner must also have switched on "Open your automation account's file folders" in Settings, Privacy. Paths are relative to the files folder and start with the top folder, e.g. public/images/logo.png. action 'status': whether Files is on, why not, and storage used against the plan's limit: call this first. 'list' {path?}: one folder (no path = the top folders); files in public/ carry public_url. 'search' {q?, top?, exts?}: find files by name across the folders (first 200 matches; truncated:true means narrow it). 'download' {path, to?, overwrite?}: save a file to THIS computer (to = a local file or folder, default the working directory); never overwrites without overwrite:true. 'upload' {localPath, path, overwrite?}: send a file from this computer (max 50 MB each); path is the target file, or a folder ending in / to keep the name. Upload into public/ only when the user wants it publicly reachable. 'mkdir' {path}. 'move' {path, to}: rename or move; moving into public/ needs confirm:true. 'make_public' {path, confirm:true}: moves private/ or temp/ to the same place under public/ and returns its public_url: ASK the user first, anyone with the address can read it. 'make_private' {path}: moves it out of public/, so its public address stops serving it; Cloudflare can keep showing the last copy for up to 4 hours (also after a delete or a replace of a public file), so never promise it is gone at once. 'delete' {path, confirm:true}: moves to the trash (not erased); ask first. Moving or renaming changes the path, so tell the user any workflow, page or app using the old path or address needs the new one. Contents stream between this computer and the hub: never paste a file's contents into the conversation to move it. A 507 files_storage_full means the plan's Files allowance is used: say how much, offer to find large or old files to remove, and leave any plan change to the user.`,
42993
+ description: `The account's Files: the folders on their automation account (n8n) that workflows read and write, and that the hub shows at Knowledge, Files. Three top folders: public/ (every file has a public_url anyone can open with no login: images for a site or email, downloads, media a workflow made), private/ (only the account, its workflows and the hub) and temp/ (scratch space). Support Plus, Pro and Embedded; the owner must also have switched on "Open your automation account's file folders" in Settings, Privacy. Paths are relative to the files folder and start with the top folder, e.g. public/images/logo.png. action 'status': whether Files is on, why not, and storage used against the plan's limit: call this first. 'list' {path?}: one folder (no path = the top folders); files in public/ carry public_url. 'search' {q?, top?, exts?}: find files by name across the folders (first 200 matches; truncated:true means narrow it). 'download' {path, to?, overwrite?}: save a file to THIS computer (to = a local file or folder, default the working directory); never overwrites without overwrite:true. 'upload' {localPath, path, overwrite?}: send a file from this computer (max 50 MB each); path is the target file, or a folder ending in / to keep the name. Upload into public/ only when the user wants it publicly reachable. 'mkdir' {path}. 'move' {path, to}: rename or move; moving into public/ needs confirm:true. 'make_public' {path, confirm:true}: moves private/ or temp/ to the same place under public/ and returns its public_url: ASK the user first, anyone with the address can read it. 'make_private' {path}: moves it out of public/; its public address stops serving it within seconds (the hub clears Cloudflare's copy, and does the same after a delete or a replace), though a browser that already opened it may show its own copy until it expires, and a downloaded copy is not recalled. 'delete' {path, confirm:true}: moves to the trash (not erased); ask first. Moving or renaming changes the path, so tell the user any workflow, page or app using the old path or address needs the new one. Contents stream between this computer and the hub: never paste a file's contents into the conversation to move it. A 507 files_storage_full means the plan's Files allowance is used: say how much, offer to find large or old files to remove, and leave any plan change to the user.`,
42992
42994
  inputSchema: {
42993
42995
  action: external_exports.enum(FILES_ACTIONS),
42994
42996
  path: external_exports.string().max(1024).optional().describe("A path in Files, starting public/, private/ or temp/. For upload, the target (a folder ending in / keeps the local name)."),
@@ -43436,6 +43438,109 @@ server.registerTool(
43436
43438
  }
43437
43439
  }
43438
43440
  );
43441
+ server.registerTool(
43442
+ "awesomate_bookings",
43443
+ {
43444
+ annotations: annotate("awesomate_bookings", MUTATING),
43445
+ description: `The business's own booking diary: customers book appointments or classes on the business's website, get an email with an invite and a link to change or cancel, and each booking lands in Contacts linked to the person. Read the awesomate-bookings skill first. Reading is every plan; every action that changes something needs Support Plus or above (on Essentials the hub answers upgrade_required: tell the owner they can do it in the hub under Contacts, Bookings, and do not retry). action 'setup': calendars, services, and this month's website bookings against the plan's limit. 'save_calendar' {key, name, timezone, hours, min_notice_minutes?, buffer_minutes?, max_per_day?, notify_email?, active?}: a person, room or resource with weekly hours ({"mon": [["09:00","17:00"]]}, 24-hour, in its own zone); a change names only what moves. 'save_service' {key, name, minutes, calendars, capacity?, price_text?, location?, cancel_cutoff_hours?, intake?, description?, active?}: capacity above 1 is a class several people join at one start; price is shown, never charged. 'open_times' {service, from?, to?, calendar?}. 'list' {from?, to?, status?}: bookings with each customer and their answers. 'book' {service, calendar, starts_at (from open_times), email, first_name?, last_name?, phone?, answers?, notify?, outside_hours?}. 'move' {booking_id, starts_at, notify?, outside_hours?}. 'cancel' {booking_id, reason?, notify?}. notify: false skips the customer's email; the calendar's notice email always hears. 'keys': the booking keys for the account's websites, each with the snippet to paste. 'create_key' {website}: the booking box for one site (https), returns the snippet. 'update_key' {key_id, active?, website?}. Nobody can double-book a time: the hub re-checks under a lock and answers 409 with why (not_open, slot_taken, session_full, day_full).`,
43446
+ inputSchema: {
43447
+ action: external_exports.enum(["setup", "save_calendar", "save_service", "open_times", "list", "book", "move", "cancel", "keys", "create_key", "update_key"]),
43448
+ key: external_exports.string().max(41).optional().describe("save_calendar, save_service: lower case, digits and underscores"),
43449
+ name: external_exports.string().max(80).optional(),
43450
+ timezone: external_exports.string().max(60).optional().describe("IANA zone, like Australia/Sydney"),
43451
+ hours: external_exports.record(external_exports.string(), external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()]))).optional().describe('save_calendar: {"mon": [["09:00","17:00"]], ...}'),
43452
+ min_notice_minutes: external_exports.number().int().min(0).optional(),
43453
+ buffer_minutes: external_exports.number().int().min(0).optional(),
43454
+ max_per_day: external_exports.number().int().min(1).nullable().optional(),
43455
+ notify_email: external_exports.string().max(254).nullable().optional(),
43456
+ active: external_exports.boolean().optional(),
43457
+ minutes: external_exports.number().int().optional().describe("save_service: length in minutes"),
43458
+ capacity: external_exports.number().int().min(1).optional(),
43459
+ price_text: external_exports.string().max(80).optional(),
43460
+ location: external_exports.string().max(500).optional(),
43461
+ description: external_exports.string().max(2e3).optional(),
43462
+ cancel_cutoff_hours: external_exports.number().int().min(0).optional(),
43463
+ intake: external_exports.array(external_exports.object({
43464
+ key: external_exports.string().max(41),
43465
+ label: external_exports.string().max(200),
43466
+ type: external_exports.enum(["text", "textarea", "select", "multiselect", "url"]),
43467
+ required: external_exports.boolean().optional(),
43468
+ options: external_exports.array(external_exports.string().max(100)).max(50).optional()
43469
+ })).max(20).optional().describe("save_service: the questions asked when booking"),
43470
+ calendars: external_exports.array(external_exports.string().max(41)).max(50).optional().describe("save_service: calendar keys that offer it"),
43471
+ service: external_exports.string().max(41).optional(),
43472
+ calendar: external_exports.string().max(41).optional(),
43473
+ from: external_exports.string().max(40).optional(),
43474
+ to: external_exports.string().max(40).optional(),
43475
+ status: external_exports.enum(["confirmed", "cancelled", "completed", "no_show"]).optional(),
43476
+ starts_at: external_exports.string().max(40).optional().describe("an ISO time from open_times"),
43477
+ email: external_exports.string().max(254).optional(),
43478
+ first_name: external_exports.string().max(80).optional(),
43479
+ last_name: external_exports.string().max(80).optional(),
43480
+ phone: external_exports.string().max(40).optional(),
43481
+ answers: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.array(external_exports.string())])).optional(),
43482
+ notify: external_exports.boolean().optional(),
43483
+ outside_hours: external_exports.boolean().optional(),
43484
+ booking_id: external_exports.string().max(40).optional(),
43485
+ reason: external_exports.string().max(500).optional(),
43486
+ website: external_exports.string().max(300).optional().describe("create_key, update_key: the site address, like https://example.com.au"),
43487
+ key_id: external_exports.number().int().optional().describe("update_key: from keys")
43488
+ }
43489
+ },
43490
+ async (a) => {
43491
+ try {
43492
+ const cfg = requireConfig();
43493
+ const base = "/api/my-crm/v1/bookings";
43494
+ const pick2 = (...ks) => Object.fromEntries(ks.map((k) => [k, a[k]]).filter(([, v]) => v !== void 0));
43495
+ const qs = (o) => Object.entries(o).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
43496
+ const origin = (w) => new URL(w).origin;
43497
+ switch (a.action) {
43498
+ case "setup":
43499
+ return textResult(await hubGet(cfg, `${base}/setup`));
43500
+ case "save_calendar":
43501
+ if (!a.key) return errorResult(new Error("save_calendar needs key."));
43502
+ return textResult(await hubPut(
43503
+ cfg,
43504
+ `${base}/calendars/${encodeURIComponent(a.key)}`,
43505
+ pick2("name", "timezone", "hours", "min_notice_minutes", "buffer_minutes", "max_per_day", "notify_email", "active")
43506
+ ));
43507
+ case "save_service":
43508
+ if (!a.key) return errorResult(new Error("save_service needs key."));
43509
+ return textResult(await hubPut(
43510
+ cfg,
43511
+ `${base}/services/${encodeURIComponent(a.key)}`,
43512
+ pick2("name", "minutes", "capacity", "price_text", "location", "description", "cancel_cutoff_hours", "intake", "calendars", "active")
43513
+ ));
43514
+ case "open_times":
43515
+ if (!a.service) return errorResult(new Error("open_times needs service."));
43516
+ return textResult(await hubGet(cfg, `${base}/open-times?${qs({ service: a.service, from: a.from, to: a.to, calendar: a.calendar })}`));
43517
+ case "list":
43518
+ return textResult(await hubGet(cfg, `${base}/?${qs({ from: a.from, to: a.to, status: a.status })}`));
43519
+ case "book":
43520
+ return textResult(await hubPost(cfg, base, pick2("service", "calendar", "starts_at", "email", "first_name", "last_name", "phone", "answers", "notify", "outside_hours")));
43521
+ case "move":
43522
+ if (!a.booking_id) return errorResult(new Error("move needs booking_id, from list."));
43523
+ return textResult(await hubPost(cfg, `${base}/${encodeURIComponent(a.booking_id)}/move`, pick2("starts_at", "notify", "outside_hours")));
43524
+ case "cancel":
43525
+ if (!a.booking_id) return errorResult(new Error("cancel needs booking_id, from list."));
43526
+ return textResult(await hubPost(cfg, `${base}/${encodeURIComponent(a.booking_id)}/cancel`, pick2("reason", "notify")));
43527
+ case "keys":
43528
+ return textResult(await hubGet(cfg, `${base}/keys`));
43529
+ case "create_key":
43530
+ if (!a.website) return errorResult(new Error("create_key needs website: the site's address, like https://example.com.au."));
43531
+ return textResult(await hubPost(cfg, `${base}/keys`, { name: new URL(a.website).hostname, allowed_origins: [origin(a.website)] }));
43532
+ case "update_key":
43533
+ if (a.key_id === void 0) return errorResult(new Error("update_key needs key_id, from keys."));
43534
+ return textResult(await hubPatch(cfg, `${base}/keys/${a.key_id}`, {
43535
+ ...a.active === void 0 ? {} : { active: a.active },
43536
+ ...a.website ? { allowed_origins: [origin(a.website)] } : {}
43537
+ }));
43538
+ }
43539
+ } catch (err) {
43540
+ return errorResult(err);
43541
+ }
43542
+ }
43543
+ );
43439
43544
  server.registerTool(
43440
43545
  "awesomate_crm_notifications",
43441
43546
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.68.0",
3
+ "version": "0.70.0",
4
4
  "description": "Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,5 +1,17 @@
1
1
  {
2
2
  "versions": [
3
+ {
4
+ "version": "0.70.0",
5
+ "highlights": [
6
+ "A file you make private, delete or replace in your Files now stops showing at its public address within seconds, instead of up to 4 hours"
7
+ ]
8
+ },
9
+ {
10
+ "version": "0.69.0",
11
+ "highlights": [
12
+ "Bookings: your customers book appointments or classes on your own website, get an email with an invite and a link to change or cancel, and each booking lands in Contacts. Claude sets up your calendars, services and the booking box (awesomate_bookings), and can book, move and cancel for you"
13
+ ]
14
+ },
3
15
  {
4
16
  "version": "0.68.0",
5
17
  "highlights": [
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: awesomate-bookings
3
+ description: Let the business's own customers book appointments or classes online, and manage those bookings. Sets up calendars (a person, room or resource with weekly hours), services (what is booked, how long, how many places, the questions asked), and the booking box for their website, then books, moves and cancels for them. Use when the user says "booking", "bookings", "appointments", "book online", "booking calendar", "scheduling", "let customers book", "Calendly", "class bookings", "who's booked tomorrow", or wants a booking page on their site. Companion to awesomate-app-builder and awesomate-hosting (the page it goes on) and awesomate-n8n (follow-ups): same connection, same token.
4
+ ---
5
+
6
+ # Awesomate Bookings: customers book themselves, bookings land in Contacts
7
+
8
+ A business's customers pick a time on the business's own website, get an email with a calendar
9
+ invite and a link to change or cancel, and the business gets a notice for each booking. Every
10
+ booking is a record in the business's own Contacts, linked to the person who made it.
11
+
12
+ Everything goes through one tool, `awesomate_bookings`. Work in plain words with the owner: ask
13
+ what people book, who with, and when they are open, then set it up and show them the result in a
14
+ sentence or two.
15
+
16
+ ## What it is made of
17
+
18
+ - **Calendars**: a person, room or resource people book. Each has weekly hours in its own time
19
+ zone, how much notice it needs, a gap between bookings, an optional daily limit, and the email
20
+ that hears about every booking (with an invite for their own calendar). Set the notice email:
21
+ without one, customers' replies to booking emails reach nobody.
22
+ - **Services**: what is booked. A length in minutes, places (1 for one person at a time, more for
23
+ a class several people join), a price shown as text (nothing is charged), where it happens,
24
+ questions to ask when booking, how many hours before the start changes close online, and which
25
+ calendars offer it.
26
+ - **The booking box**: one key per website, locked to that site's address. `create_key` returns the
27
+ snippet to paste.
28
+
29
+ ## Setting up (in this order)
30
+
31
+ 1. `awesomate_bookings` action `save_calendar` with `key` (lower case, like `nicole`), `name`,
32
+ `timezone` (like `Australia/Sydney`), `hours` (`{"mon": [["09:00","17:00"]], ...}`, 24-hour, no
33
+ overlaps), and `notify_email`.
34
+ 2. `save_service` with `key`, `name`, `minutes`, `calendars` (keys), and optionally `capacity`,
35
+ `price_text`, `location`, `cancel_cutoff_hours`, `intake` (questions:
36
+ `[{"key":"reason","label":"What brings you in?","type":"textarea","required":true}]`; types are
37
+ text, textarea, select, multiselect, url; select and multiselect take `options`).
38
+ 3. `open_times` with `service` to check the times look right before telling the owner it is ready.
39
+ 4. `create_key` with `website` (the site's address, https). Put the returned `snippet` on the page
40
+ the owner wants, or give it to them to paste:
41
+
42
+ ```html
43
+ <div data-awesomate-bookings></div>
44
+ <script src="https://hub.awesomate.ai/widget/v1/bookings.js" data-key="bk_..." async></script>
45
+ ```
46
+
47
+ On the div, `data-service="<key>"` offers one service only, and `data-accent="#0f766e"` colours the
48
+ buttons to match the site. On a WordPress site, a Custom HTML block holds both lines. On a site you
49
+ build with awesomate-app-builder, put them in the page where booking belongs (a "Book now" section,
50
+ or a /book page) and deploy as usual. The box only works on the website named on its key: to use it
51
+ on another domain, `update_key` with `allowed_origins`.
52
+
53
+ Changing a calendar or service later: call the same `save_` action with only what changes.
54
+
55
+ ## Running the diary
56
+
57
+ - `list` (optionally `from`, `to`, `status`): upcoming bookings with each customer's name, email,
58
+ phone and answers. Use it for "who's booked tomorrow".
59
+ - `book`: book someone in (`service`, `calendar`, `starts_at` from `open_times`, the customer's
60
+ `email`, `first_name`, `last_name`, `phone`, `answers`). `notify: false` when the customer should
61
+ not get an email (a booking taken by phone that they already know about). Staff may book outside
62
+ the hours with `outside_hours: true`; nobody can double-book a time.
63
+ - `move` (`booking_id`, `starts_at`) and `cancel` (`booking_id`, `reason`): the customer is emailed
64
+ unless `notify: false`, and the calendar's notice email always is.
65
+
66
+ ## Follow-ups through n8n (Pro)
67
+
68
+ Each booking is a record of the `booking` kind in Contacts. On Pro, an account with one of its own
69
+ apps (`awesomate_crm_apps`) can add a hook with `awesomate_crm_hooks` and `kinds: ["booking"]` to
70
+ send each booking's created, updated and archived events to its own n8n: an SMS reminder, copying it
71
+ into Google Calendar with the owner's own n8n credential, a thank-you after the visit. Bookings
72
+ themselves cannot be written through `awesomate_crm_write`; only these actions change them.
73
+
74
+ ## Plans and limits
75
+
76
+ - Every plan can set up bookings and take them on its website. Essentials takes up to 50 website
77
+ bookings a month; bookings the business makes itself never count.
78
+ - Changing bookings from Claude Code (the write actions here) needs Support Plus or above. On
79
+ Essentials, the tool answers `upgrade_required`: say the owner can do the same in the hub under
80
+ Contacts, Bookings, and do not retry.
81
+ - Payments and deposits are not part of bookings yet. Do not promise them; the price is shown only.
82
+ - Two-way sync with the owner's own Google or Outlook calendar is not available yet: the notice
83
+ email's invite is how a booking reaches their calendar. Do not promise automatic sync.