@awesomate/hosting-mcp 0.51.0 → 0.53.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
@@ -42804,17 +42804,19 @@ var crmAttribute = external_exports.object({
42804
42804
  choices: external_exports.array(external_exports.string().max(100)).max(200).optional().describe("required for choice / choices"),
42805
42805
  required: external_exports.boolean().optional(),
42806
42806
  sensitivity: external_exports.enum(["ordinary", "personal", "sensitive"]).optional().describe("sensitive values are never readable by AI"),
42807
- readable_by_ai: external_exports.boolean().optional().describe("default false: only readable attributes reach awesomate_crm_rows")
42807
+ readable_by_ai: external_exports.boolean().optional().describe("default false: only readable attributes reach awesomate_crm_rows"),
42808
+ visible_to: external_exports.array(external_exports.string().max(31)).max(10).nullable().optional().describe("which of an app's signed-in roles see it, e.g. ['staff']; leave out for everyone who can read the record")
42808
42809
  });
42809
42810
  server.registerTool(
42810
42811
  "awesomate_crm_kinds",
42811
42812
  {
42812
42813
  annotations: annotate("awesomate_crm_kinds", MUTATING),
42813
- description: "The kinds of app data in the client's own database (the tables an app keeps: jobs, memos, bookings, messages), created directly from here on Support Plus and above. action 'list': every kind with its attributes and links. 'define' {key, label?, label_plural?, attributes, links?}: a new kind; links: [{key, to, label?, required?}] where to is 'contact' or a kind already made, and each shows as <key>_id. 'add_attribute' {kind, attribute}. 'archive_attribute' {kind, key}: leaves the views, values kept. 'archive' {kind}: kept, restorable. No migration and no SQL: a kind is registry rows plus generated views. Business things whose values must be traced to a source (a property, an installed system) are 'tracked' kinds the owner accepts in the hub; they are refused here. Mark an attribute readable_by_ai only when the owner wants Claude and agents to see it, and never for anything sensitive. 'set_access' {kind, access} (or access on define): who among the people signed in to the account's apps (awesomate_crm_apps) may read and change records of this kind, by their role: {read: {<role>: <rule>}, write: {<role>: <rule>}}. A rule is all, none, own (records they created), or linked:<link path ending at a contact> (linked:customer: jobs whose customer is them; linked:job.customer: memos on their jobs), joined with |. A role with no rule gets nothing; the default lets owner and staff do everything and members nothing. A write must leave the record inside the person's rule. Confirm the design with the user before defining: a kind is part of their data, not scratch space.",
42814
+ description: "The kinds of app data in the client's own database (the tables an app keeps: jobs, memos, bookings, messages), created directly from here on Support Plus and above. action 'list': every kind with its attributes and links. 'define' {key, label?, label_plural?, attributes, links?}: a new kind; links: [{key, to, label?, required?}] where to is 'contact' or a kind already made, and each shows as <key>_id. 'add_attribute' {kind, attribute}. 'archive_attribute' {kind, key}: leaves the views, values kept. 'set_visibility' {kind, key, visible_to}: which of an app's signed-in roles see that attribute (['staff'] for a cost or an internal note on a customer's job; null for everyone who can read the record; [] for none of them). Others read it as null and cannot write it; the database enforces it, and the account, Claude and hooks always see every attribute. 'archive' {kind}: kept, restorable. No migration and no SQL: a kind is registry rows plus generated views. Business things whose values must be traced to a source (a property, an installed system) are 'tracked' kinds the owner accepts in the hub; they are refused here. Mark an attribute readable_by_ai only when the owner wants Claude and agents to see it, and never for anything sensitive. 'set_access' {kind, access} (or access on define): who among the people signed in to the account's apps (awesomate_crm_apps) may read and change records of this kind, by their role: {read: {<role>: <rule>}, write: {<role>: <rule>}}. A rule is all, none, own (records they created), or linked:<link path ending at a contact> (linked:customer: jobs whose customer is them; linked:job.customer: memos on their jobs), joined with |. A role with no rule gets nothing; the default lets owner and staff do everything and members nothing. A write must leave the record inside the person's rule. Confirm the design with the user before defining: a kind is part of their data, not scratch space.",
42814
42815
  inputSchema: {
42815
- action: external_exports.enum(["list", "define", "add_attribute", "archive_attribute", "archive", "set_access"]),
42816
- kind: external_exports.string().max(41).optional().describe("the kind to change (add_attribute, archive_attribute, archive, set_access)"),
42817
- key: external_exports.string().max(41).optional().describe("define: the new kind; archive_attribute: the attribute"),
42816
+ action: external_exports.enum(["list", "define", "add_attribute", "archive_attribute", "archive", "set_access", "set_visibility"]),
42817
+ kind: external_exports.string().max(41).optional().describe("the kind to change (add_attribute, archive_attribute, archive, set_access, set_visibility)"),
42818
+ key: external_exports.string().max(41).optional().describe("define: the new kind; archive_attribute, set_visibility: the attribute"),
42819
+ visible_to: external_exports.array(external_exports.string().max(31)).max(10).nullable().optional().describe("set_visibility: the roles that see it, or null for everyone"),
42818
42820
  label: external_exports.string().max(80).optional(),
42819
42821
  label_plural: external_exports.string().max(80).optional(),
42820
42822
  attributes: external_exports.array(crmAttribute).max(60).optional(),
@@ -42826,7 +42828,7 @@ server.registerTool(
42826
42828
  }).optional().describe("define / set_access: {read: {<role>: <rule>}, write: {<role>: <rule>}}")
42827
42829
  }
42828
42830
  },
42829
- async ({ action, kind, key, label, label_plural, attributes, links, attribute, access }) => {
42831
+ async ({ action, kind, key, label, label_plural, attributes, links, attribute, access, visible_to }) => {
42830
42832
  try {
42831
42833
  const cfg = requireConfig();
42832
42834
  if (action === "list") return textResult(await hubGet(cfg, "/api/my-crm/v1/kinds"));
@@ -42840,6 +42842,10 @@ server.registerTool(
42840
42842
  return textResult(await hubPut(cfg, `/api/my-crm/v1/kinds/${k}/access`, { access }));
42841
42843
  }
42842
42844
  if (action === "add_attribute") return textResult(await hubPost(cfg, `/api/my-crm/v1/kinds/${k}/attributes`, { attribute }));
42845
+ if (action === "set_visibility") {
42846
+ if (!key || visible_to === void 0) return errorResult(new Error("set_visibility needs key (the attribute) and visible_to (a list of roles, or null for everyone)."));
42847
+ return textResult(await hubPut(cfg, `/api/my-crm/v1/kinds/${k}/attributes/${encodeURIComponent(key)}/visibility`, { visible_to }));
42848
+ }
42843
42849
  if (action === "archive_attribute") {
42844
42850
  if (!key) return errorResult(new Error("archive_attribute needs key, the attribute."));
42845
42851
  return textResult(await hubDelete(cfg, `/api/my-crm/v1/kinds/${k}/attributes/${encodeURIComponent(key)}`));
@@ -42887,13 +42893,14 @@ server.registerTool(
42887
42893
  "awesomate_crm_apps",
42888
42894
  {
42889
42895
  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. 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, or the n8n credential) and never into browser code, a repository or a reply to the user; 'revoke_key' {app_id, key_id} stops it at once. 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.",
42896
+ 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
42897
  inputSchema: {
42892
42898
  action: external_exports.enum(["list", "create", "update", "keys", "create_key", "revoke_key", "customer_lookup"]),
42893
42899
  app_id: external_exports.string().max(48).optional().describe("update, keys, create_key, revoke_key, customer_lookup: the app"),
42894
42900
  access: external_exports.enum(["read", "write"]).optional().describe("create_key"),
42895
42901
  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
42902
  key_id: external_exports.string().max(64).optional().describe("revoke_key: from keys"),
42903
+ n8n_credential: external_exports.boolean().optional().describe("create_key: put the key into an n8n credential on their n8n instead of the answer"),
42897
42904
  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"),
42898
42905
  name: external_exports.string().max(80).optional(),
42899
42906
  allowed_origins: external_exports.array(external_exports.string().max(200)).max(10).optional(),
@@ -42902,7 +42909,7 @@ server.registerTool(
42902
42909
  status: external_exports.enum(["active", "disabled"]).optional().describe("update only")
42903
42910
  }
42904
42911
  },
42905
- async ({ action, app_id, name, allowed_origins, sign_up, default_role, status, access, kinds, key_id, roles }) => {
42912
+ async ({ action, app_id, name, allowed_origins, sign_up, default_role, status, access, kinds, key_id, roles, n8n_credential }) => {
42906
42913
  try {
42907
42914
  const cfg = requireConfig();
42908
42915
  if (action === "list") return textResult(await hubGet(cfg, "/api/my-crm/v1/apps"));
@@ -42920,7 +42927,7 @@ server.registerTool(
42920
42927
  return textResult(await hubDelete(cfg, `${keys}/${encodeURIComponent(key_id)}`));
42921
42928
  }
42922
42929
  if (!name || !access) return errorResult(new Error("create_key needs a name and access (read or write)."));
42923
- return textResult(await hubPost(cfg, keys, { name, access, ...kinds ? { kinds } : {} }));
42930
+ return textResult(await hubPost(cfg, keys, { name, access, ...kinds ? { kinds } : {}, ...n8n_credential === void 0 ? {} : { n8n_credential } }));
42924
42931
  }
42925
42932
  const fields = Object.fromEntries(Object.entries({ name, allowed_origins, sign_up, default_role }).filter(([, v]) => v !== void 0));
42926
42933
  if (action === "create") return textResult(await hubPost(cfg, "/api/my-crm/v1/apps", fields));
@@ -43003,7 +43010,7 @@ server.registerTool(
43003
43010
  "awesomate_crm_hooks",
43004
43011
  {
43005
43012
  annotations: annotate("awesomate_crm_hooks", MUTATING),
43006
- 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.",
43013
+ 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.",
43007
43014
  inputSchema: {
43008
43015
  action: external_exports.enum(["list", "create", "update", "remove", "test"]),
43009
43016
  app_id: external_exports.string().max(48).describe("the app, from awesomate_crm_apps list"),
@@ -43012,16 +43019,17 @@ server.registerTool(
43012
43019
  url: external_exports.string().max(500).optional().describe("create/update: the production URL of a Webhook node on the account's own n8n"),
43013
43020
  kinds: external_exports.array(external_exports.string().max(41)).max(20).optional(),
43014
43021
  events: external_exports.array(external_exports.enum(["created", "updated", "archived"])).max(3).optional().describe("default all three"),
43015
- enabled: external_exports.boolean().optional().describe("update only")
43022
+ enabled: external_exports.boolean().optional().describe("update only"),
43023
+ n8n_credential: external_exports.boolean().optional().describe("create: put the secret into an n8n credential instead of the answer (recommended)")
43016
43024
  }
43017
43025
  },
43018
- async ({ action, app_id, hook_id, name, url, kinds, events, enabled }) => {
43026
+ async ({ action, app_id, hook_id, name, url, kinds, events, enabled, n8n_credential }) => {
43019
43027
  try {
43020
43028
  const cfg = requireConfig();
43021
43029
  const path = `/api/my-crm/v1/apps/${encodeURIComponent(app_id)}/hooks`;
43022
43030
  if (action === "list") return textResult(await hubGet(cfg, path));
43023
43031
  const fields = Object.fromEntries(Object.entries({ name, url, kinds, events }).filter(([, v]) => v !== void 0));
43024
- if (action === "create") return textResult(await hubPost(cfg, path, fields));
43032
+ if (action === "create") return textResult(await hubPost(cfg, path, { ...fields, ...n8n_credential === void 0 ? {} : { n8n_credential } }));
43025
43033
  if (!hook_id) return errorResult(new Error(`${action} needs hook_id, from list.`));
43026
43034
  const one = `${path}/${encodeURIComponent(hook_id)}`;
43027
43035
  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.51.0",
3
+ "version": "0.53.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,19 @@
1
1
  {
2
2
  "versions": [
3
+ {
4
+ "version": "0.53.0",
5
+ "highlights": [
6
+ "Fields only your team sees: mark an attribute visible to staff only (awesomate_crm_kinds, set_visibility), such as the cost behind a quote or an internal note on a job. Customers signed in to your portal read it as empty and cannot change it",
7
+ "Your own database enforces it, not the portal's code, and Bree, answering as the customer, never sees it either. You and Claude always see every field"
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.52.0",
12
+ "highlights": [
13
+ "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",
14
+ "Removing the hook or revoking the key removes that credential too, so n8n never offers a dead one"
15
+ ]
16
+ },
3
17
  {
4
18
  "version": "0.51.0",
5
19
  "highlights": [
@@ -44,8 +44,13 @@ Ask, one or two at a time:
44
44
 
45
45
  - **Link the thing to the customer's contact.** That link is what "their own" means.
46
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.
47
+ it. Never for anything sensitive. `readable_by_ai` is only about AI.
48
+ - **Fields only the team sees** (a cost, an internal note): give the attribute
49
+ `visible_to: ['staff']` when defining it, or later with `awesomate_crm_kinds
50
+ {action:'set_visibility'}`. Customers read it as null and cannot write it; the database
51
+ enforces it, and so does what the assistant sees. Without `visible_to`, everyone who can read
52
+ the record sees the attribute. Don't make such an attribute `required` on a kind customers
53
+ create, or they cannot create one.
49
54
  - Show the owner the design ("A job has a title, a status and a quote, and belongs to a
50
55
  customer. Messages hang off a job.") and get a yes first.
51
56
 
@@ -133,12 +138,14 @@ name and the default mode with the owner: switching it on lets an AI reply to th
133
138
  ## 7. Optional: automation
134
139
 
135
140
  - **Server key** (`awesomate_crm_apps {action:'create_key'}`): for the app's own server or an n8n
136
- workflow to read and write records. Read or write, only the kinds it needs. Shown once: put it
137
- straight into the server's environment or an n8n credential, never in a reply or a repository.
141
+ workflow to read and write records. Read or write, only the kinds it needs. For n8n pass
142
+ `n8n_credential: true`: the hub puts the key into a credential on their n8n and you never see
143
+ it. Otherwise it is shown once: straight into the server's environment, never a reply or a repo.
138
144
  - **Record events** (`awesomate_crm_hooks`): when a record of a kind is created, updated or
139
145
  archived, the account's own n8n gets an event at once. Build and activate the workflow first
140
- (awesomate-n8n skill), then create the hook with its production webhook URL and put the secret
141
- in the Webhook node's Header Auth credential. Use `test` to check it. Example: a job is
146
+ (awesomate-n8n skill), then create the hook with its production webhook URL and
147
+ `n8n_credential: true`, and pick the credential it names in the Webhook node (Header Auth).
148
+ Use `test` to check it. Example: a job is
142
149
  requested, the workflow drafts a quote and writes it back with a write key for `job`.
143
150
 
144
151
  ## 8. Check it works