@awesomate/hosting-mcp 0.49.0 → 0.50.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
@@ -41217,6 +41217,7 @@ var TITLES = {
41217
41217
  awesomate_crm_apps: "Set up sign-in for your own apps",
41218
41218
  awesomate_crm_app_users: "Manage the people who sign in to your apps",
41219
41219
  awesomate_crm_assistant: "Set up the AI in your app's conversations",
41220
+ awesomate_crm_hooks: "Send record changes to your n8n",
41220
41221
  awesomate_crm_email: "Draft an email to a contact list"
41221
41222
  };
41222
41223
  function annotate(name, kind) {
@@ -42992,6 +42993,39 @@ server.registerTool(
42992
42993
  }
42993
42994
  }
42994
42995
  );
42996
+ server.registerTool(
42997
+ "awesomate_crm_hooks",
42998
+ {
42999
+ annotations: annotate("awesomate_crm_hooks", MUTATING),
43000
+ description: "Send changes to an app's records to the account's own n8n: when a record of a named kind is created, updated or archived, the hub POSTs an event to a Webhook node on THEIR n8n (no other host is accepted), in order, at least once, retrying while n8n is down. Every writer counts: the app's people, its server key, Claude, a recipe, the assistant. Use it for 'when a job is requested, draft a quote', then write the result back with an app server key (awesomate_crm_apps create_key) from the same workflow. Event body: {id (stable: drop repeats), event: created|updated|archived, kind, record_id, changed: [attributes and links that changed], by, at, record (the record as it is when sent; null once archived), app}. Pro and above to change; reading is every plan. action 'list' {app_id}: each hook with delivered/missed counts and its last error. 'create' {app_id, name, url, kinds, events?}: url is the PRODUCTION webhook URL of an active workflow on their n8n (create and activate the workflow first with the awesomate_n8n tools); returns a secret ONCE: put it straight into an n8n Header Auth credential on that Webhook node (header X-Awesomate-Webhook-Secret), never into a reply. A new hook starts from now. 'update' {app_id, hook_id, name?, url?, kinds?, events?, enabled?}: enabled true retries at once. 'remove' {app_id, hook_id}. 'test' {app_id, hook_id}: one test event now ({event: 'test'}), and what n8n answered. Confirm the kinds and what the workflow will do with the user before creating one.",
43001
+ inputSchema: {
43002
+ action: external_exports.enum(["list", "create", "update", "remove", "test"]),
43003
+ app_id: external_exports.string().max(48).describe("the app, from awesomate_crm_apps list"),
43004
+ hook_id: external_exports.string().max(64).optional().describe("update, remove, test: from list"),
43005
+ name: external_exports.string().max(80).optional(),
43006
+ url: external_exports.string().max(500).optional().describe("create/update: the production URL of a Webhook node on the account's own n8n"),
43007
+ kinds: external_exports.array(external_exports.string().max(41)).max(20).optional(),
43008
+ events: external_exports.array(external_exports.enum(["created", "updated", "archived"])).max(3).optional().describe("default all three"),
43009
+ enabled: external_exports.boolean().optional().describe("update only")
43010
+ }
43011
+ },
43012
+ async ({ action, app_id, hook_id, name, url, kinds, events, enabled }) => {
43013
+ try {
43014
+ const cfg = requireConfig();
43015
+ const path = `/api/my-crm/v1/apps/${encodeURIComponent(app_id)}/hooks`;
43016
+ if (action === "list") return textResult(await hubGet(cfg, path));
43017
+ const fields = Object.fromEntries(Object.entries({ name, url, kinds, events }).filter(([, v]) => v !== void 0));
43018
+ if (action === "create") return textResult(await hubPost(cfg, path, fields));
43019
+ if (!hook_id) return errorResult(new Error(`${action} needs hook_id, from list.`));
43020
+ const one = `${path}/${encodeURIComponent(hook_id)}`;
43021
+ if (action === "remove") return textResult(await hubDelete(cfg, one));
43022
+ if (action === "test") return textResult(await hubPost(cfg, `${one}/test`, {}));
43023
+ return textResult(await hubPatch(cfg, one, { ...fields, ...enabled === void 0 ? {} : { enabled } }));
43024
+ } catch (err) {
43025
+ return errorResult(err);
43026
+ }
43027
+ }
43028
+ );
42995
43029
  var crmParam = external_exports.object({
42996
43030
  name: external_exports.string().max(41).describe("lower_case"),
42997
43031
  type: external_exports.enum(["text", "number", "date", "datetime", "boolean", "uuid", "text_list", "number_list"]).optional().describe("default text"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.49.0",
3
+ "version": "0.50.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,14 @@
1
1
  {
2
2
  "versions": [
3
+ {
4
+ "version": "0.50.0",
5
+ "highlights": [
6
+ "Your n8n can now hear about your app's records (awesomate_crm_hooks): when a job is requested, changed or archived, a workflow runs straight away, with the record attached",
7
+ "Events arrive in order and keep coming back while n8n is down, so a quiet weekend doesn't lose anything. Each has an id, so a workflow can ignore a repeat",
8
+ "Only your own n8n can receive them, protected by a secret your Webhook node checks. Pair it with an app server key to write the result back, such as a drafted quote",
9
+ "New skill, awesomate-portals: ask Claude for a customer portal and it designs the records, who sees what, sign-in by email link and the page, step by step"
10
+ ]
11
+ },
3
12
  {
4
13
  "version": "0.49.0",
5
14
  "highlights": [
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: awesomate-portals
3
+ description: Build a portal or members' app on the account's own data with @awesomate/sdk. Customers sign in by email link and see only their own records, staff see everything, lists update live, and optionally an AI assistant answers in conversations. Use when the user says "customer portal", "client login", "members' area", "let my customers see their jobs / bookings / orders", "staff dashboard", "chat with customers", "portal", or wants people outside the business to sign in to something of theirs. Companion to awesomate-app-builder (hosting the page), awesomate-database and awesomate-n8n: same connection, same token.
4
+ ---
5
+
6
+ # Awesomate Portals: people sign in and see their own records
7
+
8
+ A portal is three things the account already has, put together: **kinds** (the app's own tables,
9
+ in the account's own database), **access rules** on each kind (who sees and changes what, enforced
10
+ by the database itself), and an **app** (where it runs, how people sign in). The page uses
11
+ `@awesomate/sdk` straight from the browser. There is no app server to write.
12
+
13
+ Work in plain words with the owner. They describe their business ("customers request jobs, we
14
+ quote, they see the status"), you turn that into kinds and rules, show them the design in a
15
+ sentence or two, and only then create anything. Kinds, rules and apps are part of their data,
16
+ not scratch space.
17
+
18
+ ## 0. First, every time
19
+
20
+ 1. `awesomate_whoami`: everything below acts on that account. Fix the connection if the slug is
21
+ not the one the user means.
22
+ 2. `awesomate_get_context`: check the plan. **Sign-in for an app's people is Pro and above.**
23
+ Creating kinds and writing data is Support Plus and above. Reading is every plan. A 403
24
+ carries the upgrade path: relay it honestly, never retry it.
25
+
26
+ ## 1. Understand who signs in and what they do
27
+
28
+ Ask, one or two at a time:
29
+ - Who signs in? Usually **customers** (role `member`) and **staff** (role `staff`).
30
+ - What does a customer see? ("their own jobs, and the messages on them")
31
+ - What can a customer do? ("request a job, send a message")
32
+ - What do staff do? ("see every job, set status and quote, reply")
33
+ - Who may sign up: only people the owner adds (`invite`, the default and the safe choice), or
34
+ anyone with the link (`open`, everyone gets the default role)?
35
+
36
+ ## 2. Design the kinds
37
+
38
+ `awesomate_crm_kinds {action:'define'}`. Typical portal:
39
+
40
+ | Kind | Attributes | Links |
41
+ |---|---|---|
42
+ | `job` | `title` (text, required), `status` (choice), `quote` (money) | `customer` to `contact` |
43
+ | `message` | `body` (long_text, required) | `job` to `job` (required) |
44
+
45
+ - **Link the thing to the customer's contact.** That link is what "their own" means.
46
+ - Mark an attribute `readable_by_ai` only when the owner wants Claude and AI assistants to see
47
+ it. Never for anything sensitive. App people see every attribute of a record they may read;
48
+ `readable_by_ai` is only about AI.
49
+ - Show the owner the design ("A job has a title, a status and a quote, and belongs to a
50
+ customer. Messages hang off a job.") and get a yes first.
51
+
52
+ ## 3. Set the access rules
53
+
54
+ `awesomate_crm_kinds {action:'set_access'}`. Rules are per kind and per role:
55
+ `all`, `none`, `own` (records they created), or `linked:<path ending at a contact>`.
56
+
57
+ ```json
58
+ job: {"read": {"member": "linked:customer", "staff": "all"}, "write": {"member": "linked:customer", "staff": "all"}}
59
+ message: {"read": {"member": "linked:job.customer", "staff": "all"}, "write": {"member": "linked:job.customer", "staff": "all"}}
60
+ ```
61
+
62
+ - A role with no rule gets nothing. The database applies these to every read and every write, so
63
+ a mistake in the page can never show anyone more than their role allows.
64
+ - A person can only link a record to something they can see: a customer can point a job at their
65
+ own contact, never someone else's.
66
+ - **Known gap:** staff cannot yet put a job under a customer from the portal (they can only link
67
+ to contacts they can see, and app people do not read the contact list). Until that lands, the
68
+ owner creates or reassigns jobs for customers through Claude (`awesomate_crm_write`).
69
+
70
+ ## 4. Create the app and add people
71
+
72
+ 1. `awesomate_crm_apps {action:'create', name, allowed_origins, sign_up}`. `allowed_origins` are
73
+ the exact addresses the page runs on (`https://portal.example.com.au`, plus
74
+ `http://localhost:5173` while building). The name appears in the sign-in email. The answer
75
+ has the **publishable key** (`pk_...`): it is not a secret and goes in the page.
76
+ 2. `awesomate_crm_app_users {action:'add', email, role}` for each person. A customer is matched to
77
+ their contact by email. Staff get `role: 'staff'`. No email is sent; they ask for a link on the
78
+ page.
79
+
80
+ ## 5. Build the page
81
+
82
+ Any static page works (host it with the awesomate-app-builder skill, `kind: 'static'`). The SDK
83
+ loads from a CDN, or from npm in a Vite app:
84
+
85
+ ```html
86
+ <script type="module">
87
+ import { createAppClient } from 'https://cdn.jsdelivr.net/npm/@awesomate/sdk@0.6/+esm';
88
+ const app = createAppClient({ publishableKey: 'pk_...' });
89
+
90
+ // The email link signs them in by itself (also when it opens in a tab already on this page).
91
+ app.auth.onChange((user) => (user ? showPortal(user) : showSignIn()));
92
+ app.auth.onSignInError((err) => showMessage(err.message));
93
+ const user = await app.auth.user();
94
+ user ? showPortal(user) : showSignIn();
95
+
96
+ // Sign-in form
97
+ await app.auth.signInWithLink(email, { redirectTo: location.href });
98
+
99
+ // Data: the same calls for everyone; the rules decide what each person gets.
100
+ const stop = app.live('job', { orderBy: ['created_at', 'desc'], limit: 100 }, (rows) => renderJobs(rows));
101
+ await app.write('job', { title: 'Leaking tap', status: 'open' }, { links: { customer: user.contact_id } });
102
+ await app.write('message', { body }, { links: { job: jobId } });
103
+ </script>
104
+ ```
105
+
106
+ - Choose the view from `user.role` (`staff` sees everything and edit controls; `member` sees
107
+ their own records and a request form).
108
+ - `live()` keeps a list current: anything changed anywhere (staff, Claude, n8n) shows at once.
109
+ - To test customer and staff side by side in one browser, give the staff view its own
110
+ `storageKey` (for example when the address has `?staff`).
111
+ - Serve the HTML with `Cache-Control: no-cache` (an `.htaccess` rule on Awesomate hosting), or
112
+ people keep an old copy of the page after you change it.
113
+ - **Never put the account's token (`amt_pat_...`) or a server key (`ak_...`) in a page.** Only
114
+ the `pk_` key belongs in the browser.
115
+
116
+ ## 6. Optional: an AI in the conversations
117
+
118
+ `awesomate_crm_assistant` adds an assistant (for example "Bree") to the message threads. Each
119
+ conversation is Off (people only, the default), Draft (a suggested reply only staff see) or Auto
120
+ (she replies, marked as AI, when she is sure). She reads as the customer she is answering, so
121
+ the access rules bound her, and only `readable_by_ai` attributes reach her. The tool description
122
+ lists the two staff-only kinds it needs (`assistant_thread`, `assistant_draft`) and the
123
+ `from_assistant` attribute on the message kind. Give her a published, public, strict Knowledge
124
+ agent (awesomate-knowledge skill) so she can answer questions about the business. Confirm the
125
+ name and the default mode with the owner: switching it on lets an AI reply to their customers.
126
+
127
+ ## 7. Optional: automation
128
+
129
+ - **Server key** (`awesomate_crm_apps {action:'create_key'}`): for the app's own server or an n8n
130
+ workflow to read and write records. Read or write, only the kinds it needs. Shown once: put it
131
+ straight into the server's environment or an n8n credential, never in a reply or a repository.
132
+ - **Record events** (`awesomate_crm_hooks`): when a record of a kind is created, updated or
133
+ archived, the account's own n8n gets an event at once. Build and activate the workflow first
134
+ (awesomate-n8n skill), then create the hook with its production webhook URL and put the secret
135
+ in the Webhook node's Header Auth credential. Use `test` to check it. Example: a job is
136
+ requested, the workflow drafts a quote and writes it back with a write key for `job`.
137
+
138
+ ## 8. Check it works
139
+
140
+ - Sign in as a customer and as staff (two windows). The customer must not see another
141
+ customer's records; try it.
142
+ - Write as one, watch the other update live.
143
+ - With an assistant: set one job to Draft, ask a question as the customer, see the draft as staff.
144
+ - Tell the owner what you built in their terms, and what is not built yet.