@awesomate/hosting-mcp 0.22.3 → 0.24.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
@@ -40857,10 +40857,85 @@ ${JSON.stringify(details, null, 2)}`;
40857
40857
  }
40858
40858
  return { content: [{ type: "text", text }], isError: true };
40859
40859
  }
40860
- var READ_ONLY = { readOnlyHint: true };
40861
- var DESTRUCTIVE = { destructiveHint: true };
40860
+ var READ_ONLY = { readOnlyHint: true, destructiveHint: false };
40861
+ var MUTATING = { readOnlyHint: false, destructiveHint: false };
40862
+ var DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true };
40863
+ var TITLES = {
40864
+ awesomate_whoami: "Check connected account",
40865
+ awesomate_get_context: "Get account context",
40866
+ awesomate_get_hosting_status: "Get hosting status",
40867
+ awesomate_get_hosting_account: "Get hosting account",
40868
+ awesomate_list_sites: "List WordPress sites",
40869
+ awesomate_list_domains: "List domains",
40870
+ awesomate_get_limits: "Get plan limits",
40871
+ awesomate_get_plan_features: "Get plan features",
40872
+ awesomate_dashboard_metrics: "Get dashboard metrics",
40873
+ awesomate_account_report: "Get account report",
40874
+ awesomate_privacy_settings: "Get privacy settings",
40875
+ awesomate_app_list: "List apps",
40876
+ awesomate_app_context: "Get apps context",
40877
+ awesomate_n8n_context: "Get n8n context",
40878
+ awesomate_n8n_storage: "Get n8n storage usage",
40879
+ awesomate_dns_check: "Check domain DNS",
40880
+ awesomate_site_uptime: "Get site uptime",
40881
+ awesomate_notifications: "Account notifications",
40882
+ awesomate_support: "Get support help",
40883
+ // Destructive: rmSync/writeFileSync under ~/.claude/skills overwrites files
40884
+ // on the user's own machine, so it must always prompt.
40885
+ awesomate_skill_update: "Update Awesomate skills",
40886
+ awesomate_site_create: "Create WordPress site",
40887
+ awesomate_site_audit: "Audit site for SEO and AEO",
40888
+ awesomate_uninstall_site: "Delete WordPress site",
40889
+ awesomate_snapshot_site: "Snapshot site",
40890
+ awesomate_list_snapshots: "List site snapshots",
40891
+ awesomate_rollback_site: "Roll site back to snapshot",
40892
+ awesomate_site_staging_create: "Create staging site",
40893
+ awesomate_site_staging_promote: "Publish staging to live",
40894
+ awesomate_site_staging_discard: "Discard staging site",
40895
+ awesomate_domain_add: "Add custom domain",
40896
+ awesomate_run_wp_cli: "Run WP-CLI command",
40897
+ awesomate_wp_post: "Manage WordPress posts",
40898
+ awesomate_wp_media_import: "Import media to WordPress",
40899
+ awesomate_wp_settings: "Change WordPress settings",
40900
+ awesomate_app_create: "Create app",
40901
+ awesomate_app_get: "Get app details",
40902
+ awesomate_app_scaffold: "Get app scaffold guide",
40903
+ awesomate_app_deploy_info: "Get app deploy briefing",
40904
+ awesomate_app_health: "Check app health",
40905
+ awesomate_app_set_env: "Set app secret",
40906
+ awesomate_app_provision_db: "Add database to app",
40907
+ awesomate_n8n_workflows: "List n8n workflows",
40908
+ awesomate_n8n_inspect: "Inspect n8n workflow",
40909
+ awesomate_n8n_executions: "List n8n executions",
40910
+ awesomate_n8n_errors: "Get n8n errors",
40911
+ awesomate_n8n_findings: "Get n8n findings",
40912
+ awesomate_n8n_kpis: "Get n8n KPIs",
40913
+ awesomate_n8n_node_docs: "Get n8n node docs",
40914
+ awesomate_n8n_deploy: "Deploy n8n workflow",
40915
+ awesomate_n8n_test: "Test-fire n8n workflow",
40916
+ awesomate_n8n_datatable_write: "Write to n8n data table",
40917
+ awesomate_n8n_attach_to_app: "Attach n8n to app",
40918
+ awesomate_n8n_provision_pg: "Provision Postgres for n8n",
40919
+ awesomate_request_build: "Request a build from Awesomate",
40920
+ awesomate_knowledge_status: "Get Knowledge Base status",
40921
+ awesomate_knowledge_provision: "Enable Knowledge Base",
40922
+ awesomate_knowledge_search: "Search Knowledge Base",
40923
+ awesomate_knowledge_ask: "Ask the Knowledge Base",
40924
+ awesomate_knowledge_sources: "Manage knowledge sources",
40925
+ awesomate_knowledge_upload: "Upload file to Knowledge Base",
40926
+ awesomate_knowledge_agent: "Configure knowledge agent",
40927
+ awesomate_knowledge_agents: "Manage knowledge agents",
40928
+ awesomate_knowledge_collections: "Manage knowledge collections",
40929
+ awesomate_knowledge_people: "Manage knowledge entities",
40930
+ awesomate_knowledge_data: "Query knowledge data warehouse"
40931
+ };
40932
+ function annotate(name, kind) {
40933
+ const title = TITLES[name];
40934
+ if (!title) throw new Error(`No TITLES entry for tool "${name}" \u2014 add one before registering it.`);
40935
+ return { title, ...kind };
40936
+ }
40862
40937
  function readTool(name, description, path) {
40863
- server.registerTool(name, { description, inputSchema: {}, annotations: READ_ONLY }, async () => {
40938
+ server.registerTool(name, { description, inputSchema: {}, annotations: annotate(name, READ_ONLY) }, async () => {
40864
40939
  try {
40865
40940
  return textResult(await hubGet(requireConfig(), path));
40866
40941
  } catch (err) {
@@ -40871,7 +40946,7 @@ function readTool(name, description, path) {
40871
40946
  server.registerTool(
40872
40947
  "awesomate_whoami",
40873
40948
  {
40874
- annotations: READ_ONLY,
40949
+ annotations: annotate("awesomate_whoami", READ_ONLY),
40875
40950
  description: "Zero-network identity check: which Awesomate account this session is connected to (slug, plan, token expiry) and WHY (env var, project pin file, sole profile, or default). Call it after connecting, after switching folders, and any time the account in play matters \u2014 if the slug is not the account the user expects, STOP and fix the pin/connection before doing any work.",
40876
40951
  inputSchema: {}
40877
40952
  },
@@ -40895,7 +40970,7 @@ server.registerTool(
40895
40970
  server.registerTool(
40896
40971
  "awesomate_get_context",
40897
40972
  {
40898
- annotations: READ_ONLY,
40973
+ annotations: annotate("awesomate_get_context", READ_ONLY),
40899
40974
  description: "THE session entry point \u2014 call it FIRST, before any domain context. Returns the connected account: plan, capabilities, limits, scopes, token expiry, cPanel routing, PLUS `attention` ({unreadNotifications, erroringWorkflows7d, patExpiresInDays} \u2014 null means unknown, never zero; when something is non-zero, mention it to the user in one line before starting the asked task), `latestMcpVersion` (if serverVersion still lags it after a restart, the npx cache is stale \u2014 remedy: rm -rf ~/.npm/_npx, then restart), and `skill` ({updateAvailable, staleSkills, whatsNew} \u2014 if updateAvailable, mention ONCE with a whatsNew line, offer awesomate_skill_update, never mid-task). If the token is near expiry (patExpiresInDays < 7), re-running Connect Claude Code from hub.awesomate.ai/claude refreshes skills AND renews the token in one go.",
40900
40975
  inputSchema: {}
40901
40976
  },
@@ -40916,7 +40991,7 @@ readTool(
40916
40991
  server.registerTool(
40917
40992
  "awesomate_n8n_deploy",
40918
40993
  {
40919
- annotations: DESTRUCTIVE,
40994
+ annotations: annotate("awesomate_n8n_deploy", DESTRUCTIVE),
40920
40995
  description: "Workflow lifecycle writes on the client's n8n, all consent-gated and audited server-side. Actions: 'validate' (structural + n8n-mcp check of workflowJson \u2014 ALWAYS validate before create_draft), 'create_draft' (creates an INACTIVE '[CLI] ' workflow tagged awm:client-cli; returns its webhook URLs), 'activate'/'deactivate' (activation strips the [CLI] prefix; production webhooks respond only while active; agency-managed workflows are refused), 'promote' (swaps a TESTED draft into the live workflow IN PLACE \u2014 live id + webhookIds preserved so external callers keep working; pass workflowId=the LIVE id and draftId=the tested draft; the draft is archived '[promoted <date>]'; response includes operationId for rollback), 'rollback' (restore a promote's pre-swap state \u2014 pass operationId), 'update_draft' (replace an INACTIVE self-built draft's full JSON in place \u2014 pass workflowId + workflowJson; live workflows are refused, use the promote path), 'delete_draft' (inactive self-built drafts only). Get explicit user approval before activate, promote, rollback, and delete_draft. 429 quota_exceeded = daily plan limit; 403 consent_required \u2192 send user to settingsUrl; 409 n8n_credential_invalid = the user's OWN n8n rejected the stored API key (NOT a hub outage \u2014 reads and validate keep working because they never touch their instance): tell them to create a new API key in their n8n under Settings \u2192 n8n API, paste it into their Awesomate account at hub.awesomate.ai/n8n/settings, then retry.",
40921
40996
  inputSchema: {
40922
40997
  action: external_exports.enum(["validate", "create_draft", "update_draft", "activate", "deactivate", "promote", "rollback", "delete_draft"]),
@@ -40972,6 +41047,7 @@ server.registerTool(
40972
41047
  server.registerTool(
40973
41048
  "awesomate_n8n_test",
40974
41049
  {
41050
+ annotations: annotate("awesomate_n8n_test", MUTATING),
40975
41051
  description: "Fire a synthetic test payload at an ACTIVE self-built workflow's production webhook and get back the webhook response plus the executionId it caused (then poll GET /api/my-n8n/executions/:id for node-by-node results). Sends the X-Awesomate-Test header. Real side effects DO run (emails actually send) \u2014 warn the user, use their own address, prefix subjects with [TEST]. Only works on awm:client-cli-tagged workflows.",
40976
41052
  inputSchema: {
40977
41053
  workflowId: external_exports.string(),
@@ -40991,7 +41067,7 @@ server.registerTool(
40991
41067
  server.registerTool(
40992
41068
  "awesomate_n8n_workflows",
40993
41069
  {
40994
- annotations: READ_ONLY,
41070
+ annotations: annotate("awesomate_n8n_workflows", READ_ONLY),
40995
41071
  description: "The client's workflows. No id \u2192 EVERY workflow as a node-level summary in one call (nodeCount, nodeTypes, triggers, usesAi, communityNodes, dates; ?active filter + pagination) \u2014 use this for the session's world picture instead of fetching workflows one by one. With id \u2192 that workflow's JSON: detail 'full' (default \u2014 nodes with parameters, connections, settings) or 'structure' (nodes WITHOUT parameters + connections \u2014 cheap shape check for big workflows). 403 consent_required \u2192 send the user to settingsUrl and re-check.",
40996
41072
  inputSchema: {
40997
41073
  id: external_exports.string().optional().describe("Workflow id for a single-workflow read; omit for the all-workflows summary"),
@@ -41027,7 +41103,7 @@ server.registerTool(
41027
41103
  server.registerTool(
41028
41104
  "awesomate_n8n_inspect",
41029
41105
  {
41030
- annotations: READ_ONLY,
41106
+ annotations: annotate("awesomate_n8n_inspect", READ_ONLY),
41031
41107
  description: `Instance inventory reads, one tool: 'nodes' (distinct node types in use, counts, versions, community/AI flags), 'datatables' (tables + columns + row counts), 'datatable_rows' (pass datatableId; limit \u2264 100), 'possibilities' (facts for "what could I automate": connected services with live-usage cross-check, unused connections, top nodes, AI tools [null = unknown, not none], community packages, counts \u2014 YOU turn these into suggestions, grounded only in what's actually there), 'credentials' (names/types/inferred service \u2014 never secrets), 'variables' ($vars keys). All consent-gated server-side; 403 consent_required \u2192 settingsUrl.`,
41032
41108
  inputSchema: {
41033
41109
  what: external_exports.enum(["nodes", "datatables", "datatable_rows", "possibilities", "credentials", "variables"]),
@@ -41059,7 +41135,7 @@ server.registerTool(
41059
41135
  server.registerTool(
41060
41136
  "awesomate_n8n_executions",
41061
41137
  {
41062
- annotations: READ_ONLY,
41138
+ annotations: annotate("awesomate_n8n_executions", READ_ONLY),
41063
41139
  description: "Execution reads. workflowId \u2192 recent executions of that workflow. executionId \u2192 detail with errorSummary \u2014 trust errorSummary.failingNode only when confident:true (structural decode; also carries errorType/code/lastNodeExecuted); when confident:false it came from a heuristic and may name a node that does not exist \u2014 verify against the workflow before editing anything. executionId + debug:true \u2192 node-by-node decode (statuses, timings, errors, 2 example items per node) \u2014 the best failure-diagnosis view; needs the 'error content analysis' privacy toggle (403 consent_required \u2192 settingsUrl), and responses with tooLarge:true mean the payload exceeded 15MB \u2014 fall back to the non-debug detail. For 'what is failing across ALL my workflows', use awesomate_n8n_errors instead.",
41064
41140
  inputSchema: {
41065
41141
  workflowId: external_exports.string().optional(),
@@ -41088,7 +41164,7 @@ server.registerTool(
41088
41164
  server.registerTool(
41089
41165
  "awesomate_n8n_node_docs",
41090
41166
  {
41091
- annotations: READ_ONLY,
41167
+ annotations: annotate("awesomate_n8n_node_docs", READ_ONLY),
41092
41168
  description: "Live n8n documentation, 500+ nodes + 2,500+ community templates \u2014 ALWAYS prefer this over memory for node schemas and typeVersions. tools: search_nodes {query}, get_node {nodeType \u2014 full form like 'n8n-nodes-base.gmail' works, add detail:'full' for everything}, search_templates {query} / get_template {templateId} (real importable community workflows \u2014 great starting points), validate_node {nodeType, config}, tools_documentation {}. Works on every plan, no consent needed. 503 node_catalog_unavailable \u2192 use the skill's references/vendor/ files instead; 422 catalog_tool_error \u2192 YOUR args were wrong (message says why), the catalog is fine.",
41093
41169
  inputSchema: {
41094
41170
  tool: external_exports.enum(["search_nodes", "get_node", "search_templates", "get_template", "validate_node", "tools_documentation"]),
@@ -41106,7 +41182,7 @@ server.registerTool(
41106
41182
  server.registerTool(
41107
41183
  "awesomate_n8n_datatable_write",
41108
41184
  {
41109
- annotations: DESTRUCTIVE,
41185
+ annotations: annotate("awesomate_n8n_datatable_write", DESTRUCTIVE),
41110
41186
  description: "Datatable writes (Support Plus+, consent-gated, audited). 'create' {name, columns:[{name,type?}], workflowId?} \u2014 ALWAYS pass workflowId when the table serves a specific workflow (datatables resolve PER PROJECT at runtime; workflowId threads that workflow's project; a projectWarning in the response means pass it). 'add_column' {tableId, name, type?} \u2014 needs the 'direct database writes' privacy toggle; names: letters/digits/underscores only. 'insert' {tableId, rows:[...]} (\u2264100), 'update' {tableId, filter, data}, 'delete_rows' {tableId, filter \u2014 REQUIRED, there is no delete-all}. Writes only work on tables created through Claude Code (403 not_self_created otherwise \u2014 agency tables are off limits). Get explicit user approval before delete_rows.",
41111
41187
  inputSchema: {
41112
41188
  action: external_exports.enum(["create", "add_column", "insert", "update", "delete_rows"]),
@@ -41141,6 +41217,7 @@ server.registerTool(
41141
41217
  server.registerTool(
41142
41218
  "awesomate_skill_update",
41143
41219
  {
41220
+ annotations: annotate("awesomate_skill_update", DESTRUCTIVE),
41144
41221
  description: "The one-step updater for the locally installed Awesomate skills \u2014 run when any response stamp or awesomate_get_context reports an update ready. Refreshes ALL bundled skills in ~/.claude/skills from this (always-latest) server package, removes files newer bundles no longer ship, re-stamps versions, and returns per-skill from\u2192to plus what's-new lines and the exact restart step. New skill content applies from the NEXT Claude Code session; finish the current task first, then hand the user the restart instruction verbatim.",
41145
41222
  inputSchema: {}
41146
41223
  },
@@ -41173,17 +41250,33 @@ server.registerTool(
41173
41250
  server.registerTool(
41174
41251
  "awesomate_support",
41175
41252
  {
41176
- description: "Help the user with the service itself \u2014 questions, being stuck, or reaching a human. action 'faq' {q} searches the published help library (answer FROM the results; never invent policy or pricing). action 'help_docs' {q} searches the full Help Centre articles live \u2014 use when the FAQ has no good answer, and cite the returned article URLs. action 'create_ticket' {subject, message} opens a support ticket \u2014 draft it in the user's words, SHOW it, and get an explicit yes before sending; it emails the Awesomate team and returns a portal URL. action 'list_tickets' shows their tickets and status. Use the awesomate-support skill for the full flow. Not for building \u2014 route n8n work to awesomate-n8n and hosting to awesomate-hosting.",
41253
+ annotations: annotate("awesomate_support", MUTATING),
41254
+ description: "Help the user with the service itself \u2014 questions, being stuck, or reaching a human. **action 'ask' {q} is the FIRST thing to try for any question about Awesomate** \u2014 it answers from Awesomate's own Knowledge Base with numbered citations, works on every plan, and composes across articles rather than matching titles; relay its answer and cite the source URLs. If it returns no_answer:true the grounding gate declined, so offer a ticket instead of guessing; if degraded:true the Knowledge Base was unavailable and you are looking at keyword matches, so do not present them as a verified answer. action 'faq' {q} searches the published help library (answer FROM the results; never invent policy or pricing). action 'help_docs' {q} searches the full Help Centre articles live \u2014 use when the FAQ has no good answer, and cite the returned article URLs. action 'create_ticket' {subject, message, category} opens a support ticket \u2014 draft it in the user's words, SHOW it, and get an explicit yes before sending; it emails the Awesomate team and returns a portal URL. **`category` is REQUIRED and must name what the ticket is actually about \u2014 'Other' is refused.** Ticket access splits by SUBJECT, not by plan: reporting a problem with AWESOMATE ITSELF ('Infrastructure fault' \u2014 site down, instance unreachable, provisioning or SSL failure; 'Claude/MCP connectivity' \u2014 pairing fails, token rejected, tools erroring) and 'Account/billing' work on EVERY plan. Asking for help USING the products ('Workflow not running', 'Workflow error', 'New automation request', 'How-to question') needs Support Plus or above and returns 403 upgrade_required below it \u2014 when that happens, answer from 'faq'/'help_docs' instead and relay the upgrade honestly; do NOT relabel the ticket as a fault to get through. action 'list_tickets' shows their tickets and status. Use the awesomate-support skill for the full flow. Not for building \u2014 route n8n work to awesomate-n8n and hosting to awesomate-hosting.",
41177
41255
  inputSchema: {
41178
- action: external_exports.enum(["faq", "help_docs", "create_ticket", "list_tickets"]),
41179
- q: external_exports.string().optional().describe("faq and help_docs: the question or topic"),
41256
+ action: external_exports.enum(["ask", "faq", "help_docs", "create_ticket", "list_tickets"]),
41257
+ q: external_exports.string().optional().describe("ask, faq and help_docs: the question or topic"),
41180
41258
  subject: external_exports.string().optional().describe("create_ticket only"),
41181
- message: external_exports.string().optional().describe("create_ticket only: the body, in the user's words")
41259
+ message: external_exports.string().optional().describe("create_ticket only: the body, in the user's words"),
41260
+ category: external_exports.enum([
41261
+ "Infrastructure fault",
41262
+ "Claude/MCP connectivity",
41263
+ "Workflow not running",
41264
+ "Workflow error",
41265
+ "New automation request",
41266
+ "Account/billing",
41267
+ "How-to question"
41268
+ ]).optional().describe(
41269
+ "create_ticket: REQUIRED. What the ticket is about. The first two and Account/billing work on every plan; the rest need Support Plus or above."
41270
+ )
41182
41271
  }
41183
41272
  },
41184
- async ({ action, q, subject, message }) => {
41273
+ async ({ action, q, subject, message, category }) => {
41185
41274
  try {
41186
41275
  const cfg = requireConfig();
41276
+ if (action === "ask") {
41277
+ if (!q) return errorResult(new Error("ask needs q"));
41278
+ return textResult(await hubGet(cfg, `/api/help/ask?q=${encodeURIComponent(q)}`));
41279
+ }
41187
41280
  if (action === "faq") {
41188
41281
  return textResult(await hubGet(cfg, `/api/help/faq${q ? `?q=${encodeURIComponent(q)}` : ""}`));
41189
41282
  }
@@ -41195,7 +41288,14 @@ server.registerTool(
41195
41288
  return textResult(await hubGet(cfg, "/api/support/tickets"));
41196
41289
  }
41197
41290
  if (!subject || !message) return errorResult(new Error("create_ticket needs subject and message"));
41198
- return textResult(await hubPost(cfg, "/api/support/tickets", { subject, body_text: message }));
41291
+ if (!category) {
41292
+ return errorResult(
41293
+ new Error(
41294
+ 'create_ticket needs a category naming what the ticket is about. Use "Infrastructure fault" or "Claude/MCP connectivity" when the problem is with Awesomate itself, "Account/billing" for money, and the workflow/how-to categories (Support Plus and above) for help using the products.'
41295
+ )
41296
+ );
41297
+ }
41298
+ return textResult(await hubPost(cfg, "/api/support/tickets", { subject, body_text: message, category }));
41199
41299
  } catch (err) {
41200
41300
  return errorResult(err);
41201
41301
  }
@@ -41204,7 +41304,7 @@ server.registerTool(
41204
41304
  server.registerTool(
41205
41305
  "awesomate_request_build",
41206
41306
  {
41207
- annotations: DESTRUCTIVE,
41307
+ annotations: annotate("awesomate_request_build", DESTRUCTIVE),
41208
41308
  description: "Submit a DONE-FOR-YOU automation request \u2014 the Awesomate team builds it, for clients who'd rather not build it themselves or whose request is beyond what you can build here. This SPENDS 1 CREDIT ($100). Two steps, always: call WITHOUT confirmCredit first \u2014 it returns the cost and the user's available balance and spends nothing; state both to the user in plain words, get an explicit yes, THEN call again with confirmCredit:true. Needs a wizard-enabled plan (Pro/Embedded) \u2014 a Support Plus user gets upgrade_required, relay it honestly. On success returns a tracking URL (progress shows on My Automations).",
41209
41309
  inputSchema: {
41210
41310
  title: external_exports.string().describe("Short name for the automation"),
@@ -41224,7 +41324,8 @@ server.registerTool(
41224
41324
  server.registerTool(
41225
41325
  "awesomate_site_create",
41226
41326
  {
41227
- description: "Create a WordPress site on the user's hosting. Plan limits are enforced server-side (429/403 with an upgrade hint if they're at their cap \u2014 relay it; 409 means WordPress already exists at that domain). Ask 'live or dev?' first per the awesomate-hosting skill. Omit `domain` and the next free siteN.{primary} subdomain is chosen for you. ASYNC and returns only {success:true} \u2014 no domain, no admin details: poll awesomate_list_sites to learn the name, and expect HTTPS to refuse connections for a minute or two while AutoSSL issues even though the site is already serving over http. See 'After creating a site' in the skill.",
41327
+ annotations: annotate("awesomate_site_create", MUTATING),
41328
+ description: "Create a WordPress site on the user's hosting. **Needs Support Plus or above from Claude** \u2014 on Essentials this returns 403 `upgrade_required` ALWAYS, not only at a cap, because building hosting from a machine token is a Support Plus capability; the Essentials site itself is real and is created by the user in the hub at hub.awesomate.ai/sites, so offer that path first rather than leading with the upgrade. Plan COUNT limits are enforced separately (403 with an upgrade hint at the cap; 409 means WordPress already exists at that domain). Ask 'live or dev?' first per the awesomate-hosting skill. Omit `domain` and the next free siteN.{primary} subdomain is chosen for you. ASYNC and returns only {success:true} \u2014 no domain, no admin details: poll awesomate_list_sites to learn the name, and expect HTTPS to refuse connections for a minute or two while AutoSSL issues even though the site is already serving over http. See 'After creating a site' in the skill.",
41228
41329
  inputSchema: {
41229
41330
  domain: external_exports.string().optional().describe("Custom domain if they have one; omit for a default *.awesomate.site subdomain"),
41230
41331
  siteTitle: external_exports.string().optional(),
@@ -41242,7 +41343,8 @@ server.registerTool(
41242
41343
  server.registerTool(
41243
41344
  "awesomate_domain_add",
41244
41345
  {
41245
- description: "Add a custom domain to the user's hosting account. Returns the DNS steps they need to complete at their registrar. Plan-gated (some plans allow zero custom domains \u2014 relay the upgrade hint on 403).",
41346
+ annotations: annotate("awesomate_domain_add", MUTATING),
41347
+ description: "Add a custom domain to the user's hosting account. Returns the DNS steps they need to complete at their registrar. **Needs Support Plus or above from Claude** \u2014 Essentials gets 403 `upgrade_required` even though its plan DOES include one custom domain, because adding it from a machine token is Support Plus; tell them it takes two clicks at hub.awesomate.ai/sites/domains instead. Plan COUNT limits are enforced separately on top.",
41246
41348
  inputSchema: { domain: external_exports.string().describe("The domain to add, e.g. example.com") }
41247
41349
  },
41248
41350
  async ({ domain }) => {
@@ -41256,7 +41358,8 @@ server.registerTool(
41256
41358
  server.registerTool(
41257
41359
  "awesomate_run_wp_cli",
41258
41360
  {
41259
- description: "Run an allowlisted WP-CLI command on one of the user's WordPress sites (plugin/theme list+activate+update, cache flush, option get/update, post/media/menu/comment/user list). ARGUMENTS CANNOT CONTAIN SPACES (letters, digits and -_./=:@+, only) \u2014 so a site title, tagline or any multi-word value is impossible here: use awesomate_wp_settings for those, and awesomate_wp_post for post/page content. Installs accept wp.org SLUGS only \u2014 never URLs. args is the command as an array, e.g. ['plugin','list'] or ['plugin','install','wordpress-seo','--activate']. A 400 wp_cli_not_allowed means that command isn't permitted; a 502 wp_cli_unavailable is a temporary server-side issue, not your command.",
41361
+ annotations: annotate("awesomate_run_wp_cli", DESTRUCTIVE),
41362
+ description: "Run an allowlisted WP-CLI command on one of the user's WordPress sites (plugin/theme list+activate+update, cache flush, option get/update, post/media/menu/comment/user list). **Needs Support Plus or above** \u2014 the whole route is behind the write gate, so even the READS (option get, plugin list) return 403 `upgrade_required` on Essentials. ARGUMENTS CANNOT CONTAIN SPACES (letters, digits and -_./=:@+, only) \u2014 so a site title, tagline or any multi-word value is impossible here: use awesomate_wp_settings for those, and awesomate_wp_post for post/page content. Installs accept wp.org SLUGS only \u2014 never URLs. args is the command as an array, e.g. ['plugin','list'] or ['plugin','install','wordpress-seo','--activate']. A 400 wp_cli_not_allowed means that command isn't permitted; a 502 wp_cli_unavailable is a temporary server-side issue, not your command.",
41260
41363
  inputSchema: {
41261
41364
  domain: external_exports.string().describe("The site domain"),
41262
41365
  args: external_exports.array(external_exports.string()).describe("WP-CLI args, e.g. ['plugin','list']")
@@ -41273,7 +41376,7 @@ server.registerTool(
41273
41376
  server.registerTool(
41274
41377
  "awesomate_uninstall_site",
41275
41378
  {
41276
- annotations: DESTRUCTIVE,
41379
+ annotations: annotate("awesomate_uninstall_site", DESTRUCTIVE),
41277
41380
  description: "Permanently delete a WordPress site (files + database). IRREVERSIBLE \u2014 snapshot first if the user might want it back, and always get explicit confirmation. You MUST pass confirm equal to the exact domain, or the hub refuses. Support Plus+.",
41278
41381
  inputSchema: {
41279
41382
  domain: external_exports.string().describe("The site domain to delete"),
@@ -41291,6 +41394,7 @@ server.registerTool(
41291
41394
  server.registerTool(
41292
41395
  "awesomate_app_provision_db",
41293
41396
  {
41397
+ annotations: annotate("awesomate_app_provision_db", MUTATING),
41294
41398
  description: "Add a Postgres database to an EXISTING Node app that doesn't have one \u2014 one DB per environment, with DATABASE_URL injected into each app environment (restart/redeploy to pick it up). The password is set server-side and never returned. Support Plus+; static apps and apps that already have a DB are refused. Use the awesomate-database skill's decision tree first \u2014 a simple list an automation reads/writes is often better as an n8n data table.",
41295
41399
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_list / _get") }
41296
41400
  },
@@ -41350,7 +41454,7 @@ var PLAN_LADDER_FALLBACK = {
41350
41454
  server.registerTool(
41351
41455
  "awesomate_get_plan_features",
41352
41456
  {
41353
- annotations: READ_ONLY,
41457
+ annotations: annotate("awesomate_get_plan_features", READ_ONLY),
41354
41458
  description: "The Awesomate plan ladder: what each plan includes for hosting (WordPress sites, custom domains, hosted apps, cPanel, shell/Claude Code access) and which plans are purchasable. Fetched live from the hub (single source of truth) with a static fallback. Use to explain what an upgrade unlocks; live per-account usage comes from awesomate_get_limits.",
41355
41459
  inputSchema: {}
41356
41460
  },
@@ -41372,6 +41476,7 @@ server.registerTool(
41372
41476
  server.registerTool(
41373
41477
  "awesomate_snapshot_site",
41374
41478
  {
41479
+ annotations: annotate("awesomate_snapshot_site", MUTATING),
41375
41480
  description: "Snapshot a WordPress site (files + database) BEFORE any risky change \u2014 an AI edit, a deploy, a plugin/theme/core update. Returns a snapshotId you can roll back to. ALWAYS snapshot before mutating a live site. Requires shell access (Support Plus+).",
41376
41481
  inputSchema: {
41377
41482
  domain: external_exports.string().describe("The site domain (as shown by awesomate_list_sites), e.g. mysite.awesomate.site"),
@@ -41391,7 +41496,7 @@ server.registerTool(
41391
41496
  server.registerTool(
41392
41497
  "awesomate_list_snapshots",
41393
41498
  {
41394
- annotations: READ_ONLY,
41499
+ annotations: annotate("awesomate_list_snapshots", READ_ONLY),
41395
41500
  description: "List a site\u2019s available snapshots (newest first) with their ids, timestamps, and reasons. Requires shell access (Support Plus+).",
41396
41501
  inputSchema: { domain: external_exports.string().describe("The site domain") }
41397
41502
  },
@@ -41406,7 +41511,7 @@ server.registerTool(
41406
41511
  server.registerTool(
41407
41512
  "awesomate_rollback_site",
41408
41513
  {
41409
- annotations: DESTRUCTIVE,
41514
+ annotations: annotate("awesomate_rollback_site", DESTRUCTIVE),
41410
41515
  description: "Restore a site to a previous snapshot (files + database). The current state is auto-snapshotted first, so a rollback is itself reversible (see preRollbackSnapshotId in the result). Confirm with the user before rolling back \u2014 it overwrites the live site. Requires shell access (Support Plus+).",
41411
41516
  inputSchema: {
41412
41517
  domain: external_exports.string().describe("The site domain"),
@@ -41426,6 +41531,7 @@ server.registerTool(
41426
41531
  server.registerTool(
41427
41532
  "awesomate_site_staging_create",
41428
41533
  {
41534
+ annotations: annotate("awesomate_site_staging_create", MUTATING),
41429
41535
  description: "Create a staging copy of a WordPress site on the client's private awesomate.dev address (files + database cloned, URLs rewritten). Use this BEFORE making user-facing changes so the user can review at the staging URL first \u2014 post to dev, review, then awesomate_site_staging_promote. The staging site is hidden from search engines and AI crawlers by policy; anyone with the link can view it. One staging copy per site \u2014 a 409 with code 'staging_exists' means promote or discard the existing one first. Cloning can take a few minutes on large sites. Requires shell access (Support Plus+).",
41430
41536
  inputSchema: {
41431
41537
  domain: external_exports.string().describe("The LIVE site domain (as shown by awesomate_list_sites), e.g. mysite.awesomate.site")
@@ -41444,7 +41550,7 @@ server.registerTool(
41444
41550
  server.registerTool(
41445
41551
  "awesomate_site_staging_promote",
41446
41552
  {
41447
- annotations: DESTRUCTIVE,
41553
+ annotations: annotate("awesomate_site_staging_promote", DESTRUCTIVE),
41448
41554
  description: "Publish the staging copy to the LIVE site (overwrites live files + database with staging). The live site is auto-snapshotted first \u2014 the result includes preSnapshotId, which awesomate_rollback_site can restore if anything looks wrong. **Confirm with the user before promoting \u2014 it replaces the live site.** Requires shell access (Support Plus+).",
41449
41555
  inputSchema: {
41450
41556
  domain: external_exports.string().describe("The LIVE site domain whose staging copy should go live")
@@ -41463,7 +41569,7 @@ server.registerTool(
41463
41569
  server.registerTool(
41464
41570
  "awesomate_site_staging_discard",
41465
41571
  {
41466
- annotations: DESTRUCTIVE,
41572
+ annotations: annotate("awesomate_site_staging_discard", DESTRUCTIVE),
41467
41573
  description: "Delete the staging copy of a site (staging WP install + its awesomate.dev address; the live site is untouched). **Confirm with the user before discarding \u2014 unpromoted staging changes are lost.** Requires shell access (Support Plus+).",
41468
41574
  inputSchema: {
41469
41575
  domain: external_exports.string().describe("The LIVE site domain whose staging copy should be discarded")
@@ -41492,7 +41598,7 @@ readTool(
41492
41598
  server.registerTool(
41493
41599
  "awesomate_app_get",
41494
41600
  {
41495
- annotations: READ_ONLY,
41601
+ annotations: annotate("awesomate_app_get", READ_ONLY),
41496
41602
  description: "Get one app plus its environments (subdomains, ports, db engine/name, deploy + health state). Poll this after awesomate_app_create \u2014 status goes provisioning \u2192 active | failed (read provision_error on failure).",
41497
41603
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_list / _create") }
41498
41604
  },
@@ -41507,6 +41613,7 @@ server.registerTool(
41507
41613
  server.registerTool(
41508
41614
  "awesomate_app_create",
41509
41615
  {
41616
+ annotations: annotate("awesomate_app_create", MUTATING),
41510
41617
  description: "Provision a new app on the client's cPanel account. Choose the stack deliberately (see the awesomate-app-builder skill): kind 'static' = a single fast landing/lead page served straight from the docroot (no DB, no server process \u2014 pick this for brochure/landing/lead-capture); kind 'node' = a dynamic app with a backend + database (logins, custom logic, an API). Pick the template by job \u2014 'node-auth-sync' (user accounts/logins, MySQL), 'node-crud-postgres' (structured data without logins: trackers/dashboards \u2014 ready-made CRUD + UI, Postgres), 'node-api-only' (webhooks/integrations/glue, no DB wiring), 'static-landing' (marketing/lead page). If dbEngine is omitted the template's native engine is used (node-crud-postgres \u2192 postgres). Returns 202 immediately with an appId + subdomain(s); poll awesomate_app_get until status=active, then fetch the starter files with awesomate_app_scaffold. Requires apps:write (Support Plus+) \u2014 a 403 means offer an upgrade. Plans also cap the NUMBER of apps (Support Plus 5, Pro 20 \u2014 check awesomate_get_limits.dimensions.apps first): a 409 with code 'app_limit' means the cap is reached \u2014 do NOT retry; tell the user their allowance is full and surface the recommendedPlan/deepLink from the error. Get the user's confirmation on the stack + name before calling.",
41511
41618
  inputSchema: {
41512
41619
  appSlug: external_exports.string().regex(/^[a-z][a-z0-9]{1,15}$/).describe("2-16 chars, starts with a letter, lowercase alphanumeric \u2014 used for subdomains + db names"),
@@ -41528,7 +41635,7 @@ server.registerTool(
41528
41635
  server.registerTool(
41529
41636
  "awesomate_app_scaffold",
41530
41637
  {
41531
- annotations: READ_ONLY,
41638
+ annotations: annotate("awesomate_app_scaffold", READ_ONLY),
41532
41639
  description: "Fetch the app's starter files (its template rendered against the live app metadata \u2014 subdomains, control-plane URL, repo \u2014 with placeholders already substituted). Call after awesomate_app_create reaches status=active: write each returned file into a fresh local project folder at its relative path, then run npm install (Node apps) and follow the bundled CLAUDE.md. This is how the starter code gets onto the user's machine \u2014 don't reconstruct templates by hand.",
41533
41640
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id from awesomate_app_create / _list") }
41534
41641
  },
@@ -41543,6 +41650,7 @@ server.registerTool(
41543
41650
  server.registerTool(
41544
41651
  "awesomate_app_deploy_info",
41545
41652
  {
41653
+ annotations: annotate("awesomate_app_deploy_info", READ_ONLY),
41546
41654
  description: "READ-ONLY deploy briefing \u2014 this never deploys anything (deploys happen via git push). Reports how to deploy an app, its per-env targets, last-deploy/health state, and how to promote (dev\u2192staging\u2192main) or roll back (git revert + push). Node apps deploy via git push (dev/staging/main \u2192 GitHub Actions \u2192 cPanel). Use the awesomate-github skill to wire push-to-deploy the first time.",
41547
41655
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id") }
41548
41656
  },
@@ -41557,6 +41665,7 @@ server.registerTool(
41557
41665
  server.registerTool(
41558
41666
  "awesomate_app_set_env",
41559
41667
  {
41668
+ annotations: annotate("awesomate_app_set_env", MUTATING),
41560
41669
  description: "Store an API key / secret for a Node app environment: it's encrypted in the hub AND injected into the app's .env (0600) then the app restarts. ALWAYS use this instead of putting a secret in code, a committed file, or leaving it in the chat. NEVER echo the value back \u2014 confirm with the key name only. If the user pastes a key in chat, store it here and tell them (per the awesomate-credentials skill) it should be rotated since it passed through the transcript. Node apps only (static sites have no server env).",
41561
41670
  inputSchema: {
41562
41671
  appId: external_exports.number().int().positive().describe("The app id"),
@@ -41576,6 +41685,7 @@ server.registerTool(
41576
41685
  server.registerTool(
41577
41686
  "awesomate_n8n_attach_to_app",
41578
41687
  {
41688
+ annotations: annotate("awesomate_n8n_attach_to_app", MUTATING),
41579
41689
  description: "Wire an app to call the user's own n8n as a backend. Give it the app + a webhook URL you already created/tested/promoted on their n8n (via awesomate_n8n_deploy \u2014 create_draft returns the webhook URL, and promote preserves it). It stores N8N_WEBHOOK_URL + a generated N8N_WEBHOOK_SECRET on the app (encrypted + injected) and RETURNS the secret so you can add a matching `X-Awesomate-Webhook-Secret` header check to the n8n workflow (so the webhook isn't world-callable). The app then calls it via src/lib/n8n.ts callWorkflow(). Node apps only. Only reach for this when the job needs an external app/credential or AI the user already has in n8n \u2014 not for pure in-app logic.",
41580
41690
  inputSchema: {
41581
41691
  appId: external_exports.number().int().positive().describe("The app id"),
@@ -41594,7 +41704,7 @@ server.registerTool(
41594
41704
  server.registerTool(
41595
41705
  "awesomate_app_health",
41596
41706
  {
41597
- annotations: READ_ONLY,
41707
+ annotations: annotate("awesomate_app_health", READ_ONLY),
41598
41708
  description: "Probe an app's environments live and report health. Node envs hit /api/ready (200 = deployed + DB up + migrations applied); static hits the root. An env that isn't deployed yet reports unreachable \u2014 expected, not a failure. Use after a deploy to confirm it came up, or when the user says something's down.",
41599
41709
  inputSchema: { appId: external_exports.number().int().positive().describe("The app id") }
41600
41710
  },
@@ -41609,6 +41719,7 @@ server.registerTool(
41609
41719
  server.registerTool(
41610
41720
  "awesomate_n8n_provision_pg",
41611
41721
  {
41722
+ annotations: annotate("awesomate_n8n_provision_pg", MUTATING),
41612
41723
  description: "Provision a NEW Postgres database on the user's own Awesomate cPanel hosting and wire it into their n8n as a ready-to-use `postgres` credential their workflows can select. This is a WRITE that creates real infrastructure \u2014 get explicit user approval first. Requires: their n8n builder access (Support Plus+, allow_client_cli_builds) AND the allow_pg_writes consent (403 consent_required \u2192 send them to settingsUrl); AND an Awesomate cPanel hosting account (409 hosting_required otherwise). Limited to 2/day. Returns the new credential id + name, the database/user, and the password ONCE (it lives in the n8n credential, never stored by Awesomate \u2014 tell the user to note or rotate it). After this, build the workflow selecting the returned Postgres credential.",
41613
41724
  inputSchema: {}
41614
41725
  },
@@ -41642,7 +41753,7 @@ ${scrubKeyMaterial(message)}` }],
41642
41753
  server.registerTool(
41643
41754
  "awesomate_knowledge_status",
41644
41755
  {
41645
- annotations: READ_ONLY,
41756
+ annotations: annotate("awesomate_knowledge_status", READ_ONLY),
41646
41757
  description: "Call FIRST for any Knowledge Base work. The account's knowledge tenant state (provisioning/active/suspended), plan entitlement, consent flag, month-to-date usage vs included quota, and purchased packs. upgrade_required:true \u2192 relay the included upsell copy + billing link honestly, do NOT retry. available:false \u2192 this hub doesn't serve Knowledge Base yet (kill switch / old hub) \u2014 also not retryable. Reads work on every plan.",
41647
41758
  inputSchema: {}
41648
41759
  },
@@ -41657,6 +41768,7 @@ server.registerTool(
41657
41768
  server.registerTool(
41658
41769
  "awesomate_knowledge_provision",
41659
41770
  {
41771
+ annotations: annotate("awesomate_knowledge_provision", MUTATING),
41660
41772
  description: "Enable the Knowledge Base for this account (creates their isolated tenant on the Awesomate knowledge platform and wires the n8n credential). Idempotent \u2014 safe to re-call; a pending:true response means provisioning continues in the background: poll awesomate_knowledge_status. consent_required \u2192 send the user to Settings \u2192 Privacy (they must flip it themselves), re-check, then retry. upgrade_required \u2192 relay the upsell, don't retry. Get the user's explicit go-ahead before enabling.",
41661
41773
  inputSchema: {}
41662
41774
  },
@@ -41671,6 +41783,7 @@ server.registerTool(
41671
41783
  server.registerTool(
41672
41784
  "awesomate_knowledge_sources",
41673
41785
  {
41786
+ annotations: annotate("awesomate_knowledge_sources", MUTATING),
41674
41787
  description: "The knowledge base's content sources \u2014 the LIBRARY. action 'search' {q?, kind?, visibility?, tags?, collectionIds?, cursor?, limit?} \u2014 find sources by title/tag substring and metadata filters (instant, free; for CONTENT search use awesomate_knowledge_search); each row carries visibility (private|internal|public \u2014 who may retrieve it, ENFORCED), tags and collections. 'tag' {sourceId, tags} \u2014 REPLACE a source's free-form tags. 'set_visibility' {sourceId|sourceIds, visibility, confirm?} \u2014 change who may retrieve the source(s); 'public' is irreversible once fetched, so it needs the user's explicit agreement and their account slug as confirm. 'move' {sourceId|sourceIds, collectionIds?, removeCollectionIds?} \u2014 add to / remove from collections. 'sync_rules' / 'sync_rule_create' {pathPrefix, includeGlobs?, visibility?, tags?, collectionIds?, onFileRemoved?} / 'sync_rule_delete' {ruleId} / 'sync_run' / 'sync_state' \u2014 keep a folder of the account's File Manager (their n8n file system: public/, private/, temp/) synced into the knowledge base, so files their workflows write become searchable; a public rule needs confirm. 'list' \u2014 ONE page of sources, most recently ingested first (default 50, max 200 via limit): read `page.has_more`/`next_cursor` and pass cursor to continue \u2014 a page is never the whole library. 'summary' \u2014 exact whole-library counts {total, by_kind, chunks, indexed_chunks, failed_sources, failed_jobs_7d}: use THIS to say what the knowledge base contains, and if failed_jobs_7d > 0 say so \u2014 those ingests are missing from every other count. 'jobs' \u2014 ingest job statuses (optional status filter: queued|running|succeeded|failed). 'add' \u2014 ingest a public page {url} or a whole site {sitemap, since?}, optionally with visibility (default private), tags and collectionIds; ALWAYS get explicit approval first (ingest costs money and counts against quota), for a local FILE on the user's machine use awesomate_knowledge_upload instead (it streams the file from disk; this tool takes URLs only). A pack_required response means the allowance is exhausted: NOTHING was purchased \u2014 present the pack price (1 credit = $100) and let the user buy from the hub if they want it. 'remove' {sourceId} \u2014 deletes the source AND its indexed content; explicit approval required.",
41675
41788
  inputSchema: {
41676
41789
  action: external_exports.enum(["list", "summary", "add", "remove", "jobs", "search", "tag", "set_visibility", "move", "sync_rules", "sync_rule_create", "sync_rule_delete", "sync_run", "sync_state"]),
@@ -41708,7 +41821,7 @@ server.registerTool(
41708
41821
  server.registerTool(
41709
41822
  "awesomate_knowledge_search",
41710
41823
  {
41711
- annotations: READ_ONLY,
41824
+ annotations: annotate("awesomate_knowledge_search", READ_ONLY),
41712
41825
  description: "Instant search over the knowledge library with live facet counts: the fastest way to see WHAT is in there and to find the exact video moment, book page, dataset or web section. Returns hits (title, kind, locator like t=612-640 or p.42, snippet with **matched words**, score) plus facets {kind, year, category, author, people, places, topics} whose counts describe the current filters: repeat a facet value to OR within it, combine facets to AND. include_media adds presigned url/poster_url to hits: they expire in minutes, use immediately, never store. Keyword-only and free (no answer quota); for a verified ANSWER use awesomate_knowledge_ask, optionally with the same filters. Citations from awesomate_knowledge_ask carry NO media URLs \u2014 when a cited source is an image/video and the user wants to SEE it, re-query here with include_media (ideally filtered by its doc id). The returned url/poster_url expire in minutes: fine to show in chat, never safe to embed in a page \u2014 see the awesomate-knowledge skill's showing-media.md.",
41713
41826
  inputSchema: {
41714
41827
  q: external_exports.string().max(2e3).optional().describe("Search words; empty lists the library filtered by the facets"),
@@ -41736,6 +41849,7 @@ server.registerTool(
41736
41849
  server.registerTool(
41737
41850
  "awesomate_knowledge_ask",
41738
41851
  {
41852
+ annotations: annotate("awesomate_knowledge_ask", READ_ONLY),
41739
41853
  description: `Ask the account's knowledge base a question and get the VERIFIED answer with numbered sources (title, locator, url) \u2014 the test surface for 'is my content in there and answering well'. Read BOTH status and grounded. grounded:true \u2192 present the answer with its numbered sources. grounded:false (status ok but ZERO sources) \u2192 the agent answered from MODEL MEMORY, not their content: say their content does not cover it, never present it as an answer from their knowledge base, never build on it. The default workspace agent is not strict-grounded, so this is common \u2014 anything customer-facing should use a purpose-built agent (awesomate_knowledge_agents) with strict grounding. no_results / failed_validation (not_in_verified_content:true) \u2192 the verified content has no answer: relay that honestly (use configured_fallback), never fill the gap from memory \u2014 an honest "it doesn't know" is the feature working. error (platform_error:true) \u2192 the platform itself failed (model/API/infra): NOT a content gap \u2014 never tell the user their content lacks the answer; retry once, then awesomate_support. Counts against the monthly answers quota.`,
41740
41854
  inputSchema: {
41741
41855
  question: external_exports.string().min(1).max(2e3),
@@ -41763,6 +41877,7 @@ server.registerTool(
41763
41877
  server.registerTool(
41764
41878
  "awesomate_knowledge_agent",
41765
41879
  {
41880
+ annotations: annotate("awesomate_knowledge_agent", MUTATING),
41766
41881
  description: "The knowledge agent's configuration. action 'get' \u2014 persona, no-answer fallback message, model tier, allowed datasets, indexed counts. 'set' \u2014 change any of those on the LIVE agent that answers real customers: read the current values first, show the user exactly what will change, get explicit approval, then call; the response echoes the change \u2014 read it back to confirm. model_tier 'opus' is plan-gated (Embedded) \u2014 relay upgrade_required honestly.",
41767
41882
  inputSchema: {
41768
41883
  action: external_exports.enum(["get", "set"]),
@@ -41790,6 +41905,7 @@ server.registerTool(
41790
41905
  server.registerTool(
41791
41906
  "awesomate_knowledge_agents",
41792
41907
  {
41908
+ annotations: annotate("awesomate_knowledge_agents", MUTATING),
41793
41909
  description: "The agent builder (multi-agent; the older awesomate_knowledge_agent tool is the single workspace default). action 'list' \u2014 every agent with status (draft/published vN/suspended). 'get' {agentId} \u2014 full config incl. system message and scope. 'create' {goal} \u2014 AI drafts the whole setup (instructions, scope, tone, test questions) from the account's own content and saves it as a PRIVATE DRAFT (never live, nothing lost); or {name} for a blank draft. 'test' {agentId, message, sessionId?} \u2014 chat with the DRAFT config: free, unmetered, the right way to check behaviour before going live. 'publish' {agentId} \u2014 makes the draft LIVE immediately for every key bound to the agent: get the user's explicit approval first, and read the version back. Editing fields, policies, API keys and the request log live in the hub UI (Knowledge \u2192 Agents); keys are shown once there and never pass through this tool.",
41794
41910
  inputSchema: {
41795
41911
  action: external_exports.enum(["list", "get", "create", "publish", "test"]),
@@ -41812,6 +41928,7 @@ server.registerTool(
41812
41928
  server.registerTool(
41813
41929
  "awesomate_knowledge_collections",
41814
41930
  {
41931
+ annotations: annotate("awesomate_knowledge_collections", MUTATING),
41815
41932
  description: "Collections: named sets of knowledge sources with a default audience \u2014 the unit a chatbot is scoped to (agent scope.tags ['collection:<collection_id>']). action 'list' \u2014 every collection with source counts by visibility. 'create' {slug, name, description?, defaultVisibility?} \u2014 defaultVisibility only pre-fills NEW sources' visibility; each source keeps its own. 'get' {collectionId}. 'update' {collectionId, slug?|name?|description?|defaultVisibility?}. 'delete' {collectionId} \u2014 removes the collection; the sources survive (explicit approval first). 'add'/'remove' {collectionId, sourceIds} \u2014 membership. A source may be in several collections. A PUBLIC chatbot over a collection whose sources are all private answers nothing \u2014 check visibility (awesomate_knowledge_sources search) before building on one.",
41816
41933
  inputSchema: {
41817
41934
  action: external_exports.enum(["list", "create", "get", "update", "delete", "add", "remove"]),
@@ -41834,6 +41951,7 @@ server.registerTool(
41834
41951
  server.registerTool(
41835
41952
  "awesomate_knowledge_people",
41836
41953
  {
41954
+ annotations: annotate("awesomate_knowledge_people", MUTATING),
41837
41955
  description: "The people, places and topics the knowledge base has recognised \u2014 so 'everything about X' and 'who appears with X' answer with citations. action 'list' {status?: named|unknown|hidden|all, cursor?} \u2014 people with counts (unnamed rows carry an opaque handle, NEVER a name; do not guess who they are); 'get' {personId} \u2014 aliases, co-mentions and witness sources; 'aliases' \u2014 pending alias suggestions (text names that probably refer to a known entity); 'rename' {personId, displayName} \u2014 only a name the USER gave, after they confirm which cluster (face/mention counts + sources), then read the result back; 'hide'/'unhide' {personId}; 'merge' {personId, intoPersonId} \u2014 explicit approval first, faces and aliases move and the source entry is hidden; 'decide' {kind, aliasNorm, decision: accept|reject, entityId? | createPersonName?} \u2014 explicit approval first, alias identity is (kind, aliasNorm); 'resolve' \u2014 re-run entity resolution: counts toward the ingestion allowance, so ask first. list/aliases/resolve return available:false when the platform hasn't enabled the layer yet \u2014 relay that honestly, don't retry. Photos of people are only viewable on the hub Knowledge \u2192 People page.",
41838
41956
  inputSchema: {
41839
41957
  action: external_exports.enum(["list", "get", "rename", "hide", "unhide", "merge", "aliases", "decide", "resolve"]),
@@ -41860,6 +41978,7 @@ server.registerTool(
41860
41978
  server.registerTool(
41861
41979
  "awesomate_knowledge_data",
41862
41980
  {
41981
+ annotations: annotate("awesomate_knowledge_data", MUTATING),
41863
41982
  description: "The knowledge platform's business-data warehouse (Pro+). action 'metrics' \u2014 headline numbers for the data tab. 'datasets' \u2014 the datasets imported and their columns: read this FIRST, measures/dimensions must name real columns. 'imports' \u2014 import job statuses. 'query' {dataset, measures:[{column, agg}], plus optional dimensions/filters passed through} \u2014 answer QUANTITATIVE questions from the user's own imported business data (revenue by month, top customers); read-only, results come back as rows to present honestly. New data is imported in the hub UI, not here.",
41864
41983
  inputSchema: {
41865
41984
  action: external_exports.enum(["metrics", "datasets", "imports", "query"]),
@@ -41887,7 +42006,7 @@ server.registerTool(
41887
42006
  server.registerTool(
41888
42007
  "awesomate_n8n_kpis",
41889
42008
  {
41890
- annotations: READ_ONLY,
42009
+ annotations: annotate("awesomate_n8n_kpis", READ_ONLY),
41891
42010
  description: "Automation KPIs from the hub's monitoring: executions, error rate, avg + p95 duration, time saved, 7-day-vs-prior deltas. No workflowId \u2192 the whole instance (the headline numbers for any report). With workflowId \u2192 that workflow, plus last_error_at and clean_days. Needs the 'aggregate monitoring' privacy toggle (on by default). Pair with awesomate_n8n_errors for what is failing and awesomate_site_uptime for the hosting side.",
41892
42011
  inputSchema: { workflowId: external_exports.string().max(64).optional() }
41893
42012
  },
@@ -41904,7 +42023,7 @@ server.registerTool(
41904
42023
  server.registerTool(
41905
42024
  "awesomate_n8n_errors",
41906
42025
  {
41907
- annotations: READ_ONLY,
42026
+ annotations: annotate("awesomate_n8n_errors", READ_ONLY),
41908
42027
  description: "What is broken across the WHOLE instance \u2014 error events grouped by fingerprint (category, workflow, node, occurrences, first/last seen), newest first. THE first call when the user says 'something is failing' or at the start of an n8n session (a quick 7-day sweep; stay silent when it's clean). Counts and categories only \u2014 drill into a specific failure with awesomate_n8n_executions. days defaults to 30 (max 90).",
41909
42028
  inputSchema: {
41910
42029
  days: external_exports.number().int().min(1).max(90).optional(),
@@ -41930,7 +42049,7 @@ readTool(
41930
42049
  server.registerTool(
41931
42050
  "awesomate_n8n_findings",
41932
42051
  {
41933
- annotations: READ_ONLY,
42052
+ annotations: annotate("awesomate_n8n_findings", READ_ONLY),
41934
42053
  description: "Findings from Awesomate's automated error analyzer for THIS account (Pro/Embedded + the 'Enable AI Error Diagnosis' privacy toggle (n8n \u2192 Settings \u2192 Privacy)): severity, workflow, occurrences, a plain-language clientSummary, needsClientAction (something only the user can fix \u2014 expired logins, third-party quotas), and fixReady (a reviewed fix is prepared \u2014 raise it with awesomate_support to have it applied). Read-only. An empty list on a healthy instance is the good state, not an error.",
41935
42054
  inputSchema: { limit: external_exports.number().int().min(1).max(100).optional() }
41936
42055
  },
@@ -41971,6 +42090,7 @@ readTool(
41971
42090
  server.registerTool(
41972
42091
  "awesomate_notifications",
41973
42092
  {
42093
+ annotations: annotate("awesomate_notifications", MUTATING),
41974
42094
  description: "The account's hub notification bell \u2014 the only channel where Awesomate pushes to the client: knowledge quota warnings (80%/100%), 'your quote is ready', support-access events. action 'list' {limit?} \u2192 notifications + unread count (surface unread ones once per session, in one line). 'read' {id} / 'read_all' \u2192 mark seen after you've relayed them. Not for sending anything.",
41975
42095
  inputSchema: {
41976
42096
  action: external_exports.enum(["list", "read", "read_all"]),
@@ -41998,6 +42118,7 @@ server.registerTool(
41998
42118
  server.registerTool(
41999
42119
  "awesomate_wp_post",
42000
42120
  {
42121
+ annotations: annotate("awesomate_wp_post", MUTATING),
42001
42122
  description: "Create, update or read a WordPress post/page on the user's own Awesomate-hosted site \u2014 titles and content with spaces/HTML are fine (unlike awesomate_run_wp_cli). 'create' {domain, title, content?, status?, postType?: post|page, excerpt?, slug?} \u2014 lands as a DRAFT unless status:'publish' is explicit; never publish content the user hasn't seen or approved. 'update' {domain, postId, any of title/content/excerpt/slug/status}. 'get' {domain, postId, includeContent?}. Writes need Support Plus+ and are audited; snapshot the site first (awesomate_snapshot_site) before the session's first content change on a live site. Find post ids via awesomate_run_wp_cli ['post','list'].",
42002
42123
  inputSchema: {
42003
42124
  action: external_exports.enum(["create", "update", "get"]),
@@ -42046,7 +42167,7 @@ server.registerTool(
42046
42167
  domain: external_exports.string().min(3).max(253).describe("a domain on the user's own hosting account"),
42047
42168
  path: external_exports.string().max(400).optional().describe("page path to audit, default /")
42048
42169
  },
42049
- annotations: READ_ONLY
42170
+ annotations: annotate("awesomate_site_audit", READ_ONLY)
42050
42171
  },
42051
42172
  async ({ domain, path }) => {
42052
42173
  try {
@@ -42061,6 +42182,7 @@ server.registerTool(
42061
42182
  server.registerTool(
42062
42183
  "awesomate_wp_media_import",
42063
42184
  {
42185
+ annotations: annotate("awesomate_wp_media_import", MUTATING),
42064
42186
  description: "Import ONE media item into the user's WordPress media library by https URL (Support Plus+, audited). Returns the attachmentId to reference from posts. URLs only \u2014 this cannot read local files; for a local file, upload it somewhere reachable first or use wp-admin. Ask before importing anything the user didn't explicitly provide.",
42065
42187
  inputSchema: {
42066
42188
  domain: external_exports.string().min(3).max(253),
@@ -42080,6 +42202,7 @@ server.registerTool(
42080
42202
  server.registerTool(
42081
42203
  "awesomate_wp_settings",
42082
42204
  {
42205
+ annotations: annotate("awesomate_wp_settings", DESTRUCTIVE),
42083
42206
  description: `Change a WordPress site's core settings \u2014 THE tool for "change my site title" / tagline. Use this, never awesomate_run_wp_cli, for any value containing spaces: that tool's argument gate rejects spaces outright, so \`option update blogname "My Business Name"\` cannot work there. Fields (send only what you're changing): title, tagline, timezone (IANA, e.g. Australia/Sydney), dateFormat, timeFormat, postsPerPage (1-100), searchEngineVisible (false hides the site from search engines \u2014 confirm before setting it). Support Plus+ and audited; flushes the object cache so the change shows. Snapshot first with awesomate_snapshot_site if this is the session's first change to a live site.`,
42084
42207
  inputSchema: {
42085
42208
  domain: external_exports.string().min(3).max(253).describe("The site domain, e.g. mybusiness.awesomate.site"),
@@ -42134,6 +42257,7 @@ var KB_UNPARSEABLE = /* @__PURE__ */ new Set([".doc", ".docx", ".ppt", ".pptx",
42134
42257
  server.registerTool(
42135
42258
  "awesomate_knowledge_upload",
42136
42259
  {
42260
+ annotations: annotate("awesomate_knowledge_upload", MUTATING),
42137
42261
  description: "Ingest ONE file from the user's own computer into their Knowledge Base (Pro+). Pass a LOCAL PATH \u2014 this server runs on their machine and streams the file to the hub itself, so the file contents never pass through the conversation. Handles documents (pdf, md, txt), business data (csv, tsv, xls, xlsx, json), audio and video (transcribed), and images. Word/PowerPoint/RTF/EPUB files are NOT parseable yet \u2014 the tool refuses them with the workaround (export to PDF, or save as .md/.txt). Max 100 MB per file; bigger media goes through the hub's Knowledge \u2192 Sources page. INGESTING COSTS MONEY and counts against the monthly allowance, so ALWAYS get explicit approval for the specific file(s) first and say what it will consume. For several files, call once per file and report progress \u2014 do not loop silently. Returns a job with its live status; poll awesomate_knowledge_sources {action:'jobs'} until it succeeds (a duplicate upload replays the earlier job \u2014 a response already reading succeeded needs no polling), then probe the content with awesomate_knowledge_ask before building anything on it. A pack_required response means the allowance is exhausted: nothing was ingested and nothing was purchased.",
42138
42262
  inputSchema: {
42139
42263
  path: external_exports.string().min(1).max(4096).optional().describe("Path to the file on the user's machine (~ is expanded). Omit when using fromFilesPath."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/hosting-mcp",
3
- "version": "0.22.3",
3
+ "version": "0.24.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",
@@ -28,36 +28,61 @@ what's actually spendable once credits promised to builds in flight are
28
28
  set aside — rather than the raw balance.) Never quote a plan, price, or
29
29
  balance from memory.
30
30
 
31
- ## 1. FAQ first, then help docs
31
+ ## 1. Ask the Knowledge Base first
32
32
 
33
33
  For any "how do I", "why does", "what is" question about the service:
34
34
 
35
- 1. Call `awesomate_support {action:'faq', q:'<their question>'}`.
36
- 2. Answer FROM the returned entries, in your own plain words. Cite nothing
37
- the FAQ and tools didn't say no invented policy, no guessed pricing.
38
- 3. If the FAQ misses or only partially answers, call
39
- `awesomate_support {action:'help_docs', q:'<topic>'}` a live search of
40
- the full Help Centre articles. Answer from the excerpts and give the
41
- article's URL so the user can read the full steps.
42
- 4. If neither answers it: "I don't have a confirmed answer for that. Want
43
- me to ask the Awesomate team? I'll draft a support ticket for you to
44
- approve first." Then go to §2 if they say yes. (An empty help_docs
45
- result can also mean the search is temporarily down — the response's
46
- help_centre_url is always safe to offer for browsing.)
35
+ 1. Call `awesomate_support {action:'ask', q:'<their question>'}`. This is
36
+ Awesomate's own Knowledge Base answering from Awesomate's own content,
37
+ with numbered citations. It works on every plan.
38
+ 2. Read the response's `source` and `degraded` fields before you answer —
39
+ they are the difference between three very different situations:
40
+
41
+ - `source:'knowledge_base'`, `no_answer:false` a grounded answer.
42
+ Relay it in your own plain words and give the citation URLs.
43
+ - `source:'knowledge_base'`, `no_answer:true` the grounding gate
44
+ declined, because the answer is not in our content. **Do not fill the
45
+ gap yourself.** Offer a ticket (§2).
46
+ - `degraded:true` the Knowledge Base was unavailable or is not
47
+ switched on, and you are looking at keyword matches from `faq` and
48
+ `docs` instead. Useful, but **never present them as a verified
49
+ answer**, and say the search was limited if it matters.
50
+
51
+ 3. Only if `ask` came back thin do you need `faq` or `help_docs` directly.
52
+ Both remain available and behave as before, and `help_centre_url` in
53
+ any response is always safe to offer for browsing.
54
+ 4. Cite nothing the tools didn't say. No invented policy, no guessed
55
+ pricing, no remembered plan limits.
47
56
 
48
57
  ## 2. Ticket flow — talking to a human
49
58
 
50
59
  When the user wants a human, or §1 came up empty:
51
60
 
61
+ **First, what is this ticket about?** Access splits by SUBJECT, not by
62
+ plan, and you must pass a `category` naming it. `Other` is refused.
63
+
64
+ | The problem is | category | Who can raise it |
65
+ |---|---|---|
66
+ | Our platform is broken: site down, n8n unreachable, provisioning or SSL failed, the hub is erroring | `Infrastructure fault` | **every plan** |
67
+ | They cannot connect or use Claude: pairing fails, the token is rejected, tools error | `Claude/MCP connectivity` | **every plan** |
68
+ | Money: invoices, plans, credits, refunds | `Account/billing` | **every plan** |
69
+ | Help USING n8n, their website, or the Knowledge Base | `Workflow not running`, `Workflow error`, `New automation request`, `How-to question` | **Support Plus and above** |
70
+
71
+ Choose the category that is actually true. If a product-help ticket comes
72
+ back `403 upgrade_required`, answer from §1 instead and relay the upgrade
73
+ honestly — **never relabel a workflow question as an outage to get it
74
+ through.** That is lying to your own support team about the state of the
75
+ platform, and it puts a false fault report in front of an engineer.
76
+
52
77
  1. Draft the ticket in the USER'S words — their description of the problem,
53
78
  what they were trying to do, what happened. Add context you observed
54
79
  (which site/app, what you already tried) in one short paragraph. No
55
80
  stack traces, no jargon dumps.
56
81
  2. Show the full draft (subject + message) and ask plainly: "Send this to
57
82
  the Awesomate team?" NEVER call `create_ticket` without an explicit yes.
58
- 3. On yes: `awesomate_support {action:'create_ticket', subject, message}`.
59
- Confirm: "Sent. The team replies by email, usually within a business
60
- day."
83
+ 3. On yes: `awesomate_support {action:'create_ticket', subject, message,
84
+ category}`. Confirm: "Sent. The team replies by email, usually within a
85
+ business day."
61
86
  4. Later, "any update on my ticket?" →
62
87
  `awesomate_support {action:'list_tickets'}` and report each ticket's
63
88
  status in one line.