@awesomate/hosting-mcp 0.50.0 → 0.52.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 +17 -9
- package/package.json +1 -1
- package/skill/CHANGELOG.json +14 -0
- package/skill/awesomate-portals/SKILL.md +16 -8
package/dist/index.js
CHANGED
|
@@ -42887,13 +42887,15 @@ server.registerTool(
|
|
|
42887
42887
|
"awesomate_crm_apps",
|
|
42888
42888
|
{
|
|
42889
42889
|
annotations: annotate("awesomate_crm_apps", MUTATING),
|
|
42890
|
-
description: "The account's own apps (a client portal, a members' app) whose users sign in by email link and then read and change only what each kind's access rules allow them (awesomate_crm_kinds set_access). Pro and above to create or change; reading is every plan. action 'list': each app with its publishable key, allowed origins, sign-up mode and default role. action 'create' {name, allowed_origins, sign_up?, default_role?}: name appears in the sign-in email; allowed_origins are the exact origins the app runs on (https://portal.example.com; http://localhost:5173 for development; capacitor://localhost for the mobile shell), with no path and no wildcard; sign_up 'invite' (default: only people added with awesomate_crm_app_users) or 'open' (anyone who uses the email link gets the default role). action 'update' {app_id, ...any of those, status?}: status 'disabled' signs everyone out of that app and stops its server keys. The publishable key (pk_...) is not a secret: it goes in the app's browser code, with @awesomate/sdk. Never put the account's own token (amt_pat_...) in browser code. Confirm origins and sign-up mode with the user: open sign-up lets strangers in under the default role. SERVER KEYS (ak_...), for the app's own server or an n8n workflow, used with @awesomate/sdk createClient({ token: <key> }) or as a Bearer token on https://hub.awesomate.ai/api/sdk/v1/server: action 'keys' {app_id} lists them (never the secrets); 'create_key' {app_id, name, access: 'read'|'write', kinds?} makes one (kinds leaves it to those kinds, 'contact' to read contacts; leave it out for every kind) and returns the key ONCE: put it straight into the app server's environment (awesomate_app_set_env for an app hosted with us
|
|
42890
|
+
description: "The account's own apps (a client portal, a members' app) whose users sign in by email link and then read and change only what each kind's access rules allow them (awesomate_crm_kinds set_access). Pro and above to create or change; reading is every plan. action 'list': each app with its publishable key, allowed origins, sign-up mode and default role. action 'create' {name, allowed_origins, sign_up?, default_role?}: name appears in the sign-in email; allowed_origins are the exact origins the app runs on (https://portal.example.com; http://localhost:5173 for development; capacitor://localhost for the mobile shell), with no path and no wildcard; sign_up 'invite' (default: only people added with awesomate_crm_app_users) or 'open' (anyone who uses the email link gets the default role). action 'update' {app_id, ...any of those, status?}: status 'disabled' signs everyone out of that app and stops its server keys. The publishable key (pk_...) is not a secret: it goes in the app's browser code, with @awesomate/sdk. Never put the account's own token (amt_pat_...) in browser code. Confirm origins and sign-up mode with the user: open sign-up lets strangers in under the default role. action 'customer_lookup' {app_id, roles?}: which roles in that app may look customers up (name, email and phone, by search or id, with @awesomate/sdk lookupCustomers) and so put a record under any customer, not only themselves. Without roles it reads the setting; roles ['staff'] switches it on for staff; [] switches it off. Never the app's default role (the one customers sign in with): that would show every customer to every other customer, and the hub refuses it. Off until the owner asks for it. SERVER KEYS (ak_...), for the app's own server or an n8n workflow, used with @awesomate/sdk createClient({ token: <key> }) or as a Bearer token on https://hub.awesomate.ai/api/sdk/v1/server: action 'keys' {app_id} lists them (never the secrets); 'create_key' {app_id, name, access: 'read'|'write', kinds?} makes one (kinds leaves it to those kinds, 'contact' to read contacts; leave it out for every kind) and returns the key ONCE: put it straight into the app server's environment (awesomate_app_set_env for an app hosted with us) and never into browser code, a repository or a reply to the user. For an n8n workflow pass n8n_credential: true instead (prefer it): the hub puts the key into a Header Auth credential 'Awesomate app key: <name>' on their n8n (Authorization: Bearer, usable only towards the hub) and returns only its id and name, so the key never passes through you. 'revoke_key' {app_id, key_id} stops it at once, and removes a credential the hub made. A key reads every attribute of an app kind, contacts only as you read them, and writes need Support Plus and above. Prefer the narrowest key: read unless the server writes, and only the kinds it uses.",
|
|
42891
42891
|
inputSchema: {
|
|
42892
|
-
action: external_exports.enum(["list", "create", "update", "keys", "create_key", "revoke_key"]),
|
|
42893
|
-
app_id: external_exports.string().max(48).optional().describe("update, keys, create_key, revoke_key: the app"),
|
|
42892
|
+
action: external_exports.enum(["list", "create", "update", "keys", "create_key", "revoke_key", "customer_lookup"]),
|
|
42893
|
+
app_id: external_exports.string().max(48).optional().describe("update, keys, create_key, revoke_key, customer_lookup: the app"),
|
|
42894
42894
|
access: external_exports.enum(["read", "write"]).optional().describe("create_key"),
|
|
42895
42895
|
kinds: external_exports.array(external_exports.string().max(41)).max(50).optional().describe("create_key: only these kinds (contact to read contacts); leave out for every kind"),
|
|
42896
42896
|
key_id: external_exports.string().max(64).optional().describe("revoke_key: from keys"),
|
|
42897
|
+
n8n_credential: external_exports.boolean().optional().describe("create_key: put the key into an n8n credential on their n8n instead of the answer"),
|
|
42898
|
+
roles: external_exports.array(external_exports.string().max(31)).max(10).optional().describe("customer_lookup: the roles that may look customers up; [] switches it off; leave out to read"),
|
|
42897
42899
|
name: external_exports.string().max(80).optional(),
|
|
42898
42900
|
allowed_origins: external_exports.array(external_exports.string().max(200)).max(10).optional(),
|
|
42899
42901
|
sign_up: external_exports.enum(["invite", "open"]).optional(),
|
|
@@ -42901,10 +42903,15 @@ server.registerTool(
|
|
|
42901
42903
|
status: external_exports.enum(["active", "disabled"]).optional().describe("update only")
|
|
42902
42904
|
}
|
|
42903
42905
|
},
|
|
42904
|
-
async ({ action, app_id, name, allowed_origins, sign_up, default_role, status, access, kinds, key_id }) => {
|
|
42906
|
+
async ({ action, app_id, name, allowed_origins, sign_up, default_role, status, access, kinds, key_id, roles, n8n_credential }) => {
|
|
42905
42907
|
try {
|
|
42906
42908
|
const cfg = requireConfig();
|
|
42907
42909
|
if (action === "list") return textResult(await hubGet(cfg, "/api/my-crm/v1/apps"));
|
|
42910
|
+
if (action === "customer_lookup") {
|
|
42911
|
+
if (!app_id) return errorResult(new Error("customer_lookup needs app_id, from list."));
|
|
42912
|
+
const path = `/api/my-crm/v1/apps/${encodeURIComponent(app_id)}/customer-lookup`;
|
|
42913
|
+
return textResult(roles === void 0 ? await hubGet(cfg, path) : await hubPut(cfg, path, { roles }));
|
|
42914
|
+
}
|
|
42908
42915
|
if (action === "keys" || action === "create_key" || action === "revoke_key") {
|
|
42909
42916
|
if (!app_id) return errorResult(new Error(`${action} needs app_id, from list.`));
|
|
42910
42917
|
const keys = `/api/my-crm/v1/apps/${encodeURIComponent(app_id)}/keys`;
|
|
@@ -42914,7 +42921,7 @@ server.registerTool(
|
|
|
42914
42921
|
return textResult(await hubDelete(cfg, `${keys}/${encodeURIComponent(key_id)}`));
|
|
42915
42922
|
}
|
|
42916
42923
|
if (!name || !access) return errorResult(new Error("create_key needs a name and access (read or write)."));
|
|
42917
|
-
return textResult(await hubPost(cfg, keys, { name, access, ...kinds ? { kinds } : {} }));
|
|
42924
|
+
return textResult(await hubPost(cfg, keys, { name, access, ...kinds ? { kinds } : {}, ...n8n_credential === void 0 ? {} : { n8n_credential } }));
|
|
42918
42925
|
}
|
|
42919
42926
|
const fields = Object.fromEntries(Object.entries({ name, allowed_origins, sign_up, default_role }).filter(([, v]) => v !== void 0));
|
|
42920
42927
|
if (action === "create") return textResult(await hubPost(cfg, "/api/my-crm/v1/apps", fields));
|
|
@@ -42997,7 +43004,7 @@ server.registerTool(
|
|
|
42997
43004
|
"awesomate_crm_hooks",
|
|
42998
43005
|
{
|
|
42999
43006
|
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);
|
|
43007
|
+
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); pass n8n_credential: true (prefer it) and the hub puts the secret straight into an n8n Header Auth credential named 'Awesomate hook: <name>' on their n8n and returns only its id and name, so nothing secret passes through you: set the Webhook node's Authentication to Header Auth and pick that credential. Without it the secret comes back ONCE: put it into an n8n Header Auth credential (header X-Awesomate-Webhook-Secret), never into a reply. Removing the hook removes a credential the hub made. 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
43008
|
inputSchema: {
|
|
43002
43009
|
action: external_exports.enum(["list", "create", "update", "remove", "test"]),
|
|
43003
43010
|
app_id: external_exports.string().max(48).describe("the app, from awesomate_crm_apps list"),
|
|
@@ -43006,16 +43013,17 @@ server.registerTool(
|
|
|
43006
43013
|
url: external_exports.string().max(500).optional().describe("create/update: the production URL of a Webhook node on the account's own n8n"),
|
|
43007
43014
|
kinds: external_exports.array(external_exports.string().max(41)).max(20).optional(),
|
|
43008
43015
|
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")
|
|
43016
|
+
enabled: external_exports.boolean().optional().describe("update only"),
|
|
43017
|
+
n8n_credential: external_exports.boolean().optional().describe("create: put the secret into an n8n credential instead of the answer (recommended)")
|
|
43010
43018
|
}
|
|
43011
43019
|
},
|
|
43012
|
-
async ({ action, app_id, hook_id, name, url, kinds, events, enabled }) => {
|
|
43020
|
+
async ({ action, app_id, hook_id, name, url, kinds, events, enabled, n8n_credential }) => {
|
|
43013
43021
|
try {
|
|
43014
43022
|
const cfg = requireConfig();
|
|
43015
43023
|
const path = `/api/my-crm/v1/apps/${encodeURIComponent(app_id)}/hooks`;
|
|
43016
43024
|
if (action === "list") return textResult(await hubGet(cfg, path));
|
|
43017
43025
|
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));
|
|
43026
|
+
if (action === "create") return textResult(await hubPost(cfg, path, { ...fields, ...n8n_credential === void 0 ? {} : { n8n_credential } }));
|
|
43019
43027
|
if (!hook_id) return errorResult(new Error(`${action} needs hook_id, from list.`));
|
|
43020
43028
|
const one = `${path}/${encodeURIComponent(hook_id)}`;
|
|
43021
43029
|
if (action === "remove") return textResult(await hubDelete(cfg, one));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/hosting-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.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",
|
package/skill/CHANGELOG.json
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.52.0",
|
|
5
|
+
"highlights": [
|
|
6
|
+
"When your n8n is the one receiving, Claude can ask the hub to put a record hook's secret or an app server key straight into an n8n credential on your instance (n8n_credential). Nobody sees the secret, not Claude and not you",
|
|
7
|
+
"Removing the hook or revoking the key removes that credential too, so n8n never offers a dead one"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.51.0",
|
|
12
|
+
"highlights": [
|
|
13
|
+
"Your app's staff can now find a customer and put a job under them, straight from the portal. Switch it on per app for the roles that need it (awesomate_crm_apps, customer_lookup). Pro and above",
|
|
14
|
+
"They see name, email and phone only, never a field you marked sensitive, and never the role your customers sign in with: customers still see only themselves"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
3
17
|
{
|
|
4
18
|
"version": "0.50.0",
|
|
5
19
|
"highlights": [
|
|
@@ -63,9 +63,11 @@ message: {"read": {"member": "linked:job.customer", "staff": "all"}, "write": {"
|
|
|
63
63
|
a mistake in the page can never show anyone more than their role allows.
|
|
64
64
|
- A person can only link a record to something they can see: a customer can point a job at their
|
|
65
65
|
own contact, never someone else's.
|
|
66
|
-
- **
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
- **Staff putting a job under a customer** needs one more switch, per app: app people read no
|
|
67
|
+
contacts but their own, so by default staff can only link to themselves. Ask the owner, then
|
|
68
|
+
`awesomate_crm_apps {action:'customer_lookup', app_id, roles:['staff']}`. Those roles can then
|
|
69
|
+
search customers (name, email, phone; never a sensitive field) and link a record to any of
|
|
70
|
+
them. Never the app's default role (the one customers sign in with); the hub refuses it.
|
|
69
71
|
|
|
70
72
|
## 4. Create the app and add people
|
|
71
73
|
|
|
@@ -84,7 +86,7 @@ loads from a CDN, or from npm in a Vite app:
|
|
|
84
86
|
|
|
85
87
|
```html
|
|
86
88
|
<script type="module">
|
|
87
|
-
import { createAppClient } from 'https://cdn.jsdelivr.net/npm/@awesomate/sdk@0.
|
|
89
|
+
import { createAppClient } from 'https://cdn.jsdelivr.net/npm/@awesomate/sdk@0.8/+esm';
|
|
88
90
|
const app = createAppClient({ publishableKey: 'pk_...' });
|
|
89
91
|
|
|
90
92
|
// The email link signs them in by itself (also when it opens in a tab already on this page).
|
|
@@ -100,6 +102,10 @@ await app.auth.signInWithLink(email, { redirectTo: location.href });
|
|
|
100
102
|
const stop = app.live('job', { orderBy: ['created_at', 'desc'], limit: 100 }, (rows) => renderJobs(rows));
|
|
101
103
|
await app.write('job', { title: 'Leaking tap', status: 'open' }, { links: { customer: user.contact_id } });
|
|
102
104
|
await app.write('message', { body }, { links: { job: jobId } });
|
|
105
|
+
|
|
106
|
+
// Staff, once customer_lookup is on for their role: find a customer, file a job for them.
|
|
107
|
+
const [customer] = await app.lookupCustomers({ q: 'jo' });
|
|
108
|
+
await app.write('job', { title: 'Gutters', status: 'open' }, { links: { customer: customer.id } });
|
|
103
109
|
</script>
|
|
104
110
|
```
|
|
105
111
|
|
|
@@ -127,12 +133,14 @@ name and the default mode with the owner: switching it on lets an AI reply to th
|
|
|
127
133
|
## 7. Optional: automation
|
|
128
134
|
|
|
129
135
|
- **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.
|
|
131
|
-
|
|
136
|
+
workflow to read and write records. Read or write, only the kinds it needs. For n8n pass
|
|
137
|
+
`n8n_credential: true`: the hub puts the key into a credential on their n8n and you never see
|
|
138
|
+
it. Otherwise it is shown once: straight into the server's environment, never a reply or a repo.
|
|
132
139
|
- **Record events** (`awesomate_crm_hooks`): when a record of a kind is created, updated or
|
|
133
140
|
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
|
|
135
|
-
|
|
141
|
+
(awesomate-n8n skill), then create the hook with its production webhook URL and
|
|
142
|
+
`n8n_credential: true`, and pick the credential it names in the Webhook node (Header Auth).
|
|
143
|
+
Use `test` to check it. Example: a job is
|
|
136
144
|
requested, the workflow drafts a quote and writes it back with a write key for `job`.
|
|
137
145
|
|
|
138
146
|
## 8. Check it works
|